Skip to content

Commit

Permalink
feat(voyager): introduce berachain client update plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
hussein-aitlahcen committed Dec 18, 2024
1 parent 8878d9b commit 3bb9ca2
Show file tree
Hide file tree
Showing 9 changed files with 623 additions and 5 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions lib/cometbft-rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,12 @@ impl Client {
.await
}

pub async fn block_by_hash(&self, hash: H256) -> Result<BlockResponse, JsonRpcError> {
self.inner
.request("block_by_hash", (hash.to_string(),))
.await
}

pub async fn blockchain(
&self,
min_height: NonZeroU64,
Expand Down
6 changes: 3 additions & 3 deletions voyager/modules/consensus/berachain/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ name = "voyager-consensus-module-berachain"
version = "0.1.0"

[dependencies]
alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] }
beacon-api-types = { workspace = true, features = ["serde", "ssz"] }
alloy = { workspace = true, features = ["rpc", "rpc-types", "transports", "transport-http", "transport-ws", "reqwest", "provider-ws"] }
beacon-api-types = { workspace = true, features = ["serde", "ssz"] }
berachain-light-client-types = { workspace = true, features = ["proto", "serde"] }
cometbft-rpc = { workspace = true }
dashmap = { workspace = true }
enumorph = { workspace = true }
Expand All @@ -19,7 +20,6 @@ protos = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
tendermint-light-client-types = { workspace = true, features = ["proto", "serde"] }
berachain-light-client-types = { workspace = true, features = ["proto", "serde"] }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
Expand Down
275 changes: 273 additions & 2 deletions voyager/modules/consensus/tendermint/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,277 @@
use voyager_message::ConsensusModule;
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;

#[tokio::main(flavor = "multi_thread")]
async fn main() {
voyager_consensus_module_tendermint::Module::run().await
Module::run().await
}

#[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())
}
}
31 changes: 31 additions & 0 deletions voyager/plugins/client-update/berachain/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[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"] }
beacon-api-types = { workspace = true, features = ["serde", "ssz"] }
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 }
ssz = { 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 }
17 changes: 17 additions & 0 deletions voyager/plugins/client-update/berachain/src/call.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
use enumorph::Enumorph;
use macros::model;
use unionlabs::ibc::core::client::height::Height;
use voyager_message::core::ChainId;

#[model]
#[derive(Enumorph)]
pub enum ModuleCall {
FetchUpdate(FetchUpdate),
}

#[model]
pub struct FetchUpdate {
pub counterparty_chain_id: ChainId,
pub update_from: Height,
pub update_to: Height,
}
6 changes: 6 additions & 0 deletions voyager/plugins/client-update/berachain/src/callback.rs
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 {}
4 changes: 4 additions & 0 deletions voyager/plugins/client-update/berachain/src/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
use macros::model;

#[model]
pub enum ModuleData {}
Loading

0 comments on commit 3bb9ca2

Please sign in to comment.