diff --git a/Cargo.lock b/Cargo.lock index e894dfaa..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", ] @@ -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..ade32717 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,13 @@ 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" flex-error = { version = "0.4.2", features = [ "eyre_tracer" ] } -ibc = "0.8.0" -ics23 = "0.6.0" +ibc = "=0.8.0" +ibc-proto = "=0.12.0" +ics23 = "=0.6.7" prost = "0.9.0" prost-types = "0.9.0" serde = "1.0" @@ -34,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", branch = "basecoin/phase-4-1" } +ibc-proto = { git = "https://github.com/informalsystems/ibc-rs.git", branch = "basecoin/phase-4-1" } 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 ``` diff --git a/src/app/mod.rs b/src/app/mod.rs index 3b755b36..df326180 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -2,12 +2,13 @@ pub(crate) mod modules; mod response; - 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, RevertibleStore, SharedStore, Store, SubStore, +}; use crate::prostgen::cosmos::auth::v1beta1::{ query_server::Query as AuthQuery, BaseAccount, QueryAccountRequest, QueryAccountResponse, QueryAccountsRequest, QueryAccountsResponse, QueryParamsRequest as AuthQueryParamsRequest, @@ -34,6 +35,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}; @@ -47,48 +53,56 @@ 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}; use tracing::{debug, info}; +type MainStore = SharedStore>; +type ModuleStore = SubStore>; +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>, - modules: Arc>>>, + store: MainStore, + pub modules: Shared> + Send + Sync>>>, + account: Shared, // TODO(hu55a1n1): get from user and move to provable store } -impl BaseCoinApp { +impl BaseCoinApp { /// Constructor. - pub(crate) fn new(store: S) -> Self { - let store = SharedStore::new(WalStore::new(store)); + pub(crate) fn new(store: S) -> Result { + let store = SharedStore::new(RevertibleStore::new(store)); // `SubStore` guarantees modules exclusive access to all paths in the store key-space. - let modules: Vec> = vec![ - Box::new(Bank { - store: SubStore::new(store.clone(), prefix::Bank), - }), - Box::new(Ibc { - store: SubStore::new(store.clone(), prefix::Ibc), - client_counter: 0, - }), + let modules: Vec> + Send + Sync>> = vec![ + Box::new(Bank::new(SubStore::new( + store.clone(), + prefix::Bank {}.identifier(), + )?)), + Box::new(Ibc::new(SubStore::new( + store.clone(), + prefix::Ibc {}.identifier(), + )?)), ]; - Self { + Ok(Self { store, modules: Arc::new(RwLock::new(modules)), - } + account: Default::default(), + }) } +} - pub(crate) fn sub_store( - &self, - prefix: I, - ) -> SubStore>, I> { - SubStore::new(self.store.clone(), prefix) +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()) } -} -impl BaseCoinApp { // try to deliver the message to all registered modules // if `module.deliver()` returns `Error::not_handled()`, try next module // Return: @@ -120,7 +134,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(); @@ -171,16 +185,44 @@ 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) => { + 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).unwrap(); // safety - cannot fail since buf is a vector + + 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, - height: self.store.read().unwrap().current_height() as i64, - ..ResponseQuery::default() + value: result.data, + proof_ops, + height: store.current_height() as i64, + ..Default::default() }; } // `Error::not_handled()` - implies query isn't known or was intercepted but not @@ -240,9 +282,20 @@ impl Application for BaseCoinApp { } fn commit(&self) -> ResponseCommit { + let mut modules = self.modules.write().unwrap(); + for m in modules.iter_mut() { + m.commit().expect("failed to commit to state"); + } + 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, @@ -327,9 +380,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 { @@ -457,3 +511,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/app/modules/bank.rs b/src/app/modules/bank.rs index 55bdd4f4..bfa11484 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; @@ -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()); @@ -73,7 +77,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() @@ -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()); } }; @@ -163,7 +167,8 @@ impl Module for Bank { data: &[u8], _path: Option<&Path>, height: Height, - ) -> Result, ModuleError> { + _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 _ => return Err(ModuleError::not_handled()), @@ -174,7 +179,18 @@ 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, + }), } } + + 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 ccd23e7f..11c3227c 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, 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, @@ -9,12 +9,22 @@ 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; 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; @@ -31,7 +41,7 @@ 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::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; @@ -39,9 +49,12 @@ 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}; use tendermint_proto::abci::{Event, EventAttribute}; +use tendermint_proto::crypto::ProofOp; use tonic::{Request, Response, Status}; use tracing::{debug, trace}; @@ -60,9 +73,31 @@ 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, - /// Counter for client identifiers - pub client_counter: u64, + store: S, + /// Counter for clients + client_counter: u64, + /// Counter for connections + 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(); + if p.encode(&mut buffer).is_ok() { + return Some(buffer); + } + } + None + } } impl ClientReader for Ibc { @@ -72,9 +107,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 { @@ -102,32 +136,87 @@ 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 client_counter(&self) -> Result { - Ok(self.client_counter) - } - fn next_consensus_state( &self, - _client_id: &ClientId, - _height: IbcHeight, + client_id: &ClientId, + height: IbcHeight, ) -> Result, ClientError> { - Ok(None) + 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> { + 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 { + Ok(self.client_counter) + } } impl ClientKeeper for Ibc { @@ -141,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( @@ -159,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( @@ -178,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) { @@ -187,65 +279,101 @@ 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!("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) + .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 { - 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::unix_epoch(), + 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 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, buffer) + .map_err(|_| ConnectionError::implementation_specific())?; + Ok(()) } 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())?; + Ok(()) } fn increase_connection_counter(&mut self) { - todo!() + self.conn_counter += 1; } } @@ -443,7 +571,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())?; @@ -463,7 +591,8 @@ impl Module for Ibc { data: &[u8], path: Option<&Path>, height: Height, - ) -> Result, ModuleError> { + prove: bool, + ) -> Result { let path = path.ok_or_else(ModuleError::not_handled)?; if path.to_string() != IBC_QUERY_PATH { return Err(ModuleError::not_handled()); @@ -480,10 +609,32 @@ 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), - } + let proof = if prove { + let proof = self + .get_proof(height, &path) + .ok_or_else(|| Error::ics02_client(ClientError::implementation_specific()))?; + Some(vec![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 }) + } + + fn commit(&mut self) -> Result, S::Error> { + self.store.commit() + } + + fn store(&self) -> S { + self.store.clone() } } @@ -497,7 +648,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 { @@ -505,7 +656,63 @@ 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 { + 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: true, + }], + }, + 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: true, + }], + }, + 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: true, + }], + }, + 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: true, }], }, _ => todo!(), @@ -605,6 +812,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/app/modules/mod.rs b/src/app/modules/mod.rs index 4d8a6a49..8cc341a1 100644 --- a/src/app/modules/mod.rs +++ b/src/app/modules/mod.rs @@ -4,11 +4,12 @@ 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, Store}; 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)] @@ -28,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 @@ -56,10 +57,25 @@ 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, + _prove: bool, + ) -> Result { Err(Error::not_handled()) } + + fn commit(&mut self) -> Result, S::Error>; + + fn store(&self) -> S; +} + +pub struct QueryResult { + pub data: Vec, + pub proof: Option>, } /// Trait for identifying modules diff --git a/src/app/store/avl/node.rs b/src/app/store/avl/node.rs index 0cad7465..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; @@ -53,6 +53,13 @@ where } } + /// Set the value of the current node. + pub(crate) fn set_value(&mut self, value: V) -> V { + let hash = Self::local_hash(&self.key, &value); + self.hash = hash; + mem::replace(&mut self.value, value) + } + /// 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..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.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); diff --git a/src/app/store/memory.rs b/src/app/store/memory.rs index 3b726d33..18d47ea2 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 { @@ -32,30 +50,14 @@ 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> { 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).unwrap(); - state.get(path).cloned() - } else { - None - } - } - } + self.get_state(height).and_then(|v| v.get(path).cloned()) } fn delete(&mut self, _path: &Path) { @@ -91,8 +93,13 @@ impl ProvableStore for InMemoryStore { .to_vec() } - fn get_proof(&self, _key: &Path) -> Option { - todo!() + 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)) } } diff --git a/src/app/store/mod.rs b/src/app/store/mod.rs index 448ba7f7..2ab86715 100644 --- a/src/app/store/mod.rs +++ b/src/app/store/mod.rs @@ -3,9 +3,8 @@ 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}; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; @@ -38,6 +37,12 @@ impl Identifier { || matches!(c, '.' | '_' | '+' | '-' | '#' | '[' | ']' | '<' | '>' | '/') }) } + + #[inline] + fn unprefixed_path(&self, path: &Path) -> Path { + // FIXME(hu55a1n1) + path.clone() + } } impl Deref for Identifier { @@ -67,14 +72,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; @@ -135,7 +132,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, @@ -157,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>; @@ -194,75 +191,106 @@ 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 #[derive(Clone)] -pub(crate) struct SubStore { - /// backing store - store: S, +pub(crate) struct SubStore { + /// 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: P, + 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 { + main_store: store, + sub_store: S::default(), + prefix, + dirty: false, + }; + sub_store.update_main_store_commitment()?; + Ok(sub_store) + } + + pub(crate) fn prefix(&self) -> Identifier { + self.prefix.clone() + } } -impl SubStore { - pub(crate) fn new(store: S, prefix: P) -> Self { - Self { store, prefix } +impl SubStore { + fn update_main_store_commitment(&mut self) -> Result>, S::Error> { + self.main_store + .set(Path::from(self.prefix.clone()), self.sub_store.root_hash()) } } -impl Store for SubStore +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) + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { + 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!") + let root_hash = self.sub_store.commit()?; + if self.dirty { + self.dirty = false; + self.update_main_store_commitment()?; + } + Ok(root_hash) } #[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, key: &Path) -> Option { - self.store.get_proof(key) + fn get_proof(&self, height: Height, key: &Path) -> Option { + self.sub_store + .get_proof(height, &self.prefix.unprefixed_path(key)) } } @@ -276,11 +304,17 @@ impl SharedStore { } } +impl Default for SharedStore { + fn default() -> Self { + Self::new(S::default()) + } +} + 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) } @@ -317,8 +351,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) } } @@ -336,53 +370,60 @@ 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 operations in preserved order - op_log: VecDeque<(Path, Vec)>, + /// operation log for recording rollback operations in preserved order + op_log: Vec, +} + +#[derive(Clone)] +enum RevertOp { + Delete(Path), + Set(Path, Vec), } -// -impl WalStore { + +impl RevertibleStore { pub(crate) fn new(store: S) -> Self { Self { store, - op_log: VecDeque::new(), + op_log: vec![], } } } -impl Store for WalStore { +impl Default for RevertibleStore { + fn default() -> Self { + Self::new(S::default()) + } +} + +impl Store for RevertibleStore { type Error = S::Error; #[inline] - fn set(&mut self, path: Path, value: Vec) -> Result<(), Self::Error> { - self.op_log.push_back((path, value)); - Ok(()) + fn set(&mut self, path: Path, value: Vec) -> Result>, Self::Error> { + let old_value = self.store.set(path.clone(), value)?; + 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) } #[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] fn delete(&mut self, _path: &Path) { - unimplemented!("WALStore doesn't support delete operations yet!") + unimplemented!("RevertibleStore doesn't support delete operations yet!") } #[inline] @@ -396,10 +437,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"); - while let Some(op) = self.op_log.pop_back() { - self.store.set(op.0, op.1)?; - } + self.op_log.clear(); Ok(()) } @@ -408,7 +446,14 @@ 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() { + match op { + RevertOp::Delete(path) => self.delete(&path), + RevertOp::Set(path, value) => { + self.set(path, value).unwrap(); // safety - reset failures are unrecoverable + } + } + } } #[inline] @@ -422,32 +467,14 @@ impl Store for WalStore { } } -impl ProvableStore for WalStore { +impl ProvableStore for RevertibleStore { #[inline] fn root_hash(&self) -> Vec { self.store.root_hash() } #[inline] - fn get_proof(&self, key: &Path) -> Option { - self.store.get_proof(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() - } + fn get_proof(&self, height: Height, key: &Path) -> Option { + self.store.get_proof(height, key) } } diff --git a/src/main.rs b/src/main.rs index 8df21c43..d1a30769 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,13 +3,15 @@ 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; 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; use structopt::StructOpt; use tendermint_abci::ServerBuilder; @@ -45,18 +47,23 @@ 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::new(app.get_store(prefix::Ibc {}.identifier()).unwrap()); + // 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, - })) + .add_service(TxServer::new(app.clone())) + .add_service(ClientQueryServer::new(ibc.clone())) + .add_service(ConnectionQueryServer::new(ibc)) .serve(addr) .await .unwrap() @@ -75,7 +82,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(); diff --git a/src/prostgen.rs b/src/prostgen.rs index 469cf371..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"); + } + } } } @@ -46,5 +72,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"); +}