diff --git a/Cargo.lock b/Cargo.lock index 501ee450dd..0fed768011 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4789,6 +4789,7 @@ dependencies = [ "chrono", "clap 3.2.25", "cnidarium", + "cnidarium-component", "console-subscriber", "csv", "decaf377 0.5.0", @@ -4862,6 +4863,7 @@ dependencies = [ "tracing", "tracing-subscriber 0.3.18", "url", + "wrapper-derive", ] [[package]] @@ -4968,6 +4970,7 @@ dependencies = [ "tonic", "tracing", "tracing-subscriber 0.3.18", + "wrapper-derive", ] [[package]] @@ -5404,6 +5407,7 @@ dependencies = [ "tonic", "tower", "tracing", + "wrapper-derive", ] [[package]] @@ -9336,6 +9340,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wrapper-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2 1.0.70", + "quote 1.0.33", + "syn 1.0.109", +] + [[package]] name = "wyz" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index e6ac21c3c2..2c316d8b5f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ members = [ "crates/misc/tct-visualize", "crates/bench", "tools/summonerd", + "crates/util/wrapper-derive", ] # Optimize for small binaries in just the wasm crate. diff --git a/crates/bin/pd/Cargo.toml b/crates/bin/pd/Cargo.toml index 925dd7b4a5..c3bb959c80 100644 --- a/crates/bin/pd/Cargo.toml +++ b/crates/bin/pd/Cargo.toml @@ -23,6 +23,7 @@ download-proving-keys = ["penumbra-proof-params/download-proving-keys"] # Workspace dependencies penumbra-proto = { path = "../../proto" } cnidarium = { path = "../../cnidarium", features = ["migration", "rpc"] } +cnidarium-component = { path = "../../cnidarium-component" } penumbra-asset = { path = "../../core/asset" } penumbra-keys = { path = "../../core/keys" } penumbra-shielded-pool = { path = "../../core/component/shielded-pool", features = [ @@ -44,6 +45,7 @@ penumbra-app = { path = "../../core/app" } penumbra-custody = { path = "../../custody" } penumbra-tower-trace = { path = "../../util/tower-trace" } penumbra-tendermint-proxy = { path = "../../util/tendermint-proxy" } +wrapper-derive = { path = "../../util/wrapper-derive" } # Penumbra dependencies decaf377 = { version = "0.5", features = ["parallel"] } diff --git a/crates/bin/pd/src/ibc.rs b/crates/bin/pd/src/ibc.rs new file mode 100644 index 0000000000..9233f25468 --- /dev/null +++ b/crates/bin/pd/src/ibc.rs @@ -0,0 +1,32 @@ +use anyhow::Result; +use cnidarium::StateRead; +use cnidarium::Storage; +use cnidarium_component::ChainStateReadExt; +use ibc_types::core::commitment::MerkleProof; +// implemented by [`SnapshotWrapper`] +use penumbra_chain::component::StateReadExt as _; + +#[derive(wrapper_derive::StateRead, wrapper_derive::ChainStateReadExt, Clone)] +pub struct SnapshotWrapper(S); + +#[async_trait::async_trait] +impl penumbra_ibc::component::rpc::Snapshot for SnapshotWrapper { + fn version(&self) -> u64 { + self.0.version() + } + + async fn get_with_proof(&self, key: Vec) -> Result<(Option>, MerkleProof)> { + self.0.get_with_proof(key).await + } +} + +#[derive(Clone)] +pub struct StorageWrapper(pub Storage); + +impl penumbra_ibc::component::rpc::Storage> + for StorageWrapper +{ + fn latest_snapshot(&self) -> SnapshotWrapper { + SnapshotWrapper(self.0.latest_snapshot()) + } +} diff --git a/crates/bin/pd/src/lib.rs b/crates/bin/pd/src/lib.rs index 253a7907ee..84e129f63e 100644 --- a/crates/bin/pd/src/lib.rs +++ b/crates/bin/pd/src/lib.rs @@ -4,6 +4,7 @@ #![recursion_limit = "512"] mod consensus; +mod ibc; mod info; mod mempool; mod metrics; @@ -16,6 +17,7 @@ pub mod upgrade; pub use crate::metrics::register_metrics; pub use consensus::Consensus; +pub use ibc::{SnapshotWrapper, StorageWrapper}; pub use info::Info; pub use mempool::Mempool; pub use penumbra_app::app::App; diff --git a/crates/bin/pd/src/main.rs b/crates/bin/pd/src/main.rs index 0975f8e3f1..555ff551d8 100644 --- a/crates/bin/pd/src/main.rs +++ b/crates/bin/pd/src/main.rs @@ -372,7 +372,8 @@ async fn main() -> anyhow::Result<()> { ) .expect("failed to spawn abci server"); - let ibc = penumbra_ibc::component::rpc::IbcQuery::new(storage.clone()); + let ibc = + penumbra_ibc::component::rpc::IbcQuery::new(pd::StorageWrapper(storage.clone())); // TODO: Once we migrate to Tonic 0.10.0, we'll be able to use the // `Routes` structure to have each component define a method that diff --git a/crates/cnidarium-component/src/chain_state_read_ext.rs b/crates/cnidarium-component/src/chain_state_read_ext.rs new file mode 100644 index 0000000000..6ccdee8926 --- /dev/null +++ b/crates/cnidarium-component/src/chain_state_read_ext.rs @@ -0,0 +1,11 @@ +use anyhow::Result; +use async_trait::async_trait; +use cnidarium::StateRead; + +#[async_trait] +pub trait ChainStateReadExt: StateRead { + async fn get_chain_id(&self) -> Result; + async fn get_revision_number(&self) -> Result; + async fn get_block_height(&self) -> Result; + async fn get_block_timestamp(&self) -> Result; +} diff --git a/crates/cnidarium-component/src/lib.rs b/crates/cnidarium-component/src/lib.rs index 8c5fbde61f..37c27633dd 100644 --- a/crates/cnidarium-component/src/lib.rs +++ b/crates/cnidarium-component/src/lib.rs @@ -39,7 +39,9 @@ #![deny(clippy::unwrap_used)] mod action_handler; +mod chain_state_read_ext; mod component; pub use action_handler::ActionHandler; +pub use chain_state_read_ext::ChainStateReadExt; pub use component::Component; diff --git a/crates/core/app/Cargo.toml b/crates/core/app/Cargo.toml index dde38a32a4..94647ce6f2 100644 --- a/crates/core/app/Cargo.toml +++ b/crates/core/app/Cargo.toml @@ -30,6 +30,7 @@ penumbra-ibc = { path = "../component/ibc", features = ["component"] } penumbra-distributions = { path = "../component/distributions" } penumbra-compact-block = { path = "../component/compact-block" } penumbra-transaction = { path = "../transaction", features = ["parallel"] } +wrapper-derive = { path = "../../util/wrapper-derive" } # Penumbra dependencies decaf377 = { version = "0.5" } diff --git a/crates/core/app/src/action_handler/actions.rs b/crates/core/app/src/action_handler/actions.rs index d56f7745ca..d7d2b48ef0 100644 --- a/crates/core/app/src/action_handler/actions.rs +++ b/crates/core/app/src/action_handler/actions.rs @@ -10,6 +10,8 @@ use penumbra_txhash::TransactionContext; mod submit; +use crate::state_delta_wrapper::StateDeltaWrapper; + use super::ActionHandler; use cnidarium_component::ActionHandler as _; @@ -109,10 +111,12 @@ impl ActionHandler for Action { Action::Spend(action) => action.execute(state).await, Action::Output(action) => action.execute(state).await, Action::IbcRelay(action) => { + let mut state = state; + let wrapper = StateDeltaWrapper(&mut state); action .clone() .with_handler::() - .execute(state) + .execute(wrapper) .await } Action::Ics20Withdrawal(action) => action.execute(state).await, diff --git a/crates/core/app/src/app/mod.rs b/crates/core/app/src/app/mod.rs index 0641f460f8..042a51dbef 100644 --- a/crates/core/app/src/app/mod.rs +++ b/crates/core/app/src/app/mod.rs @@ -29,6 +29,7 @@ use tracing::Instrument; use crate::action_handler::ActionHandler; use crate::params::AppParameters; +use crate::state_delta_wrapper::ArcStateDeltaWrapper; use crate::{genesis, CommunityPoolStateReadExt}; pub mod state_key; @@ -213,11 +214,11 @@ impl App { } } - // Run each of the begin block handlers for each component, in sequence: + // Run each of the begin block handlers for each component, in sequence. let mut arc_state_tx = Arc::new(state_tx); ShieldedPool::begin_block(&mut arc_state_tx, begin_block).await; Distributions::begin_block(&mut arc_state_tx, begin_block).await; - IBCComponent::begin_block(&mut arc_state_tx, begin_block).await; + IBCComponent::begin_block(&mut ArcStateDeltaWrapper(&mut arc_state_tx), begin_block).await; Governance::begin_block(&mut arc_state_tx, begin_block).await; Staking::begin_block(&mut arc_state_tx, begin_block).await; Fee::begin_block(&mut arc_state_tx, begin_block).await; diff --git a/crates/core/app/src/lib.rs b/crates/core/app/src/lib.rs index 37a6b618f2..05b029cb85 100644 --- a/crates/core/app/src/lib.rs +++ b/crates/core/app/src/lib.rs @@ -2,6 +2,7 @@ mod action_handler; mod community_pool_ext; mod mock_client; +mod state_delta_wrapper; mod temp_storage_ext; pub use action_handler::ActionHandler; diff --git a/crates/core/app/src/state_delta_wrapper.rs b/crates/core/app/src/state_delta_wrapper.rs new file mode 100644 index 0000000000..9f6d3c1e15 --- /dev/null +++ b/crates/core/app/src/state_delta_wrapper.rs @@ -0,0 +1,17 @@ +use anyhow::Result; +use cnidarium::StateRead; +use cnidarium::StateWrite; +use cnidarium_component::ChainStateReadExt; +use penumbra_chain::component::StateReadExt; + +#[derive( + wrapper_derive::StateRead, wrapper_derive::StateWrite, wrapper_derive::ChainStateReadExt, +)] +pub(crate) struct StateDeltaWrapper<'a, S: StateRead + StateWrite>(pub(crate) &'a mut S); + +#[derive( + wrapper_derive::StateRead, wrapper_derive::StateWrite, wrapper_derive::ChainStateReadExt, +)] +pub(crate) struct ArcStateDeltaWrapper<'a, S: StateRead + StateWrite>( + pub(crate) &'a mut std::sync::Arc, +); diff --git a/crates/core/component/chain/src/component/view.rs b/crates/core/component/chain/src/component/view.rs index 44883b5ad6..e46d6a938e 100644 --- a/crates/core/component/chain/src/component/view.rs +++ b/crates/core/component/chain/src/component/view.rs @@ -17,7 +17,6 @@ use crate::{ /// /// Note: the `get_` methods in this trait assume that the state store has been /// initialized, so they will error on an empty state. -//#[async_trait(?Send)] #[async_trait] pub trait StateReadExt: StateRead { /// Indicates if the chain parameters have been updated in this block. diff --git a/crates/core/component/ibc/Cargo.toml b/crates/core/component/ibc/Cargo.toml index 2591d1de2c..356f5bfbcd 100644 --- a/crates/core/component/ibc/Cargo.toml +++ b/crates/core/component/ibc/Cargo.toml @@ -15,17 +15,18 @@ component = [ default = ["component", "std"] std = ["ibc-types/std"] docsrs = [] -rpc = ["dep:tonic", "ibc-proto/client", "ibc-proto/server"] +rpc = ["dep:tonic", "dep:penumbra-chain", "dep:wrapper-derive", "ibc-proto/client", "ibc-proto/server"] [dependencies] # Workspace dependencies penumbra-proto = { path = "../../../proto", default-features = false } cnidarium = { path = "../../../cnidarium", optional = true } cnidarium-component = { path = "../../../cnidarium-component", optional = true } -penumbra-chain = { path = "../chain", default-features = false } penumbra-asset = { path = "../../../core/asset", default-features = false } penumbra-num = { path = "../../../core/num", default-features = false } penumbra-keys = { path = "../../../core/keys", default-features = false } +wrapper-derive = { path = "../../../util/wrapper-derive", optional = true } +penumbra-chain = { path = "../chain", default-features = false, optional = true } penumbra-txhash = { path = "../../../core/txhash", default-features = false } # Penumbra dependencies @@ -55,4 +56,6 @@ tonic = { version = "0.10", optional = true } tower = "0.4" [dev-dependencies] +penumbra-chain = { path = "../chain", default-features = false } tokio = { version = "1.3", features = ["full"] } +wrapper-derive = { path = "../../../util/wrapper-derive" } diff --git a/crates/core/component/ibc/src/component/action_handler/ibc_action.rs b/crates/core/component/ibc/src/component/action_handler/ibc_action.rs index 479db57fed..6634b5ecb4 100644 --- a/crates/core/component/ibc/src/component/action_handler/ibc_action.rs +++ b/crates/core/component/ibc/src/component/action_handler/ibc_action.rs @@ -1,19 +1,16 @@ use std::sync::Arc; use anyhow::{Context, Result}; -use async_trait::async_trait; use cnidarium::{StateRead, StateWrite}; -use cnidarium_component::ActionHandler; +use cnidarium_component::ChainStateReadExt; use crate::{ component::{app_handler::AppHandler, MsgHandler as _}, IbcActionWithHandler, IbcRelay, }; -#[async_trait] -impl ActionHandler for IbcActionWithHandler { - type CheckStatelessContext = (); - async fn check_stateless(&self, _context: ()) -> Result<()> { +impl IbcActionWithHandler { + pub async fn check_stateless(&self, _context: ()) -> Result<()> { let action = self.action(); match action { IbcRelay::CreateClient(msg) => msg.check_stateless::().await?, @@ -41,12 +38,12 @@ impl ActionHandler for IbcActionWithHandler { Ok(()) } - async fn check_stateful(&self, _state: Arc) -> Result<()> { + pub async fn check_stateful<_S: StateRead + 'static>(&self, _state: Arc<_S>) -> Result<()> { // No-op: IBC actions merge check_stateful and execute. Ok(()) } - async fn execute(&self, state: S) -> Result<()> { + pub async fn execute(&self, state: S) -> Result<()> { let action = self.action(); match action { IbcRelay::CreateClient(msg) => msg diff --git a/crates/core/component/ibc/src/component/client.rs b/crates/core/component/ibc/src/component/client.rs index ed60595d85..1de6a2ec8e 100644 --- a/crates/core/component/ibc/src/component/client.rs +++ b/crates/core/component/ibc/src/component/client.rs @@ -8,12 +8,12 @@ use ibc_types::core::client::Height; use ibc_types::path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}; use cnidarium::{StateRead, StateWrite}; +use cnidarium_component::ChainStateReadExt; use ibc_types::lightclients::tendermint::{ client_state::ClientState as TendermintClientState, consensus_state::ConsensusState as TendermintConsensusState, header::Header as TendermintHeader, }; -use penumbra_chain::component::StateReadExt as _; use penumbra_proto::{StateReadProto, StateWriteProto}; use crate::component::client_counter::{ClientCounter, VerifiedHeights}; @@ -113,7 +113,53 @@ pub(crate) trait Ics2ClientExt: StateWrite { impl Ics2ClientExt for T {} #[async_trait] -pub trait StateWriteExt: StateWrite + StateReadExt { +pub trait ConsensusStateWriteExt: StateWrite + ChainStateReadExt { + async fn put_verified_consensus_state( + &mut self, + height: Height, + client_id: ClientId, + consensus_state: TendermintConsensusState, + ) -> Result<()> { + self.put( + IBC_COMMITMENT_PREFIX + .apply_string(ClientConsensusStatePath::new(&client_id, &height).to_string()), + consensus_state, + ); + + let current_height = self.get_block_height().await?; + let current_time: ibc_types::timestamp::Timestamp = + self.get_block_timestamp().await?.into(); + + self.put_proto::( + state_key::client_processed_times(&client_id, &height), + current_time.nanoseconds(), + ); + + self.put( + state_key::client_processed_heights(&client_id, &height), + ibc_types::core::client::Height::new(0, current_height)?, + ); + + // update verified heights + let mut verified_heights = + self.get_verified_heights(&client_id) + .await? + .unwrap_or(VerifiedHeights { + heights: Vec::new(), + }); + + verified_heights.heights.push(height); + + self.put_verified_heights(&client_id, verified_heights); + + Ok(()) + } +} + +impl ConsensusStateWriteExt for T {} + +#[async_trait] +pub trait StateWriteExt: StateWrite { fn put_client_counter(&mut self, counter: ClientCounter) { self.put("ibc_client_counter".into(), counter); } @@ -155,47 +201,6 @@ pub trait StateWriteExt: StateWrite + StateReadExt { consensus_state, ); } - - async fn put_verified_consensus_state( - &mut self, - height: Height, - client_id: ClientId, - consensus_state: TendermintConsensusState, - ) -> Result<()> { - self.put( - IBC_COMMITMENT_PREFIX - .apply_string(ClientConsensusStatePath::new(&client_id, &height).to_string()), - consensus_state, - ); - - let current_height = self.get_block_height().await?; - let current_time: ibc_types::timestamp::Timestamp = - self.get_block_timestamp().await?.into(); - - self.put_proto::( - state_key::client_processed_times(&client_id, &height), - current_time.nanoseconds(), - ); - - self.put( - state_key::client_processed_heights(&client_id, &height), - ibc_types::core::client::Height::new(0, current_height)?, - ); - - // update verified heights - let mut verified_heights = - self.get_verified_heights(&client_id) - .await? - .unwrap_or(VerifiedHeights { - heights: Vec::new(), - }); - - verified_heights.heights.push(height); - - self.put_verified_heights(&client_id, verified_heights); - - Ok(()) - } } impl StateWriteExt for T {} @@ -370,9 +375,9 @@ mod tests { use super::*; use cnidarium::{ArcStateDeltaExt, StateDelta}; - use cnidarium_component::ActionHandler; use ibc_types::core::client::msgs::MsgUpdateClient; use ibc_types::{core::client::msgs::MsgCreateClient, DomainType}; + use penumbra_chain::component::StateReadExt as _; use penumbra_chain::component::StateWriteExt; use std::str::FromStr; use tendermint::Time; @@ -386,6 +391,11 @@ mod tests { MsgChannelOpenConfirm, MsgChannelOpenInit, MsgChannelOpenTry, MsgRecvPacket, MsgTimeout, }; + #[derive( + wrapper_derive::StateRead, wrapper_derive::StateWrite, wrapper_derive::ChainStateReadExt, + )] + struct StateDeltaWrapper<'a, S: StateWrite>(&'a mut S); + struct MockAppHandler {} #[async_trait] @@ -539,7 +549,9 @@ mod tests { create_client_action.check_stateless(()).await?; create_client_action.check_stateful(state.clone()).await?; let mut state_tx = state.try_begin_transaction().unwrap(); - create_client_action.execute(&mut state_tx).await?; + create_client_action + .execute(StateDeltaWrapper(&mut state_tx)) + .await?; state_tx.apply(); // Check that state reflects +1 client apps registered. @@ -549,7 +561,9 @@ mod tests { update_client_action.check_stateless(()).await?; update_client_action.check_stateful(state.clone()).await?; let mut state_tx = state.try_begin_transaction().unwrap(); - update_client_action.execute(&mut state_tx).await?; + update_client_action + .execute(StateDeltaWrapper(&mut state_tx)) + .await?; state_tx.apply(); // We've had one client update, yes. What about second client update? @@ -567,7 +581,9 @@ mod tests { .check_stateful(state.clone()) .await?; let mut state_tx = state.try_begin_transaction().unwrap(); - second_update_client_action.execute(&mut state_tx).await?; + second_update_client_action + .execute(StateDeltaWrapper(&mut state_tx)) + .await?; state_tx.apply(); Ok(()) diff --git a/crates/core/component/ibc/src/component/ibc_component.rs b/crates/core/component/ibc/src/component/ibc_component.rs index 912896a5cd..16f651911b 100644 --- a/crates/core/component/ibc/src/component/ibc_component.rs +++ b/crates/core/component/ibc/src/component/ibc_component.rs @@ -1,26 +1,22 @@ use std::sync::Arc; use anyhow::Result; -use async_trait::async_trait; use cnidarium::StateWrite; -use cnidarium_component::Component; use ibc_types::{ core::client::Height, lightclients::tendermint::ConsensusState as TendermintConsensusState, }; -use penumbra_chain::component::StateReadExt as _; use tendermint::abci; use tracing::instrument; +use cnidarium_component::ChainStateReadExt; + use crate::component::{client::StateWriteExt as _, client_counter::ClientCounter}; pub struct IBCComponent {} -#[async_trait] -impl Component for IBCComponent { - type AppState = (); - +impl IBCComponent { #[instrument(name = "ibc", skip(state, app_state))] - async fn init_chain(mut state: S, app_state: Option<&()>) { + pub async fn init_chain(mut state: S, app_state: Option<&()>) { match app_state { Some(_) => state.put_client_counter(ClientCounter(0)), None => { /* perform upgrade specific check */ } @@ -28,11 +24,10 @@ impl Component for IBCComponent { } #[instrument(name = "ibc", skip(state, begin_block))] - async fn begin_block( - state: &mut Arc, + pub async fn begin_block( + state: &mut S, begin_block: &abci::request::BeginBlock, ) { - let state = Arc::get_mut(state).expect("state should be unique"); // In BeginBlock, we want to save a copy of our consensus state to our // own state tree, so that when we get a message from our // counterparties, we can verify that they are committing the correct @@ -61,14 +56,14 @@ impl Component for IBCComponent { } #[instrument(name = "ibc", skip(_state, _end_block))] - async fn end_block( + pub async fn end_block( mut _state: &mut Arc, _end_block: &abci::request::EndBlock, ) { } #[instrument(name = "ibc", skip(_state))] - async fn end_epoch(mut _state: &mut Arc) -> Result<()> { + pub async fn end_epoch(mut _state: &mut Arc) -> Result<()> { Ok(()) } } diff --git a/crates/core/component/ibc/src/component/msg_handler.rs b/crates/core/component/ibc/src/component/msg_handler.rs index 7a57a7fa19..e14cc45484 100644 --- a/crates/core/component/ibc/src/component/msg_handler.rs +++ b/crates/core/component/ibc/src/component/msg_handler.rs @@ -15,6 +15,7 @@ mod recv_packet; mod timeout; mod update_client; mod upgrade_client; +use cnidarium_component::ChainStateReadExt; use crate::component::app_handler::{AppHandlerCheck, AppHandlerExecute}; use anyhow::Result; @@ -26,7 +27,10 @@ use cnidarium::StateWrite; #[async_trait] pub(crate) trait MsgHandler { async fn check_stateless(&self) -> Result<()>; - async fn try_execute( + async fn try_execute< + S: StateWrite + ChainStateReadExt, + H: AppHandlerCheck + AppHandlerExecute, + >( &self, state: S, ) -> Result<()>; diff --git a/crates/core/component/ibc/src/component/msg_handler/acknowledgement.rs b/crates/core/component/ibc/src/component/msg_handler/acknowledgement.rs index 34e057cda4..beab40480e 100644 --- a/crates/core/component/ibc/src/component/msg_handler/acknowledgement.rs +++ b/crates/core/component/ibc/src/component/msg_handler/acknowledgement.rs @@ -14,6 +14,7 @@ use crate::component::{ proof_verification::{commit_packet, PacketProofVerifier}, MsgHandler, }; +use cnidarium_component::ChainStateReadExt; #[async_trait] impl MsgHandler for MsgAcknowledgement { @@ -23,7 +24,10 @@ impl MsgHandler for MsgAcknowledgement { Ok(()) } - async fn try_execute( + async fn try_execute< + S: StateWrite + ChainStateReadExt, + H: AppHandlerCheck + AppHandlerExecute, + >( &self, mut state: S, ) -> Result<()> { diff --git a/crates/core/component/ibc/src/component/msg_handler/connection_open_ack.rs b/crates/core/component/ibc/src/component/msg_handler/connection_open_ack.rs index 1b3757ec6d..de2bdf3f66 100644 --- a/crates/core/component/ibc/src/component/msg_handler/connection_open_ack.rs +++ b/crates/core/component/ibc/src/component/msg_handler/connection_open_ack.rs @@ -1,13 +1,13 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use cnidarium::{StateRead, StateWrite}; +use cnidarium_component::ChainStateReadExt; use ibc_types::core::{ client::Height, connection::{events, msgs::MsgConnectionOpenAck, ConnectionEnd, Counterparty, State}, }; use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState; use ibc_types::path::{ClientConsensusStatePath, ClientStatePath, ConnectionPath}; -use penumbra_chain::component::StateReadExt as _; use crate::{ component::{ @@ -25,7 +25,7 @@ impl MsgHandler for MsgConnectionOpenAck { Ok(()) } - async fn try_execute(&self, mut state: S) -> Result<()> { + async fn try_execute(&self, mut state: S) -> Result<()> { tracing::debug!(msg = ?self); // Validate a ConnectionOpenAck message, which is sent to us by a counterparty chain that // has committed a Connection to us expected to be in the TRYOPEN state. Before executing a @@ -190,8 +190,8 @@ impl MsgHandler for MsgConnectionOpenAck { Ok(()) } } -async fn consensus_height_is_correct( - state: S, +async fn consensus_height_is_correct( + state: &S, msg: &MsgConnectionOpenAck, ) -> anyhow::Result<()> { let current_height = Height::new( @@ -205,8 +205,8 @@ async fn consensus_height_is_correct( Ok(()) } -async fn penumbra_client_state_is_well_formed( - state: S, +async fn penumbra_client_state_is_well_formed( + state: &S, msg: &MsgConnectionOpenAck, ) -> anyhow::Result<()> { let height = state.get_block_height().await?; diff --git a/crates/core/component/ibc/src/component/msg_handler/connection_open_try.rs b/crates/core/component/ibc/src/component/msg_handler/connection_open_try.rs index e7c5b12d09..6c739d859f 100644 --- a/crates/core/component/ibc/src/component/msg_handler/connection_open_try.rs +++ b/crates/core/component/ibc/src/component/msg_handler/connection_open_try.rs @@ -3,7 +3,8 @@ use crate::version::pick_connection_version; use crate::IBC_COMMITMENT_PREFIX; use anyhow::{Context, Result}; use async_trait::async_trait; -use cnidarium::{StateRead, StateWrite}; +use cnidarium::StateWrite; +use cnidarium_component::ChainStateReadExt; use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState; use ibc_types::path::{ClientConsensusStatePath, ClientStatePath, ConnectionPath}; use ibc_types::{ @@ -13,7 +14,6 @@ use ibc_types::{ State as ConnectionState, }, }; -use penumbra_chain::component::StateReadExt as _; use crate::component::{ client::StateReadExt as _, @@ -30,7 +30,7 @@ impl MsgHandler for MsgConnectionOpenTry { Ok(()) } - async fn try_execute(&self, mut state: S) -> Result<()> { + async fn try_execute(&self, mut state: S) -> Result<()> { tracing::debug!(msg = ?self); // Validate a ConnectionOpenTry message, which is sent to us by a counterparty chain that @@ -50,11 +50,11 @@ impl MsgHandler for MsgConnectionOpenTry { // POSTERIOR STATE: (INIT, TRYOPEN) // verify that the consensus height is correct - consensus_height_is_correct(&mut state, self).await?; + consensus_height_is_correct(&state, self).await?; // verify that the client state (which is a Penumbra client) is well-formed for a // penumbra client. - penumbra_client_state_is_well_formed(&mut state, self).await?; + penumbra_client_state_is_well_formed(&state, self).await?; // TODO(erwan): how to handle this with ibc-rs@0.23.0? // if this msg provides a previous_connection_id to resume from, then check that the @@ -197,8 +197,8 @@ impl MsgHandler for MsgConnectionOpenTry { Ok(()) } } -async fn consensus_height_is_correct( - state: S, +async fn consensus_height_is_correct( + state: &S, msg: &MsgConnectionOpenTry, ) -> anyhow::Result<()> { let current_height = IBCHeight::new( @@ -211,8 +211,8 @@ async fn consensus_height_is_correct( Ok(()) } -async fn penumbra_client_state_is_well_formed( - state: S, +async fn penumbra_client_state_is_well_formed( + state: &S, msg: &MsgConnectionOpenTry, ) -> anyhow::Result<()> { let height = state.get_block_height().await?; diff --git a/crates/core/component/ibc/src/component/msg_handler/create_client.rs b/crates/core/component/ibc/src/component/msg_handler/create_client.rs index 93350861f8..fbf3ae9c5f 100644 --- a/crates/core/component/ibc/src/component/msg_handler/create_client.rs +++ b/crates/core/component/ibc/src/component/msg_handler/create_client.rs @@ -1,13 +1,14 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use cnidarium::StateWrite; +use cnidarium_component::ChainStateReadExt; use ibc_types::{ core::client::{events::CreateClient, msgs::MsgCreateClient, ClientId}, lightclients::tendermint::client_type, }; use crate::component::{ - client::{StateReadExt as _, StateWriteExt as _}, + client::{ConsensusStateWriteExt as _, StateReadExt as _, StateWriteExt as _}, client_counter::ClientCounter, ics02_validation, MsgHandler, }; @@ -28,7 +29,7 @@ impl MsgHandler for MsgCreateClient { // - client type // - consensus state // - processed time and height - async fn try_execute(&self, mut state: S) -> Result<()> { + async fn try_execute(&self, mut state: S) -> Result<()> { tracing::debug!(msg = ?self); let client_state = ics02_validation::get_tendermint_client_state(self.client_state.clone())?; diff --git a/crates/core/component/ibc/src/component/msg_handler/misbehavior.rs b/crates/core/component/ibc/src/component/msg_handler/misbehavior.rs index 8bcde276cf..e81b69d766 100644 --- a/crates/core/component/ibc/src/component/msg_handler/misbehavior.rs +++ b/crates/core/component/ibc/src/component/msg_handler/misbehavior.rs @@ -7,12 +7,13 @@ use ibc_types::lightclients::tendermint::client_state::ClientState as Tendermint use ibc_types::lightclients::tendermint::header::Header as TendermintHeader; use ibc_types::lightclients::tendermint::misbehaviour::Misbehaviour as TendermintMisbehavior; use ibc_types::lightclients::tendermint::TENDERMINT_CLIENT_TYPE; -use penumbra_chain::component::StateReadExt as _; use tendermint_light_client_verifier::{ types::{TrustedBlockState, UntrustedBlockState}, ProdVerifier, Verdict, Verifier, }; +use cnidarium_component::ChainStateReadExt; + use super::update_client::verify_header_validator_set; use super::MsgHandler; use crate::component::{client::StateWriteExt as _, ics02_validation, ClientStateReadExt as _}; @@ -35,7 +36,7 @@ impl MsgHandler for MsgSubmitMisbehaviour { Ok(()) } - async fn try_execute(&self, mut state: S) -> Result<()> { + async fn try_execute(&self, mut state: S) -> Result<()> { tracing::debug!(msg = ?self); let untrusted_misbehavior = @@ -111,8 +112,8 @@ fn client_is_not_frozen(client: &TendermintClientState) -> anyhow::Result<()> { } } -async fn verify_misbehavior_header( - state: S, +async fn verify_misbehavior_header( + state: &S, client_id: &ClientId, mb_header: &TendermintHeader, trusted_client_state: &TendermintClientState, diff --git a/crates/core/component/ibc/src/component/msg_handler/recv_packet.rs b/crates/core/component/ibc/src/component/msg_handler/recv_packet.rs index 45975fc30d..68943d434e 100644 --- a/crates/core/component/ibc/src/component/msg_handler/recv_packet.rs +++ b/crates/core/component/ibc/src/component/msg_handler/recv_packet.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use cnidarium::StateWrite; +use cnidarium_component::ChainStateReadExt; use ibc_types::core::{ channel::{ channel::{Order as ChannelOrder, State as ChannelState}, @@ -11,7 +12,6 @@ use ibc_types::core::{ client::Height as IBCHeight, connection::State as ConnectionState, }; -use penumbra_chain::component::StateReadExt; use crate::component::{ app_handler::{AppHandlerCheck, AppHandlerExecute}, @@ -29,7 +29,10 @@ impl MsgHandler for MsgRecvPacket { Ok(()) } - async fn try_execute( + async fn try_execute< + S: StateWrite + ChainStateReadExt, + H: AppHandlerCheck + AppHandlerExecute, + >( &self, mut state: S, ) -> Result<()> { diff --git a/crates/core/component/ibc/src/component/msg_handler/timeout.rs b/crates/core/component/ibc/src/component/msg_handler/timeout.rs index 300b3f0cc0..bc1f592f8d 100644 --- a/crates/core/component/ibc/src/component/msg_handler/timeout.rs +++ b/crates/core/component/ibc/src/component/msg_handler/timeout.rs @@ -1,6 +1,7 @@ use anyhow::Result; use async_trait::async_trait; use cnidarium::StateWrite; +use cnidarium_component::ChainStateReadExt; use ibc_types::core::channel::{ channel::{Order as ChannelOrder, State as ChannelState}, events, @@ -25,7 +26,10 @@ impl MsgHandler for MsgTimeout { Ok(()) } - async fn try_execute( + async fn try_execute< + S: StateWrite + ChainStateReadExt, + H: AppHandlerCheck + AppHandlerExecute, + >( &self, mut state: S, ) -> Result<()> { diff --git a/crates/core/component/ibc/src/component/msg_handler/update_client.rs b/crates/core/component/ibc/src/component/msg_handler/update_client.rs index 635e622c03..68e1ac97cf 100644 --- a/crates/core/component/ibc/src/component/msg_handler/update_client.rs +++ b/crates/core/component/ibc/src/component/msg_handler/update_client.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use cnidarium::{StateRead, StateWrite}; +use cnidarium_component::ChainStateReadExt; use ibc_types::{ core::{client::events::UpdateClient, client::msgs::MsgUpdateClient, client::ClientId}, lightclients::tendermint::client_state::ClientState as TendermintClientState, @@ -9,7 +10,6 @@ use ibc_types::{ consensus_state::ConsensusState as TendermintConsensusState, TENDERMINT_CLIENT_TYPE, }, }; -use penumbra_chain::component::StateReadExt as _; use tendermint::validator; use tendermint_light_client_verifier::{ types::{TrustedBlockState, UntrustedBlockState}, @@ -17,7 +17,9 @@ use tendermint_light_client_verifier::{ }; use crate::component::{ - client::{Ics2ClientExt as _, StateReadExt as _, StateWriteExt as _}, + client::{ + ConsensusStateWriteExt as _, Ics2ClientExt as _, StateReadExt as _, StateWriteExt as _, + }, ics02_validation, MsgHandler, }; @@ -29,7 +31,7 @@ impl MsgHandler for MsgUpdateClient { Ok(()) } - async fn try_execute(&self, mut state: S) -> Result<()> { + async fn try_execute(&self, mut state: S) -> Result<()> { // Optimization: no-op if the update is already committed. We no-op // to Ok(()) rather than erroring to avoid having two "racing" relay // transactions fail just because they both contain the same client @@ -190,8 +192,8 @@ async fn update_is_already_committed( } } -async fn client_is_not_expired( - state: S, +async fn client_is_not_expired( + state: &S, client_id: &ClientId, client_state: &TendermintClientState, ) -> anyhow::Result<()> { diff --git a/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs b/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs index 69e5068734..b393b6ce46 100644 --- a/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs +++ b/crates/core/component/ibc/src/component/msg_handler/upgrade_client.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use cnidarium::StateWrite; +use cnidarium_component::ChainStateReadExt; use ibc_types::{ core::{ client::{events, msgs::MsgUpgradeClient}, @@ -12,6 +13,7 @@ use ibc_types::{ }, }; +use crate::component::client::ConsensusStateWriteExt as _; use crate::component::{ client::{StateReadExt as _, StateWriteExt as _}, proof_verification::ClientUpgradeProofVerifier, @@ -37,7 +39,7 @@ impl MsgHandler for MsgUpgradeClient { // // the first consensus state of the upgraded client uses a sentinel root, against which no // proofs will verify. subsequent client updates, post-upgrade, will provide usable roots. - async fn try_execute(&self, mut state: S) -> Result<()> { + async fn try_execute(&self, mut state: S) -> Result<()> { tracing::debug!(msg = ?self); let upgraded_client_state_tm = TendermintClientState::try_from(self.client_state.clone()) diff --git a/crates/core/component/ibc/src/component/proof_verification.rs b/crates/core/component/ibc/src/component/proof_verification.rs index 777787a2ab..c18562606d 100644 --- a/crates/core/component/ibc/src/component/proof_verification.rs +++ b/crates/core/component/ibc/src/component/proof_verification.rs @@ -1,5 +1,6 @@ use crate::component::client::StateReadExt; +use cnidarium_component::ChainStateReadExt; use core::time::Duration; use ibc_proto::Protobuf; use ibc_types::path::{ClientConsensusStatePath, ClientUpgradePath}; @@ -28,7 +29,7 @@ use ibc_types::{ use async_trait::async_trait; use cnidarium::StateRead; use num_traits::float::FloatCore; -use penumbra_chain::component::StateReadExt as _; +//use penumbra_chain::component::StateReadExt as _; use sha2::{Digest, Sha256}; // NOTE: this is underspecified. @@ -417,13 +418,13 @@ pub trait PacketProofVerifier: StateReadExt + inner::Inner { } } -impl PacketProofVerifier for T {} +impl PacketProofVerifier for T {} mod inner { use super::*; #[async_trait] - pub trait Inner: StateReadExt { + pub trait Inner: StateRead + ChainStateReadExt { async fn get_trusted_client_and_consensus_state( &self, client_id: &ClientId, @@ -471,5 +472,5 @@ mod inner { } } - impl Inner for T {} + impl Inner for T {} } diff --git a/crates/core/component/ibc/src/component/rpc.rs b/crates/core/component/ibc/src/component/rpc.rs index e58fe727cd..f9661c28a0 100644 --- a/crates/core/component/ibc/src/component/rpc.rs +++ b/crates/core/component/ibc/src/component/rpc.rs @@ -1,21 +1,39 @@ -use cnidarium::Storage; +use anyhow::Result; +use async_trait::async_trait; +use ibc_types::core::commitment::MerkleProof; +use std::marker::PhantomData; + +use cnidarium_component::ChainStateReadExt; use tonic::transport::server::Routes; mod client_query; mod connection_query; mod consensus_query; +// Implemented by [`cnidarium::Storage`]. +// Used as a wrapper so external crates can implemented their own [`ChainStateReadExt`]. +pub trait Storage: Send + Sync + 'static { + fn latest_snapshot(&self) -> C; +} + +// Implemented by [`cnidarium::Snapshot`]. +#[async_trait] +pub trait Snapshot { + fn version(&self) -> u64; + async fn get_with_proof(&self, key: Vec) -> Result<(Option>, MerkleProof)>; +} + // TODO: hide and replace with a routes() constructor that // bundles up all the internal services #[derive(Clone)] -pub struct IbcQuery(cnidarium::Storage); +pub struct IbcQuery(S, PhantomData); -impl IbcQuery { - pub fn new(storage: cnidarium::Storage) -> Self { - Self(storage) +impl> IbcQuery { + pub fn new(storage: S) -> Self { + Self(storage, PhantomData) } } -pub fn routes(_storage: Storage) -> Routes { +pub fn routes(_storage: cnidarium::Storage) -> Routes { unimplemented!("functionality we need is only in tonic 0.10") } diff --git a/crates/core/component/ibc/src/component/rpc/client_query.rs b/crates/core/component/ibc/src/component/rpc/client_query.rs index 15e31f72b1..7fe17d6dd6 100644 --- a/crates/core/component/ibc/src/component/rpc/client_query.rs +++ b/crates/core/component/ibc/src/component/rpc/client_query.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; - +use cnidarium_component::ChainStateReadExt; use ibc_proto::ibc::core::client::v1::query_server::Query as ClientQuery; use ibc_proto::ibc::core::client::v1::{ ConsensusStateWithHeight, IdentifiedClientState, QueryClientParamsRequest, @@ -11,7 +11,6 @@ use ibc_proto::ibc::core::client::v1::{ QueryUpgradedClientStateResponse, QueryUpgradedConsensusStateRequest, QueryUpgradedConsensusStateResponse, }; - use ibc_types::core::client::ClientId; use ibc_types::core::client::Height; use ibc_types::lightclients::tendermint::client_state::TENDERMINT_CLIENT_STATE_TYPE_URL; @@ -23,15 +22,15 @@ use ibc_types::DomainType; use std::str::FromStr; use tonic::{Response, Status}; +use crate::component::rpc::Snapshot; +use crate::component::rpc::{IbcQuery, Storage}; use crate::component::ClientStateReadExt; use crate::prefix::MerklePrefixExt; use crate::IBC_COMMITMENT_PREFIX; use penumbra_chain::component::StateReadExt as _; -use super::IbcQuery; - #[async_trait] -impl ClientQuery for IbcQuery { +impl> ClientQuery for IbcQuery { async fn client_state( &self, request: tonic::Request, @@ -40,6 +39,8 @@ impl ClientQuery for IbcQuery { let client_id = ClientId::from_str(&request.get_ref().client_id) .map_err(|e| tonic::Status::invalid_argument(format!("invalid client id: {e}")))?; let height = Height { + // TODO: need to pass in the `SnapshotWrapper` type somehow, + // need to make a generic arg that will wrap the snapshot? revision_number: snapshot .get_revision_number() .await diff --git a/crates/core/component/ibc/src/component/rpc/connection_query.rs b/crates/core/component/ibc/src/component/rpc/connection_query.rs index 91999fa056..7521255134 100644 --- a/crates/core/component/ibc/src/component/rpc/connection_query.rs +++ b/crates/core/component/ibc/src/component/rpc/connection_query.rs @@ -1,5 +1,6 @@ use async_trait::async_trait; +use cnidarium_component::ChainStateReadExt; use ibc_proto::ibc::core::client::v1::{Height, IdentifiedClientState}; use ibc_proto::ibc::core::connection::v1::query_server::Query as ConnectionQuery; use ibc_proto::ibc::core::connection::v1::{ @@ -19,6 +20,7 @@ use ibc_types::DomainType; use prost::Message; use std::str::FromStr; +use crate::component::rpc::{Snapshot, Storage}; use crate::component::ConnectionStateReadExt; use crate::prefix::MerklePrefixExt; use crate::IBC_COMMITMENT_PREFIX; @@ -26,7 +28,7 @@ use crate::IBC_COMMITMENT_PREFIX; use super::IbcQuery; #[async_trait] -impl ConnectionQuery for IbcQuery { +impl> ConnectionQuery for IbcQuery { /// Connection queries an IBC connection end. async fn connection( &self, diff --git a/crates/core/component/ibc/src/component/rpc/consensus_query.rs b/crates/core/component/ibc/src/component/rpc/consensus_query.rs index bc22c31b53..2e88efa3ea 100644 --- a/crates/core/component/ibc/src/component/rpc/consensus_query.rs +++ b/crates/core/component/ibc/src/component/rpc/consensus_query.rs @@ -1,6 +1,7 @@ use crate::prefix::MerklePrefixExt; use crate::IBC_COMMITMENT_PREFIX; use async_trait::async_trait; +use cnidarium_component::ChainStateReadExt; use ibc_proto::ibc::core::channel::v1::query_server::Query as ConsensusQuery; use ibc_proto::ibc::core::channel::v1::{ Channel, PacketState, QueryChannelClientStateRequest, QueryChannelClientStateResponse, @@ -23,18 +24,18 @@ use ibc_types::path::{ use ibc_types::DomainType; use ibc_types::core::channel::{ChannelId, IdentifiedChannelEnd, PortId}; - use ibc_types::core::connection::ConnectionId; use prost::Message; use std::str::FromStr; +use crate::component::rpc::{Snapshot, Storage}; use crate::component::{ChannelStateReadExt, ConnectionStateReadExt}; use super::IbcQuery; #[async_trait] -impl ConsensusQuery for IbcQuery { +impl> ConsensusQuery for IbcQuery { /// Channel queries an IBC Channel. async fn channel( &self, diff --git a/crates/util/wrapper-derive/Cargo.toml b/crates/util/wrapper-derive/Cargo.toml new file mode 100644 index 0000000000..fb313011b6 --- /dev/null +++ b/crates/util/wrapper-derive/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wrapper-derive" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +syn = "1.0" +quote = "1.0" +proc-macro2 = "1.0" diff --git a/crates/util/wrapper-derive/src/lib.rs b/crates/util/wrapper-derive/src/lib.rs new file mode 100644 index 0000000000..4fed8551a3 --- /dev/null +++ b/crates/util/wrapper-derive/src/lib.rs @@ -0,0 +1,233 @@ +extern crate proc_macro; + +use proc_macro::TokenStream; +use quote::quote; +use syn::{parse_macro_input, DeriveInput}; + +fn is_arc_type(ty: &syn::Type) -> bool { + if let syn::Type::Path(type_path) = ty { + return type_path.path.segments.iter().any(|seg| seg.ident == "Arc"); + } + + if let syn::Type::Reference(type_ref) = ty { + return is_arc_type(&type_ref.elem); + } + + false +} + +#[proc_macro_derive(StateRead)] +pub fn state_read_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let _ = match &input.data { + syn::Data::Struct(s) => { + &s.fields + .iter() + .next() + .expect("struct must have one field") + .ty + } + _ => panic!("StateRead can only be derived for structs"), + }; + + let expanded = quote! { + impl #impl_generics StateRead for #name #ty_generics #where_clause { + type GetRawFut = S::GetRawFut; + type PrefixRawStream = S::PrefixRawStream; + type PrefixKeysStream = S::PrefixKeysStream; + type NonconsensusPrefixRawStream = S::NonconsensusPrefixRawStream; + type NonconsensusRangeRawStream = S::NonconsensusRangeRawStream; + + fn get_raw(&self, key: &str) -> Self::GetRawFut { + self.0.get_raw(key) + } + + fn nonverifiable_get_raw(&self, key: &[u8]) -> Self::GetRawFut { + self.0.nonverifiable_get_raw(key) + } + + fn object_get(&self, key: &'static str) -> Option { + self.0.object_get(key) + } + + fn object_type(&self, key: &'static str) -> Option { + self.0.object_type(key) + } + + fn prefix_raw(&self, prefix: &str) -> Self::PrefixRawStream { + self.0.prefix_raw(prefix) + } + + fn prefix_keys(&self, prefix: &str) -> Self::PrefixKeysStream { + self.0.prefix_keys(prefix) + } + + fn nonverifiable_prefix_raw(&self, prefix: &[u8]) -> Self::NonconsensusPrefixRawStream { + self.0.nonverifiable_prefix_raw(prefix) + } + + fn nonverifiable_range_raw( + &self, + prefix: Option<&[u8]>, + range: impl std::ops::RangeBounds>, + ) -> Result { + self.0.nonverifiable_range_raw(prefix, range) + } + } + + }; + + TokenStream::from(expanded) +} + +#[proc_macro_derive(StateWrite)] +pub fn state_write_derive(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let field_type = match &input.data { + syn::Data::Struct(s) => { + &s.fields + .iter() + .next() + .expect("struct must have one field") + .ty + } + _ => panic!("StateWrite can only be derived for structs"), + }; + + let expanded = if is_arc_type(field_type) { + quote! { + impl #impl_generics StateWrite for #name #ty_generics #where_clause { + fn put_raw(&mut self, key: String, value: Vec) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.put_raw(key, value) + } + + fn delete(&mut self, key: String) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.delete(key) + } + + fn nonverifiable_put_raw(&mut self, key: Vec, value: Vec) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.nonverifiable_put_raw(key, value) + } + + fn nonverifiable_delete(&mut self, key: Vec) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.nonverifiable_delete(key) + } + + fn object_put(&mut self, key: &'static str, value: T) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.object_put(key, value) + } + + fn object_delete(&mut self, key: &'static str) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.object_delete(key) + } + + fn object_merge(&mut self, objects: std::collections::BTreeMap<&'static str, Option>>) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.object_merge(objects) + } + + fn record(&mut self, event: tendermint::abci::Event) { + let state = std::sync::Arc::get_mut(&mut self.0).expect("state is not unique"); + state.record(event) + } + } + } + } else { + quote! { + impl #impl_generics StateWrite for #name #ty_generics #where_clause { + fn put_raw(&mut self, key: String, value: Vec) { + self.0.put_raw(key, value) + } + + fn delete(&mut self, key: String) { + self.0.delete(key) + } + + fn nonverifiable_put_raw(&mut self, key: Vec, value: Vec) { + self.0.nonverifiable_put_raw(key, value) + } + + fn nonverifiable_delete(&mut self, key: Vec) { + self.0.nonverifiable_delete(key) + } + + fn object_put(&mut self, key: &'static str, value: T) { + self.0.object_put(key, value) + } + + fn object_delete(&mut self, key: &'static str) { + self.0.object_delete(key) + } + + fn object_merge(&mut self, objects: std::collections::BTreeMap<&'static str, Option>>) { + self.0.object_merge(objects) + } + + fn record(&mut self, event: tendermint::abci::Event) { + self.0.record(event) + } + } + } + }; + + TokenStream::from(expanded) +} + +#[proc_macro_derive(ChainStateReadExt)] +pub fn chain_state_read_ext(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let generics = &input.generics; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let _ = match &input.data { + syn::Data::Struct(s) => { + &s.fields + .iter() + .next() + .expect("struct must have one field") + .ty + } + _ => panic!("ChainStateReadExt can only be derived for structs"), + }; + + let expanded = quote! { + #[async_trait::async_trait] + impl #impl_generics ChainStateReadExt for #name #ty_generics #where_clause { + async fn get_chain_id(&self) -> Result { + self.0.get_chain_id().await + } + + async fn get_revision_number(&self) -> Result { + self.0.get_revision_number().await + } + + async fn get_block_height(&self) -> Result { + self.0.get_block_height().await + } + + async fn get_block_timestamp(&self) -> Result { + self.0.get_block_timestamp().await + } + } + }; + + TokenStream::from(expanded) +}