-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(voyager): introduce berachain client update plugin
- Loading branch information
1 parent
8878d9b
commit fdc39ee
Showing
7 changed files
with
526 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
use std::{ | ||
fmt::Debug, | ||
num::{NonZeroU64, ParseIntError}, | ||
}; | ||
|
||
use ics23::ibc_api::SDK_SPECS; | ||
use jsonrpsee::{ | ||
core::{async_trait, RpcResult}, | ||
types::ErrorObject, | ||
Extensions, | ||
}; | ||
use serde::{Deserialize, Serialize}; | ||
use serde_json::Value; | ||
use tendermint_light_client_types::{ClientState, ConsensusState, Fraction}; | ||
use tracing::{debug, error, instrument}; | ||
use unionlabs::{ | ||
ibc::core::{client::height::Height, commitment::merkle_root::MerkleRoot}, | ||
option_unwrap, result_unwrap, ErrorReporter, | ||
}; | ||
use voyager_message::{ | ||
core::{ChainId, ConsensusType}, | ||
module::{ConsensusModuleInfo, ConsensusModuleServer}, | ||
rpc::json_rpc_error_to_error_object, | ||
ConsensusModule, | ||
}; | ||
use voyager_vm::BoxDynError; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct Module { | ||
pub chain_id: ChainId, | ||
|
||
pub tm_client: cometbft_rpc::Client, | ||
pub chain_revision: u64, | ||
pub grpc_url: String, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
pub struct Config { | ||
pub ws_url: String, | ||
pub grpc_url: String, | ||
} | ||
|
||
impl ConsensusModule for Module { | ||
type Config = Config; | ||
|
||
async fn new(config: Self::Config, info: ConsensusModuleInfo) -> Result<Self, BoxDynError> { | ||
let tm_client = cometbft_rpc::Client::new(config.ws_url).await?; | ||
|
||
let chain_id = tm_client.status().await?.node_info.network.to_string(); | ||
|
||
info.ensure_chain_id(&chain_id)?; | ||
info.ensure_consensus_type(ConsensusType::TENDERMINT)?; | ||
|
||
let chain_revision = chain_id | ||
.split('-') | ||
.last() | ||
.ok_or_else(|| ChainIdParseError { | ||
found: chain_id.clone(), | ||
source: None, | ||
})? | ||
.parse() | ||
.map_err(|err| ChainIdParseError { | ||
found: chain_id.clone(), | ||
source: Some(err), | ||
})?; | ||
|
||
Ok(Self { | ||
tm_client, | ||
chain_id: ChainId::new(chain_id), | ||
chain_revision, | ||
grpc_url: config.grpc_url, | ||
}) | ||
} | ||
} | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
#[error("unable to parse chain id: expected format `<chain>-<revision-number>`, found `{found}`")] | ||
pub struct ChainIdParseError { | ||
found: String, | ||
#[source] | ||
source: Option<ParseIntError>, | ||
} | ||
|
||
impl Module { | ||
#[must_use] | ||
pub fn make_height(&self, height: u64) -> Height { | ||
Height::new_with_revision(self.chain_revision, height) | ||
} | ||
|
||
async fn latest_height(&self, finalized: bool) -> Result<Height, cometbft_rpc::JsonRpcError> { | ||
let commit_response = self.tm_client.commit(None).await?; | ||
|
||
let mut height = commit_response | ||
.signed_header | ||
.header | ||
.height | ||
.inner() | ||
.try_into() | ||
.expect("value is >= 0; qed;"); | ||
|
||
if finalized && !commit_response.canonical { | ||
debug!( | ||
"commit is not canonical and finalized height was requested, \ | ||
latest finalized height is the previous block" | ||
); | ||
height -= 1; | ||
} | ||
|
||
debug!(height, "latest height"); | ||
|
||
Ok(self.make_height(height)) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl ConsensusModuleServer for Module { | ||
/// Query the latest finalized height of this chain. | ||
#[instrument(skip_all, fields(chain_id = %self.chain_id))] | ||
async fn query_latest_height(&self, _: &Extensions, finalized: bool) -> RpcResult<Height> { | ||
self.latest_height(finalized) | ||
.await | ||
// TODO: Add more context here | ||
.map_err(|err| ErrorObject::owned(-1, ErrorReporter(err).to_string(), None::<()>)) | ||
} | ||
|
||
/// Query the latest finalized timestamp of this chain. | ||
// TODO: Use a better timestamp type here | ||
#[instrument(skip_all, fields(chain_id = %self.chain_id))] | ||
async fn query_latest_timestamp(&self, _: &Extensions, finalized: bool) -> RpcResult<i64> { | ||
let mut commit_response = self | ||
.tm_client | ||
.commit(None) | ||
.await | ||
.map_err(json_rpc_error_to_error_object)?; | ||
|
||
if finalized && commit_response.canonical { | ||
debug!( | ||
"commit is not canonical and finalized timestamp was \ | ||
requested, fetching commit at previous block" | ||
); | ||
commit_response = self | ||
.tm_client | ||
.commit(Some( | ||
(u64::try_from(commit_response.signed_header.header.height.inner() - 1) | ||
.expect("should be fine")) | ||
.try_into() | ||
.expect("should be fine"), | ||
)) | ||
.await | ||
.map_err(json_rpc_error_to_error_object)?; | ||
|
||
if !commit_response.canonical { | ||
error!( | ||
?commit_response, | ||
"commit for previous height is not canonical? continuing \ | ||
anyways, but this may cause issues downstream" | ||
); | ||
} | ||
} | ||
|
||
Ok(commit_response | ||
.signed_header | ||
.header | ||
.time | ||
.as_unix_nanos() | ||
.try_into() | ||
.expect("should be fine")) | ||
} | ||
|
||
#[instrument(skip_all, fields(chain_id = %self.chain_id))] | ||
async fn self_client_state(&self, _: &Extensions, height: Height) -> RpcResult<Value> { | ||
let params = protos::cosmos::staking::v1beta1::query_client::QueryClient::connect( | ||
self.grpc_url.clone(), | ||
) | ||
.await | ||
.unwrap() | ||
.params(protos::cosmos::staking::v1beta1::QueryParamsRequest {}) | ||
.await | ||
.unwrap() | ||
.into_inner() | ||
.params | ||
.unwrap(); | ||
|
||
let commit = self | ||
.tm_client | ||
.commit(Some(height.height().try_into().unwrap())) | ||
.await | ||
.unwrap(); | ||
|
||
let height = commit.signed_header.header.height; | ||
|
||
let unbonding_period = std::time::Duration::new( | ||
params | ||
.unbonding_time | ||
.clone() | ||
.unwrap() | ||
.seconds | ||
.try_into() | ||
.unwrap(), | ||
params | ||
.unbonding_time | ||
.clone() | ||
.unwrap() | ||
.nanos | ||
.try_into() | ||
.unwrap(), | ||
); | ||
|
||
Ok(serde_json::to_value(ClientState { | ||
chain_id: self.chain_id.to_string(), | ||
// https://github.com/cometbft/cometbft/blob/da0e55604b075bac9e1d5866cb2e62eaae386dd9/light/verifier.go#L16 | ||
trust_level: Fraction { | ||
numerator: 1, | ||
denominator: const { option_unwrap!(NonZeroU64::new(3)) }, | ||
}, | ||
// https://github.com/cosmos/relayer/blob/23d1e5c864b35d133cad6a0ef06970a2b1e1b03f/relayer/chains/cosmos/provider.go#L177 | ||
trusting_period: unionlabs::google::protobuf::duration::Duration::new( | ||
(unbonding_period * 85 / 100).as_secs().try_into().unwrap(), | ||
(unbonding_period * 85 / 100) | ||
.subsec_nanos() | ||
.try_into() | ||
.unwrap(), | ||
) | ||
.unwrap(), | ||
unbonding_period: unionlabs::google::protobuf::duration::Duration::new( | ||
unbonding_period.as_secs().try_into().unwrap(), | ||
unbonding_period.subsec_nanos().try_into().unwrap(), | ||
) | ||
.unwrap(), | ||
// https://github.com/cosmos/relayer/blob/23d1e5c864b35d133cad6a0ef06970a2b1e1b03f/relayer/chains/cosmos/provider.go#L177 | ||
max_clock_drift: const { | ||
result_unwrap!(unionlabs::google::protobuf::duration::Duration::new( | ||
60 * 10, | ||
0 | ||
)) | ||
}, | ||
frozen_height: None, | ||
latest_height: Height::new_with_revision( | ||
self.chain_revision, | ||
height.inner().try_into().expect("is within bounds; qed;"), | ||
), | ||
proof_specs: SDK_SPECS.into(), | ||
upgrade_path: vec!["upgrade".into(), "upgradedIBCState".into()], | ||
}) | ||
.unwrap()) | ||
} | ||
|
||
/// The consensus state on this chain at the specified `Height`. | ||
#[instrument(skip_all, fields(chain_id = %self.chain_id))] | ||
async fn self_consensus_state(&self, _: &Extensions, height: Height) -> RpcResult<Value> { | ||
let commit = self | ||
.tm_client | ||
.commit(Some(height.height().try_into().unwrap())) | ||
.await | ||
.map_err(|e| { | ||
ErrorObject::owned( | ||
-1, | ||
format!("error fetching commit: {}", ErrorReporter(e)), | ||
None::<()>, | ||
) | ||
})?; | ||
|
||
Ok(serde_json::to_value(&ConsensusState { | ||
root: MerkleRoot { | ||
hash: commit.signed_header.header.app_hash.into_encoding(), | ||
}, | ||
next_validators_hash: commit.signed_header.header.next_validators_hash, | ||
timestamp: commit.signed_header.header.time, | ||
}) | ||
.unwrap()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
[package] | ||
edition = "2021" | ||
name = "voyager-client-update-plugin-berachain" | ||
version = "0.1.0" | ||
|
||
[dependencies] | ||
alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] } | ||
berachain-light-client-types = { workspace = true, features = ["proto", "serde"] } | ||
cometbft-rpc = { workspace = true } | ||
cometbft-types.workspace = true | ||
dashmap = { workspace = true } | ||
enumorph = { workspace = true } | ||
ethereum-light-client-types = { workspace = true, features = ["serde"] } | ||
futures = { workspace = true } | ||
ics23 = { workspace = true } | ||
jsonrpsee = { workspace = true, features = ["macros", "server", "tracing"] } | ||
macros = { workspace = true } | ||
num-bigint = { workspace = true } | ||
prost = { workspace = true } | ||
protos = { workspace = true } | ||
serde = { workspace = true, features = ["derive"] } | ||
serde_json = { workspace = true } | ||
thiserror = { workspace = true } | ||
tokio = { workspace = true } | ||
tracing = { workspace = true } | ||
tracing-subscriber = { workspace = true } | ||
unionlabs = { workspace = true } | ||
voyager-message = { workspace = true } | ||
voyager-vm = { workspace = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use enumorph::Enumorph; | ||
use macros::model; | ||
use unionlabs::ibc::core::client::height::Height; | ||
|
||
#[model] | ||
#[derive(Enumorph)] | ||
pub enum ModuleCall { | ||
FetchUpdate(FetchUpdate), | ||
} | ||
|
||
#[model] | ||
pub struct FetchUpdate { | ||
pub update_from: Height, | ||
pub update_to: Height, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
use enumorph::Enumorph; | ||
use macros::model; | ||
|
||
#[model] | ||
#[derive(Enumorph)] | ||
pub enum ModuleCallback {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
use macros::model; | ||
|
||
#[model] | ||
pub enum ModuleData {} |
Oops, something went wrong.