From c78f6275be4d6602d7bdc7ca9fb20febc526197e Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 29 Sep 2021 22:58:38 +0530 Subject: [PATCH 01/42] Include commitment, connection and ics23 prost gen files --- src/prostgen.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/prostgen.rs b/src/prostgen.rs index 469cf371..4f201c43 100644 --- a/src/prostgen.rs +++ b/src/prostgen.rs @@ -46,5 +46,21 @@ pub mod ibc { include!("prostgen/ibc.core.client.v1.rs"); } } + + pub mod commitment { + pub mod v1 { + include!("prostgen/ibc.core.commitment.v1.rs"); + } + } + + pub mod connection { + pub mod v1 { + include!("prostgen/ibc.core.connection.v1.rs"); + } + } } } + +pub mod ics23 { + include!("prostgen/ics23.rs"); +} From a8bb61fa7e5dc211ac618ac55f5682e85b972a64 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 29 Sep 2021 23:28:59 +0530 Subject: [PATCH 02/42] Implement IBC Connection Reader/Keeper traits --- src/app/mod.rs | 1 + src/app/modules/ibc.rs | 133 +++++++++++++++++++++++++++++++++-------- src/main.rs | 1 + 3 files changed, 111 insertions(+), 24 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 3b755b36..45b55198 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -72,6 +72,7 @@ impl BaseCoinApp { Box::new(Ibc { store: SubStore::new(store.clone(), prefix::Ibc), client_counter: 0, + conn_counter: 0, }), ]; Self { diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index ccd23e7f..2da27c07 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -1,4 +1,4 @@ -use crate::app::modules::{Error as ModuleError, Module}; +use crate::app::modules::{Error as ModuleError, Identifiable, Module}; use crate::app::store::{Height, Path, ProvableStore, Store}; use crate::prostgen::ibc::core::client::v1::{ query_server::Query as ClientQuery, ConsensusStateWithHeight, Height as RawHeight, @@ -31,7 +31,8 @@ use ibc::core::ics04_channel::packet::{Receipt, Sequence}; use ibc::core::ics05_port::capabilities::Capability; use ibc::core::ics05_port::context::PortReader; use ibc::core::ics05_port::error::Error as PortError; -use ibc::core::ics23_commitment::commitment::CommitmentPrefix; +use ibc::core::ics07_tendermint::consensus_state::ConsensusState; +use ibc::core::ics23_commitment::commitment::{CommitmentPrefix, CommitmentRoot}; use ibc::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use ibc::core::ics24_host::IBC_QUERY_PATH; use ibc::core::ics26_routing::context::Ics26Context; @@ -41,6 +42,7 @@ use ibc::timestamp::Timestamp; use ibc::Height as IbcHeight; use prost::Message; use prost_types::Any; +use tendermint::{Hash, Time}; use tendermint_proto::abci::{Event, EventAttribute}; use tonic::{Request, Response, Status}; use tracing::{debug, trace}; @@ -61,8 +63,10 @@ pub struct Ibc { /// Handle to store instance. /// The module is guaranteed exclusive access to all paths in the store key-space. pub store: S, - /// Counter for client identifiers + /// Counter for clients pub client_counter: u64, + /// Counter for connections + pub conn_counter: u64, } impl ClientReader for Ibc { @@ -72,9 +76,8 @@ impl ClientReader for Ibc { .unwrap(); // safety - path must be valid since ClientId is a valid Identifier self.store .get(Height::Pending, &path) - .map(|v| serde_json::from_str(&String::from_utf8(v).unwrap()).unwrap()) + .map(|v| serde_json::from_str(&String::from_utf8(v).unwrap()).unwrap()) // safety - data on the store is assumed to be well-formed .ok_or_else(ClientError::implementation_specific) - // safety - data on the store is assumed to be well-formed } fn client_state(&self, client_id: &ClientId) -> Result { @@ -187,65 +190,91 @@ impl ClientKeeper for Ibc { } impl ConnectionReader for Ibc { - fn connection_end(&self, _conn_id: &ConnectionId) -> Result { - todo!() + fn connection_end(&self, conn_id: &ConnectionId) -> Result { + let path = format!("connection/{}", conn_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier + self.store + .get(Height::Pending, &path) + // safety - data on the store is assumed to be well-formed + .map(|v| serde_json::from_str(&String::from_utf8(v).unwrap()).unwrap()) + .ok_or_else(ConnectionError::implementation_specific) } - fn client_state(&self, _client_id: &ClientId) -> Result { - todo!() + fn client_state(&self, client_id: &ClientId) -> Result { + ClientReader::client_state(self, client_id).map_err(ConnectionError::ics02_client) } fn host_current_height(&self) -> IbcHeight { - todo!() + IbcHeight::new(0, self.store.current_height()) } fn host_oldest_height(&self) -> IbcHeight { - todo!() + IbcHeight::zero() } fn commitment_prefix(&self) -> CommitmentPrefix { - todo!() + use super::prefix::Ibc as IbcPrefix; + CommitmentPrefix::from_bytes(IbcPrefix {}.identifier().as_bytes()) } fn client_consensus_state( &self, - _client_id: &ClientId, - _height: IbcHeight, + client_id: &ClientId, + height: IbcHeight, ) -> Result { - todo!() + ClientReader::consensus_state(self, client_id, height) + .map_err(ConnectionError::ics02_client) } fn host_consensus_state( &self, _height: IbcHeight, ) -> Result { - todo!() + Ok(AnyConsensusState::Tendermint(ConsensusState::new( + CommitmentRoot::from_bytes(&[]), + Time::now(), + Hash::None, + ))) } fn connection_counter(&self) -> Result { - todo!() + Ok(self.conn_counter) } } impl ConnectionKeeper for Ibc { fn store_connection( &mut self, - _connection_id: ConnectionId, - _connection_end: &ConnectionEnd, + connection_id: ConnectionId, + connection_end: &ConnectionEnd, ) -> Result<(), ConnectionError> { - todo!() + let path = format!("connection/{}", connection_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier + self.store + .set(path, serde_json::to_string(&connection_end).unwrap().into()) // safety - cannot fail since ClientType's Serialize impl doesn't fail + .map_err(|_| ConnectionError::implementation_specific()) } fn store_connection_to_client( &mut self, - _connection_id: ConnectionId, - _client_id: &ClientId, + connection_id: ConnectionId, + client_id: &ClientId, ) -> Result<(), ConnectionError> { - todo!() + let path = format!("clients/{}/connections", client_id) + .try_into() + .unwrap(); // safety - path must be valid since ClientId is a valid Identifier + let mut conn_ids: Vec = self + .store + .get(Height::Pending, &path) + .map(|v| serde_json::from_str(&String::from_utf8(v).unwrap()).unwrap()) // safety - data on the store is assumed to be well-formed + .unwrap_or_default(); + + conn_ids.push(connection_id); + self.store + .set(path, serde_json::to_string(&conn_ids).unwrap().into()) // safety - cannot fail since ClientType's Serialize impl doesn't fail + .map_err(|_| ConnectionError::implementation_specific()) } fn increase_connection_counter(&mut self) { - todo!() + self.conn_counter += 1; } } @@ -508,6 +537,62 @@ impl From for Event { index: false, }], }, + IbcEvent::OpenInitConnection(conn_open_init) => Self { + r#type: "connection_open_init".to_string(), + attributes: vec![EventAttribute { + key: "connection_id".as_bytes().to_vec(), + value: conn_open_init + .connection_id() + .as_ref() + .unwrap() + .to_string() + .as_bytes() + .to_vec(), + index: false, + }], + }, + IbcEvent::OpenTryConnection(conn_open_try) => Self { + r#type: "connection_open_try".to_string(), + attributes: vec![EventAttribute { + key: "connection_id".as_bytes().to_vec(), + value: conn_open_try + .connection_id() + .as_ref() + .unwrap() + .to_string() + .as_bytes() + .to_vec(), + index: false, + }], + }, + IbcEvent::OpenAckConnection(conn_open_ack) => Self { + r#type: "connection_open_ack".to_string(), + attributes: vec![EventAttribute { + key: "connection_id".as_bytes().to_vec(), + value: conn_open_ack + .connection_id() + .as_ref() + .unwrap() + .to_string() + .as_bytes() + .to_vec(), + index: false, + }], + }, + IbcEvent::OpenConfirmConnection(conn_open_confirm) => Self { + r#type: "connection_open_confirm".to_string(), + attributes: vec![EventAttribute { + key: "connection_id".as_bytes().to_vec(), + value: conn_open_confirm + .connection_id() + .as_ref() + .unwrap() + .to_string() + .as_bytes() + .to_vec(), + index: false, + }], + }, _ => todo!(), } } diff --git a/src/main.rs b/src/main.rs index 8df21c43..13b5895c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,7 @@ async fn grpc_serve(app: BaseCoinApp, host: Strin .add_service(ClientQueryServer::new(Ibc { store: app.sub_store(prefix::Ibc), client_counter: 0, + conn_counter: 0, })) .serve(addr) .await From 08f5a6a63dd3f00feed9f23500ac63cff0cc1bac Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 29 Sep 2021 23:33:18 +0530 Subject: [PATCH 03/42] Add ibc-proto crate as dependency --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e894dfaa..66ec9cc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1526,6 +1526,7 @@ dependencies = [ "cosmrs", "flex-error", "ibc", + "ibc-proto", "ics23", "prost", "prost-types", diff --git a/Cargo.toml b/Cargo.toml index e6cc969e..94a854f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ bytes = "1.0.1" cosmrs = "0.3.0" flex-error = { version = "0.4.2", features = [ "eyre_tracer" ] } ibc = "0.8.0" +ibc-proto = "0.12.0" ics23 = "0.6.0" prost = "0.9.0" prost-types = "0.9.0" From fd501499282e9b1c6e5aaaba61dd6c142b085607 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Thu, 30 Sep 2021 15:23:20 +0530 Subject: [PATCH 04/42] Implement Connection gRPC query server --- src/app/modules/ibc.rs | 83 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 14 ++++--- 2 files changed, 92 insertions(+), 5 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 2da27c07..fb323fe1 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -9,6 +9,15 @@ use crate::prostgen::ibc::core::client::v1::{ QueryUpgradedClientStateRequest, QueryUpgradedClientStateResponse, QueryUpgradedConsensusStateRequest, QueryUpgradedConsensusStateResponse, }; +use crate::prostgen::ibc::core::commitment::v1::MerklePrefix; +use crate::prostgen::ibc::core::connection::v1::{ + query_server::Query as ConnectionQuery, ConnectionEnd as RawConnectionEnd, + Counterparty as RawCounterParty, QueryClientConnectionsRequest, QueryClientConnectionsResponse, + QueryConnectionClientStateRequest, QueryConnectionClientStateResponse, + QueryConnectionConsensusStateRequest, QueryConnectionConsensusStateResponse, + QueryConnectionRequest, QueryConnectionResponse, QueryConnectionsRequest, + QueryConnectionsResponse, Version as RawVersion, +}; use std::convert::TryInto; use std::num::ParseIntError; @@ -40,6 +49,7 @@ use ibc::core::ics26_routing::handler::{decode, dispatch}; use ibc::events::IbcEvent; use ibc::timestamp::Timestamp; use ibc::Height as IbcHeight; +use ibc_proto::ibc::core::connection::v1::ConnectionEnd as IbcRawConnectionEnd; use prost::Message; use prost_types::Any; use tendermint::{Hash, Time}; @@ -690,6 +700,79 @@ impl ClientQuery for Ibc { } } +#[tonic::async_trait] +impl ConnectionQuery for Ibc { + async fn connection( + &self, + request: Request, + ) -> Result, Status> { + let conn_id = ConnectionId::from_str(&request.get_ref().connection_id) + .map_err(|_| Status::invalid_argument("invalid connection id"))?; + let conn = ConnectionReader::connection_end(self, &conn_id).ok(); + Ok(Response::new(QueryConnectionResponse { + connection: conn.map(|c| ConnectionEndWrapper(c.into()).into()), + proof: vec![], + proof_height: None, + })) + } + + async fn connections( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + + async fn client_connections( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + + async fn connection_client_state( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } + + async fn connection_consensus_state( + &self, + _request: Request, + ) -> Result, Status> { + todo!() + } +} + +struct ConnectionEndWrapper(IbcRawConnectionEnd); + +impl From for RawConnectionEnd { + fn from(conn: ConnectionEndWrapper) -> Self { + Self { + client_id: conn.0.client_id, + versions: conn + .0 + .versions + .into_iter() + .map(|v| RawVersion { + identifier: v.identifier, + features: v.features, + }) + .collect(), + state: conn.0.state, + counterparty: conn.0.counterparty.map(|c| RawCounterParty { + client_id: c.client_id, + connection_id: c.connection_id, + prefix: c.prefix.map(|p| MerklePrefix { + key_prefix: p.key_prefix, + }), + }), + delay_period: 0, + } + } +} + struct IbcHeightExt(IcsHeight); #[derive(Debug)] diff --git a/src/main.rs b/src/main.rs index 13b5895c..7bbf64b4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use crate::prostgen::cosmos::auth::v1beta1::query_server::QueryServer as AuthQue use crate::prostgen::cosmos::base::tendermint::v1beta1::service_server::ServiceServer as HealthServer; use crate::prostgen::cosmos::staking::v1beta1::query_server::QueryServer as StakingQueryServer; use crate::prostgen::ibc::core::client::v1::query_server::QueryServer as ClientQueryServer; +use crate::prostgen::ibc::core::connection::v1::query_server::QueryServer as ConnectionQueryServer; use structopt::StructOpt; use tendermint_abci::ServerBuilder; @@ -48,16 +49,19 @@ struct Opt { async fn grpc_serve(app: BaseCoinApp, host: String, port: u16) { let addr = format!("{}:{}", host, port).parse().unwrap(); + let ibc = Ibc { + store: app.sub_store(prefix::Ibc), + client_counter: 0, + conn_counter: 0, + }; + // TODO(hu55a1n1): implement these services for `auth` and `staking` modules Server::builder() .add_service(HealthServer::new(app.clone())) .add_service(AuthQueryServer::new(app.clone())) .add_service(StakingQueryServer::new(app.clone())) - .add_service(ClientQueryServer::new(Ibc { - store: app.sub_store(prefix::Ibc), - client_counter: 0, - conn_counter: 0, - })) + .add_service(ClientQueryServer::new(ibc.clone())) + .add_service(ConnectionQueryServer::new(ibc)) .serve(addr) .await .unwrap() From 6151dc0f00a7a2b786619587eb67758723e21227 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Thu, 30 Sep 2021 23:58:35 +0530 Subject: [PATCH 05/42] Fix memory get impl bug --- src/app/store/memory.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index 3b726d33..dbbe7550 100644 --- a/src/app/store/memory.rs +++ b/src/app/store/memory.rs @@ -48,8 +48,8 @@ impl Store for InMemoryStore { // Access one of the committed blocks Height::Stable(height) => { let h = height as usize; - if h < self.store.len() { - let state = self.store.get(h).unwrap(); + if h <= self.store.len() { + let state = self.store.get(h - 1).unwrap(); state.get(path).cloned() } else { None From 82ccfd45132fe836862bd1a8652e6df1e61d0a2c Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 1 Oct 2021 00:00:04 +0530 Subject: [PATCH 06/42] Change serde for ConnectionEnd to use protobufs --- src/app/modules/ibc.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index fb323fe1..71146ca7 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -201,12 +201,15 @@ impl ClientKeeper for Ibc { impl ConnectionReader for Ibc { fn connection_end(&self, conn_id: &ConnectionId) -> Result { - let path = format!("connection/{}", conn_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier - self.store + let path = format!("connections/{}", conn_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier + let value = self + .store .get(Height::Pending, &path) - // safety - data on the store is assumed to be well-formed - .map(|v| serde_json::from_str(&String::from_utf8(v).unwrap()).unwrap()) - .ok_or_else(ConnectionError::implementation_specific) + .ok_or_else(ConnectionError::implementation_specific)?; + let connection_end = IbcRawConnectionEnd::decode(value.as_slice()); + connection_end + .map_err(|_| ConnectionError::implementation_specific()) + .map(|v| v.try_into().unwrap()) // safety - data on the store is assumed to be well-formed } fn client_state(&self, client_id: &ClientId) -> Result { @@ -257,9 +260,14 @@ impl ConnectionKeeper for Ibc { connection_id: ConnectionId, connection_end: &ConnectionEnd, ) -> Result<(), ConnectionError> { - let path = format!("connection/{}", connection_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier + let data: IbcRawConnectionEnd = connection_end.clone().into(); + let mut buffer = Vec::new(); + data.encode(&mut buffer) + .map_err(|_| ConnectionError::implementation_specific())?; + + let path = format!("connections/{}", connection_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier self.store - .set(path, serde_json::to_string(&connection_end).unwrap().into()) // safety - cannot fail since ClientType's Serialize impl doesn't fail + .set(path, buffer) .map_err(|_| ConnectionError::implementation_specific()) } From 92215ed2dda127e1988458b24d77d093bebe26ac Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 1 Oct 2021 00:01:38 +0530 Subject: [PATCH 07/42] Provide placeholder proofs in ABCI query response --- src/app/mod.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 45b55198..d5ac5018 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -47,7 +47,8 @@ use tendermint_proto::abci::{ Event, RequestDeliverTx, RequestInfo, RequestInitChain, RequestQuery, ResponseCommit, ResponseDeliverTx, ResponseInfo, ResponseInitChain, ResponseQuery, }; -use tendermint_proto::p2p::DefaultNodeInfo; +use tendermint_proto::crypto::{ProofOp, ProofOps}; +use tendermint_proto::p2p::{DefaultNodeInfo, ProtocolVersion}; use tonic::{Request, Response, Status}; use tracing::{debug, info}; @@ -175,11 +176,19 @@ impl Application for BaseCoinApp { ) { // success - implies query was handled by this module, so return response Ok(result) => { + // TODO(hu55a1n1): Add proof support return ResponseQuery { code: 0, log: "exists".to_string(), key: request.data, value: result, + proof_ops: Some(ProofOps { + ops: vec![ProofOp { + r#type: "dummy proof".to_string(), + key: vec![0], + data: vec![0], + }], + }), height: self.store.read().unwrap().current_height() as i64, ..ResponseQuery::default() }; From 3952aba02c06dd75e3cc826f09521fea156c35c4 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 6 Oct 2021 20:38:58 +0530 Subject: [PATCH 08/42] Update Cargo.toml --- Cargo.toml | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 94a854f9..0dbb8af0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,6 @@ description = """ use of tendermint-rs. """ -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] bytes = "1.0.1" cosmrs = "0.3.0" From 81a76a0cfb5de06def0a22f9ed7266d3e1f9010b Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 6 Oct 2021 22:24:31 +0530 Subject: [PATCH 09/42] Implement Tx simulate service --- src/app/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 2 ++ src/prostgen.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index d5ac5018..1064860b 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2,7 +2,6 @@ pub(crate) mod modules; mod response; - pub(crate) mod store; use crate::app::modules::{prefix, Bank, Error, ErrorDetail, Ibc, Identifiable, Module}; @@ -34,6 +33,11 @@ use crate::prostgen::cosmos::staking::v1beta1::{ QueryValidatorUnbondingDelegationsRequest, QueryValidatorUnbondingDelegationsResponse, QueryValidatorsRequest, QueryValidatorsResponse, }; +use crate::prostgen::cosmos::tx::v1beta1::service_server::Service as TxService; +use crate::prostgen::cosmos::tx::v1beta1::{ + BroadcastTxRequest, BroadcastTxResponse, GetTxRequest, GetTxResponse, GetTxsEventRequest, + GetTxsEventResponse, SimulateRequest, SimulateResponse, +}; use std::convert::TryInto; use std::sync::{Arc, RwLock}; @@ -467,3 +471,44 @@ impl StakingQuery for BaseCoinApp { })) } } + +#[tonic::async_trait] +impl TxService for BaseCoinApp { + async fn simulate( + &self, + request: Request, + ) -> Result, Status> { + // TODO(hu55a1n1): implement tx based simulate + let _: Tx = request + .into_inner() + .tx_bytes + .as_slice() + .try_into() + .map_err(|_| Status::invalid_argument("failed to deserialize tx"))?; + Ok(Response::new(SimulateResponse { + gas_info: None, + result: None, + })) + } + + async fn get_tx( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn broadcast_tx( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } + + async fn get_txs_event( + &self, + _request: Request, + ) -> Result, Status> { + unimplemented!() + } +} diff --git a/src/main.rs b/src/main.rs index 7bbf64b4..f3067c84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use crate::app::BaseCoinApp; use crate::prostgen::cosmos::auth::v1beta1::query_server::QueryServer as AuthQueryServer; use crate::prostgen::cosmos::base::tendermint::v1beta1::service_server::ServiceServer as HealthServer; use crate::prostgen::cosmos::staking::v1beta1::query_server::QueryServer as StakingQueryServer; +use crate::prostgen::cosmos::tx::v1beta1::service_server::ServiceServer as TxServer; use crate::prostgen::ibc::core::client::v1::query_server::QueryServer as ClientQueryServer; use crate::prostgen::ibc::core::connection::v1::query_server::QueryServer as ConnectionQueryServer; @@ -60,6 +61,7 @@ async fn grpc_serve(app: BaseCoinApp, host: Strin .add_service(HealthServer::new(app.clone())) .add_service(AuthQueryServer::new(app.clone())) .add_service(StakingQueryServer::new(app.clone())) + .add_service(TxServer::new(app.clone())) .add_service(ClientQueryServer::new(ibc.clone())) .add_service(ConnectionQueryServer::new(ibc)) .serve(addr) diff --git a/src/prostgen.rs b/src/prostgen.rs index 4f201c43..d604c424 100644 --- a/src/prostgen.rs +++ b/src/prostgen.rs @@ -36,6 +36,32 @@ pub mod cosmos { include!("prostgen/cosmos.base.query.v1beta1.rs"); } } + + pub mod abci { + pub mod v1beta1 { + include!("prostgen/cosmos.base.abci.v1beta1.rs"); + } + } + } + + pub mod crypto { + pub mod multisig { + pub mod v1beta1 { + include!("prostgen/cosmos.crypto.multisig.v1beta1.rs"); + } + } + } + + pub mod tx { + pub mod v1beta1 { + include!("prostgen/cosmos.tx.v1beta1.rs"); + } + + pub mod signing { + pub mod v1beta1 { + include!("prostgen/cosmos.tx.signing.v1beta1.rs"); + } + } } } From 0899fd702e0cf09efd5e2c0854b290b25de8279d Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 11 Oct 2021 12:39:04 +0530 Subject: [PATCH 10/42] Allow modules to return query proofs --- src/app/mod.rs | 16 +++++----------- src/app/modules/bank.rs | 9 ++++++--- src/app/modules/mod.rs | 15 +++++++++++++-- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 1064860b..1d2dc5d6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -51,8 +51,8 @@ use tendermint_proto::abci::{ Event, RequestDeliverTx, RequestInfo, RequestInitChain, RequestQuery, ResponseCommit, ResponseDeliverTx, ResponseInfo, ResponseInitChain, ResponseQuery, }; -use tendermint_proto::crypto::{ProofOp, ProofOps}; -use tendermint_proto::p2p::{DefaultNodeInfo, ProtocolVersion}; +use tendermint_proto::crypto::ProofOps; +use tendermint_proto::p2p::DefaultNodeInfo; use tonic::{Request, Response, Status}; use tracing::{debug, info}; @@ -185,16 +185,10 @@ impl Application for BaseCoinApp { code: 0, log: "exists".to_string(), key: request.data, - value: result, - proof_ops: Some(ProofOps { - ops: vec![ProofOp { - r#type: "dummy proof".to_string(), - key: vec![0], - data: vec![0], - }], - }), + value: result.data, + proof_ops: result.proof.map(|ops| ProofOps { ops }), height: self.store.read().unwrap().current_height() as i64, - ..ResponseQuery::default() + ..Default::default() }; } // `Error::not_handled()` - implies query isn't known or was intercepted but not diff --git a/src/app/modules/bank.rs b/src/app/modules/bank.rs index 55bdd4f4..fd87c718 100644 --- a/src/app/modules/bank.rs +++ b/src/app/modules/bank.rs @@ -1,4 +1,4 @@ -use crate::app::modules::{Error as ModuleError, Module}; +use crate::app::modules::{Error as ModuleError, Module, QueryResult}; use crate::app::store::{Height, Path, Store}; use std::collections::HashMap; @@ -163,7 +163,7 @@ impl Module for Bank { data: &[u8], _path: Option<&Path>, height: Height, - ) -> Result, ModuleError> { + ) -> Result { let account_id = match String::from_utf8(data.to_vec()) { Ok(s) if s.starts_with("cosmos") => s, // TODO(hu55a1n1): check if valid identifier _ => return Err(ModuleError::not_handled()), @@ -174,7 +174,10 @@ impl Module for Bank { let path = format!("accounts/{}", account_id).try_into().unwrap(); // safety - account_id is a valid identifier match self.store.get(height, &path) { None => Err(Error::non_existent_account(account_id).into()), - Some(balance) => Ok(balance), + Some(balance) => Ok(QueryResult { + data: balance, + proof: None, + }), } } } diff --git a/src/app/modules/mod.rs b/src/app/modules/mod.rs index 4d8a6a49..6b1dcec3 100644 --- a/src/app/modules/mod.rs +++ b/src/app/modules/mod.rs @@ -9,6 +9,7 @@ use crate::app::store::{self, Height, Path}; use flex_error::{define_error, TraceError}; use prost_types::Any; use tendermint_proto::abci::Event; +use tendermint_proto::crypto::ProofOp; define_error! { #[derive(PartialEq, Eq)] @@ -56,12 +57,22 @@ pub(crate) trait Module { /// ## Return /// * `Error::not_handled()` if message isn't known to OR hasn't been responded to (but possibly intercepted) by this module /// * Other errors iff query was meant to be consumed by module but resulted in an error - /// * Query result on success - fn query(&self, _data: &[u8], _path: Option<&Path>, _height: Height) -> Result, Error> { + /// * Query result on success + fn query( + &self, + _data: &[u8], + _path: Option<&Path>, + _height: Height, + ) -> Result { Err(Error::not_handled()) } } +pub struct QueryResult { + pub data: Vec, + pub proof: Option>, +} + /// Trait for identifying modules /// This is used to get `Module` prefixes that are used for creating prefixed key-space proxy-stores pub(crate) trait Identifiable { From e5bc7665fd7a0c1ec2244418309cc37edb897c82 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 11 Oct 2021 12:40:49 +0530 Subject: [PATCH 11/42] Implement proof support for InMemoryStore --- src/app/store/memory.rs | 4 ++-- src/app/store/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index dbbe7550..ea4048ee 100644 --- a/src/app/store/memory.rs +++ b/src/app/store/memory.rs @@ -91,8 +91,8 @@ impl ProvableStore for InMemoryStore { .to_vec() } - fn get_proof(&self, _key: &Path) -> Option { - todo!() + fn get_proof(&self, key: &Path) -> Option { + self.store.last().and_then(|v| v.get_proof(key)) } } diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index 448ba7f7..b7fd5890 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -345,7 +345,7 @@ pub(crate) struct WalStore { /// operation log for recording operations in preserved order op_log: VecDeque<(Path, Vec)>, } -// + impl WalStore { pub(crate) fn new(store: S) -> Self { Self { From bd5613c2bc43e61bd2689370aa72898910b345f8 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 11 Oct 2021 12:43:56 +0530 Subject: [PATCH 12/42] Proof support for IBC module --- src/app/modules/ibc.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 71146ca7..0ebf62a6 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -1,4 +1,4 @@ -use crate::app::modules::{Error as ModuleError, Identifiable, Module}; +use crate::app::modules::{Error as ModuleError, Identifiable, Module, QueryResult}; use crate::app::store::{Height, Path, ProvableStore, Store}; use crate::prostgen::ibc::core::client::v1::{ query_server::Query as ClientQuery, ConsensusStateWithHeight, Height as RawHeight, @@ -54,6 +54,7 @@ use prost::Message; use prost_types::Any; use tendermint::{Hash, Time}; use tendermint_proto::abci::{Event, EventAttribute}; +use tendermint_proto::crypto::ProofOp; use tonic::{Request, Response, Status}; use tracing::{debug, trace}; @@ -490,7 +491,7 @@ impl Ics20Context for Ibc {} impl Ics26Context for Ibc {} -impl Module for Ibc { +impl Module for Ibc { fn deliver(&mut self, message: Any) -> Result, ModuleError> { let msg = decode(message).map_err(|_| ModuleError::not_handled())?; @@ -510,7 +511,7 @@ impl Module for Ibc { data: &[u8], path: Option<&Path>, height: Height, - ) -> Result, ModuleError> { + ) -> Result { let path = path.ok_or_else(ModuleError::not_handled)?; if path.to_string() != IBC_QUERY_PATH { return Err(ModuleError::not_handled()); @@ -527,9 +528,22 @@ impl Module for Ibc { height ); - match self.store.get(height, &path) { - None => Err(Error::ics02_client(ClientError::implementation_specific()).into()), - Some(client_state) => Ok(client_state), + match (self.store.get(height, &path), self.store.get_proof(&path)) { + (Some(client_state), Some(proof)) => { + let mut buffer = Vec::new(); + proof + .encode(&mut buffer) + .map_err(|_| Error::ics02_client(ClientError::implementation_specific()))?; + Ok(QueryResult { + data: client_state, + proof: Some(vec![ProofOp { + r#type: "".to_string(), + key: path.to_string().into_bytes(), + data: buffer, + }]), + }) + } + _ => Err(Error::ics02_client(ClientError::implementation_specific()).into()), } } } From a921346bc8c8fbf8b6ef24f21e38b37ce8010176 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 12 Oct 2021 13:03:29 +0530 Subject: [PATCH 13/42] Modify module query method to add proof arg --- src/app/mod.rs | 2 +- src/app/modules/bank.rs | 1 + src/app/modules/mod.rs | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 1d2dc5d6..092729d6 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -177,10 +177,10 @@ impl Application for BaseCoinApp { &request.data, path.as_ref(), Height::from(request.height as u64), + request.prove, ) { // success - implies query was handled by this module, so return response Ok(result) => { - // TODO(hu55a1n1): Add proof support return ResponseQuery { code: 0, log: "exists".to_string(), diff --git a/src/app/modules/bank.rs b/src/app/modules/bank.rs index fd87c718..b66fb7c1 100644 --- a/src/app/modules/bank.rs +++ b/src/app/modules/bank.rs @@ -163,6 +163,7 @@ impl Module for Bank { data: &[u8], _path: Option<&Path>, height: Height, + _prove: bool, ) -> Result { let account_id = match String::from_utf8(data.to_vec()) { Ok(s) if s.starts_with("cosmos") => s, // TODO(hu55a1n1): check if valid identifier diff --git a/src/app/modules/mod.rs b/src/app/modules/mod.rs index 6b1dcec3..2f255e02 100644 --- a/src/app/modules/mod.rs +++ b/src/app/modules/mod.rs @@ -63,6 +63,7 @@ pub(crate) trait Module { _data: &[u8], _path: Option<&Path>, _height: Height, + _prove: bool, ) -> Result { Err(Error::not_handled()) } From e78f7c77437981c1d27d829f70e8c0f4a4d5f1c8 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 12 Oct 2021 13:05:03 +0530 Subject: [PATCH 14/42] Refine query proof impl for IBC module --- src/app/modules/ibc.rs | 51 ++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 0ebf62a6..fd153e78 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -80,6 +80,18 @@ pub struct Ibc { pub conn_counter: u64, } +impl Ibc { + fn get_proof(&self, path: &Path) -> Option> { + if let Some(p) = self.store.get_proof(path) { + let mut buffer = Vec::new(); + if p.encode(&mut buffer).is_ok() { + return Some(buffer); + } + } + None + } +} + impl ClientReader for Ibc { fn client_type(&self, client_id: &ClientId) -> Result { let path = format!("clients/{}/clientType", client_id) @@ -511,6 +523,7 @@ impl Module for Ibc { data: &[u8], path: Option<&Path>, height: Height, + prove: bool, ) -> Result { let path = path.ok_or_else(ModuleError::not_handled)?; if path.to_string() != IBC_QUERY_PATH { @@ -528,23 +541,27 @@ impl Module for Ibc { height ); - match (self.store.get(height, &path), self.store.get_proof(&path)) { - (Some(client_state), Some(proof)) => { - let mut buffer = Vec::new(); - proof - .encode(&mut buffer) - .map_err(|_| Error::ics02_client(ClientError::implementation_specific()))?; - Ok(QueryResult { - data: client_state, - proof: Some(vec![ProofOp { - r#type: "".to_string(), - key: path.to_string().into_bytes(), - data: buffer, - }]), - }) - } - _ => Err(Error::ics02_client(ClientError::implementation_specific()).into()), - } + let proof = if prove { + let proof = self + .get_proof(&path) + .ok_or_else(|| Error::ics02_client(ClientError::implementation_specific()))?; + Some(ProofOp { + r#type: "".to_string(), + key: path.to_string().into_bytes(), + data: proof, + }) + } else { + None + }; + + let data = self + .store + .get(height, &path) + .ok_or_else(|| Error::ics02_client(ClientError::implementation_specific()))?; + Ok(QueryResult { + data, + proof: proof.map(|p| vec![p]), + }) } } From 75e45e4288b74356167a07a5bf881904ef9d74a1 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 12 Oct 2021 13:06:12 +0530 Subject: [PATCH 15/42] Enable indexing for IBC events --- src/app/modules/ibc.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index fd153e78..d12ec587 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -575,7 +575,7 @@ impl From for Event { attributes: vec![EventAttribute { key: "client_id".as_bytes().to_vec(), value: c.client_id().to_string().as_bytes().to_vec(), - index: false, + index: true, }], }, IbcEvent::UpdateClient(c) => Self { @@ -583,7 +583,7 @@ impl From for Event { attributes: vec![EventAttribute { key: "client_id".as_bytes().to_vec(), value: c.client_id().to_string().as_bytes().to_vec(), - index: false, + index: true, }], }, IbcEvent::OpenInitConnection(conn_open_init) => Self { @@ -597,7 +597,7 @@ impl From for Event { .to_string() .as_bytes() .to_vec(), - index: false, + index: true, }], }, IbcEvent::OpenTryConnection(conn_open_try) => Self { @@ -611,7 +611,7 @@ impl From for Event { .to_string() .as_bytes() .to_vec(), - index: false, + index: true, }], }, IbcEvent::OpenAckConnection(conn_open_ack) => Self { @@ -625,7 +625,7 @@ impl From for Event { .to_string() .as_bytes() .to_vec(), - index: false, + index: true, }], }, IbcEvent::OpenConfirmConnection(conn_open_confirm) => Self { @@ -639,7 +639,7 @@ impl From for Event { .to_string() .as_bytes() .to_vec(), - index: false, + index: true, }], }, _ => todo!(), From 3bc037c2ba81bd7b6d6be53b3619dc260b905730 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 12 Oct 2021 13:07:49 +0530 Subject: [PATCH 16/42] Fix missing prefix for store proof --- src/app/store/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index b7fd5890..2b09542f 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -262,7 +262,7 @@ where #[inline] fn get_proof(&self, key: &Path) -> Option { - self.store.get_proof(key) + self.store.get_proof(&self.prefix.prefixed_path(key)) } } From 5a7d148599f5b7335e48355dedcf3822f045efdb Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 18 Oct 2021 21:45:09 +0530 Subject: [PATCH 17/42] Update ibc deps --- Cargo.toml | 4 ++-- src/app/modules/ibc.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0dbb8af0..88f369c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,8 @@ description = """ bytes = "1.0.1" cosmrs = "0.3.0" flex-error = { version = "0.4.2", features = [ "eyre_tracer" ] } -ibc = "0.8.0" -ibc-proto = "0.12.0" +ibc = "=0.8.0" +ibc-proto = "=0.12.0" ics23 = "0.6.0" prost = "0.9.0" prost-types = "0.9.0" diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index d12ec587..1307d376 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -24,6 +24,7 @@ use std::num::ParseIntError; use std::str::FromStr; use ibc::applications::ics20_fungible_token_transfer::context::Ics20Context; +use ibc::clients::ics07_tendermint::consensus_state::ConsensusState; use ibc::core::ics02_client::client_consensus::AnyConsensusState; use ibc::core::ics02_client::client_state::AnyClientState; use ibc::core::ics02_client::client_type::ClientType; @@ -40,7 +41,6 @@ use ibc::core::ics04_channel::packet::{Receipt, Sequence}; use ibc::core::ics05_port::capabilities::Capability; use ibc::core::ics05_port::context::PortReader; use ibc::core::ics05_port::error::Error as PortError; -use ibc::core::ics07_tendermint::consensus_state::ConsensusState; use ibc::core::ics23_commitment::commitment::{CommitmentPrefix, CommitmentRoot}; use ibc::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use ibc::core::ics24_host::IBC_QUERY_PATH; @@ -257,7 +257,7 @@ impl ConnectionReader for Ibc { ) -> Result { Ok(AnyConsensusState::Tendermint(ConsensusState::new( CommitmentRoot::from_bytes(&[]), - Time::now(), + Time::unix_epoch(), Hash::None, ))) } From 5c53a51f896ecbb1cf39aea257834c300299e44d Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 25 Oct 2021 21:09:41 +0530 Subject: [PATCH 18/42] Return ConsensusStateNotFound error --- src/app/modules/ibc.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 1307d376..b8d094db 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -128,13 +128,29 @@ impl ClientReader for Ibc { let value = self .store .get(Height::Pending, &path) - .ok_or_else(ClientError::implementation_specific)?; + .ok_or_else(|| ClientError::consensus_state_not_found(client_id.clone(), height))?; let consensus_state = Any::decode(value.as_slice()); consensus_state .map_err(|_| ClientError::implementation_specific()) .map(|v| v.try_into().unwrap()) // safety - data on the store is assumed to be well-formed } + fn next_consensus_state( + &self, + _client_id: &ClientId, + _height: IbcHeight, + ) -> Result, ClientError> { + todo!() + } + + fn prev_consensus_state( + &self, + _client_id: &ClientId, + _height: IbcHeight, + ) -> Result, ClientError> { + todo!() + } + fn client_counter(&self) -> Result { Ok(self.client_counter) } From 9a4de4adaf791a0c728960cb9140430743b2dd52 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 25 Oct 2021 21:54:44 +0530 Subject: [PATCH 19/42] Implement next/prev_consensus_state() --- src/app/modules/ibc.rs | 67 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index b8d094db..a8f79e36 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -137,18 +137,73 @@ impl ClientReader for Ibc { fn next_consensus_state( &self, - _client_id: &ClientId, - _height: IbcHeight, + client_id: &ClientId, + height: IbcHeight, ) -> Result, ClientError> { - todo!() + let path = format!("clients/{}/consensusStates", client_id) + .try_into() + .unwrap(); // safety - path must be valid since ClientId and height are valid Identifiers + + let keys = self.store.get_keys(&path); + let found_path = keys.iter().find(|path| { + let cs_height = path + .to_string() + .split('/') + .last() + .expect("invalid path") // safety - prefixed paths will have atleast one '/' + .parse::() + .expect("couldn't parse Path as Height"); // safety - data on the store is assumed to be well-formed + + height > cs_height.0 + }); + + if let Some(path) = found_path { + // safety - data on the store is assumed to be well-formed + let consensus_state = self.store.get(Height::Pending, path).unwrap(); + let consensus_state = + Any::decode(consensus_state.as_slice()).expect("failed to decode consensus state"); + + Ok(Some(consensus_state.try_into()?)) + } else { + Ok(None) + } } fn prev_consensus_state( &self, - _client_id: &ClientId, - _height: IbcHeight, + client_id: &ClientId, + height: IbcHeight, ) -> Result, ClientError> { - todo!() + let path = format!("clients/{}/consensusStates", client_id) + .try_into() + .unwrap(); // safety - path must be valid since ClientId and height are valid Identifiers + + let keys = self.store.get_keys(&path); + let pos = keys.iter().position(|path| { + let cs_height = path + .to_string() + .split('/') + .last() + .expect("invalid path") // safety - prefixed paths will have atleast one '/' + .parse::() + .expect("couldn't parse Path as Height"); // safety - data on the store is assumed to be well-formed + + height >= cs_height.0 + }); + + if let Some(pos) = pos { + if pos > 0 { + let prev_path = &keys[pos - 1]; + // safety - data on the store is assumed to be well-formed + let consensus_state = self.store.get(Height::Pending, prev_path).unwrap(); + let consensus_state = Any::decode(consensus_state.as_slice()) + .expect("failed to decode consensus state"); + + return Ok(Some(consensus_state.try_into()?)); + } + } + + Ok(None) } fn client_counter(&self) -> Result { From 114a79d3cef569c5fb3eedbb806679f345e7f48f Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 26 Oct 2021 00:12:09 +0530 Subject: [PATCH 20/42] Patch IBC deps --- Cargo.lock | 10 ++++++++++ Cargo.toml | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 66ec9cc5..4fcb3e0c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2109,3 +2109,13 @@ dependencies = [ "syn", "synstructure", ] + +[[patch.unused]] +name = "ibc" +version = "0.7.3" +source = "git+https://github.com/informalsystems/ibc-rs.git?rev=e578787#e5787877a7f0f8e9a4f5668d744c76c7a2e9fb41" + +[[patch.unused]] +name = "ibc-proto" +version = "0.11.0" +source = "git+https://github.com/informalsystems/ibc-rs.git?rev=e578787#e5787877a7f0f8e9a4f5668d744c76c7a2e9fb41" diff --git a/Cargo.toml b/Cargo.toml index 88f369c2..e0024fbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,3 +33,7 @@ tracing = "0.1.26" tracing-subscriber = "0.2.18" tonic="0.6" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } + +[patch.crates-io] +ibc = { git = "https://github.com/informalsystems/ibc-rs.git", rev = "e578787" } +ibc-proto = { git = "https://github.com/informalsystems/ibc-rs.git", rev = "e578787" } From 25406a1c4c554027ccb0f73185c71eb55132093e Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 26 Oct 2021 00:14:38 +0530 Subject: [PATCH 21/42] Placeholder impl for account nonce --- src/app/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 092729d6..4ba86430 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -63,6 +63,7 @@ use tracing::{debug, info}; pub(crate) struct BaseCoinApp { store: SharedStore>, modules: Arc>>>, + account: Arc>, // TODO(hu55a1n1): get from user and move to provable store } impl BaseCoinApp { @@ -83,6 +84,7 @@ impl BaseCoinApp { Self { store, modules: Arc::new(RwLock::new(modules)), + account: Default::default(), } } @@ -335,9 +337,10 @@ impl AuthQuery for BaseCoinApp { ) -> Result, Status> { debug!("Got auth account request"); - let account = BaseAccount::default(); + let mut account = self.account.write().unwrap(); let mut buf = Vec::new(); account.encode(&mut buf).unwrap(); // safety - cannot fail since buf is a vector + account.sequence += 1; Ok(Response::new(QueryAccountResponse { account: Some(Any { From 50ff86c0b0b2599150ba189617ac19cb8f9028c6 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 3 Nov 2021 01:17:09 +0530 Subject: [PATCH 22/42] Print committed app hash --- src/app/mod.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 4ba86430..214f82dd 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -252,7 +252,13 @@ impl Application for BaseCoinApp { fn commit(&self) -> ResponseCommit { let mut state = self.store.write().unwrap(); let data = state.commit().expect("failed to commit to state"); - info!("Committed height {}", state.current_height() - 1); + info!( + "Committed height {} with hash({})", + state.current_height() - 1, + data.iter() + .map(|b| format!("{:02X}", b)) + .collect::() + ); ResponseCommit { data, retain_height: 0, From dec059721017d3f52b1f56896954e7114ffb25f5 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 3 Nov 2021 01:23:59 +0530 Subject: [PATCH 23/42] Modify Store::get_proof() to include height --- src/app/modules/ibc.rs | 15 ++++++--------- src/app/store/memory.rs | 16 ++++++++++++++-- src/app/store/mod.rs | 17 +++++++++-------- 3 files changed, 29 insertions(+), 19 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index a8f79e36..5fc7099f 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -81,8 +81,8 @@ pub struct Ibc { } impl Ibc { - fn get_proof(&self, path: &Path) -> Option> { - if let Some(p) = self.store.get_proof(path) { + fn get_proof(&self, height: Height, path: &Path) -> Option> { + if let Some(p) = self.store.get_proof(height, path) { let mut buffer = Vec::new(); if p.encode(&mut buffer).is_ok() { return Some(buffer); @@ -614,13 +614,13 @@ impl Module for Ibc { let proof = if prove { let proof = self - .get_proof(&path) + .get_proof(height, &path) .ok_or_else(|| Error::ics02_client(ClientError::implementation_specific()))?; - Some(ProofOp { + Some(vec![ProofOp { r#type: "".to_string(), key: path.to_string().into_bytes(), data: proof, - }) + }]) } else { None }; @@ -629,10 +629,7 @@ impl Module for Ibc { .store .get(height, &path) .ok_or_else(|| Error::ics02_client(ClientError::implementation_specific()))?; - Ok(QueryResult { - data, - proof: proof.map(|p| vec![p]), - }) + Ok(QueryResult { data, proof }) } } diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index ea4048ee..f41d7bc2 100644 --- a/src/app/store/memory.rs +++ b/src/app/store/memory.rs @@ -91,8 +91,20 @@ impl ProvableStore for InMemoryStore { .to_vec() } - fn get_proof(&self, key: &Path) -> Option { - self.store.last().and_then(|v| v.get_proof(key)) + fn get_proof(&self, height: Height, key: &Path) -> Option { + match height { + Height::Pending => self.pending.get_proof(key), + Height::Latest => self.store.last().and_then(|v| v.get_proof(key)), + Height::Stable(height) => { + let h = height as usize; + if h <= self.store.len() { + let state = self.store.get(h - 1).unwrap(); + state.get_proof(key) + } else { + None + } + } + } } } diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index 2b09542f..b5edf1b1 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -135,7 +135,7 @@ impl From for ModuleError { pub(crate) type RawHeight = u64; /// Store height to query -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub enum Height { Pending, Latest, @@ -194,7 +194,7 @@ pub trait ProvableStore: Store { fn root_hash(&self) -> Vec; /// Return proof of existence for key - fn get_proof(&self, key: &Path) -> Option; + fn get_proof(&self, height: Height, key: &Path) -> Option; } /// A wrapper store that implements a prefixed key-space for other shared stores @@ -261,8 +261,9 @@ where } #[inline] - fn get_proof(&self, key: &Path) -> Option { - self.store.get_proof(&self.prefix.prefixed_path(key)) + fn get_proof(&self, height: Height, key: &Path) -> Option { + self.store + .get_proof(height, &self.prefix.prefixed_path(key)) } } @@ -317,8 +318,8 @@ impl ProvableStore for SharedStore { } #[inline] - fn get_proof(&self, key: &Path) -> Option { - self.read().unwrap().get_proof(key) + fn get_proof(&self, height: Height, key: &Path) -> Option { + self.read().unwrap().get_proof(height, key) } } @@ -429,8 +430,8 @@ impl ProvableStore for WalStore { } #[inline] - fn get_proof(&self, key: &Path) -> Option { - self.store.get_proof(key) + fn get_proof(&self, height: Height, key: &Path) -> Option { + self.store.get_proof(height, key) } } From ba775c41ddb077728aedfef719030457490fa241 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 3 Nov 2021 01:31:41 +0530 Subject: [PATCH 24/42] Minor refactoring --- src/app/store/memory.rs | 49 +++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index f41d7bc2..9e914e97 100644 --- a/src/app/store/memory.rs +++ b/src/app/store/memory.rs @@ -19,6 +19,24 @@ pub(crate) struct InMemoryStore { pending: State, } +impl InMemoryStore { + #[inline] + fn get_state(&self, height: Height) -> Option<&State> { + match height { + Height::Pending => Some(&self.pending), + Height::Latest => self.store.last(), + Height::Stable(height) => { + let h = height as usize; + if h <= self.store.len() { + self.store.get(h - 1) + } else { + None + } + } + } + } +} + impl Default for InMemoryStore { /// The store starts out with an empty state. We also initialize the pending location as empty. fn default() -> Self { @@ -40,22 +58,7 @@ impl Store for InMemoryStore { fn get(&self, height: Height, path: &Path) -> Option> { trace!("get at path = {} at height = {:?}", path.as_str(), height); - match height { - // Request to access the pending block - Height::Pending => self.pending.get(path).cloned(), - // Access the last committed block - Height::Latest => self.store.last().and_then(|s| s.get(path).cloned()), - // Access one of the committed blocks - Height::Stable(height) => { - let h = height as usize; - if h <= self.store.len() { - let state = self.store.get(h - 1).unwrap(); - state.get(path).cloned() - } else { - None - } - } - } + self.get_state(height).and_then(|v| v.get(path).cloned()) } fn delete(&mut self, _path: &Path) { @@ -92,19 +95,7 @@ impl ProvableStore for InMemoryStore { } fn get_proof(&self, height: Height, key: &Path) -> Option { - match height { - Height::Pending => self.pending.get_proof(key), - Height::Latest => self.store.last().and_then(|v| v.get_proof(key)), - Height::Stable(height) => { - let h = height as usize; - if h <= self.store.len() { - let state = self.store.get(h - 1).unwrap(); - state.get_proof(key) - } else { - None - } - } - } + self.get_state(height).and_then(|v| v.get_proof(key)) } } From 80d3d1541777335e614b1865dac3091b81b2feca Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Thu, 4 Nov 2021 21:29:14 +0530 Subject: [PATCH 25/42] Fix AVL store get_proof() for overwritten keys --- src/app/store/avl/node.rs | 17 +++++++++++++++++ src/app/store/avl/tree.rs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/app/store/avl/node.rs b/src/app/store/avl/node.rs index 0cad7465..a7029339 100644 --- a/src/app/store/avl/node.rs +++ b/src/app/store/avl/node.rs @@ -53,6 +53,13 @@ where } } + /// Set the value of the current node. + pub(crate) fn set_value(&mut self, value: V) { + let hash = Self::local_hash(&self.key, &value); + self.value = value; + self.hash = hash; + } + /// The left height, or `None` if there is no left child. fn left_height(&self) -> Option { self.left.as_ref().map(|left| left.height) @@ -63,6 +70,16 @@ where self.right.as_ref().map(|right| right.height) } + /// Compute the local hash for a given key and value. + fn local_hash(key: &K, value: &V) -> Hash { + let mut sha = Sha256::new(); + sha.update(proof::LEAF_PREFIX); + sha.update(key.as_bytes()); + sha.update(value.borrow()); + let hash = sha.finalize(); + Hash::from_bytes(HASH_ALGO, &hash).unwrap() + } + /// The left merkle hash, if any pub fn left_hash(&self) -> Option<&[u8]> { Some(self.left.as_ref()?.merkle_hash.as_bytes()) diff --git a/src/app/store/avl/tree.rs b/src/app/store/avl/tree.rs index 9f70efac..5effbd19 100644 --- a/src/app/store/avl/tree.rs +++ b/src/app/store/avl/tree.rs @@ -62,7 +62,7 @@ where match node.key.cmp(&key) { Ordering::Greater => AvlTree::insert_rec(&mut node.left, key, value), Ordering::Less => AvlTree::insert_rec(&mut node.right, key, value), - Ordering::Equal => node.value = value, + Ordering::Equal => node.set_value(value), } node.update(); AvlTree::balance_node(node_ref); From f011cb31658cf624786ee0ab273777e29f511809 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Thu, 4 Nov 2021 21:30:37 +0530 Subject: [PATCH 26/42] Add trace log to get_proof() --- src/app/store/memory.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index 9e914e97..b57f4f85 100644 --- a/src/app/store/memory.rs +++ b/src/app/store/memory.rs @@ -95,6 +95,11 @@ impl ProvableStore for InMemoryStore { } fn get_proof(&self, height: Height, key: &Path) -> Option { + trace!( + "get proof at path = {} at height = {:?}", + key.as_str(), + height + ); self.get_state(height).and_then(|v| v.get_proof(key)) } } From 3d628fcb05075d28180739c755aab6841cf93b14 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 5 Nov 2021 01:14:23 +0530 Subject: [PATCH 27/42] Add store() and commit() method to Module trait --- src/app/modules/bank.rs | 12 ++++++++++-- src/app/modules/ibc.rs | 12 ++++++++++-- src/app/modules/mod.rs | 8 ++++++-- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/app/modules/bank.rs b/src/app/modules/bank.rs index b66fb7c1..fc4b8fcf 100644 --- a/src/app/modules/bank.rs +++ b/src/app/modules/bank.rs @@ -1,5 +1,5 @@ use crate::app::modules::{Error as ModuleError, Module, QueryResult}; -use crate::app::store::{Height, Path, Store}; +use crate::app::store::{Height, Identifier, Path, Store}; use std::collections::HashMap; use std::convert::TryInto; @@ -73,7 +73,7 @@ impl Bank { } } -impl Module for Bank { +impl Module for Bank { fn deliver(&mut self, message: Any) -> Result, ModuleError> { let message: MsgSend = Self::decode::(message)? .try_into() @@ -181,4 +181,12 @@ impl Module for Bank { }), } } + + fn commit(&mut self) -> Result, S::Error> { + self.store.commit() + } + + fn store(&self) -> S { + self.store.clone() + } } diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 5fc7099f..9a4e62fa 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -1,5 +1,5 @@ use crate::app::modules::{Error as ModuleError, Identifiable, Module, QueryResult}; -use crate::app::store::{Height, Path, ProvableStore, Store}; +use crate::app::store::{Height, Path, ProvableStore, Store, Identifier}; use crate::prostgen::ibc::core::client::v1::{ query_server::Query as ClientQuery, ConsensusStateWithHeight, Height as RawHeight, QueryClientParamsRequest, QueryClientParamsResponse, QueryClientStateRequest, @@ -574,7 +574,7 @@ impl Ics20Context for Ibc {} impl Ics26Context for Ibc {} -impl Module for Ibc { +impl Module for Ibc { fn deliver(&mut self, message: Any) -> Result, ModuleError> { let msg = decode(message).map_err(|_| ModuleError::not_handled())?; @@ -631,6 +631,14 @@ impl Module for Ibc { .ok_or_else(|| Error::ics02_client(ClientError::implementation_specific()))?; Ok(QueryResult { data, proof }) } + + fn commit(&mut self) -> Result, S::Error> { + self.store.commit() + } + + fn store(&self) -> S { + self.store.clone() + } } struct IbcEventWrapper(IbcEvent); diff --git a/src/app/modules/mod.rs b/src/app/modules/mod.rs index 2f255e02..6943e048 100644 --- a/src/app/modules/mod.rs +++ b/src/app/modules/mod.rs @@ -4,7 +4,7 @@ mod ibc; pub(crate) use self::bank::Bank; pub(crate) use self::ibc::Ibc; -use crate::app::store::{self, Height, Path}; +use crate::app::store::{self, Height, Path, SubStore, Store}; use flex_error::{define_error, TraceError}; use prost_types::Any; @@ -29,7 +29,7 @@ define_error! { } /// Module trait -pub(crate) trait Module { +pub(crate) trait Module { /// Similar to [ABCI CheckTx method](https://docs.tendermint.com/master/spec/abci/abci.html#checktx) /// > CheckTx need not execute the transaction in full, but rather a light-weight yet /// > stateful validation, like checking signatures and account balances, but not running @@ -67,6 +67,10 @@ pub(crate) trait Module { ) -> Result { Err(Error::not_handled()) } + + fn commit(&mut self) -> Result, S::Error>; + + fn store(&self) -> S; } pub struct QueryResult { From 0c0023d5d97b84ee655e336702cb97409bd21408 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 5 Nov 2021 01:18:05 +0530 Subject: [PATCH 28/42] Implement sub-stores as separate stores with only commitments in main store --- src/app/mod.rs | 74 +++++++++++++++++++++++-------- src/app/modules/bank.rs | 2 +- src/app/modules/ibc.rs | 2 +- src/app/modules/mod.rs | 2 +- src/app/store/mod.rs | 98 +++++++++++++++++++++++++---------------- src/main.rs | 13 ++++-- 6 files changed, 129 insertions(+), 62 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 214f82dd..15d08038 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,7 +6,9 @@ pub(crate) mod store; use crate::app::modules::{prefix, Bank, Error, ErrorDetail, Ibc, Identifiable, Module}; use crate::app::response::ResponseFromErrorExt; -use crate::app::store::{Height, Path, ProvableStore, SharedStore, Store, SubStore, WalStore}; +use crate::app::store::{ + Height, Identifier, Path, ProvableStore, SharedStore, Store, SubStore, WalStore, +}; use crate::prostgen::cosmos::auth::v1beta1::{ query_server::Query as AuthQuery, BaseAccount, QueryAccountRequest, QueryAccountResponse, QueryAccountsRequest, QueryAccountsResponse, QueryParamsRequest as AuthQueryParamsRequest, @@ -51,6 +53,7 @@ use tendermint_proto::abci::{ Event, RequestDeliverTx, RequestInfo, RequestInitChain, RequestQuery, ResponseCommit, ResponseDeliverTx, ResponseInfo, ResponseInitChain, ResponseQuery, }; +use tendermint_proto::crypto::ProofOp; use tendermint_proto::crypto::ProofOps; use tendermint_proto::p2p::DefaultNodeInfo; use tonic::{Request, Response, Status}; @@ -62,41 +65,45 @@ use tracing::{debug, info}; #[derive(Clone)] pub(crate) struct BaseCoinApp { store: SharedStore>, - modules: Arc>>>, + pub modules: Arc< + RwLock>, Identifier>> + Send + Sync>>>, + >, account: Arc>, // TODO(hu55a1n1): get from user and move to provable store } -impl BaseCoinApp { +impl BaseCoinApp { /// Constructor. - pub(crate) fn new(store: S) -> Self { + pub(crate) fn new(store: S) -> Result { let store = SharedStore::new(WalStore::new(store)); // `SubStore` guarantees modules exclusive access to all paths in the store key-space. - let modules: Vec> = vec![ + let modules: Vec< + Box>, Identifier>> + Send + Sync>, + > = vec![ Box::new(Bank { - store: SubStore::new(store.clone(), prefix::Bank), + store: SubStore::new(store.clone(), prefix::Bank {}.identifier())?, }), Box::new(Ibc { - store: SubStore::new(store.clone(), prefix::Ibc), + store: SubStore::new(store.clone(), prefix::Ibc {}.identifier())?, client_counter: 0, conn_counter: 0, }), ]; - Self { + Ok(Self { store, modules: Arc::new(RwLock::new(modules)), account: Default::default(), - } + }) } - pub(crate) fn sub_store( + pub(crate) fn sub_store( &self, - prefix: I, - ) -> SubStore>, I> { - SubStore::new(self.store.clone(), prefix) + prefix: Identifier, + ) -> SubStore>, Identifier> { + SubStore::new(self.store.clone(), prefix).unwrap() } } -impl BaseCoinApp { +impl BaseCoinApp { // try to deliver the message to all registered modules // if `module.deliver()` returns `Error::not_handled()`, try next module // Return: @@ -128,7 +135,7 @@ impl BaseCoinApp { } } -impl Application for BaseCoinApp { +impl Application for BaseCoinApp { fn info(&self, request: RequestInfo) -> ResponseInfo { let (last_block_height, last_block_app_hash) = { let state = self.store.read().unwrap(); @@ -182,14 +189,40 @@ impl Application for BaseCoinApp { request.prove, ) { // success - implies query was handled by this module, so return response - Ok(result) => { + Ok(mut result) => { + let store = self.store.read().unwrap(); + let proof_ops = if request.prove { + let proof = store + .get_proof( + Height::from(request.height as u64), + &"ibc".to_owned().try_into().unwrap(), + ) + .unwrap(); + let mut buffer = Vec::new(); + proof.encode(&mut buffer); + + let mut ops = vec![]; + if let Some(mut proofs) = result.proof { + ops.append(&mut proofs); + } + ops.push(ProofOp { + r#type: "".to_string(), + // FIXME(hu55a1n1) + key: "ibc".to_string().into_bytes(), + data: buffer, + }); + Some(ProofOps { ops }) + } else { + None + }; + return ResponseQuery { code: 0, log: "exists".to_string(), key: request.data, value: result.data, - proof_ops: result.proof.map(|ops| ProofOps { ops }), - height: self.store.read().unwrap().current_height() as i64, + proof_ops, + height: store.current_height() as i64, ..Default::default() }; } @@ -250,6 +283,11 @@ impl Application for BaseCoinApp { } fn commit(&self) -> ResponseCommit { + let mut modules = self.modules.write().unwrap(); + for m in modules.iter_mut() { + m.commit(); + } + let mut state = self.store.write().unwrap(); let data = state.commit().expect("failed to commit to state"); info!( diff --git a/src/app/modules/bank.rs b/src/app/modules/bank.rs index fc4b8fcf..e81e98f8 100644 --- a/src/app/modules/bank.rs +++ b/src/app/modules/bank.rs @@ -73,7 +73,7 @@ impl Bank { } } -impl Module for Bank { +impl Module for Bank { fn deliver(&mut self, message: Any) -> Result, ModuleError> { let message: MsgSend = Self::decode::(message)? .try_into() diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 9a4e62fa..31218ab4 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -1,5 +1,5 @@ use crate::app::modules::{Error as ModuleError, Identifiable, Module, QueryResult}; -use crate::app::store::{Height, Path, ProvableStore, Store, Identifier}; +use crate::app::store::{Height, Identifier, Path, ProvableStore, Store}; use crate::prostgen::ibc::core::client::v1::{ query_server::Query as ClientQuery, ConsensusStateWithHeight, Height as RawHeight, QueryClientParamsRequest, QueryClientParamsResponse, QueryClientStateRequest, diff --git a/src/app/modules/mod.rs b/src/app/modules/mod.rs index 6943e048..9f48a81a 100644 --- a/src/app/modules/mod.rs +++ b/src/app/modules/mod.rs @@ -4,7 +4,7 @@ mod ibc; pub(crate) use self::bank::Bank; pub(crate) use self::ibc::Ibc; -use crate::app::store::{self, Height, Path, SubStore, Store}; +use crate::app::store::{self, Height, Path, Store, SubStore}; use flex_error::{define_error, TraceError}; use prost_types::Any; diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index b5edf1b1..c225a4fb 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -38,6 +38,12 @@ impl Identifier { || matches!(c, '.' | '_' | '+' | '-' | '#' | '[' | ']' | '<' | '>' | '/') }) } + + #[inline] + fn unprefixed_path(&self, path: &Path) -> Path { + // FIXME(hu55a1n1) + path.clone() + } } impl Deref for Identifier { @@ -202,68 +208,92 @@ pub trait ProvableStore: Store { pub(crate) struct SubStore { /// backing store store: S, + /// sub store + sub_store: S, /// prefix for key-space prefix: P, + dirty: bool, } -impl SubStore { - pub(crate) fn new(store: S, prefix: P) -> Self { - Self { store, prefix } +impl SubStore { + pub(crate) fn new(store: S, prefix: Identifier) -> Result { + let mut sub_store = Self { + store, + sub_store: S::default(), + prefix, + dirty: false, + }; + sub_store.update_parent_hash()?; + Ok(sub_store) } } -impl Store for SubStore +impl SubStore { + fn update_parent_hash(&mut self) -> Result<(), S::Error> { + self.store + .set(Path::from(self.prefix.clone()), self.sub_store.root_hash()) + } +} + +impl Store for SubStore where - S: Store, - P: Identifiable + Send + Sync + Clone, + S: Default + ProvableStore, { type Error = S::Error; #[inline] fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { - self.store.set(self.prefix.prefixed_path(&path), value) + self.dirty = true; + self.sub_store + .set(self.prefix.unprefixed_path(&path), value) } #[inline] fn get(&self, height: Height, path: &Path) -> Option> { - self.store.get(height, &self.prefix.prefixed_path(path)) + self.sub_store + .get(height, &self.prefix.unprefixed_path(path)) } #[inline] fn delete(&mut self, path: &Path) { - self.store.delete(&self.prefix.prefixed_path(path)) + self.dirty = true; + self.sub_store.delete(&self.prefix.unprefixed_path(path)) } #[inline] fn commit(&mut self) -> Result, Self::Error> { - panic!("sub-stores may not commit!") + if self.dirty { + self.dirty = false; + self.update_parent_hash()?; + } + self.sub_store.commit() } #[inline] fn current_height(&self) -> RawHeight { - self.store.current_height() + self.sub_store.current_height() } #[inline] fn get_keys(&self, key_prefix: &Path) -> Vec { - self.store.get_keys(&self.prefix.prefixed_path(key_prefix)) + self.sub_store + .get_keys(&self.prefix.unprefixed_path(key_prefix)) } } -impl ProvableStore for SubStore +impl ProvableStore for SubStore where - S: ProvableStore, - P: Identifiable + Send + Sync + Clone, + S: Default + ProvableStore, { #[inline] fn root_hash(&self) -> Vec { - self.store.root_hash() + self.sub_store.root_hash() } #[inline] fn get_proof(&self, height: Height, key: &Path) -> Option { - self.store - .get_proof(height, &self.prefix.prefixed_path(key)) + self.sub_store + .get_proof(height, &self.prefix.unprefixed_path(key)) } } @@ -277,6 +307,12 @@ impl SharedStore { } } +impl Default for SharedStore { + fn default() -> Self { + Self::new(S::default()) + } +} + impl Store for SharedStore { type Error = S::Error; @@ -356,6 +392,12 @@ impl WalStore { } } +impl Default for WalStore { + fn default() -> Self { + Self::new(S::default()) + } +} + impl Store for WalStore { type Error = S::Error; @@ -397,7 +439,7 @@ impl Store for WalStore { fn apply(&mut self) -> Result<(), Self::Error> { // note that we do NOT call the backing store's apply here - this allows users to create // multilayered `WalStore`s - trace!("Applying operation log"); + trace!("Applying operation log if any"); while let Some(op) = self.op_log.pop_back() { self.store.set(op.0, op.1)?; } @@ -434,21 +476,3 @@ impl ProvableStore for WalStore { self.store.get_proof(height, key) } } - -/// Trait for generating a prefixed-path used by `SubStore` methods -/// A blanket implementation is provided for all `Identifiable` types -pub(crate) trait PrefixedPath: Sized { - fn prefixed_path(&self, s: &Path) -> Path; -} - -impl PrefixedPath for T { - #[inline] - fn prefixed_path(&self, s: &Path) -> Path { - let prefix = self.identifier().into(); - if !s.as_str().starts_with(&format!("{}/", prefix.as_str())) { - Path::from(prefix).append(s) - } else { - s.clone() - } - } -} diff --git a/src/main.rs b/src/main.rs index f3067c84..eaa72aee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod app; mod prostgen; -use crate::app::modules::{prefix, Ibc}; +use crate::app::modules::{prefix, Ibc, Identifiable}; use crate::app::store::{InMemoryStore, ProvableStore}; use crate::app::BaseCoinApp; use crate::prostgen::cosmos::auth::v1beta1::query_server::QueryServer as AuthQueryServer; @@ -47,11 +47,16 @@ struct Opt { } #[tokio::main] -async fn grpc_serve(app: BaseCoinApp, host: String, port: u16) { +async fn grpc_serve( + app: BaseCoinApp, + host: String, + port: u16, +) { let addr = format!("{}:{}", host, port).parse().unwrap(); let ibc = Ibc { - store: app.sub_store(prefix::Ibc), + // FIXME(hu55a1n1) + store: app.modules.clone().read().unwrap().get(1).unwrap().store(), client_counter: 0, conn_counter: 0, }; @@ -82,7 +87,7 @@ fn main() { tracing::info!("Starting app and waiting for Tendermint to connect..."); - let app = BaseCoinApp::new(InMemoryStore::default()); + let app = BaseCoinApp::new(InMemoryStore::default()).expect("Failed to init app"); let app_copy = app.clone(); let grpc_port = opt.grpc_port; let grpc_host = opt.host.clone(); From a84bc0862ce999e41c930b08639a07d2d7b50aca Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 5 Nov 2021 01:28:20 +0530 Subject: [PATCH 29/42] Depend on ibc-rs's basecoin/phase-4 branch --- Cargo.lock | 4 ++-- Cargo.toml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4fcb3e0c..9aab2adb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2113,9 +2113,9 @@ dependencies = [ [[patch.unused]] name = "ibc" version = "0.7.3" -source = "git+https://github.com/informalsystems/ibc-rs.git?rev=e578787#e5787877a7f0f8e9a4f5668d744c76c7a2e9fb41" +source = "git+https://github.com/informalsystems/ibc-rs.git?branch=basecoin/phase-4#e19248d9f81a98deb1e708f8bf008072484bfc06" [[patch.unused]] name = "ibc-proto" version = "0.11.0" -source = "git+https://github.com/informalsystems/ibc-rs.git?rev=e578787#e5787877a7f0f8e9a4f5668d744c76c7a2e9fb41" +source = "git+https://github.com/informalsystems/ibc-rs.git?branch=basecoin/phase-4#e19248d9f81a98deb1e708f8bf008072484bfc06" diff --git a/Cargo.toml b/Cargo.toml index e0024fbf..692061a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ cosmrs = "0.3.0" flex-error = { version = "0.4.2", features = [ "eyre_tracer" ] } ibc = "=0.8.0" ibc-proto = "=0.12.0" -ics23 = "0.6.0" +ics23 = "=0.6.7" prost = "0.9.0" prost-types = "0.9.0" serde = "1.0" @@ -35,5 +35,5 @@ tonic="0.6" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } [patch.crates-io] -ibc = { git = "https://github.com/informalsystems/ibc-rs.git", rev = "e578787" } -ibc-proto = { git = "https://github.com/informalsystems/ibc-rs.git", rev = "e578787" } +ibc = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4" } +ibc-proto = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4" } From 8f7716dc3e5aafec9567266e706546f79c57fac7 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 5 Nov 2021 21:32:45 +0530 Subject: [PATCH 30/42] Fix clippy warnings --- src/app/mod.rs | 29 +++++++++++------------------ src/app/modules/bank.rs | 2 +- src/app/modules/ibc.rs | 2 +- src/app/modules/mod.rs | 2 +- src/app/store/mod.rs | 10 +--------- src/main.rs | 2 +- 6 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 15d08038..5903f5c7 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -59,16 +59,18 @@ use tendermint_proto::p2p::DefaultNodeInfo; use tonic::{Request, Response, Status}; use tracing::{debug, info}; +type MainStore = SharedStore>; +type ModuleStore = SubStore, Identifier>; +type Shared = Arc>; + /// BaseCoin ABCI application. /// /// Can be safely cloned and sent across threads, but not shared. #[derive(Clone)] pub(crate) struct BaseCoinApp { - store: SharedStore>, - pub modules: Arc< - RwLock>, Identifier>> + Send + Sync>>>, - >, - account: Arc>, // TODO(hu55a1n1): get from user and move to provable store + store: MainStore, + pub modules: Shared> + Send + Sync>>>, + account: Shared, // TODO(hu55a1n1): get from user and move to provable store } impl BaseCoinApp { @@ -76,9 +78,7 @@ impl BaseCoinApp { pub(crate) fn new(store: S) -> Result { let store = SharedStore::new(WalStore::new(store)); // `SubStore` guarantees modules exclusive access to all paths in the store key-space. - let modules: Vec< - Box>, Identifier>> + Send + Sync>, - > = vec![ + let modules: Vec> + Send + Sync>> = vec![ Box::new(Bank { store: SubStore::new(store.clone(), prefix::Bank {}.identifier())?, }), @@ -94,13 +94,6 @@ impl BaseCoinApp { account: Default::default(), }) } - - pub(crate) fn sub_store( - &self, - prefix: Identifier, - ) -> SubStore>, Identifier> { - SubStore::new(self.store.clone(), prefix).unwrap() - } } impl BaseCoinApp { @@ -189,7 +182,7 @@ impl Application for BaseCoinApp { request.prove, ) { // success - implies query was handled by this module, so return response - Ok(mut result) => { + Ok(result) => { let store = self.store.read().unwrap(); let proof_ops = if request.prove { let proof = store @@ -199,7 +192,7 @@ impl Application for BaseCoinApp { ) .unwrap(); let mut buffer = Vec::new(); - proof.encode(&mut buffer); + proof.encode(&mut buffer).unwrap(); // safety - cannot fail since buf is a vector let mut ops = vec![]; if let Some(mut proofs) = result.proof { @@ -285,7 +278,7 @@ impl Application for BaseCoinApp { fn commit(&self) -> ResponseCommit { let mut modules = self.modules.write().unwrap(); for m in modules.iter_mut() { - m.commit(); + m.commit().expect("failed to commit to state"); } let mut state = self.store.write().unwrap(); diff --git a/src/app/modules/bank.rs b/src/app/modules/bank.rs index e81e98f8..9db2bfe0 100644 --- a/src/app/modules/bank.rs +++ b/src/app/modules/bank.rs @@ -1,5 +1,5 @@ use crate::app::modules::{Error as ModuleError, Module, QueryResult}; -use crate::app::store::{Height, Identifier, Path, Store}; +use crate::app::store::{Height, Path, Store}; use std::collections::HashMap; use std::convert::TryInto; diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 31218ab4..8879fa35 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -1,5 +1,5 @@ use crate::app::modules::{Error as ModuleError, Identifiable, Module, QueryResult}; -use crate::app::store::{Height, Identifier, Path, ProvableStore, Store}; +use crate::app::store::{Height, Path, ProvableStore, Store}; use crate::prostgen::ibc::core::client::v1::{ query_server::Query as ClientQuery, ConsensusStateWithHeight, Height as RawHeight, QueryClientParamsRequest, QueryClientParamsResponse, QueryClientStateRequest, diff --git a/src/app/modules/mod.rs b/src/app/modules/mod.rs index 9f48a81a..8cc341a1 100644 --- a/src/app/modules/mod.rs +++ b/src/app/modules/mod.rs @@ -4,7 +4,7 @@ mod ibc; pub(crate) use self::bank::Bank; pub(crate) use self::ibc::Ibc; -use crate::app::store::{self, Height, Path, Store, SubStore}; +use crate::app::store::{self, Height, Path, Store}; use flex_error::{define_error, TraceError}; use prost_types::Any; diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index c225a4fb..b8ab4c3e 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -3,7 +3,7 @@ mod memory; pub(crate) use memory::InMemoryStore; -use crate::app::modules::{Error as ModuleError, Identifiable}; +use crate::app::modules::Error as ModuleError; use std::collections::VecDeque; use std::convert::{TryFrom, TryInto}; @@ -73,14 +73,6 @@ impl TryFrom for Identifier { #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Clone)] pub struct Path(String); -impl Path { - fn append(mut self, path: &Path) -> Self { - self.0.push('/'); - self.0.push_str(&path.0); - self - } -} - impl Deref for Path { type Target = String; diff --git a/src/main.rs b/src/main.rs index eaa72aee..b70c399f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod app; mod prostgen; -use crate::app::modules::{prefix, Ibc, Identifiable}; +use crate::app::modules::Ibc; use crate::app::store::{InMemoryStore, ProvableStore}; use crate::app::BaseCoinApp; use crate::prostgen::cosmos::auth::v1beta1::query_server::QueryServer as AuthQueryServer; From 41a4b090d578f3dc134a69b4fd91c95d986cc3cd Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Fri, 5 Nov 2021 23:40:17 +0530 Subject: [PATCH 31/42] Remove generic prefix from SubStore --- src/app/mod.rs | 6 ++---- src/app/store/mod.rs | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 5903f5c7..4c074821 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,9 +6,7 @@ pub(crate) mod store; use crate::app::modules::{prefix, Bank, Error, ErrorDetail, Ibc, Identifiable, Module}; use crate::app::response::ResponseFromErrorExt; -use crate::app::store::{ - Height, Identifier, Path, ProvableStore, SharedStore, Store, SubStore, WalStore, -}; +use crate::app::store::{Height, Path, ProvableStore, SharedStore, Store, SubStore, WalStore}; use crate::prostgen::cosmos::auth::v1beta1::{ query_server::Query as AuthQuery, BaseAccount, QueryAccountRequest, QueryAccountResponse, QueryAccountsRequest, QueryAccountsResponse, QueryParamsRequest as AuthQueryParamsRequest, @@ -60,7 +58,7 @@ use tonic::{Request, Response, Status}; use tracing::{debug, info}; type MainStore = SharedStore>; -type ModuleStore = SubStore, Identifier>; +type ModuleStore = SubStore>; type Shared = Arc>; /// BaseCoin ABCI application. diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index b8ab4c3e..ae784e60 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -197,17 +197,17 @@ pub trait ProvableStore: Store { /// A wrapper store that implements a prefixed key-space for other shared stores #[derive(Clone)] -pub(crate) struct SubStore { +pub(crate) struct SubStore { /// backing store store: S, /// sub store sub_store: S, /// prefix for key-space - prefix: P, + prefix: Identifier, dirty: bool, } -impl SubStore { +impl SubStore { pub(crate) fn new(store: S, prefix: Identifier) -> Result { let mut sub_store = Self { store, @@ -220,14 +220,14 @@ impl SubStore { } } -impl SubStore { +impl SubStore { fn update_parent_hash(&mut self) -> Result<(), S::Error> { self.store .set(Path::from(self.prefix.clone()), self.sub_store.root_hash()) } } -impl Store for SubStore +impl Store for SubStore where S: Default + ProvableStore, { @@ -273,7 +273,7 @@ where } } -impl ProvableStore for SubStore +impl ProvableStore for SubStore where S: Default + ProvableStore, { From f3dc2ad844f7dd264aefa1691e38f3aa41819169 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Sat, 6 Nov 2021 00:07:46 +0530 Subject: [PATCH 32/42] More cleanup --- src/app/mod.rs | 26 +++++++++++++++++--------- src/app/modules/bank.rs | 8 ++++++-- src/app/modules/ibc.rs | 14 +++++++++++--- src/app/store/mod.rs | 4 ++++ src/main.rs | 9 ++------- 5 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 4c074821..93267fb1 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -6,7 +6,9 @@ pub(crate) mod store; use crate::app::modules::{prefix, Bank, Error, ErrorDetail, Ibc, Identifiable, Module}; use crate::app::response::ResponseFromErrorExt; -use crate::app::store::{Height, Path, ProvableStore, SharedStore, Store, SubStore, WalStore}; +use crate::app::store::{ + Height, Identifier, Path, ProvableStore, SharedStore, Store, SubStore, WalStore, +}; use crate::prostgen::cosmos::auth::v1beta1::{ query_server::Query as AuthQuery, BaseAccount, QueryAccountRequest, QueryAccountResponse, QueryAccountsRequest, QueryAccountsResponse, QueryParamsRequest as AuthQueryParamsRequest, @@ -77,14 +79,14 @@ impl BaseCoinApp { let store = SharedStore::new(WalStore::new(store)); // `SubStore` guarantees modules exclusive access to all paths in the store key-space. let modules: Vec> + Send + Sync>> = vec![ - Box::new(Bank { - store: SubStore::new(store.clone(), prefix::Bank {}.identifier())?, - }), - Box::new(Ibc { - store: SubStore::new(store.clone(), prefix::Ibc {}.identifier())?, - client_counter: 0, - conn_counter: 0, - }), + Box::new(Bank::new(SubStore::new( + store.clone(), + prefix::Bank {}.identifier(), + )?)), + Box::new(Ibc::new(SubStore::new( + store.clone(), + prefix::Ibc {}.identifier(), + )?)), ]; Ok(Self { store, @@ -95,6 +97,12 @@ impl BaseCoinApp { } impl BaseCoinApp { + pub(crate) fn get_store(&self, prefix: Identifier) -> Option> { + let modules = self.modules.read().unwrap(); + let module = modules.iter().find(|m| m.store().prefix() == prefix); + module.map(|m| m.store()) + } + // try to deliver the message to all registered modules // if `module.deliver()` returns `Error::not_handled()`, try next module // Return: diff --git a/src/app/modules/bank.rs b/src/app/modules/bank.rs index 9db2bfe0..bfa11484 100644 --- a/src/app/modules/bank.rs +++ b/src/app/modules/bank.rs @@ -61,10 +61,14 @@ pub struct Balances(HashMap); pub struct Bank { /// Handle to store instance /// The module is guaranteed exclusive access to all paths in the store key-space. - pub store: S, + store: S, } impl Bank { + pub fn new(store: S) -> Self { + Self { store } + } + fn decode(message: Any) -> Result { if message.type_url != "/cosmos.bank.v1beta1.MsgSend" { return Err(ModuleError::not_handled()); @@ -95,7 +99,7 @@ impl Module for Bank { let mut src_balances: Balances = match self.store.get(Height::Pending, &src_path) { Some(sb) => serde_json::from_str(&String::from_utf8(sb).unwrap()).unwrap(), // safety - data on the store is assumed to be well-formed None => { - return Err(Error::non_existent_account(message.from_address.to_string()).into()) + return Err(Error::non_existent_account(message.from_address.to_string()).into()); } }; diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 8879fa35..2ad3de66 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -73,14 +73,22 @@ impl From for ModuleError { pub struct Ibc { /// Handle to store instance. /// The module is guaranteed exclusive access to all paths in the store key-space. - pub store: S, + store: S, /// Counter for clients - pub client_counter: u64, + client_counter: u64, /// Counter for connections - pub conn_counter: u64, + conn_counter: u64, } impl Ibc { + pub fn new(store: S) -> Self { + Self { + store, + client_counter: 0, + conn_counter: 0, + } + } + fn get_proof(&self, height: Height, path: &Path) -> Option> { if let Some(p) = self.store.get_proof(height, path) { let mut buffer = Vec::new(); diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index ae784e60..a775c80c 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -218,6 +218,10 @@ impl SubStore { sub_store.update_parent_hash()?; Ok(sub_store) } + + pub(crate) fn prefix(&self) -> Identifier { + self.prefix.clone() + } } impl SubStore { diff --git a/src/main.rs b/src/main.rs index b70c399f..d1a30769 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod app; mod prostgen; -use crate::app::modules::Ibc; +use crate::app::modules::{prefix, Ibc, Identifiable}; use crate::app::store::{InMemoryStore, ProvableStore}; use crate::app::BaseCoinApp; use crate::prostgen::cosmos::auth::v1beta1::query_server::QueryServer as AuthQueryServer; @@ -54,12 +54,7 @@ async fn grpc_serve( ) { let addr = format!("{}:{}", host, port).parse().unwrap(); - let ibc = Ibc { - // FIXME(hu55a1n1) - store: app.modules.clone().read().unwrap().get(1).unwrap().store(), - client_counter: 0, - conn_counter: 0, - }; + let ibc = Ibc::new(app.get_store(prefix::Ibc {}.identifier()).unwrap()); // TODO(hu55a1n1): implement these services for `auth` and `staking` modules Server::builder() From e7e89755b9f5f91ac8c834b1d2e0b0927528a7ca Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Sun, 7 Nov 2021 00:23:27 +0530 Subject: [PATCH 33/42] Change WalStore strategy to apply in-place and reset using log --- src/app/store/mod.rs | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index a775c80c..f9e0c2bc 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -375,15 +375,15 @@ impl DerefMut for SharedStore { pub(crate) struct WalStore { /// backing store store: S, - /// operation log for recording operations in preserved order - op_log: VecDeque<(Path, Vec)>, + /// operation log for recording rollback operations in preserved order + op_log: Vec, } impl WalStore { pub(crate) fn new(store: S) -> Self { Self { store, - op_log: VecDeque::new(), + op_log: vec![], } } } @@ -399,24 +399,14 @@ impl Store for WalStore { #[inline] fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { - self.op_log.push_back((path, value)); + self.store.set(path.clone(), value)?; + self.op_log.push(path); Ok(()) } #[inline] fn get(&self, height: Height, path: &Path) -> Option> { - match height { - // for pending height first look for path matches in the `op_log` and return the most - // recent one. If not found call backing store's `get()`. - Height::Pending => self - .op_log - .iter() - .filter(|op| &op.0 == path) - .last() - .map(|op| op.1.clone()) - .or_else(|| self.store.get(height, path)), - _ => self.store.get(height, path), - } + self.store.get(height, path) } #[inline] @@ -435,10 +425,7 @@ impl Store for WalStore { fn apply(&mut self) -> Result<(), Self::Error> { // note that we do NOT call the backing store's apply here - this allows users to create // multilayered `WalStore`s - trace!("Applying operation log if any"); - while let Some(op) = self.op_log.pop_back() { - self.store.set(op.0, op.1)?; - } + self.op_log.clear(); Ok(()) } @@ -447,7 +434,9 @@ impl Store for WalStore { // note that we do NOT call the backing store's reset here - this allows users to create // multilayered `WalStore`s trace!("Rollback operation log changes"); - self.op_log.clear() + while let Some(op) = self.op_log.pop() { + self.store.delete(&op); + } } #[inline] From baf693519ddade7639dc17b43be30bbe032e8dde Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Sun, 7 Nov 2021 00:25:34 +0530 Subject: [PATCH 34/42] Update substore hash after commit --- src/app/store/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index f9e0c2bc..8a2baf13 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -258,11 +258,12 @@ where #[inline] fn commit(&mut self) -> Result, Self::Error> { + let root_hash = self.sub_store.commit()?; if self.dirty { self.dirty = false; self.update_parent_hash()?; } - self.sub_store.commit() + Ok(root_hash) } #[inline] From 8ef7fbf8118005121eb578b8f4d0f61e94415b25 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 8 Nov 2021 13:42:10 +0530 Subject: [PATCH 35/42] Refactor SubStore and update docs --- src/app/store/mod.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index 8a2baf13..a725ac33 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -5,7 +5,6 @@ pub(crate) use memory::InMemoryStore; use crate::app::modules::Error as ModuleError; -use std::collections::VecDeque; use std::convert::{TryFrom, TryInto}; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; @@ -198,24 +197,25 @@ pub trait ProvableStore: Store { /// A wrapper store that implements a prefixed key-space for other shared stores #[derive(Clone)] pub(crate) struct SubStore { - /// backing store - store: S, - /// sub store + /// main store - used to stores a commitment to this sub-store at path `` + main_store: S, + /// sub store - the underlying store that actually holds the KV pairs for this sub-store sub_store: S, /// prefix for key-space prefix: Identifier, + /// boolean to keep track of changes to know when to update commitment in main-store dirty: bool, } impl SubStore { pub(crate) fn new(store: S, prefix: Identifier) -> Result { let mut sub_store = Self { - store, + main_store: store, sub_store: S::default(), prefix, dirty: false, }; - sub_store.update_parent_hash()?; + sub_store.update_main_store_commitment()?; Ok(sub_store) } @@ -225,8 +225,8 @@ impl SubStore { } impl SubStore { - fn update_parent_hash(&mut self) -> Result<(), S::Error> { - self.store + fn update_main_store_commitment(&mut self) -> Result<(), S::Error> { + self.main_store .set(Path::from(self.prefix.clone()), self.sub_store.root_hash()) } } @@ -261,7 +261,7 @@ where let root_hash = self.sub_store.commit()?; if self.dirty { self.dirty = false; - self.update_parent_hash()?; + self.update_main_store_commitment()?; } Ok(root_hash) } From 6e0380926a94a6f11cbd142983280ddb9a99dc5f Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Sat, 20 Nov 2021 01:27:55 +0530 Subject: [PATCH 36/42] Remove redundant definitions of {next, prev}_consensus_state() --- src/app/modules/ibc.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index 2ad3de66..fc923496 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -217,22 +217,6 @@ impl ClientReader for Ibc { fn client_counter(&self) -> Result { Ok(self.client_counter) } - - fn next_consensus_state( - &self, - _client_id: &ClientId, - _height: IbcHeight, - ) -> Result, ClientError> { - Ok(None) - } - - fn prev_consensus_state( - &self, - _client_id: &ClientId, - _height: IbcHeight, - ) -> Result, ClientError> { - Ok(None) - } } impl ClientKeeper for Ibc { From e8bb36b2ad0d222806bf474b818c298c94fdb3bb Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Sat, 20 Nov 2021 01:53:16 +0530 Subject: [PATCH 37/42] Update ibc patched deps --- Cargo.lock | 18 ++++-------------- Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9aab2adb..18552a61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -691,8 +691,7 @@ dependencies = [ [[package]] name = "ibc" version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93a6044c39dae068cc6bc4a60e678f02b780693f46970a5c80fd19c6cdb0e363" +source = "git+https://github.com/informalsystems/ibc-rs.git?branch=basecoin/phase-4-1#a5f2fd3b81f94040d298bd3f6a69fe09b7d1c786" dependencies = [ "bytes", "chrono", @@ -705,6 +704,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "sha2", "subtle-encoding", "tendermint", "tendermint-light-client", @@ -716,13 +716,13 @@ dependencies = [ [[package]] name = "ibc-proto" version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d7650d11886f290bb223d4120eca28c8b2e114cf0362188364303a4c0230e7" +source = "git+https://github.com/informalsystems/ibc-rs.git?branch=basecoin/phase-4-1#a5f2fd3b81f94040d298bd3f6a69fe09b7d1c786" dependencies = [ "bytes", "getrandom 0.2.3", "prost", "prost-types", + "serde", "tendermint-proto", "tonic", ] @@ -2109,13 +2109,3 @@ dependencies = [ "syn", "synstructure", ] - -[[patch.unused]] -name = "ibc" -version = "0.7.3" -source = "git+https://github.com/informalsystems/ibc-rs.git?branch=basecoin/phase-4#e19248d9f81a98deb1e708f8bf008072484bfc06" - -[[patch.unused]] -name = "ibc-proto" -version = "0.11.0" -source = "git+https://github.com/informalsystems/ibc-rs.git?branch=basecoin/phase-4#e19248d9f81a98deb1e708f8bf008072484bfc06" diff --git a/Cargo.toml b/Cargo.toml index 692061a0..ade32717 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,5 +35,5 @@ tonic="0.6" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } [patch.crates-io] -ibc = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4" } -ibc-proto = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4" } +ibc = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4-1" } +ibc-proto = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4-1" } From 5a948c51f2081d8dd60045cd65e78f6c06ed1f78 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 29 Nov 2021 16:55:23 +0530 Subject: [PATCH 38/42] Return old value from AVL set() --- src/app/store/avl/node.rs | 5 +++-- src/app/store/avl/tree.rs | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/app/store/avl/node.rs b/src/app/store/avl/node.rs index a7029339..762a0563 100644 --- a/src/app/store/avl/node.rs +++ b/src/app/store/avl/node.rs @@ -54,10 +54,11 @@ where } /// Set the value of the current node. - pub(crate) fn set_value(&mut self, value: V) { + pub(crate) fn set_value(&mut self, mut value: V) -> V { let hash = Self::local_hash(&self.key, &value); - self.value = value; + std::mem::swap(&mut self.value, &mut value); self.hash = hash; + value } /// The left height, or `None` if there is no left child. diff --git a/src/app/store/avl/tree.rs b/src/app/store/avl/tree.rs index 5effbd19..e64aac88 100644 --- a/src/app/store/avl/tree.rs +++ b/src/app/store/avl/tree.rs @@ -51,18 +51,20 @@ where } /// Insert a value into the AVL tree, this operation runs in amortized O(log(n)). - pub fn insert(&mut self, key: K, value: V) { + pub fn insert(&mut self, key: K, value: V) -> Option { let node_ref = &mut self.root; - AvlTree::insert_rec(node_ref, key, value); + let mut old_value = None; + AvlTree::insert_rec(node_ref, key, value, &mut old_value); + old_value } /// Insert a value in the tree. - fn insert_rec(node_ref: &mut NodeRef, key: K, value: V) { + fn insert_rec(node_ref: &mut NodeRef, key: K, value: V, old_value: &mut Option) { if let Some(node) = node_ref { match node.key.cmp(&key) { - Ordering::Greater => AvlTree::insert_rec(&mut node.left, key, value), - Ordering::Less => AvlTree::insert_rec(&mut node.right, key, value), - Ordering::Equal => node.set_value(value), + Ordering::Greater => AvlTree::insert_rec(&mut node.left, key, value, old_value), + Ordering::Less => AvlTree::insert_rec(&mut node.right, key, value, old_value), + Ordering::Equal => *old_value = Some(node.set_value(value)), } node.update(); AvlTree::balance_node(node_ref); From 564fe241e82d6e283413543eb0604c032e4bff88 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 29 Nov 2021 17:02:16 +0530 Subject: [PATCH 39/42] Modify Store::set() to return old value for updates --- src/app/modules/ibc.rs | 15 ++++++++++----- src/app/store/memory.rs | 5 ++--- src/app/store/mod.rs | 14 +++++++------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/app/modules/ibc.rs b/src/app/modules/ibc.rs index fc923496..11c3227c 100644 --- a/src/app/modules/ibc.rs +++ b/src/app/modules/ibc.rs @@ -230,7 +230,8 @@ impl ClientKeeper for Ibc { .unwrap(); // safety - path must be valid since ClientId is a valid Identifier self.store .set(path, serde_json::to_string(&client_type).unwrap().into()) // safety - cannot fail since ClientType's Serialize impl doesn't fail - .map_err(|_| ClientError::implementation_specific()) + .map_err(|_| ClientError::implementation_specific())?; + Ok(()) } fn store_client_state( @@ -248,7 +249,8 @@ impl ClientKeeper for Ibc { .unwrap(); // safety - path must be valid since ClientId is a valid Identifier self.store .set(path, buffer) - .map_err(|_| ClientError::implementation_specific()) + .map_err(|_| ClientError::implementation_specific())?; + Ok(()) } fn store_consensus_state( @@ -267,7 +269,8 @@ impl ClientKeeper for Ibc { .unwrap(); // safety - path must be valid since ClientId and height are valid Identifiers self.store .set(path, buffer) - .map_err(|_| ClientError::implementation_specific()) + .map_err(|_| ClientError::implementation_specific())?; + Ok(()) } fn increase_client_counter(&mut self) { @@ -344,7 +347,8 @@ impl ConnectionKeeper for Ibc { let path = format!("connections/{}", connection_id).try_into().unwrap(); // safety - path must be valid since ClientId is a valid Identifier self.store .set(path, buffer) - .map_err(|_| ConnectionError::implementation_specific()) + .map_err(|_| ConnectionError::implementation_specific())?; + Ok(()) } fn store_connection_to_client( @@ -364,7 +368,8 @@ impl ConnectionKeeper for Ibc { conn_ids.push(connection_id); self.store .set(path, serde_json::to_string(&conn_ids).unwrap().into()) // safety - cannot fail since ClientType's Serialize impl doesn't fail - .map_err(|_| ConnectionError::implementation_specific()) + .map_err(|_| ConnectionError::implementation_specific())?; + Ok(()) } fn increase_connection_counter(&mut self) { diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index b57f4f85..18d47ea2 100644 --- a/src/app/store/memory.rs +++ b/src/app/store/memory.rs @@ -50,10 +50,9 @@ impl Default for InMemoryStore { impl Store for InMemoryStore { type Error = (); // underlying store ops are infallible - fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { trace!("set at path = {}", path.as_str()); - self.pending.insert(path, value); - Ok(()) + Ok(self.pending.insert(path, value)) } fn get(&self, height: Height, path: &Path) -> Option> { diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index a725ac33..8317b8c3 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -154,7 +154,7 @@ pub trait Store: Send + Sync + Clone { type Error: core::fmt::Debug; /// Set `value` for `path` - fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error>; + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error>; /// Get associated `value` for `path` at specified `height` fn get(&self, height: Height, path: &Path) -> Option>; @@ -225,7 +225,7 @@ impl SubStore { } impl SubStore { - fn update_main_store_commitment(&mut self) -> Result<(), S::Error> { + fn update_main_store_commitment(&mut self) -> Result>, S::Error> { self.main_store .set(Path::from(self.prefix.clone()), self.sub_store.root_hash()) } @@ -238,7 +238,7 @@ where type Error = S::Error; #[inline] - fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { self.dirty = true; self.sub_store .set(self.prefix.unprefixed_path(&path), value) @@ -314,7 +314,7 @@ impl Store for SharedStore { type Error = S::Error; #[inline] - fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { self.write().unwrap().set(path, value) } @@ -399,10 +399,10 @@ impl Store for WalStore { type Error = S::Error; #[inline] - fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { - self.store.set(path.clone(), value)?; + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { + let old_value = self.store.set(path.clone(), value)?; self.op_log.push(path); - Ok(()) + Ok(old_value) } #[inline] From 10faf2d13373bdc8302bacc9bd7fe6da5e26eba0 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 29 Nov 2021 17:30:53 +0530 Subject: [PATCH 40/42] Fix AVL set_node --- src/app/store/avl/node.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/store/avl/node.rs b/src/app/store/avl/node.rs index 762a0563..bd50d068 100644 --- a/src/app/store/avl/node.rs +++ b/src/app/store/avl/node.rs @@ -1,4 +1,4 @@ -use std::borrow::Borrow; +use std::{borrow::Borrow, mem}; use sha2::{Digest, Sha256}; use tendermint::hash::Hash; @@ -54,11 +54,10 @@ where } /// Set the value of the current node. - pub(crate) fn set_value(&mut self, mut value: V) -> V { + pub(crate) fn set_value(&mut self, value: V) -> V { let hash = Self::local_hash(&self.key, &value); - std::mem::swap(&mut self.value, &mut value); self.hash = hash; - value + mem::replace(&mut self.value, value) } /// The left height, or `None` if there is no left child. From c0c13df840d9fe65876ab928ac88df4bd218b960 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 29 Nov 2021 17:33:45 +0530 Subject: [PATCH 41/42] Fix WALStore impl --- src/app/mod.rs | 6 +++--- src/app/store/mod.rs | 38 +++++++++++++++++++++++++++----------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/src/app/mod.rs b/src/app/mod.rs index 93267fb1..df326180 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -7,7 +7,7 @@ pub(crate) mod store; use crate::app::modules::{prefix, Bank, Error, ErrorDetail, Ibc, Identifiable, Module}; use crate::app::response::ResponseFromErrorExt; use crate::app::store::{ - Height, Identifier, Path, ProvableStore, SharedStore, Store, SubStore, WalStore, + Height, Identifier, Path, ProvableStore, RevertibleStore, SharedStore, Store, SubStore, }; use crate::prostgen::cosmos::auth::v1beta1::{ query_server::Query as AuthQuery, BaseAccount, QueryAccountRequest, QueryAccountResponse, @@ -59,7 +59,7 @@ use tendermint_proto::p2p::DefaultNodeInfo; use tonic::{Request, Response, Status}; use tracing::{debug, info}; -type MainStore = SharedStore>; +type MainStore = SharedStore>; type ModuleStore = SubStore>; type Shared = Arc>; @@ -76,7 +76,7 @@ pub(crate) struct BaseCoinApp { impl BaseCoinApp { /// Constructor. pub(crate) fn new(store: S) -> Result { - let store = SharedStore::new(WalStore::new(store)); + let store = SharedStore::new(RevertibleStore::new(store)); // `SubStore` guarantees modules exclusive access to all paths in the store key-space. let modules: Vec> + Send + Sync>> = vec![ Box::new(Bank::new(SubStore::new( diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index 8317b8c3..2ab86715 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -370,17 +370,22 @@ impl DerefMut for SharedStore { } } -/// A wrapper store that implements rudimentary `apply()`/`reset()` support using write-ahead -/// logging for other stores +/// A wrapper store that implements rudimentary `apply()`/`reset()` support for other stores #[derive(Clone)] -pub(crate) struct WalStore { +pub(crate) struct RevertibleStore { /// backing store store: S, /// operation log for recording rollback operations in preserved order - op_log: Vec, + op_log: Vec, } -impl WalStore { +#[derive(Clone)] +enum RevertOp { + Delete(Path), + Set(Path, Vec), +} + +impl RevertibleStore { pub(crate) fn new(store: S) -> Self { Self { store, @@ -389,19 +394,25 @@ impl WalStore { } } -impl Default for WalStore { +impl Default for RevertibleStore { fn default() -> Self { Self::new(S::default()) } } -impl Store for WalStore { +impl Store for RevertibleStore { type Error = S::Error; #[inline] fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { let old_value = self.store.set(path.clone(), value)?; - self.op_log.push(path); + match old_value { + // None implies this was an insert op, so we record the revert op as delete op + None => self.op_log.push(RevertOp::Delete(path)), + // Some old value implies this was an update op, so we record the revert op as a set op + // with the old value + Some(ref old_value) => self.op_log.push(RevertOp::Set(path, old_value.clone())), + } Ok(old_value) } @@ -412,7 +423,7 @@ impl Store for WalStore { #[inline] fn delete(&mut self, _path: &Path) { - unimplemented!("WALStore doesn't support delete operations yet!") + unimplemented!("RevertibleStore doesn't support delete operations yet!") } #[inline] @@ -436,7 +447,12 @@ impl Store for WalStore { // multilayered `WalStore`s trace!("Rollback operation log changes"); while let Some(op) = self.op_log.pop() { - self.store.delete(&op); + match op { + RevertOp::Delete(path) => self.delete(&path), + RevertOp::Set(path, value) => { + self.set(path, value).unwrap(); // safety - reset failures are unrecoverable + } + } } } @@ -451,7 +467,7 @@ impl Store for WalStore { } } -impl ProvableStore for WalStore { +impl ProvableStore for RevertibleStore { #[inline] fn root_hash(&self) -> Vec { self.store.root_hash() From 3ea1bcbaa1616ab812dd9c4981f796585c2927cb Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 6 Dec 2021 17:41:16 +0530 Subject: [PATCH 42/42] Update ibc.md --- docs/modules/ibc.md | 64 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/docs/modules/ibc.md b/docs/modules/ibc.md index 36c133f9..28851a2c 100644 --- a/docs/modules/ibc.md +++ b/docs/modules/ibc.md @@ -11,14 +11,14 @@ This module has been tested with `hermes`. Edit your `genesis.json` file (default location `~/.tendermint/config/genesis.json`) to update the `chain_id`. ```json { - "chain_id": "basecoin" + "chain_id": "basecoin-0" } ``` Edit the `config.toml` file (default location `~/.hermes/config.toml`) for `hermes` and add an entry for the basecoin chain: ```toml [[chains]] -id = 'basecoin' +id = 'basecoin-0' rpc_addr = 'http://127.0.0.1:26357' grpc_addr = 'http://127.0.0.1:9093' websocket_addr = 'ws://localhost:26357/websocket' @@ -29,6 +29,62 @@ store_prefix = 'basecoin' gas_price = { price = 0.001, denom = 'stake' } clock_drift = '5s' trusting_period = '14days' +proof_specs = ''' +[ + { + "leaf_spec": { + "hash": 1, + "prehash_key": 0, + "prehash_value": 0, + "length": 0, + "prefix": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "inner_spec": { + "child_order": [ + 0, + 1, + 2 + ], + "child_size": 32, + "min_prefix_length": 0, + "max_prefix_length": 64, + "empty_child": [ + 0, + 32 + ], + "hash": 1 + }, + "max_depth": 0, + "min_depth": 0 + }, + { + "leaf_spec": { + "hash": 1, + "prehash_key": 0, + "prehash_value": 0, + "length": 0, + "prefix": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + "inner_spec": { + "child_order": [ + 0, + 1, + 2 + ], + "child_size": 32, + "min_prefix_length": 0, + "max_prefix_length": 64, + "empty_child": [ + 0, + 32 + ], + "hash": 1 + }, + "max_depth": 0, + "min_depth": 0 + } +] +''' ``` **Note:** The above settings must match the corresponding settings in Tendermint's `config.toml`. @@ -47,6 +103,6 @@ $ hermes keys add basecoin-0 -f user_seed.json ### Step 4: Create and Update a client Assuming the `basecoin-0` chain and tendermint are running (see instructions on [README.md#run-the-basecoin-app-and-tendermint](../../README.md#step-4-run-the-basecoin-app-and-tendermint)). ```shell -$ hermes tx raw create-client basecoin ibc-0 -$ hermes tx raw update-client basecoin 07-tendermint-0 +$ hermes tx raw create-client basecoin-0 ibc-0 +$ hermes tx raw update-client basecoin-0 07-tendermint-0 ```