From 3021889894e9b78b1c7410666ac76819e46a23fd Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 13:51:45 -0400 Subject: [PATCH 01/25] split A.4.1, proto tooling changes --- tools/proto-compiler/src/constants.rs | 7 ++++--- tools/proto-compiler/src/functions.rs | 8 ++++---- tools/proto-compiler/src/main.rs | 6 +++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/tools/proto-compiler/src/constants.rs b/tools/proto-compiler/src/constants.rs index 3474f85fa..e0c5d01de 100644 --- a/tools/proto-compiler/src/constants.rs +++ b/tools/proto-compiler/src/constants.rs @@ -21,8 +21,7 @@ const HEXSTRING: &str = r#"#[serde(with = "crate::serializers::bytes::hexstring" const BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::base64string")]"#; const VEC_BASE64STRING: &str = r#"#[serde(with = "crate::serializers::bytes::vec_base64string")]"#; const OPTIONAL: &str = r#"#[serde(with = "crate::serializers::optional")]"#; -const VEC_SKIP_IF_EMPTY: &str = - r#"#[serde(skip_serializing_if = "::prost::alloc::vec::Vec::is_empty", with = "serde_bytes")]"#; +const BYTES_SKIP_IF_EMPTY: &str = r#"#[serde(skip_serializing_if = "bytes::Bytes::is_empty")]"#; const NULLABLEVECARRAY: &str = r#"#[serde(with = "crate::serializers::txs")]"#; const NULLABLE: &str = r#"#[serde(with = "crate::serializers::nullable")]"#; const ALIAS_POWER_QUOTED: &str = @@ -31,6 +30,7 @@ const PART_SET_HEADER_TOTAL: &str = r#"#[serde(with = "crate::serializers::part_set_header_total")]"#; const RENAME_EDPUBKEY: &str = r#"#[serde(rename = "tendermint/PubKeyEd25519", with = "crate::serializers::bytes::base64string")]"#; const RENAME_SECPPUBKEY: &str = r#"#[serde(rename = "tendermint/PubKeySecp256k1", with = "crate::serializers::bytes::base64string")]"#; +const RENAME_SRPUBKEY: &str = r#"#[serde(rename = "tendermint/PubKeySr25519", with = "crate::serializers::bytes::base64string")]"#; const RENAME_DUPLICATEVOTE: &str = r#"#[serde(rename = "tendermint/DuplicateVoteEvidence")]"#; const RENAME_LIGHTCLIENTATTACK: &str = r#"#[serde(rename = "tendermint/LightClientAttackEvidence")]"#; @@ -101,7 +101,7 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[ (".tendermint.abci.ResponseInfo.last_block_app_hash", DEFAULT), ( ".tendermint.abci.ResponseInfo.last_block_app_hash", - VEC_SKIP_IF_EMPTY, + BYTES_SKIP_IF_EMPTY, ), (".tendermint.types.BlockID.hash", HEXSTRING), (".tendermint.types.BlockID.part_set_header", ALIAS_PARTS), @@ -154,6 +154,7 @@ pub static CUSTOM_FIELD_ATTRIBUTES: &[(&str, &str)] = &[ ".tendermint.crypto.PublicKey.sum.secp256k1", RENAME_SECPPUBKEY, ), + (".tendermint.crypto.PublicKey.sum.sr25519", RENAME_SRPUBKEY), ( ".tendermint.types.Evidence.sum.duplicate_vote_evidence", RENAME_DUPLICATEVOTE, diff --git a/tools/proto-compiler/src/functions.rs b/tools/proto-compiler/src/functions.rs index 6055dcf03..df2158019 100644 --- a/tools/proto-compiler/src/functions.rs +++ b/tools/proto-compiler/src/functions.rs @@ -13,7 +13,7 @@ use walkdir::WalkDir; /// Clone or open+fetch a repository and check out a specific commitish /// In case of an existing repository, the origin remote will be set to `url`. -pub fn get_commitish(dir: &PathBuf, url: &str, commitish: &str) { +pub fn get_commitish(dir: &Path, url: &str, commitish: &str) { let repo = if dir.exists() { fetch_existing(dir, url) } else { @@ -39,7 +39,7 @@ fn clone_new(dir: &Path, url: &str) -> Repository { builder.clone(url, dir).unwrap() } -fn fetch_existing(dir: &PathBuf, url: &str) -> Repository { +fn fetch_existing(dir: &Path, url: &str) -> Repository { println!( " [info] => Fetching from {} into existing {} folder", url, @@ -156,7 +156,7 @@ fn find_reference_or_commit<'a>( } /// Copy generated files to target folder -pub fn copy_files(src_dir: &PathBuf, target_dir: &PathBuf) { +pub fn copy_files(src_dir: &Path, target_dir: &Path) { // Remove old compiled files remove_dir_all(target_dir).unwrap_or_default(); create_dir_all(target_dir).unwrap(); @@ -208,7 +208,7 @@ pub fn find_proto_files(proto_paths: Vec) -> Vec { } /// Create tendermint.rs with library information -pub fn generate_tendermint_lib(prost_dir: &PathBuf, tendermint_lib_target: &PathBuf) { +pub fn generate_tendermint_lib(prost_dir: &Path, tendermint_lib_target: &Path) { let file_names = WalkDir::new(prost_dir) .into_iter() .filter_map(|e| e.ok()) diff --git a/tools/proto-compiler/src/main.rs b/tools/proto-compiler/src/main.rs index 53b5cb42d..ea65b32bf 100644 --- a/tools/proto-compiler/src/main.rs +++ b/tools/proto-compiler/src/main.rs @@ -55,8 +55,12 @@ fn main() { // List available proto files let protos = find_proto_files(proto_paths); - // Compile proto files with added annotations, exchange prost_types to our own let mut pb = prost_build::Config::new(); + + // Use shared Bytes buffers for ABCI messages: + pb.bytes(&[".tendermint.abci"]); + + // Compile proto files with added annotations, exchange prost_types to our own pb.out_dir(&out_dir); for type_attribute in CUSTOM_TYPE_ATTRIBUTES { pb.type_attribute(type_attribute.0, type_attribute.1); From 38a5de39c6f2b31b70df61ecd2fdd450417be062 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 13:31:02 -0400 Subject: [PATCH 02/25] split A.2, changelog abci --- .changelog/unreleased/breaking-changes/862-abci-domain-types.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/breaking-changes/862-abci-domain-types.md diff --git a/.changelog/unreleased/breaking-changes/862-abci-domain-types.md b/.changelog/unreleased/breaking-changes/862-abci-domain-types.md new file mode 100644 index 000000000..220b52dc6 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/862-abci-domain-types.md @@ -0,0 +1,2 @@ +- `[tendermint]` Added domain types for ABCI + ([#862](https://github.com/informalsystems/tendermint-rs/issues/862)) \ No newline at end of file From b7f312558bdccaa547ae2c73d9970140be3dc438 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 14:39:09 -0400 Subject: [PATCH 03/25] split A.1, tendermint and abci changes --- abci/src/application.rs | 12 +- abci/src/application/kvstore.rs | 38 +-- abci/src/client.rs | 5 - abci/src/codec.rs | 17 +- abci/tests/kvstore_app.rs | 6 +- config/src/prelude.rs | 2 +- proto/Cargo.toml | 4 +- proto/src/chrono.rs | 53 +++ proto/src/lib.rs | 1 + proto/src/prelude.rs | 2 +- proto/src/prost/gogoproto.rs | 0 proto/src/prost/google.protobuf.rs | 0 proto/src/prost/tendermint.abci.rs | 86 ++--- proto/src/prost/tendermint.crypto.rs | 42 +-- proto/src/prost/tendermint.p2p.rs | 46 +-- proto/src/prost/tendermint.types.rs | 112 +++---- proto/src/tendermint.rs | 64 ++-- tendermint/Cargo.toml | 4 +- tendermint/src/abci.rs | 68 ++-- tendermint/src/abci/code.rs | 108 ------ tendermint/src/abci/data.rs | 60 ---- .../abci/doc/request-applysnapshotchunk.md | 21 ++ tendermint/src/abci/doc/request-beginblock.md | 6 + tendermint/src/abci/doc/request-checktx.md | 11 + tendermint/src/abci/doc/request-commit.md | 4 + tendermint/src/abci/doc/request-delivertx.md | 3 + tendermint/src/abci/doc/request-echo.md | 3 + tendermint/src/abci/doc/request-endblock.md | 5 + tendermint/src/abci/doc/request-flush.md | 3 + tendermint/src/abci/doc/request-info.md | 3 + tendermint/src/abci/doc/request-initchain.md | 3 + .../src/abci/doc/request-listsnapshots.md | 3 + .../src/abci/doc/request-loadsnapshotchunk.md | 3 + .../src/abci/doc/request-offersnapshot.md | 20 ++ tendermint/src/abci/doc/request-query.md | 3 + tendermint/src/abci/doc/request-setoption.md | 1 + .../abci/doc/response-applysnapshotchunk.md | 7 + .../src/abci/doc/response-beginblock.md | 3 + tendermint/src/abci/doc/response-checktx.md | 3 + tendermint/src/abci/doc/response-commit.md | 3 + tendermint/src/abci/doc/response-delivertx.md | 4 + tendermint/src/abci/doc/response-echo.md | 3 + tendermint/src/abci/doc/response-endblock.md | 3 + tendermint/src/abci/doc/response-exception.md | 1 + tendermint/src/abci/doc/response-flush.md | 3 + tendermint/src/abci/doc/response-info.md | 3 + tendermint/src/abci/doc/response-initchain.md | 3 + .../src/abci/doc/response-listsnapshots.md | 3 + .../abci/doc/response-loadsnapshotchunk.md | 3 + .../src/abci/doc/response-offersnapshot.md | 7 + tendermint/src/abci/doc/response-query.md | 3 + tendermint/src/abci/doc/response-setoption.md | 1 + tendermint/src/abci/event.rs | 185 +++++++++++ tendermint/src/abci/gas.rs | 70 ---- tendermint/src/abci/info.rs | 21 -- tendermint/src/abci/kind.rs | 23 ++ tendermint/src/abci/log.rs | 35 -- tendermint/src/abci/path.rs | 28 -- tendermint/src/abci/request.rs | 312 ++++++++++++++++++ .../src/abci/request/apply_snapshot_chunk.rs | 69 ++++ tendermint/src/abci/request/begin_block.rs | 73 ++++ tendermint/src/abci/request/check_tx.rs | 71 ++++ tendermint/src/abci/request/deliver_tx.rs | 34 ++ tendermint/src/abci/request/echo.rs | 36 ++ tendermint/src/abci/request/end_block.rs | 36 ++ tendermint/src/abci/request/info.rs | 44 +++ tendermint/src/abci/request/init_chain.rs | 74 +++++ .../src/abci/request/load_snapshot_chunk.rs | 44 +++ tendermint/src/abci/request/offer_snapshot.rs | 50 +++ tendermint/src/abci/request/query.rs | 63 ++++ tendermint/src/abci/request/set_option.rs | 38 +++ tendermint/src/abci/response.rs | 295 +++++++++++++++++ .../src/abci/response/apply_snapshot_chunk.rs | 88 +++++ tendermint/src/abci/response/begin_block.rs | 41 +++ tendermint/src/abci/response/check_tx.rs | 91 +++++ tendermint/src/abci/response/commit.rs | 45 +++ tendermint/src/abci/response/deliver_tx.rs | 79 +++++ tendermint/src/abci/response/echo.rs | 36 ++ tendermint/src/abci/response/end_block.rs | 62 ++++ tendermint/src/abci/response/exception.rs | 36 ++ tendermint/src/abci/response/info.rs | 55 +++ tendermint/src/abci/response/init_chain.rs | 61 ++++ .../src/abci/response/list_snapshots.rs | 45 +++ .../src/abci/response/load_snapshot_chunk.rs | 41 +++ .../src/abci/response/offer_snapshot.rs | 62 ++++ tendermint/src/abci/response/query.rs | 81 +++++ tendermint/src/abci/response/set_option.rs | 41 +++ tendermint/src/abci/responses.rs | 147 --------- tendermint/src/abci/tag.rs | 91 ----- tendermint/src/abci/transaction.rs | 138 -------- tendermint/src/abci/transaction/hash.rs | 116 ------- tendermint/src/abci/types.rs | 309 +++++++++++++++++ tendermint/src/block.rs | 15 +- tendermint/src/block/size.rs | 32 +- tendermint/src/consensus/params.rs | 67 +++- tendermint/src/error.rs | 63 +++- tendermint/src/evidence.rs | 17 +- tendermint/src/hash.rs | 6 +- tendermint/src/prelude.rs | 2 +- 99 files changed, 3236 insertions(+), 1107 deletions(-) create mode 100644 proto/src/chrono.rs create mode 100644 proto/src/prost/gogoproto.rs create mode 100644 proto/src/prost/google.protobuf.rs delete mode 100644 tendermint/src/abci/code.rs delete mode 100644 tendermint/src/abci/data.rs create mode 100644 tendermint/src/abci/doc/request-applysnapshotchunk.md create mode 100644 tendermint/src/abci/doc/request-beginblock.md create mode 100644 tendermint/src/abci/doc/request-checktx.md create mode 100644 tendermint/src/abci/doc/request-commit.md create mode 100644 tendermint/src/abci/doc/request-delivertx.md create mode 100644 tendermint/src/abci/doc/request-echo.md create mode 100644 tendermint/src/abci/doc/request-endblock.md create mode 100644 tendermint/src/abci/doc/request-flush.md create mode 100644 tendermint/src/abci/doc/request-info.md create mode 100644 tendermint/src/abci/doc/request-initchain.md create mode 100644 tendermint/src/abci/doc/request-listsnapshots.md create mode 100644 tendermint/src/abci/doc/request-loadsnapshotchunk.md create mode 100644 tendermint/src/abci/doc/request-offersnapshot.md create mode 100644 tendermint/src/abci/doc/request-query.md create mode 100644 tendermint/src/abci/doc/request-setoption.md create mode 100644 tendermint/src/abci/doc/response-applysnapshotchunk.md create mode 100644 tendermint/src/abci/doc/response-beginblock.md create mode 100644 tendermint/src/abci/doc/response-checktx.md create mode 100644 tendermint/src/abci/doc/response-commit.md create mode 100644 tendermint/src/abci/doc/response-delivertx.md create mode 100644 tendermint/src/abci/doc/response-echo.md create mode 100644 tendermint/src/abci/doc/response-endblock.md create mode 100644 tendermint/src/abci/doc/response-exception.md create mode 100644 tendermint/src/abci/doc/response-flush.md create mode 100644 tendermint/src/abci/doc/response-info.md create mode 100644 tendermint/src/abci/doc/response-initchain.md create mode 100644 tendermint/src/abci/doc/response-listsnapshots.md create mode 100644 tendermint/src/abci/doc/response-loadsnapshotchunk.md create mode 100644 tendermint/src/abci/doc/response-offersnapshot.md create mode 100644 tendermint/src/abci/doc/response-query.md create mode 100644 tendermint/src/abci/doc/response-setoption.md create mode 100644 tendermint/src/abci/event.rs delete mode 100644 tendermint/src/abci/gas.rs delete mode 100644 tendermint/src/abci/info.rs create mode 100644 tendermint/src/abci/kind.rs delete mode 100644 tendermint/src/abci/log.rs delete mode 100644 tendermint/src/abci/path.rs create mode 100644 tendermint/src/abci/request.rs create mode 100644 tendermint/src/abci/request/apply_snapshot_chunk.rs create mode 100644 tendermint/src/abci/request/begin_block.rs create mode 100644 tendermint/src/abci/request/check_tx.rs create mode 100644 tendermint/src/abci/request/deliver_tx.rs create mode 100644 tendermint/src/abci/request/echo.rs create mode 100644 tendermint/src/abci/request/end_block.rs create mode 100644 tendermint/src/abci/request/info.rs create mode 100644 tendermint/src/abci/request/init_chain.rs create mode 100644 tendermint/src/abci/request/load_snapshot_chunk.rs create mode 100644 tendermint/src/abci/request/offer_snapshot.rs create mode 100644 tendermint/src/abci/request/query.rs create mode 100644 tendermint/src/abci/request/set_option.rs create mode 100644 tendermint/src/abci/response.rs create mode 100644 tendermint/src/abci/response/apply_snapshot_chunk.rs create mode 100644 tendermint/src/abci/response/begin_block.rs create mode 100644 tendermint/src/abci/response/check_tx.rs create mode 100644 tendermint/src/abci/response/commit.rs create mode 100644 tendermint/src/abci/response/deliver_tx.rs create mode 100644 tendermint/src/abci/response/echo.rs create mode 100644 tendermint/src/abci/response/end_block.rs create mode 100644 tendermint/src/abci/response/exception.rs create mode 100644 tendermint/src/abci/response/info.rs create mode 100644 tendermint/src/abci/response/init_chain.rs create mode 100644 tendermint/src/abci/response/list_snapshots.rs create mode 100644 tendermint/src/abci/response/load_snapshot_chunk.rs create mode 100644 tendermint/src/abci/response/offer_snapshot.rs create mode 100644 tendermint/src/abci/response/query.rs create mode 100644 tendermint/src/abci/response/set_option.rs delete mode 100644 tendermint/src/abci/responses.rs delete mode 100644 tendermint/src/abci/tag.rs delete mode 100644 tendermint/src/abci/transaction.rs delete mode 100644 tendermint/src/abci/transaction/hash.rs create mode 100644 tendermint/src/abci/types.rs diff --git a/abci/src/application.rs b/abci/src/application.rs index a3c025a56..193307832 100644 --- a/abci/src/application.rs +++ b/abci/src/application.rs @@ -8,11 +8,11 @@ pub mod kvstore; use tendermint_proto::abci::{ request::Value, response, Request, RequestApplySnapshotChunk, RequestBeginBlock, RequestCheckTx, RequestDeliverTx, RequestEcho, RequestEndBlock, RequestInfo, RequestInitChain, - RequestLoadSnapshotChunk, RequestOfferSnapshot, RequestQuery, RequestSetOption, Response, + RequestLoadSnapshotChunk, RequestOfferSnapshot, RequestQuery, Response, ResponseApplySnapshotChunk, ResponseBeginBlock, ResponseCheckTx, ResponseCommit, ResponseDeliverTx, ResponseEcho, ResponseEndBlock, ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, - ResponseQuery, ResponseSetOption, + ResponseQuery, }; /// An ABCI application. @@ -76,12 +76,6 @@ pub trait Application: Send + Clone + 'static { Default::default() } - /// Allows the Tendermint node to request that the application set an - /// option to a particular value. - fn set_option(&self, _request: RequestSetOption) -> ResponseSetOption { - Default::default() - } - /// Used during state sync to discover available snapshots on peers. fn list_snapshots(&self) -> ResponseListSnapshots { Default::default() @@ -123,7 +117,6 @@ impl RequestDispatcher for A { Value::Echo(req) => response::Value::Echo(self.echo(req)), Value::Flush(_) => response::Value::Flush(self.flush()), Value::Info(req) => response::Value::Info(self.info(req)), - Value::SetOption(req) => response::Value::SetOption(self.set_option(req)), Value::InitChain(req) => response::Value::InitChain(self.init_chain(req)), Value::Query(req) => response::Value::Query(self.query(req)), Value::BeginBlock(req) => response::Value::BeginBlock(self.begin_block(req)), @@ -141,6 +134,7 @@ impl RequestDispatcher for A { Value::ApplySnapshotChunk(req) => { response::Value::ApplySnapshotChunk(self.apply_snapshot_chunk(req)) }, + Value::SetOption(_) => response::Value::SetOption(Default::default()), }), } } diff --git a/abci/src/application/kvstore.rs b/abci/src/application/kvstore.rs index 33aa15eff..14af7f335 100644 --- a/abci/src/application/kvstore.rs +++ b/abci/src/application/kvstore.rs @@ -50,7 +50,7 @@ use crate::{ /// // Deliver a transaction and then commit the transaction /// client /// .deliver_tx(RequestDeliverTx { -/// tx: "test-key=test-value".as_bytes().to_owned(), +/// tx: "test-key=test-value".into(), /// }) /// .unwrap(); /// client.commit().unwrap(); @@ -58,7 +58,7 @@ use crate::{ /// // We should be able to query for the data we just delivered above /// let res = client /// .query(RequestQuery { -/// data: "test-key".as_bytes().to_owned(), +/// data: "test-key".into(), /// path: "".to_string(), /// height: 0, /// prove: false, @@ -129,17 +129,17 @@ impl Application for KeyValueStoreApp { version: "0.1.0".to_string(), app_version: 1, last_block_height, - last_block_app_hash, + last_block_app_hash: last_block_app_hash.into(), } } fn query(&self, request: RequestQuery) -> ResponseQuery { - let key = match String::from_utf8(request.data.clone()) { + let key = match std::str::from_utf8(&request.data) { Ok(s) => s, Err(e) => panic!("Failed to intepret key as UTF-8: {}", e), }; debug!("Attempting to get key: {}", key); - match self.get(key.clone()) { + match self.get(key) { Ok((height, value_opt)) => match value_opt { Some(value) => ResponseQuery { code: 0, @@ -147,7 +147,7 @@ impl Application for KeyValueStoreApp { info: "".to_string(), index: 0, key: request.data, - value: value.into_bytes(), + value: value.into_bytes().into(), proof_ops: None, height, codespace: "".to_string(), @@ -158,7 +158,7 @@ impl Application for KeyValueStoreApp { info: "".to_string(), index: 0, key: request.data, - value: vec![], + value: Default::default(), proof_ops: None, height, codespace: "".to_string(), @@ -171,7 +171,7 @@ impl Application for KeyValueStoreApp { fn check_tx(&self, _request: RequestCheckTx) -> ResponseCheckTx { ResponseCheckTx { code: 0, - data: vec![], + data: Default::default(), log: "".to_string(), info: "".to_string(), gas_wanted: 1, @@ -183,17 +183,17 @@ impl Application for KeyValueStoreApp { } fn deliver_tx(&self, request: RequestDeliverTx) -> ResponseDeliverTx { - let tx = String::from_utf8(request.tx).unwrap(); + let tx = std::str::from_utf8(&request.tx).unwrap(); let tx_parts = tx.split('=').collect::>(); let (key, value) = if tx_parts.len() == 2 { (tx_parts[0], tx_parts[1]) } else { - (tx.as_ref(), tx.as_ref()) + (tx, tx) }; let _ = self.set(key, value).unwrap(); ResponseDeliverTx { code: 0, - data: vec![], + data: Default::default(), log: "".to_string(), info: "".to_string(), gas_wanted: 0, @@ -202,18 +202,18 @@ impl Application for KeyValueStoreApp { r#type: "app".to_string(), attributes: vec![ EventAttribute { - key: "key".as_bytes().to_owned(), - value: key.as_bytes().to_owned(), + key: "key".to_string().into_bytes().into(), + value: key.to_string().into_bytes().into(), index: true, }, EventAttribute { - key: "index_key".as_bytes().to_owned(), - value: "index is working".as_bytes().to_owned(), + key: "index_key".to_string().into_bytes().into(), + value: "index is working".to_string().into_bytes().into(), index: true, }, EventAttribute { - key: "noindex_key".as_bytes().to_owned(), - value: "index is working".as_bytes().to_owned(), + key: "noindex_key".to_string().into_bytes().into(), + value: "index is working".to_string().into_bytes().into(), index: false, }, ], @@ -228,7 +228,7 @@ impl Application for KeyValueStoreApp { let (height, app_hash) = channel_recv(&result_rx).unwrap(); info!("Committed height {}", height); ResponseCommit { - data: app_hash, + data: app_hash.into(), retain_height: height - 1, } } @@ -285,7 +285,7 @@ impl KeyValueStoreDriver { // As in the Go-based key/value store, simply encode the number of // items as the "app hash" let mut app_hash = BytesMut::with_capacity(MAX_VARINT_LENGTH); - encode_varint(self.store.len() as u64, &mut app_hash); + prost::encoding::encode_varint(self.store.len() as u64, &mut app_hash); self.app_hash = app_hash.to_vec(); self.height += 1; channel_send(&result_tx, (self.height, self.app_hash.clone())) diff --git a/abci/src/client.rs b/abci/src/client.rs index 3e9b1cce0..81446c596 100644 --- a/abci/src/client.rs +++ b/abci/src/client.rs @@ -113,11 +113,6 @@ impl Client { perform!(self, Commit, RequestCommit {}) } - /// Request that the application set an option to a particular value. - pub fn set_option(&mut self, req: RequestSetOption) -> Result { - perform!(self, SetOption, req) - } - /// Used during state sync to discover available snapshots on peers. pub fn list_snapshots(&mut self) -> Result { perform!(self, ListSnapshots, RequestListSnapshots {}) diff --git a/abci/src/codec.rs b/abci/src/codec.rs index ce85acd24..66645a6ae 100644 --- a/abci/src/codec.rs +++ b/abci/src/codec.rs @@ -130,7 +130,7 @@ where message.encode(&mut buf).map_err(Error::encode)?; let buf = buf.freeze(); - encode_varint(buf.len() as u64, &mut dst); + prost::encoding::encode_varint(buf.len() as u64, &mut dst); dst.put(buf); Ok(()) } @@ -142,11 +142,11 @@ where { let src_len = src.len(); let mut tmp = src.clone().freeze(); - let encoded_len = match decode_varint(&mut tmp) { + let encoded_len = match prost::encoding::decode_varint(&mut tmp) { Ok(len) => len, // We've potentially only received a partial length delimiter Err(_) if src_len <= MAX_VARINT_LENGTH => return Ok(None), - Err(e) => return Err(e), + Err(e) => return Err(Error::decode(e)), }; let remaining = tmp.remaining() as u64; if remaining < encoded_len { @@ -164,14 +164,3 @@ where Ok(Some(res)) } } - -// encode_varint and decode_varint will be removed once -// https://github.com/tendermint/tendermint/issues/5783 lands in Tendermint. -pub fn encode_varint(val: u64, mut buf: &mut B) { - prost::encoding::encode_varint(val << 1, &mut buf); -} - -pub fn decode_varint(mut buf: &mut B) -> Result { - let len = prost::encoding::decode_varint(&mut buf).map_err(Error::decode)?; - Ok(len >> 1) -} diff --git a/abci/tests/kvstore_app.rs b/abci/tests/kvstore_app.rs index 21d48dfa1..05c821483 100644 --- a/abci/tests/kvstore_app.rs +++ b/abci/tests/kvstore_app.rs @@ -25,19 +25,19 @@ mod kvstore_app_integration { client .deliver_tx(RequestDeliverTx { - tx: "test-key=test-value".as_bytes().to_owned(), + tx: "test-key=test-value".into(), }) .unwrap(); client.commit().unwrap(); let res = client .query(RequestQuery { - data: "test-key".as_bytes().to_owned(), + data: "test-key".into(), path: "".to_string(), height: 0, prove: false, }) .unwrap(); - assert_eq!(res.value, "test-value".as_bytes().to_owned()); + assert_eq!(res.value, "test-value".as_bytes()); } } diff --git a/config/src/prelude.rs b/config/src/prelude.rs index a5b91aa13..aa9a20b65 100644 --- a/config/src/prelude.rs +++ b/config/src/prelude.rs @@ -1,7 +1,7 @@ // Re-export according to alloc::prelude::v1 because it is not yet stabilized // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html -pub use alloc::borrow::ToOwned; pub use alloc::{ + borrow::ToOwned, boxed::Box, format, string::{String, ToString}, diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 1b5c5a6c3..afd8e8023 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -19,7 +19,7 @@ all-features = true [dependencies] prost = { version = "0.11", default-features = false } prost-types = { version = "0.11", default-features = false } -bytes = { version = "1.0", default-features = false } +bytes = { version = "1.0", default-features = false, features = ["serde"]} serde = { version = "1.0", default-features = false, features = ["derive"] } serde_bytes = { version = "0.11", default-features = false, features = ["alloc"] } subtle-encoding = { version = "0.5", default-features = false, features = ["hex", "base64", "alloc"] } @@ -28,6 +28,8 @@ num-derive = { version = "0.3", default-features = false } # TODO(thane): Remove restrictions once js-sys issue is resolved (causes the Substrate no_std check to fail) time = { version = ">=0.3, <0.3.12", default-features = false, features = ["macros", "parsing"] } flex-error = { version = "0.4.4", default-features = false } +# TODO(hdevalence): re-added while rebasing the ABCI domain types PR again +chrono = { version = "0.4", default-features = false, features = ["serde", "alloc"] } [dev-dependencies] serde_json = { version = "1.0", default-features = false, features = ["alloc"] } diff --git a/proto/src/chrono.rs b/proto/src/chrono.rs new file mode 100644 index 000000000..3b969f0f0 --- /dev/null +++ b/proto/src/chrono.rs @@ -0,0 +1,53 @@ +use core::convert::TryInto; + +use chrono::{DateTime, Duration, TimeZone, Utc}; + +use crate::google::protobuf as pb; + +impl From> for pb::Timestamp { + fn from(dt: DateTime) -> pb::Timestamp { + pb::Timestamp { + seconds: dt.timestamp(), + // This can exceed 1_000_000_000 in the case of a leap second, but + // even with a leap second it should be under 2_147_483_647. + nanos: dt + .timestamp_subsec_nanos() + .try_into() + .expect("timestamp_subsec_nanos bigger than i32::MAX"), + } + } +} + +impl From for DateTime { + fn from(ts: pb::Timestamp) -> DateTime { + Utc.timestamp(ts.seconds, ts.nanos as u32) + } +} + +// Note: we convert a protobuf::Duration into a chrono::Duration, not a +// std::time::Duration, because std::time::Durations are unsigned, but the +// protobuf duration is signed. + +impl From for pb::Duration { + fn from(d: Duration) -> pb::Duration { + // chrono's Duration stores the fractional part as `nanos: i32` + // internally but doesn't provide a way to access it, only a way to get + // the *total* number of nanoseconds. so we have to do this cool and fun + // hoop-jumping maneuver + let seconds = d.num_seconds(); + let nanos = (d - Duration::seconds(seconds)) + .num_nanoseconds() + .expect("we computed the fractional part, so there's no overflow") + .try_into() + .expect("the fractional part fits in i32"); + + pb::Duration { seconds, nanos } + } +} + +impl From for Duration { + fn from(d: pb::Duration) -> Duration { + // there's no constructor that supplies both at once + Duration::seconds(d.seconds) + Duration::nanoseconds(d.nanos as i64) + } +} diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 49e41fde6..312403de6 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -19,6 +19,7 @@ pub mod google { } } +mod chrono; mod error; #[allow(warnings)] mod tendermint; diff --git a/proto/src/prelude.rs b/proto/src/prelude.rs index a5b91aa13..aa9a20b65 100644 --- a/proto/src/prelude.rs +++ b/proto/src/prelude.rs @@ -1,7 +1,7 @@ // Re-export according to alloc::prelude::v1 because it is not yet stabilized // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html -pub use alloc::borrow::ToOwned; pub use alloc::{ + borrow::ToOwned, boxed::Box, format, string::{String, ToString}, diff --git a/proto/src/prost/gogoproto.rs b/proto/src/prost/gogoproto.rs new file mode 100644 index 000000000..e69de29bb diff --git a/proto/src/prost/google.protobuf.rs b/proto/src/prost/google.protobuf.rs new file mode 100644 index 000000000..e69de29bb diff --git a/proto/src/prost/tendermint.abci.rs b/proto/src/prost/tendermint.abci.rs index e2f493e6f..8e03b04e8 100644 --- a/proto/src/prost/tendermint.abci.rs +++ b/proto/src/prost/tendermint.abci.rs @@ -81,15 +81,15 @@ pub struct RequestInitChain { pub consensus_params: ::core::option::Option, #[prost(message, repeated, tag="4")] pub validators: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", tag="5")] - pub app_state_bytes: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="5")] + pub app_state_bytes: ::prost::bytes::Bytes, #[prost(int64, tag="6")] pub initial_height: i64, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestQuery { - #[prost(bytes="vec", tag="1")] - pub data: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub data: ::prost::bytes::Bytes, #[prost(string, tag="2")] pub path: ::prost::alloc::string::String, #[prost(int64, tag="3")] @@ -99,8 +99,8 @@ pub struct RequestQuery { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestBeginBlock { - #[prost(bytes="vec", tag="1")] - pub hash: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub hash: ::prost::bytes::Bytes, #[prost(message, optional, tag="2")] pub header: ::core::option::Option, #[prost(message, optional, tag="3")] @@ -110,15 +110,15 @@ pub struct RequestBeginBlock { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestCheckTx { - #[prost(bytes="vec", tag="1")] - pub tx: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub tx: ::prost::bytes::Bytes, #[prost(enumeration="CheckTxType", tag="2")] pub r#type: i32, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestDeliverTx { - #[prost(bytes="vec", tag="1")] - pub tx: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub tx: ::prost::bytes::Bytes, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct RequestEndBlock { @@ -139,8 +139,8 @@ pub struct RequestOfferSnapshot { #[prost(message, optional, tag="1")] pub snapshot: ::core::option::Option, /// light client-verified app hash for snapshot height - #[prost(bytes="vec", tag="2")] - pub app_hash: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="2")] + pub app_hash: ::prost::bytes::Bytes, } /// loads a snapshot chunk #[derive(Clone, PartialEq, ::prost::Message)] @@ -157,8 +157,8 @@ pub struct RequestLoadSnapshotChunk { pub struct RequestApplySnapshotChunk { #[prost(uint32, tag="1")] pub index: u32, - #[prost(bytes="vec", tag="2")] - pub chunk: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="2")] + pub chunk: ::prost::bytes::Bytes, #[prost(string, tag="3")] pub sender: ::prost::alloc::string::String, } @@ -237,10 +237,10 @@ pub struct ResponseInfo { #[prost(int64, tag="4")] #[serde(with = "crate::serializers::from_str", default)] pub last_block_height: i64, - #[prost(bytes="vec", tag="5")] + #[prost(bytes="bytes", tag="5")] #[serde(default)] - #[serde(skip_serializing_if = "::prost::alloc::vec::Vec::is_empty", with = "serde_bytes")] - pub last_block_app_hash: ::prost::alloc::vec::Vec, + #[serde(skip_serializing_if = "bytes::Bytes::is_empty")] + pub last_block_app_hash: ::prost::bytes::Bytes, } /// nondeterministic #[derive(Clone, PartialEq, ::prost::Message)] @@ -259,8 +259,8 @@ pub struct ResponseInitChain { pub consensus_params: ::core::option::Option, #[prost(message, repeated, tag="2")] pub validators: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", tag="3")] - pub app_hash: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="3")] + pub app_hash: ::prost::bytes::Bytes, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ResponseQuery { @@ -276,10 +276,10 @@ pub struct ResponseQuery { pub info: ::prost::alloc::string::String, #[prost(int64, tag="5")] pub index: i64, - #[prost(bytes="vec", tag="6")] - pub key: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", tag="7")] - pub value: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="6")] + pub key: ::prost::bytes::Bytes, + #[prost(bytes="bytes", tag="7")] + pub value: ::prost::bytes::Bytes, #[prost(message, optional, tag="8")] pub proof_ops: ::core::option::Option, #[prost(int64, tag="9")] @@ -296,8 +296,8 @@ pub struct ResponseBeginBlock { pub struct ResponseCheckTx { #[prost(uint32, tag="1")] pub code: u32, - #[prost(bytes="vec", tag="2")] - pub data: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="2")] + pub data: ::prost::bytes::Bytes, /// nondeterministic #[prost(string, tag="3")] pub log: ::prost::alloc::string::String, @@ -325,8 +325,8 @@ pub struct ResponseCheckTx { pub struct ResponseDeliverTx { #[prost(uint32, tag="1")] pub code: u32, - #[prost(bytes="vec", tag="2")] - pub data: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="2")] + pub data: ::prost::bytes::Bytes, /// nondeterministic #[prost(string, tag="3")] pub log: ::prost::alloc::string::String, @@ -355,8 +355,8 @@ pub struct ResponseEndBlock { #[derive(Clone, PartialEq, ::prost::Message)] pub struct ResponseCommit { /// reserve 1 - #[prost(bytes="vec", tag="2")] - pub data: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="2")] + pub data: ::prost::bytes::Bytes, #[prost(int64, tag="3")] pub retain_height: i64, } @@ -407,8 +407,8 @@ pub mod response_offer_snapshot { } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ResponseLoadSnapshotChunk { - #[prost(bytes="vec", tag="1")] - pub chunk: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub chunk: ::prost::bytes::Bytes, } #[derive(Clone, PartialEq, ::prost::Message)] pub struct ResponseApplySnapshotChunk { @@ -502,10 +502,10 @@ pub struct Event { /// EventAttribute is a single key-value pair, associated with an event. #[derive(Clone, PartialEq, ::prost::Message)] pub struct EventAttribute { - #[prost(bytes="vec", tag="1")] - pub key: ::prost::alloc::vec::Vec, - #[prost(bytes="vec", tag="2")] - pub value: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub key: ::prost::bytes::Bytes, + #[prost(bytes="bytes", tag="2")] + pub value: ::prost::bytes::Bytes, /// nondeterministic #[prost(bool, tag="3")] pub index: bool, @@ -519,8 +519,8 @@ pub struct TxResult { pub height: i64, #[prost(uint32, tag="2")] pub index: u32, - #[prost(bytes="vec", tag="3")] - pub tx: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="3")] + pub tx: ::prost::bytes::Bytes, #[prost(message, optional, tag="4")] pub result: ::core::option::Option, } @@ -531,8 +531,8 @@ pub struct TxResult { #[derive(Clone, PartialEq, ::prost::Message)] pub struct Validator { /// The first 20 bytes of SHA256(public key) - #[prost(bytes="vec", tag="1")] - pub address: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="1")] + pub address: ::prost::bytes::Bytes, /// PubKey pub_key = 2 \[(gogoproto.nullable)=false\]; /// /// The voting power @@ -589,11 +589,11 @@ pub struct Snapshot { #[prost(uint32, tag="3")] pub chunks: u32, /// Arbitrary snapshot hash, equal only if identical - #[prost(bytes="vec", tag="4")] - pub hash: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="4")] + pub hash: ::prost::bytes::Bytes, /// Arbitrary application metadata - #[prost(bytes="vec", tag="5")] - pub metadata: ::prost::alloc::vec::Vec, + #[prost(bytes="bytes", tag="5")] + pub metadata: ::prost::bytes::Bytes, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)] #[repr(i32)] diff --git a/proto/src/prost/tendermint.crypto.rs b/proto/src/prost/tendermint.crypto.rs index 4b47ef593..0fac07973 100644 --- a/proto/src/prost/tendermint.crypto.rs +++ b/proto/src/prost/tendermint.crypto.rs @@ -1,24 +1,3 @@ -/// PublicKey defines the keys available for use with Tendermint Validators -#[derive(::serde::Deserialize, ::serde::Serialize)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PublicKey { - #[prost(oneof="public_key::Sum", tags="1, 2")] - pub sum: ::core::option::Option, -} -/// Nested message and enum types in `PublicKey`. -pub mod public_key { - #[derive(::serde::Deserialize, ::serde::Serialize)] - #[serde(tag = "type", content = "value")] - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Sum { - #[prost(bytes, tag="1")] - #[serde(rename = "tendermint/PubKeyEd25519", with = "crate::serializers::bytes::base64string")] - Ed25519(::prost::alloc::vec::Vec), - #[prost(bytes, tag="2")] - #[serde(rename = "tendermint/PubKeySecp256k1", with = "crate::serializers::bytes::base64string")] - Secp256k1(::prost::alloc::vec::Vec), - } -} #[derive(::serde::Deserialize, ::serde::Serialize)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct Proof { @@ -71,3 +50,24 @@ pub struct ProofOps { #[prost(message, repeated, tag="1")] pub ops: ::prost::alloc::vec::Vec, } +/// PublicKey defines the keys available for use with Tendermint Validators +#[derive(::serde::Deserialize, ::serde::Serialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PublicKey { + #[prost(oneof="public_key::Sum", tags="1, 2")] + pub sum: ::core::option::Option, +} +/// Nested message and enum types in `PublicKey`. +pub mod public_key { + #[derive(::serde::Deserialize, ::serde::Serialize)] + #[serde(tag = "type", content = "value")] + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Sum { + #[prost(bytes, tag="1")] + #[serde(rename = "tendermint/PubKeyEd25519", with = "crate::serializers::bytes::base64string")] + Ed25519(::prost::alloc::vec::Vec), + #[prost(bytes, tag="2")] + #[serde(rename = "tendermint/PubKeySecp256k1", with = "crate::serializers::bytes::base64string")] + Secp256k1(::prost::alloc::vec::Vec), + } +} diff --git a/proto/src/prost/tendermint.p2p.rs b/proto/src/prost/tendermint.p2p.rs index 8ed7b702f..4dc33809c 100644 --- a/proto/src/prost/tendermint.p2p.rs +++ b/proto/src/prost/tendermint.p2p.rs @@ -43,29 +43,6 @@ pub struct DefaultNodeInfoOther { pub rpc_address: ::prost::alloc::string::String, } #[derive(Clone, PartialEq, ::prost::Message)] -pub struct PexRequest { -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct PexAddrs { - #[prost(message, repeated, tag="1")] - pub addrs: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct Message { - #[prost(oneof="message::Sum", tags="1, 2")] - pub sum: ::core::option::Option, -} -/// Nested message and enum types in `Message`. -pub mod message { - #[derive(Clone, PartialEq, ::prost::Oneof)] - pub enum Sum { - #[prost(message, tag="1")] - PexRequest(super::PexRequest), - #[prost(message, tag="2")] - PexAddrs(super::PexAddrs), - } -} -#[derive(Clone, PartialEq, ::prost::Message)] pub struct PacketPing { } #[derive(Clone, PartialEq, ::prost::Message)] @@ -104,3 +81,26 @@ pub struct AuthSigMessage { #[prost(bytes="vec", tag="2")] pub sig: ::prost::alloc::vec::Vec, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PexRequest { +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct PexAddrs { + #[prost(message, repeated, tag="1")] + pub addrs: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Message { + #[prost(oneof="message::Sum", tags="1, 2")] + pub sum: ::core::option::Option, +} +/// Nested message and enum types in `Message`. +pub mod message { + #[derive(Clone, PartialEq, ::prost::Oneof)] + pub enum Sum { + #[prost(message, tag="1")] + PexRequest(super::PexRequest), + #[prost(message, tag="2")] + PexAddrs(super::PexAddrs), + } +} diff --git a/proto/src/prost/tendermint.types.rs b/proto/src/prost/tendermint.types.rs index 4e1ab5d7e..5279c0113 100644 --- a/proto/src/prost/tendermint.types.rs +++ b/proto/src/prost/tendermint.types.rs @@ -304,62 +304,6 @@ impl SignedMsgType { } } } -#[derive(::serde::Deserialize, ::serde::Serialize)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CanonicalBlockId { - #[prost(bytes="vec", tag="1")] - pub hash: ::prost::alloc::vec::Vec, - #[prost(message, optional, tag="2")] - #[serde(alias = "parts")] - pub part_set_header: ::core::option::Option, -} -#[derive(::serde::Deserialize, ::serde::Serialize)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CanonicalPartSetHeader { - #[prost(uint32, tag="1")] - pub total: u32, - #[prost(bytes="vec", tag="2")] - pub hash: ::prost::alloc::vec::Vec, -} -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CanonicalProposal { - /// type alias for byte - #[prost(enumeration="SignedMsgType", tag="1")] - pub r#type: i32, - /// canonicalization requires fixed size encoding here - #[prost(sfixed64, tag="2")] - pub height: i64, - /// canonicalization requires fixed size encoding here - #[prost(sfixed64, tag="3")] - pub round: i64, - #[prost(int64, tag="4")] - pub pol_round: i64, - #[prost(message, optional, tag="5")] - pub block_id: ::core::option::Option, - #[prost(message, optional, tag="6")] - pub timestamp: ::core::option::Option, - #[prost(string, tag="7")] - pub chain_id: ::prost::alloc::string::String, -} -#[derive(::serde::Deserialize, ::serde::Serialize)] -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct CanonicalVote { - /// type alias for byte - #[prost(enumeration="SignedMsgType", tag="1")] - pub r#type: i32, - /// canonicalization requires fixed size encoding here - #[prost(sfixed64, tag="2")] - pub height: i64, - /// canonicalization requires fixed size encoding here - #[prost(sfixed64, tag="3")] - pub round: i64, - #[prost(message, optional, tag="4")] - pub block_id: ::core::option::Option, - #[prost(message, optional, tag="5")] - pub timestamp: ::core::option::Option, - #[prost(string, tag="6")] - pub chain_id: ::prost::alloc::string::String, -} #[derive(Clone, PartialEq, ::prost::Message)] pub struct EventDataRoundState { #[prost(int64, tag="1")] @@ -508,6 +452,62 @@ pub struct EvidenceList { } #[derive(::serde::Deserialize, ::serde::Serialize)] #[derive(Clone, PartialEq, ::prost::Message)] +pub struct CanonicalBlockId { + #[prost(bytes="vec", tag="1")] + pub hash: ::prost::alloc::vec::Vec, + #[prost(message, optional, tag="2")] + #[serde(alias = "parts")] + pub part_set_header: ::core::option::Option, +} +#[derive(::serde::Deserialize, ::serde::Serialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CanonicalPartSetHeader { + #[prost(uint32, tag="1")] + pub total: u32, + #[prost(bytes="vec", tag="2")] + pub hash: ::prost::alloc::vec::Vec, +} +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CanonicalProposal { + /// type alias for byte + #[prost(enumeration="SignedMsgType", tag="1")] + pub r#type: i32, + /// canonicalization requires fixed size encoding here + #[prost(sfixed64, tag="2")] + pub height: i64, + /// canonicalization requires fixed size encoding here + #[prost(sfixed64, tag="3")] + pub round: i64, + #[prost(int64, tag="4")] + pub pol_round: i64, + #[prost(message, optional, tag="5")] + pub block_id: ::core::option::Option, + #[prost(message, optional, tag="6")] + pub timestamp: ::core::option::Option, + #[prost(string, tag="7")] + pub chain_id: ::prost::alloc::string::String, +} +#[derive(::serde::Deserialize, ::serde::Serialize)] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CanonicalVote { + /// type alias for byte + #[prost(enumeration="SignedMsgType", tag="1")] + pub r#type: i32, + /// canonicalization requires fixed size encoding here + #[prost(sfixed64, tag="2")] + pub height: i64, + /// canonicalization requires fixed size encoding here + #[prost(sfixed64, tag="3")] + pub round: i64, + #[prost(message, optional, tag="4")] + pub block_id: ::core::option::Option, + #[prost(message, optional, tag="5")] + pub timestamp: ::core::option::Option, + #[prost(string, tag="6")] + pub chain_id: ::prost::alloc::string::String, +} +#[derive(::serde::Deserialize, ::serde::Serialize)] +#[derive(Clone, PartialEq, ::prost::Message)] pub struct Block { #[prost(message, optional, tag="1")] pub header: ::core::option::Option
, diff --git a/proto/src/tendermint.rs b/proto/src/tendermint.rs index da758ea33..6576ded7d 100644 --- a/proto/src/tendermint.rs +++ b/proto/src/tendermint.rs @@ -1,63 +1,63 @@ //! Tendermint-proto auto-generated sub-modules for Tendermint -pub mod consensus { - include!("prost/tendermint.consensus.rs"); +pub mod statesync { + include!("prost/tendermint.statesync.rs"); } -pub mod types { - include!("prost/tendermint.types.rs"); +pub mod abci { + include!("prost/tendermint.abci.rs"); } -pub mod mempool { - include!("prost/tendermint.mempool.rs"); +pub mod store { + include!("prost/tendermint.store.rs"); } -pub mod rpc { - pub mod grpc { - include!("prost/tendermint.rpc.grpc.rs"); - } +pub mod version { + include!("prost/tendermint.version.rs"); } -pub mod blockchain { - include!("prost/tendermint.blockchain.rs"); +pub mod types { + include!("prost/tendermint.types.rs"); } -pub mod libs { - pub mod bits { - include!("prost/tendermint.libs.bits.rs"); - } +pub mod consensus { + include!("prost/tendermint.consensus.rs"); } -pub mod state { - include!("prost/tendermint.state.rs"); +pub mod p2p { + include!("prost/tendermint.p2p.rs"); } -pub mod version { - include!("prost/tendermint.version.rs"); +pub mod privval { + include!("prost/tendermint.privval.rs"); } -pub mod store { - include!("prost/tendermint.store.rs"); +pub mod blockchain { + include!("prost/tendermint.blockchain.rs"); } -pub mod privval { - include!("prost/tendermint.privval.rs"); +pub mod crypto { + include!("prost/tendermint.crypto.rs"); } -pub mod statesync { - include!("prost/tendermint.statesync.rs"); +pub mod mempool { + include!("prost/tendermint.mempool.rs"); } -pub mod p2p { - include!("prost/tendermint.p2p.rs"); +pub mod state { + include!("prost/tendermint.state.rs"); } -pub mod abci { - include!("prost/tendermint.abci.rs"); +pub mod libs { + pub mod bits { + include!("prost/tendermint.libs.bits.rs"); + } } -pub mod crypto { - include!("prost/tendermint.crypto.rs"); +pub mod rpc { + pub mod grpc { + include!("prost/tendermint.rpc.grpc.rs"); + } } pub mod meta { diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index f80d6085e..cc50f5cbe 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/informalsystems/tendermint-rs/tree/main/tenderm readme = "../README.md" categories = ["cryptography", "cryptography::cryptocurrencies", "database"] keywords = ["blockchain", "bft", "consensus", "cosmos", "tendermint"] -edition = "2018" +edition = "2021" description = """ Tendermint is a high-performance blockchain consensus engine that powers @@ -54,6 +54,8 @@ zeroize = { version = "1.1", default-features = false, features = ["zeroize_deri flex-error = { version = "0.4.4", default-features = false } k256 = { version = "0.11", optional = true, default-features = false, features = ["ecdsa", "sha256"] } ripemd160 = { version = "0.9", default-features = false, optional = true } +# TODO(hdevalence): re-added while rebasing the ABCI domain types PR again +chrono = { version = "0.4", default-features = false, features = ["serde", "alloc"] } [features] default = ["std"] diff --git a/tendermint/src/abci.rs b/tendermint/src/abci.rs index 9a0f84761..cb039806d 100644 --- a/tendermint/src/abci.rs +++ b/tendermint/src/abci.rs @@ -1,29 +1,53 @@ -//! Application BlockChain Interface (ABCI) +//! Application BlockChain Interface ([ABCI]) is the interface between Tendermint +//! (a consensus engine for Byzantine-fault-tolerant replication of a state +//! machine) and an application (the state machine to be replicated). //! -//! NOTE: This module contains types for ABCI responses as consumed from RPC -//! endpoints. It does not contain an ABCI protocol implementation. +//! Using ABCI involves writing an application driven by ABCI methods, exposing +//! that application as an ABCI server, and having Tendermint connect to the +//! server as an ABCI client. //! -//! For that, see: +//! This module does not include an ABCI server implementation itself. Instead, +//! it provides a common set of Rust domain types that model the ABCI protocol, +//! which can be used by both ABCI applications and ABCI server implementations. //! -//! +//! One ABCI server implementation is provided by the [`tendermint_abci`][tmabci] +//! crate. +//! +//! Each ABCI method corresponds to a request/response pair. ABCI requests are +//! modeled by the [`Request`] enum, and responses are modeled by the +//! [`Response`] enum. As described in the [methods and types][mat] page, ABCI +//! methods are split into four categories. Tendermint opens one ABCI connection +//! for each category of messages. These categories are modeled by the +//! [`MethodKind`] enum and by per-category request and response enums: +//! +//! * [`ConsensusRequest`] / [`ConsensusResponse`] for [`MethodKind::Consensus`] methods; +//! * [`MempoolRequest`] / [`MempoolResponse`] for [`MethodKind::Mempool`] methods; +//! * [`InfoRequest`] / [`InfoResponse`] for [`MethodKind::Info`] methods; +//! * [`SnapshotRequest`] / [`SnapshotResponse`] for [`MethodKind::Snapshot`] methods. +//! +//! The domain types in this module have conversions to and from the Protobuf +//! types defined in the [`tendermint_proto`] crate. These conversions are +//! required for ABCI server implementations, which use the protobufs to +//! communicate with Tendermint, but should not be required for ABCI +//! applications, which should use the domain types in an interface defined by +//! their choice of ABCI server implementation. +//! +//! [ABCI]: https://docs.tendermint.com/master/spec/abci/ +//! [mat]: https://docs.tendermint.com/master/spec/abci/abci.html +//! [tmabci]: https://github.com/informalsystems/tendermint-rs/tree/master/abci + +mod event; +mod kind; + +pub mod request; +pub mod response; +pub mod types; -mod code; -mod data; -mod gas; -mod info; -mod log; -mod path; -pub mod responses; -pub mod tag; -pub mod transaction; +pub use event::{Event, EventAttribute, EventAttributeIndexExt}; +#[doc(inline)] pub use self::{ - code::Code, - data::Data, - gas::Gas, - info::Info, - log::Log, - path::Path, - responses::{DeliverTx, Event, Responses}, - transaction::Transaction, + kind::MethodKind, + request::{ConsensusRequest, InfoRequest, MempoolRequest, Request, SnapshotRequest}, + response::{ConsensusResponse, InfoResponse, MempoolResponse, Response, SnapshotResponse}, }; diff --git a/tendermint/src/abci/code.rs b/tendermint/src/abci/code.rs deleted file mode 100644 index cdf7e0067..000000000 --- a/tendermint/src/abci/code.rs +++ /dev/null @@ -1,108 +0,0 @@ -use core::fmt; - -use serde::{ - de::{Deserialize, Deserializer, Visitor}, - Serialize, Serializer, -}; - -/// ABCI application response codes. -/// -/// These presently use 0 for success and non-zero for errors: -/// -/// -/// -/// Note that in the future there may potentially be non-zero success codes. -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Code { - /// Success - Ok, - - /// Error codes - Err(u32), -} - -impl Default for Code { - fn default() -> Code { - Code::Ok - } -} - -impl Code { - /// Was the response OK? - pub fn is_ok(self) -> bool { - match self { - Code::Ok => true, - Code::Err(_) => false, - } - } - - /// Was the response an error? - pub fn is_err(self) -> bool { - !self.is_ok() - } - - /// Get the integer error value for this code - pub fn value(self) -> u32 { - u32::from(self) - } -} - -impl From for Code { - fn from(value: u32) -> Code { - match value { - 0 => Code::Ok, - err => Code::Err(err), - } - } -} - -impl From for u32 { - fn from(code: Code) -> u32 { - match code { - Code::Ok => 0, - Code::Err(err) => err, - } - } -} - -impl Serialize for Code { - fn serialize(&self, serializer: S) -> Result { - self.value().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for Code { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct CodeVisitor; - - impl<'de> Visitor<'de> for CodeVisitor { - type Value = Code; - - fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.write_str("integer or string") - } - - fn visit_u64(self, val: u64) -> Result - where - E: serde::de::Error, - { - Ok(Code::from(val as u32)) - } - - fn visit_str(self, val: &str) -> Result - where - E: serde::de::Error, - { - match val.parse::() { - Ok(val) => self.visit_u64(val), - Err(_) => Err(E::custom("failed to parse integer")), - } - } - } - - deserializer.deserialize_any(CodeVisitor) - } -} diff --git a/tendermint/src/abci/data.rs b/tendermint/src/abci/data.rs deleted file mode 100644 index 76bcbea3d..000000000 --- a/tendermint/src/abci/data.rs +++ /dev/null @@ -1,60 +0,0 @@ -use serde::{Deserialize, Serialize}; - -use crate::prelude::*; - -/// ABCI transaction data. -/// -/// Transactions are opaque binary blobs which are validated according to -/// application-specific rules. -#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] -#[serde(transparent)] -pub struct Data(#[serde(with = "crate::serializers::bytes::base64string")] Vec); - -impl From> for Data { - fn from(value: Vec) -> Self { - Self(value) - } -} - -impl From for Vec { - fn from(value: Data) -> Self { - value.0 - } -} - -impl Data { - /// Get value - pub fn value(&self) -> &Vec { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use crate::{abci::Data, prelude::*}; - - #[test] - fn test_deserialization() { - let json = "\"ChYKFGNvbm5lY3Rpb25fb3Blbl9pbml0\""; - let mydata: Data = serde_json::from_str(json).unwrap(); - assert_eq!( - mydata.0, - vec![ - // By chance this is a protobuf struct. - 10, // Field 1 is a String - 22, // Field 1 length is 22 - 10, // Sub-field 1 is String - 20, // Sub-field 1 length is 20 - 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 95, 111, 112, 101, 110, 95, 105, - 110, 105, 116 // "connection_open_init" - ] - ); - } - - #[test] - fn test_serialization() { - let mydata: Data = vec![1, 2, 3, 4].into(); - let json = serde_json::to_string(&mydata).unwrap(); - assert_eq!(json, "\"AQIDBA==\""); - } -} diff --git a/tendermint/src/abci/doc/request-applysnapshotchunk.md b/tendermint/src/abci/doc/request-applysnapshotchunk.md new file mode 100644 index 000000000..fe1c35598 --- /dev/null +++ b/tendermint/src/abci/doc/request-applysnapshotchunk.md @@ -0,0 +1,21 @@ +Applies a snapshot chunk. + +The application can choose to refetch chunks and/or ban P2P peers as +appropriate. Tendermint will not do this unless instructed by the +application. + +The application may want to verify each chunk, e.g., by attaching chunk +hashes in [`Snapshot::metadata`] and/or incrementally verifying contents +against `app_hash`. + +When all chunks have been accepted, Tendermint will make an ABCI [`Info`] +request to verify that `last_block_app_hash` and `last_block_height` match +the expected values, and record the `app_version` in the node state. It then +switches to fast sync or consensus and joins the network. + +If Tendermint is unable to retrieve the next chunk after some time (e.g., +because no suitable peers are available), it will reject the snapshot and try +a different one via `OfferSnapshot`. The application should be prepared to +reset and accept it or abort as appropriate. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#applysnapshotchunk) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-beginblock.md b/tendermint/src/abci/doc/request-beginblock.md new file mode 100644 index 000000000..44b98920a --- /dev/null +++ b/tendermint/src/abci/doc/request-beginblock.md @@ -0,0 +1,6 @@ +Signals the beginning of a new block. + +Called prior to any [`DeliverTx`]s. The `header` contains the height, +timestamp, and more -- it exactly matches the Tendermint block header. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#beginblock) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-checktx.md b/tendermint/src/abci/doc/request-checktx.md new file mode 100644 index 000000000..7d97a6287 --- /dev/null +++ b/tendermint/src/abci/doc/request-checktx.md @@ -0,0 +1,11 @@ +Check whether a transaction should be included in the mempool. + +`CheckTx` is not involved in processing blocks, only in deciding whether a +transaction should be included in the mempool. Every node runs `CheckTx` +before adding a transaction to its local mempool. The transaction may come +from an external user or another node. `CheckTx` need not execute the +transaction in full, but can instead perform lightweight or statateful +validation (e.g., checking signatures or account balances) instead of more +expensive checks (like running code in a virtual machine). + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#checktx) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-commit.md b/tendermint/src/abci/doc/request-commit.md new file mode 100644 index 000000000..0013b8302 --- /dev/null +++ b/tendermint/src/abci/doc/request-commit.md @@ -0,0 +1,4 @@ +Signals the application that it can write the queued state transitions +from the block to its state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#commit) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-delivertx.md b/tendermint/src/abci/doc/request-delivertx.md new file mode 100644 index 000000000..4d449cc56 --- /dev/null +++ b/tendermint/src/abci/doc/request-delivertx.md @@ -0,0 +1,3 @@ +Execute a transaction against the application state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#delivertx) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-echo.md b/tendermint/src/abci/doc/request-echo.md new file mode 100644 index 000000000..92658169f --- /dev/null +++ b/tendermint/src/abci/doc/request-echo.md @@ -0,0 +1,3 @@ +Echoes a string to test an ABCI implementation. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#echo) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-endblock.md b/tendermint/src/abci/doc/request-endblock.md new file mode 100644 index 000000000..6e23b6d7c --- /dev/null +++ b/tendermint/src/abci/doc/request-endblock.md @@ -0,0 +1,5 @@ +Signals the end of a block. + +Called after all transactions, and prior to each `Commit`. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#endblock) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-flush.md b/tendermint/src/abci/doc/request-flush.md new file mode 100644 index 000000000..c556d8b2d --- /dev/null +++ b/tendermint/src/abci/doc/request-flush.md @@ -0,0 +1,3 @@ +Indicates that any pending requests should be completed and their responses flushed. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#flush) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-info.md b/tendermint/src/abci/doc/request-info.md new file mode 100644 index 000000000..471ec36c7 --- /dev/null +++ b/tendermint/src/abci/doc/request-info.md @@ -0,0 +1,3 @@ +Requests information about the application state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#info) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-initchain.md b/tendermint/src/abci/doc/request-initchain.md new file mode 100644 index 000000000..49180f351 --- /dev/null +++ b/tendermint/src/abci/doc/request-initchain.md @@ -0,0 +1,3 @@ +Called on genesis to initialize chain state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#initchain) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-listsnapshots.md b/tendermint/src/abci/doc/request-listsnapshots.md new file mode 100644 index 000000000..bc89accad --- /dev/null +++ b/tendermint/src/abci/doc/request-listsnapshots.md @@ -0,0 +1,3 @@ +Asks the application for a list of snapshots. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#listsnapshots) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-loadsnapshotchunk.md b/tendermint/src/abci/doc/request-loadsnapshotchunk.md new file mode 100644 index 000000000..70b686d41 --- /dev/null +++ b/tendermint/src/abci/doc/request-loadsnapshotchunk.md @@ -0,0 +1,3 @@ +Used during state sync to retrieve snapshot chunks from peers. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#loadsnapshotchunk) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-offersnapshot.md b/tendermint/src/abci/doc/request-offersnapshot.md new file mode 100644 index 000000000..db0e60b17 --- /dev/null +++ b/tendermint/src/abci/doc/request-offersnapshot.md @@ -0,0 +1,20 @@ +Offers a list of snapshots to the application. + +`OfferSnapshot` is called when bootstrapping a node using state sync. The +application may accept or reject snapshots as appropriate. Upon accepting, +Tendermint will retrieve and apply snapshot chunks via +[`ApplySnapshotChunk`]. The application may also choose to reject a snapshot +in the chunk response, in which case it should be prepared to accept further +`OfferSnapshot` calls. + +Only `app_hash` can be trusted, as it has been verified by the light client. +Any other data can be spoofed by adversaries, so applications should employ +additional verification schemes to avoid denial-of-service attacks. The +verified `app_hash` is automatically checked against the restored application +at the end of snapshot restoration. + +See also the [`Snapshot`] data type and the [ABCI state sync documentation][ssd]. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#offersnapshot) + +[ssd]: https://docs.tendermint.com/master/spec/abci/apps.html#state-sync \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-query.md b/tendermint/src/abci/doc/request-query.md new file mode 100644 index 000000000..5d061c54e --- /dev/null +++ b/tendermint/src/abci/doc/request-query.md @@ -0,0 +1,3 @@ +Queries for data from the application at current or past height. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#query) \ No newline at end of file diff --git a/tendermint/src/abci/doc/request-setoption.md b/tendermint/src/abci/doc/request-setoption.md new file mode 100644 index 000000000..f3c869b34 --- /dev/null +++ b/tendermint/src/abci/doc/request-setoption.md @@ -0,0 +1 @@ +Undocumented, non-deterministic, was removed from Tendermint in 0.35. \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-applysnapshotchunk.md b/tendermint/src/abci/doc/response-applysnapshotchunk.md new file mode 100644 index 000000000..bffabe7af --- /dev/null +++ b/tendermint/src/abci/doc/response-applysnapshotchunk.md @@ -0,0 +1,7 @@ +Returns the result of applying a snapshot chunk and associated data. + +The application can choose to refetch chunks and/or ban P2P peers as +appropriate. Tendermint will not do this unless instructed by the +application. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#applysnapshotchunk) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-beginblock.md b/tendermint/src/abci/doc/response-beginblock.md new file mode 100644 index 000000000..255efd098 --- /dev/null +++ b/tendermint/src/abci/doc/response-beginblock.md @@ -0,0 +1,3 @@ +Returns events that occurred when beginning a new block. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#beginblock) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-checktx.md b/tendermint/src/abci/doc/response-checktx.md new file mode 100644 index 000000000..cd31b1703 --- /dev/null +++ b/tendermint/src/abci/doc/response-checktx.md @@ -0,0 +1,3 @@ +Returns the result of checking a transaction for mempool inclusion. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#checktx) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-commit.md b/tendermint/src/abci/doc/response-commit.md new file mode 100644 index 000000000..822aab48d --- /dev/null +++ b/tendermint/src/abci/doc/response-commit.md @@ -0,0 +1,3 @@ +Returns the result of persisting the application state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#commit) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-delivertx.md b/tendermint/src/abci/doc/response-delivertx.md new file mode 100644 index 000000000..cb83a6fd9 --- /dev/null +++ b/tendermint/src/abci/doc/response-delivertx.md @@ -0,0 +1,4 @@ +Returns events that occurred while executing a transaction against the +application state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#delivertx) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-echo.md b/tendermint/src/abci/doc/response-echo.md new file mode 100644 index 000000000..92658169f --- /dev/null +++ b/tendermint/src/abci/doc/response-echo.md @@ -0,0 +1,3 @@ +Echoes a string to test an ABCI implementation. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#echo) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-endblock.md b/tendermint/src/abci/doc/response-endblock.md new file mode 100644 index 000000000..062cabb84 --- /dev/null +++ b/tendermint/src/abci/doc/response-endblock.md @@ -0,0 +1,3 @@ +Returns validator updates that occur after the end of a block. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#endblock) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-exception.md b/tendermint/src/abci/doc/response-exception.md new file mode 100644 index 000000000..5d8fb6c67 --- /dev/null +++ b/tendermint/src/abci/doc/response-exception.md @@ -0,0 +1 @@ +Returns an exception (undocumented, nondeterministic). \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-flush.md b/tendermint/src/abci/doc/response-flush.md new file mode 100644 index 000000000..6c411e1bf --- /dev/null +++ b/tendermint/src/abci/doc/response-flush.md @@ -0,0 +1,3 @@ +Indicates that all pending requests have been completed with their responses flushed. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#flush) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-info.md b/tendermint/src/abci/doc/response-info.md new file mode 100644 index 000000000..e0c64b1f5 --- /dev/null +++ b/tendermint/src/abci/doc/response-info.md @@ -0,0 +1,3 @@ +Returns information about the application state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#info) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-initchain.md b/tendermint/src/abci/doc/response-initchain.md new file mode 100644 index 000000000..b7ea62de7 --- /dev/null +++ b/tendermint/src/abci/doc/response-initchain.md @@ -0,0 +1,3 @@ +Returned on genesis after initializing chain state. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#initchain) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-listsnapshots.md b/tendermint/src/abci/doc/response-listsnapshots.md new file mode 100644 index 000000000..48255b800 --- /dev/null +++ b/tendermint/src/abci/doc/response-listsnapshots.md @@ -0,0 +1,3 @@ +Returns a list of local state snapshots. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#listsnapshots) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-loadsnapshotchunk.md b/tendermint/src/abci/doc/response-loadsnapshotchunk.md new file mode 100644 index 000000000..2eaf1c614 --- /dev/null +++ b/tendermint/src/abci/doc/response-loadsnapshotchunk.md @@ -0,0 +1,3 @@ +Returns a snapshot chunk from the application. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#loadsnapshotchunk) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-offersnapshot.md b/tendermint/src/abci/doc/response-offersnapshot.md new file mode 100644 index 000000000..0da7a66fa --- /dev/null +++ b/tendermint/src/abci/doc/response-offersnapshot.md @@ -0,0 +1,7 @@ +Returns the application's response to a snapshot offer. + +See also the [`Snapshot`] data type and the [ABCI state sync documentation][ssd]. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#offersnapshot) + +[ssd]: https://docs.tendermint.com/master/spec/abci/apps.html#state-sync \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-query.md b/tendermint/src/abci/doc/response-query.md new file mode 100644 index 000000000..57eb3bf4a --- /dev/null +++ b/tendermint/src/abci/doc/response-query.md @@ -0,0 +1,3 @@ +Returns data queried from the application. + +[ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#query) \ No newline at end of file diff --git a/tendermint/src/abci/doc/response-setoption.md b/tendermint/src/abci/doc/response-setoption.md new file mode 100644 index 000000000..f3c869b34 --- /dev/null +++ b/tendermint/src/abci/doc/response-setoption.md @@ -0,0 +1 @@ +Undocumented, non-deterministic, was removed from Tendermint in 0.35. \ No newline at end of file diff --git a/tendermint/src/abci/event.rs b/tendermint/src/abci/event.rs new file mode 100644 index 000000000..13c47ae39 --- /dev/null +++ b/tendermint/src/abci/event.rs @@ -0,0 +1,185 @@ +use crate::prelude::*; + +/// An event that occurred while processing a request. +/// +/// Application developers can attach additional information to +/// [`BeginBlock`](super::response::BeginBlock), +/// [`EndBlock`](super::response::EndBlock), +/// [`CheckTx`](super::response::CheckTx), and +/// [`DeliverTx`](super::response::DeliverTx) responses. Later, transactions may +/// be queried using these events. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#events) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Event { + /// The kind of event. + /// + /// Tendermint calls this the `type`, but we use `kind` to avoid confusion + /// with Rust types and follow Rust conventions. + pub kind: String, + /// A list of [`EventAttribute`]s describing the event. + pub attributes: Vec, +} + +impl Event { + /// Construct an event from generic data. + /// + /// The `From` impls on [`EventAttribute`] and the [`EventAttributeIndexExt`] + /// trait allow ergonomic event construction, as in this example: + /// + /// ``` + /// use tendermint::abci::{Event, EventAttributeIndexExt}; + /// + /// let event = Event::new( + /// "app", + /// vec![ + /// ("key1", "value1").index(), + /// ("key2", "value2").index(), + /// ("key3", "value3").no_index(), // will not be indexed + /// ], + /// ); + /// ``` + // XXX(hdevalence): remove vec! from example after https://github.com/rust-lang/rust/pull/65819 + pub fn new(kind: K, attributes: I) -> Self + where + K: Into, + I: IntoIterator, + I::Item: Into, + { + Self { + kind: kind.into(), + attributes: attributes.into_iter().map(Into::into).collect(), + } + } +} + +/// A key-value pair describing an [`Event`]. +/// +/// Generic methods are provided for more ergonomic attribute construction, see +/// [`Event::new`] for details. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#events) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EventAttribute { + /// The event key. + pub key: String, + /// The event value. + pub value: String, + /// Whether Tendermint's indexer should index this event. + /// + /// **This field is nondeterministic**. + pub index: bool, +} + +impl, V: Into> From<(K, V, bool)> for EventAttribute { + fn from((key, value, index): (K, V, bool)) -> Self { + EventAttribute { + key: key.into(), + value: value.into(), + index, + } + } +} + +/// Adds convenience methods to tuples for more ergonomic [`EventAttribute`] +/// construction. +/// +/// See [`Event::new`] for details. +#[allow(missing_docs)] +pub trait EventAttributeIndexExt: private::Sealed { + type Key; + type Value; + + /// Indicate that this key/value pair should be indexed by Tendermint. + fn index(self) -> (Self::Key, Self::Value, bool); + /// Indicate that this key/value pair should not be indexed by Tendermint. + fn no_index(self) -> (Self::Key, Self::Value, bool); +} + +impl, V: Into> EventAttributeIndexExt for (K, V) { + type Key = K; + type Value = V; + fn index(self) -> (K, V, bool) { + let (key, value) = self; + (key, value, true) + } + fn no_index(self) -> (K, V, bool) { + let (key, value) = self; + (key, value, false) + } +} + +mod private { + use crate::prelude::*; + + pub trait Sealed {} + + impl, V: Into> Sealed for (K, V) {} +} + +impl, V: Into> From<(K, V)> for EventAttribute { + fn from((key, value): (K, V)) -> Self { + (key, value, false).into() + } +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::EventAttribute { + fn from(event: EventAttribute) -> Self { + Self { + key: event.key.into(), + value: event.value.into(), + index: event.index, + } + } +} + +impl TryFrom for EventAttribute { + type Error = crate::Error; + + fn try_from(event: pb::EventAttribute) -> Result { + // We insist that keys and values are strings, like tm 0.35 did. + Ok(Self { + key: String::from_utf8(event.key.to_vec()) + .map_err(|e| crate::Error::parse(e.to_string()))?, + value: String::from_utf8(event.value.to_vec()) + .map_err(|e| crate::Error::parse(e.to_string()))?, + index: event.index, + }) + } +} + +impl Protobuf for EventAttribute {} + +impl From for pb::Event { + fn from(event: Event) -> Self { + Self { + r#type: event.kind, + attributes: event.attributes.into_iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for Event { + type Error = crate::Error; + + fn try_from(event: pb::Event) -> Result { + Ok(Self { + kind: event.r#type, + attributes: event + .attributes + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +impl Protobuf for Event {} diff --git a/tendermint/src/abci/gas.rs b/tendermint/src/abci/gas.rs deleted file mode 100644 index b086c3941..000000000 --- a/tendermint/src/abci/gas.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! Gas: abstract representation for the cost of resources used by nodes when -//! processing transactions. -//! -//! For more information, see: -//! -//! - -use core::{ - fmt::{self, Display}, - str::FromStr, -}; - -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; - -use crate::{error::Error, prelude::*}; - -/// Gas: representation of transaction processing resource costs -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] -pub struct Gas(u64); - -impl Gas { - /// Get the inner integer value - pub fn value(self) -> u64 { - self.0 - } -} - -impl From for Gas { - fn from(amount: u64) -> Gas { - Gas(amount) - } -} - -impl From for u64 { - fn from(gas: Gas) -> u64 { - gas.0 - } -} - -impl Display for Gas { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl FromStr for Gas { - type Err = Error; - - fn from_str(s: &str) -> Result { - let res = s - .parse::() - .map_err(|e| Error::parse_int(s.to_string(), e))? - .into(); - - Ok(res) - } -} - -impl<'de> Deserialize<'de> for Gas { - fn deserialize>(deserializer: D) -> Result { - Self::from_str(&String::deserialize(deserializer)?) - .map_err(|e| D::Error::custom(format!("{}", e))) - } -} - -impl Serialize for Gas { - fn serialize(&self, serializer: S) -> Result { - self.to_string().serialize(serializer) - } -} diff --git a/tendermint/src/abci/info.rs b/tendermint/src/abci/info.rs deleted file mode 100644 index c024aacd3..000000000 --- a/tendermint/src/abci/info.rs +++ /dev/null @@ -1,21 +0,0 @@ -use core::fmt::{self, Display}; - -use serde::{Deserialize, Serialize}; - -use crate::prelude::*; - -/// ABCI info -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct Info(String); - -impl AsRef for Info { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl Display for Info { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/tendermint/src/abci/kind.rs b/tendermint/src/abci/kind.rs new file mode 100644 index 000000000..065a01c9a --- /dev/null +++ b/tendermint/src/abci/kind.rs @@ -0,0 +1,23 @@ +/// A category of ABCI method. +/// +/// ABCI methods are split into four categories. Tendermint opens one ABCI +/// connection for each category and refers to these categories as *connections*, +/// but nothing actually restricts an ABCI connection from calling methods in +/// multiple categories. +/// +/// This enum breaks out the `Flush` method as a distinct category, since it is +/// used to control the execution of other methods. +pub enum MethodKind { + /// A consensus method, driven by the consensus protocol and responsible for + /// block execution. + Consensus, + /// A mempool method, used for validating new transactions before they're + /// shared or included in a block. + Mempool, + /// A snapshot method, used for serving and restoring state snapshots. + Snapshot, + /// An info method, used for initialization and user queries. + Info, + /// The flush method requests that all pending method requests are fully executed. + Flush, +} diff --git a/tendermint/src/abci/log.rs b/tendermint/src/abci/log.rs deleted file mode 100644 index 7668affe3..000000000 --- a/tendermint/src/abci/log.rs +++ /dev/null @@ -1,35 +0,0 @@ -use core::{fmt, fmt::Display}; - -use serde::{Deserialize, Serialize}; - -use crate::prelude::*; - -/// ABCI log data -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(transparent)] -pub struct Log(String); - -impl Log { - /// Convenience function: get value - pub fn value(&self) -> &String { - &self.0 - } -} - -impl From<&str> for Log { - fn from(s: &str) -> Self { - Log(s.to_owned()) - } -} - -impl AsRef for Log { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl Display for Log { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/tendermint/src/abci/path.rs b/tendermint/src/abci/path.rs deleted file mode 100644 index 849de2cec..000000000 --- a/tendermint/src/abci/path.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! Paths to ABCI data - -use core::{ - fmt::{self, Display}, - str::FromStr, -}; - -use serde::{Deserialize, Serialize}; - -use crate::{error::Error, prelude::*}; - -/// Path to ABCI data -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct Path(String); - -impl Display for Path { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) - } -} - -impl FromStr for Path { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(Path(s.to_owned())) - } -} diff --git a/tendermint/src/abci/request.rs b/tendermint/src/abci/request.rs new file mode 100644 index 000000000..ea15e0bc0 --- /dev/null +++ b/tendermint/src/abci/request.rs @@ -0,0 +1,312 @@ +//! ABCI requests and request data. +//! +//! The [`Request`] enum records all possible ABCI requests. Requests that +//! contain data are modeled as a separate struct, to avoid duplication of field +//! definitions. + +// IMPORTANT NOTE ON DOCUMENTATION: +// +// The documentation for each request type is adapted from the ABCI Methods and +// Types spec document. However, the same logical request may appear three +// times, as a struct with the request data, as a Request variant, and as a +// CategoryRequest variant. +// +// To avoid duplication, this documentation is stored in the doc/ folder in +// individual .md files, which are pasted onto the relevant items using #[doc = +// include_str!(...)]. +// +// This is also why certain submodules have #[allow(unused)] imports to bring +// items into scope for doc links, rather than changing the doc links -- it +// allows the doc comments to be copied without editing. +use core::convert::{TryFrom, TryInto}; + +// bring into scope for doc links +#[allow(unused)] +use super::types::Snapshot; +use super::MethodKind; +use crate::{prelude::*, Error}; + +mod apply_snapshot_chunk; +mod begin_block; +mod check_tx; +mod deliver_tx; +mod echo; +mod end_block; +mod info; +mod init_chain; +mod load_snapshot_chunk; +mod offer_snapshot; +mod query; +mod set_option; + +pub use apply_snapshot_chunk::ApplySnapshotChunk; +pub use begin_block::BeginBlock; +pub use check_tx::{CheckTx, CheckTxKind}; +pub use deliver_tx::DeliverTx; +pub use echo::Echo; +pub use end_block::EndBlock; +pub use info::Info; +pub use init_chain::InitChain; +pub use load_snapshot_chunk::LoadSnapshotChunk; +pub use offer_snapshot::OfferSnapshot; +pub use query::Query; +pub use set_option::SetOption; + +/// All possible ABCI requests. +#[allow(clippy::large_enum_variant)] +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Request { + #[doc = include_str!("doc/request-echo.md")] + Echo(Echo), + #[doc = include_str!("doc/request-flush.md")] + Flush, + #[doc = include_str!("doc/request-info.md")] + Info(Info), + #[doc = include_str!("doc/request-setoption.md")] + SetOption(SetOption), + #[doc = include_str!("doc/request-initchain.md")] + InitChain(InitChain), + #[doc = include_str!("doc/request-query.md")] + Query(Query), + #[doc = include_str!("doc/request-beginblock.md")] + BeginBlock(BeginBlock), + #[doc = include_str!("doc/request-checktx.md")] + CheckTx(CheckTx), + #[doc = include_str!("doc/request-delivertx.md")] + DeliverTx(DeliverTx), + #[doc = include_str!("doc/request-endblock.md")] + EndBlock(EndBlock), + #[doc = include_str!("doc/request-commit.md")] + Commit, + #[doc = include_str!("doc/request-listsnapshots.md")] + ListSnapshots, + #[doc = include_str!("doc/request-offersnapshot.md")] + OfferSnapshot(OfferSnapshot), + #[doc = include_str!("doc/request-loadsnapshotchunk.md")] + LoadSnapshotChunk(LoadSnapshotChunk), + #[doc = include_str!("doc/request-applysnapshotchunk.md")] + ApplySnapshotChunk(ApplySnapshotChunk), +} + +impl Request { + /// Get the method kind for this request. + pub fn kind(&self) -> MethodKind { + use Request::*; + match self { + Flush => MethodKind::Flush, + InitChain(_) => MethodKind::Consensus, + BeginBlock(_) => MethodKind::Consensus, + DeliverTx(_) => MethodKind::Consensus, + EndBlock(_) => MethodKind::Consensus, + Commit => MethodKind::Consensus, + CheckTx(_) => MethodKind::Mempool, + ListSnapshots => MethodKind::Snapshot, + OfferSnapshot(_) => MethodKind::Snapshot, + LoadSnapshotChunk(_) => MethodKind::Snapshot, + ApplySnapshotChunk(_) => MethodKind::Snapshot, + Info(_) => MethodKind::Info, + Query(_) => MethodKind::Info, + Echo(_) => MethodKind::Info, + SetOption(_) => MethodKind::Info, + } + } +} + +/// The consensus category of ABCI requests. +#[allow(clippy::large_enum_variant)] +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ConsensusRequest { + #[doc = include_str!("doc/request-initchain.md")] + InitChain(InitChain), + #[doc = include_str!("doc/request-beginblock.md")] + BeginBlock(BeginBlock), + #[doc = include_str!("doc/request-delivertx.md")] + DeliverTx(DeliverTx), + #[doc = include_str!("doc/request-endblock.md")] + EndBlock(EndBlock), + #[doc = include_str!("doc/request-commit.md")] + Commit, +} + +impl From for Request { + fn from(req: ConsensusRequest) -> Self { + match req { + ConsensusRequest::InitChain(x) => Self::InitChain(x), + ConsensusRequest::BeginBlock(x) => Self::BeginBlock(x), + ConsensusRequest::DeliverTx(x) => Self::DeliverTx(x), + ConsensusRequest::EndBlock(x) => Self::EndBlock(x), + ConsensusRequest::Commit => Self::Commit, + } + } +} + +impl TryFrom for ConsensusRequest { + type Error = Error; + fn try_from(req: Request) -> Result { + match req { + Request::InitChain(x) => Ok(Self::InitChain(x)), + Request::BeginBlock(x) => Ok(Self::BeginBlock(x)), + Request::DeliverTx(x) => Ok(Self::DeliverTx(x)), + Request::EndBlock(x) => Ok(Self::EndBlock(x)), + Request::Commit => Ok(Self::Commit), + _ => Err(Error::invalid_abci_request_type()), + } + } +} + +/// The mempool category of ABCI requests. +#[derive(Clone, PartialEq, Debug)] +pub enum MempoolRequest { + #[doc = include_str!("doc/request-checktx.md")] + CheckTx(CheckTx), +} + +impl From for Request { + fn from(req: MempoolRequest) -> Self { + match req { + MempoolRequest::CheckTx(x) => Self::CheckTx(x), + } + } +} + +impl TryFrom for MempoolRequest { + type Error = Error; + fn try_from(req: Request) -> Result { + match req { + Request::CheckTx(x) => Ok(Self::CheckTx(x)), + _ => Err(Error::invalid_abci_request_type()), + } + } +} + +/// The info category of ABCI requests. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum InfoRequest { + #[doc = include_str!("doc/request-info.md")] + Info(Info), + #[doc = include_str!("doc/request-query.md")] + Query(Query), + #[doc = include_str!("doc/request-echo.md")] + Echo(Echo), + #[doc = include_str!("doc/request-setoption.md")] + SetOption(SetOption), +} + +impl From for Request { + fn from(req: InfoRequest) -> Self { + match req { + InfoRequest::Info(x) => Self::Info(x), + InfoRequest::Query(x) => Self::Query(x), + InfoRequest::Echo(x) => Self::Echo(x), + InfoRequest::SetOption(x) => Self::SetOption(x), + } + } +} + +impl TryFrom for InfoRequest { + type Error = Error; + fn try_from(req: Request) -> Result { + match req { + Request::Info(x) => Ok(Self::Info(x)), + Request::Query(x) => Ok(Self::Query(x)), + Request::Echo(x) => Ok(Self::Echo(x)), + Request::SetOption(x) => Ok(Self::SetOption(x)), + _ => Err(Error::invalid_abci_request_type()), + } + } +} + +/// The snapshot category of ABCI requests. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum SnapshotRequest { + #[doc = include_str!("doc/request-listsnapshots.md")] + ListSnapshots, + #[doc = include_str!("doc/request-offersnapshot.md")] + OfferSnapshot(OfferSnapshot), + #[doc = include_str!("doc/request-loadsnapshotchunk.md")] + LoadSnapshotChunk(LoadSnapshotChunk), + #[doc = include_str!("doc/request-applysnapshotchunk.md")] + ApplySnapshotChunk(ApplySnapshotChunk), +} + +impl From for Request { + fn from(req: SnapshotRequest) -> Self { + match req { + SnapshotRequest::ListSnapshots => Self::ListSnapshots, + SnapshotRequest::OfferSnapshot(x) => Self::OfferSnapshot(x), + SnapshotRequest::LoadSnapshotChunk(x) => Self::LoadSnapshotChunk(x), + SnapshotRequest::ApplySnapshotChunk(x) => Self::ApplySnapshotChunk(x), + } + } +} + +impl TryFrom for SnapshotRequest { + type Error = Error; + fn try_from(req: Request) -> Result { + match req { + Request::ListSnapshots => Ok(Self::ListSnapshots), + Request::OfferSnapshot(x) => Ok(Self::OfferSnapshot(x)), + Request::LoadSnapshotChunk(x) => Ok(Self::LoadSnapshotChunk(x)), + Request::ApplySnapshotChunk(x) => Ok(Self::ApplySnapshotChunk(x)), + _ => Err(Error::invalid_abci_request_type()), + } + } +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::Request { + fn from(request: Request) -> pb::Request { + use pb::request::Value; + let value = match request { + Request::Echo(x) => Some(Value::Echo(x.into())), + Request::Flush => Some(Value::Flush(Default::default())), + Request::Info(x) => Some(Value::Info(x.into())), + Request::SetOption(x) => Some(Value::SetOption(x.into())), + Request::InitChain(x) => Some(Value::InitChain(x.into())), + Request::Query(x) => Some(Value::Query(x.into())), + Request::BeginBlock(x) => Some(Value::BeginBlock(x.into())), + Request::CheckTx(x) => Some(Value::CheckTx(x.into())), + Request::DeliverTx(x) => Some(Value::DeliverTx(x.into())), + Request::EndBlock(x) => Some(Value::EndBlock(x.into())), + Request::Commit => Some(Value::Commit(Default::default())), + Request::ListSnapshots => Some(Value::ListSnapshots(Default::default())), + Request::OfferSnapshot(x) => Some(Value::OfferSnapshot(x.into())), + Request::LoadSnapshotChunk(x) => Some(Value::LoadSnapshotChunk(x.into())), + Request::ApplySnapshotChunk(x) => Some(Value::ApplySnapshotChunk(x.into())), + }; + pb::Request { value } + } +} + +impl TryFrom for Request { + type Error = Error; + + fn try_from(request: pb::Request) -> Result { + use pb::request::Value; + match request.value { + Some(Value::Echo(x)) => Ok(Request::Echo(x.try_into()?)), + Some(Value::Flush(pb::RequestFlush {})) => Ok(Request::Flush), + Some(Value::Info(x)) => Ok(Request::Info(x.try_into()?)), + Some(Value::SetOption(x)) => Ok(Request::SetOption(x.try_into()?)), + Some(Value::InitChain(x)) => Ok(Request::InitChain(x.try_into()?)), + Some(Value::Query(x)) => Ok(Request::Query(x.try_into()?)), + Some(Value::BeginBlock(x)) => Ok(Request::BeginBlock(x.try_into()?)), + Some(Value::CheckTx(x)) => Ok(Request::CheckTx(x.try_into()?)), + Some(Value::DeliverTx(x)) => Ok(Request::DeliverTx(x.try_into()?)), + Some(Value::EndBlock(x)) => Ok(Request::EndBlock(x.try_into()?)), + Some(Value::Commit(pb::RequestCommit {})) => Ok(Request::Commit), + Some(Value::ListSnapshots(pb::RequestListSnapshots {})) => Ok(Request::ListSnapshots), + Some(Value::OfferSnapshot(x)) => Ok(Request::OfferSnapshot(x.try_into()?)), + Some(Value::LoadSnapshotChunk(x)) => Ok(Request::LoadSnapshotChunk(x.try_into()?)), + Some(Value::ApplySnapshotChunk(x)) => Ok(Request::ApplySnapshotChunk(x.try_into()?)), + None => Err(crate::Error::missing_data()), + } + } +} + +impl Protobuf for Request {} diff --git a/tendermint/src/abci/request/apply_snapshot_chunk.rs b/tendermint/src/abci/request/apply_snapshot_chunk.rs new file mode 100644 index 000000000..f893cbb47 --- /dev/null +++ b/tendermint/src/abci/request/apply_snapshot_chunk.rs @@ -0,0 +1,69 @@ +use bytes::Bytes; + +// bring into scope for doc links +#[allow(unused)] +use super::{super::types::Snapshot, Info, LoadSnapshotChunk}; +use crate::prelude::*; + +/// Applies a snapshot chunk. +/// +/// The application can choose to refetch chunks and/or ban P2P peers as +/// appropriate. Tendermint will not do this unless instructed by the +/// application. +/// +/// The application may want to verify each chunk, e.g., by attaching chunk +/// hashes in [`Snapshot::metadata`] and/or incrementally verifying contents +/// against `app_hash`. +/// +/// When all chunks have been accepted, Tendermint will make an ABCI [`Info`] +/// request to verify that `last_block_app_hash` and `last_block_height` match +/// the expected values, and record the `app_version` in the node state. It then +/// switches to fast sync or consensus and joins the network. +/// +/// If Tendermint is unable to retrieve the next chunk after some time (e.g., +/// because no suitable peers are available), it will reject the snapshot and try +/// a different one via `OfferSnapshot`. The application should be prepared to +/// reset and accept it or abort as appropriate. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#applysnapshotchunk) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ApplySnapshotChunk { + /// The chunk index, starting from `0`. Tendermint applies chunks sequentially. + pub index: u32, + /// The binary chunk contents, as returned by [`LoadSnapshotChunk`]. + pub chunk: Bytes, + /// The P2P ID of the node who sent this chunk. + pub sender: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestApplySnapshotChunk { + fn from(apply_snapshot_chunk: ApplySnapshotChunk) -> Self { + Self { + index: apply_snapshot_chunk.index, + chunk: apply_snapshot_chunk.chunk, + sender: apply_snapshot_chunk.sender, + } + } +} + +impl TryFrom for ApplySnapshotChunk { + type Error = crate::Error; + + fn try_from(apply_snapshot_chunk: pb::RequestApplySnapshotChunk) -> Result { + Ok(Self { + index: apply_snapshot_chunk.index, + chunk: apply_snapshot_chunk.chunk, + sender: apply_snapshot_chunk.sender, + }) + } +} + +impl Protobuf for ApplySnapshotChunk {} diff --git a/tendermint/src/abci/request/begin_block.rs b/tendermint/src/abci/request/begin_block.rs new file mode 100644 index 000000000..13ad8301c --- /dev/null +++ b/tendermint/src/abci/request/begin_block.rs @@ -0,0 +1,73 @@ +use bytes::Bytes; + +use super::super::types::{Evidence, LastCommitInfo}; +// bring into scope for doc links +#[allow(unused)] +use super::DeliverTx; +use crate::{block, prelude::*, Error}; + +#[doc = include_str!("../doc/request-beginblock.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct BeginBlock { + /// The block's hash. + /// + /// This can be derived from the block header. + pub hash: Bytes, + /// The block header. + pub header: block::Header, + /// Information about the last commit. + /// + /// This includes the round, the list of validators, and which validators + /// signed the last block. + pub last_commit_info: LastCommitInfo, + /// Evidence of validator misbehavior. + pub byzantine_validators: Vec, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestBeginBlock { + fn from(begin_block: BeginBlock) -> Self { + Self { + hash: begin_block.hash, + header: Some(begin_block.header.into()), + last_commit_info: Some(begin_block.last_commit_info.into()), + byzantine_validators: begin_block + .byzantine_validators + .into_iter() + .map(Into::into) + .collect(), + } + } +} + +impl TryFrom for BeginBlock { + type Error = Error; + + fn try_from(begin_block: pb::RequestBeginBlock) -> Result { + Ok(Self { + hash: begin_block.hash, + header: begin_block + .header + .ok_or_else(Error::missing_header)? + .try_into()?, + last_commit_info: begin_block + .last_commit_info + .ok_or_else(Error::missing_last_commit_info)? + .try_into()?, + byzantine_validators: begin_block + .byzantine_validators + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +impl Protobuf for BeginBlock {} diff --git a/tendermint/src/abci/request/check_tx.rs b/tendermint/src/abci/request/check_tx.rs new file mode 100644 index 000000000..0fbf8f10f --- /dev/null +++ b/tendermint/src/abci/request/check_tx.rs @@ -0,0 +1,71 @@ +use bytes::Bytes; + +use crate::prelude::*; + +#[doc = include_str!("../doc/request-checktx.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct CheckTx { + /// The transaction bytes. + pub tx: Bytes, + /// The kind of check to perform. + /// + /// Note: this field is called `type` in the protobuf, but we call it `kind` + /// to avoid the Rust keyword. + pub kind: CheckTxKind, +} + +/// The possible kinds of [`CheckTx`] checks. +/// +/// Note: the +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#checktx) +/// calls this `CheckTxType`, but we follow the Rust convention and name it `CheckTxKind` +/// to avoid confusion with Rust types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum CheckTxKind { + /// A full check is required (the default). + New = 0, + /// Indicates that the mempool is initiating a recheck of the transaction. + Recheck = 1, +} + +impl Default for CheckTxKind { + fn default() -> Self { + CheckTxKind::New + } +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestCheckTx { + fn from(check_tx: CheckTx) -> Self { + Self { + tx: check_tx.tx, + r#type: check_tx.kind as i32, + } + } +} + +impl TryFrom for CheckTx { + type Error = crate::Error; + + fn try_from(check_tx: pb::RequestCheckTx) -> Result { + let kind = match check_tx.r#type { + 0 => CheckTxKind::New, + 1 => CheckTxKind::Recheck, + _ => return Err(crate::Error::unsupported_check_tx_type()), + }; + Ok(Self { + tx: check_tx.tx, + kind, + }) + } +} + +impl Protobuf for CheckTx {} diff --git a/tendermint/src/abci/request/deliver_tx.rs b/tendermint/src/abci/request/deliver_tx.rs new file mode 100644 index 000000000..4c9bc6d50 --- /dev/null +++ b/tendermint/src/abci/request/deliver_tx.rs @@ -0,0 +1,34 @@ +use bytes::Bytes; + +use crate::prelude::*; + +#[doc = include_str!("../doc/request-delivertx.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct DeliverTx { + /// The bytes of the transaction to execute. + pub tx: Bytes, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestDeliverTx { + fn from(deliver_tx: DeliverTx) -> Self { + Self { tx: deliver_tx.tx } + } +} + +impl TryFrom for DeliverTx { + type Error = crate::Error; + + fn try_from(deliver_tx: pb::RequestDeliverTx) -> Result { + Ok(Self { tx: deliver_tx.tx }) + } +} + +impl Protobuf for DeliverTx {} diff --git a/tendermint/src/abci/request/echo.rs b/tendermint/src/abci/request/echo.rs new file mode 100644 index 000000000..3ae62456e --- /dev/null +++ b/tendermint/src/abci/request/echo.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/request-echo.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Echo { + /// The message to send back. + pub message: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestEcho { + fn from(echo: Echo) -> Self { + Self { + message: echo.message, + } + } +} + +impl TryFrom for Echo { + type Error = crate::Error; + + fn try_from(echo: pb::RequestEcho) -> Result { + Ok(Self { + message: echo.message, + }) + } +} + +impl Protobuf for Echo {} diff --git a/tendermint/src/abci/request/end_block.rs b/tendermint/src/abci/request/end_block.rs new file mode 100644 index 000000000..7095ea39f --- /dev/null +++ b/tendermint/src/abci/request/end_block.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/request-endblock.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct EndBlock { + /// The height of the block just executed. + pub height: i64, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestEndBlock { + fn from(end_block: EndBlock) -> Self { + Self { + height: end_block.height, + } + } +} + +impl TryFrom for EndBlock { + type Error = crate::Error; + + fn try_from(end_block: pb::RequestEndBlock) -> Result { + Ok(Self { + height: end_block.height, + }) + } +} + +impl Protobuf for EndBlock {} diff --git a/tendermint/src/abci/request/info.rs b/tendermint/src/abci/request/info.rs new file mode 100644 index 000000000..1a2c900d8 --- /dev/null +++ b/tendermint/src/abci/request/info.rs @@ -0,0 +1,44 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/request-info.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Info { + /// The Tendermint software semantic version. + pub version: String, + /// The Tendermint block protocol version. + pub block_version: u64, + /// The Tendermint p2p protocol version. + pub p2p_version: u64, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestInfo { + fn from(info: Info) -> Self { + Self { + version: info.version, + block_version: info.block_version, + p2p_version: info.p2p_version, + } + } +} + +impl TryFrom for Info { + type Error = crate::Error; + + fn try_from(info: pb::RequestInfo) -> Result { + Ok(Self { + version: info.version, + block_version: info.block_version, + p2p_version: info.p2p_version, + }) + } +} + +impl Protobuf for Info {} diff --git a/tendermint/src/abci/request/init_chain.rs b/tendermint/src/abci/request/init_chain.rs new file mode 100644 index 000000000..8072c2f6e --- /dev/null +++ b/tendermint/src/abci/request/init_chain.rs @@ -0,0 +1,74 @@ +use bytes::Bytes; +use chrono::{DateTime, Utc}; + +use super::super::types::ValidatorUpdate; +use crate::{block, consensus, prelude::*}; + +/// Called on genesis to initialize chain state. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#initchain) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct InitChain { + /// The genesis time. + pub time: DateTime, + /// The ID of the blockchain. + pub chain_id: String, + /// Initial consensus-critical parameters. + pub consensus_params: consensus::Params, + /// Initial genesis validators, sorted by voting power. + pub validators: Vec, + /// Serialized JSON bytes containing the initial application state. + pub app_state_bytes: Bytes, + /// Height of the initial block (typically `1`). + pub initial_height: block::Height, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +use crate::Error; + +impl From for pb::RequestInitChain { + fn from(init_chain: InitChain) -> Self { + Self { + time: Some(init_chain.time.into()), + chain_id: init_chain.chain_id, + consensus_params: Some(init_chain.consensus_params.into()), + validators: init_chain.validators.into_iter().map(Into::into).collect(), + app_state_bytes: init_chain.app_state_bytes, + initial_height: init_chain.initial_height.into(), + } + } +} + +impl TryFrom for InitChain { + type Error = Error; + + fn try_from(init_chain: pb::RequestInitChain) -> Result { + Ok(Self { + time: init_chain + .time + .ok_or_else(Error::missing_genesis_time)? + .try_into()?, + chain_id: init_chain.chain_id, + consensus_params: init_chain + .consensus_params + .ok_or_else(Error::missing_consensus_params)? + .try_into()?, + validators: init_chain + .validators + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + app_state_bytes: init_chain.app_state_bytes, + initial_height: init_chain.initial_height.try_into()?, + }) + } +} + +impl Protobuf for InitChain {} diff --git a/tendermint/src/abci/request/load_snapshot_chunk.rs b/tendermint/src/abci/request/load_snapshot_chunk.rs new file mode 100644 index 000000000..4c56ed6e3 --- /dev/null +++ b/tendermint/src/abci/request/load_snapshot_chunk.rs @@ -0,0 +1,44 @@ +use crate::{block, prelude::*}; + +#[doc = include_str!("../doc/request-loadsnapshotchunk.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct LoadSnapshotChunk { + /// The height of the snapshot the chunks belong to. + pub height: block::Height, + /// An application-specific identifier of the format of the snapshot chunk. + pub format: u32, + /// The chunk index, starting from `0` for the initial chunk. + pub chunk: u32, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestLoadSnapshotChunk { + fn from(load_snapshot_chunk: LoadSnapshotChunk) -> Self { + Self { + height: load_snapshot_chunk.height.into(), + format: load_snapshot_chunk.format, + chunk: load_snapshot_chunk.chunk, + } + } +} + +impl TryFrom for LoadSnapshotChunk { + type Error = crate::Error; + + fn try_from(load_snapshot_chunk: pb::RequestLoadSnapshotChunk) -> Result { + Ok(Self { + height: load_snapshot_chunk.height.try_into()?, + format: load_snapshot_chunk.format, + chunk: load_snapshot_chunk.chunk, + }) + } +} + +impl Protobuf for LoadSnapshotChunk {} diff --git a/tendermint/src/abci/request/offer_snapshot.rs b/tendermint/src/abci/request/offer_snapshot.rs new file mode 100644 index 000000000..db8f2fae0 --- /dev/null +++ b/tendermint/src/abci/request/offer_snapshot.rs @@ -0,0 +1,50 @@ +use bytes::Bytes; + +use super::super::types::Snapshot; +// bring into scope for doc links +#[allow(unused)] +use super::ApplySnapshotChunk; +use crate::prelude::*; + +#[doc = include_str!("../doc/request-offersnapshot.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct OfferSnapshot { + /// The snapshot offered for restoration. + pub snapshot: Snapshot, + /// The light client verified app hash for this height. + // XXX(hdevalence): replace with apphash + pub app_hash: Bytes, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestOfferSnapshot { + fn from(offer_snapshot: OfferSnapshot) -> Self { + Self { + snapshot: Some(offer_snapshot.snapshot.into()), + app_hash: offer_snapshot.app_hash, + } + } +} + +impl TryFrom for OfferSnapshot { + type Error = crate::Error; + + fn try_from(offer_snapshot: pb::RequestOfferSnapshot) -> Result { + Ok(Self { + snapshot: offer_snapshot + .snapshot + .ok_or_else(crate::Error::missing_data)? + .try_into()?, + app_hash: offer_snapshot.app_hash, + }) + } +} + +impl Protobuf for OfferSnapshot {} diff --git a/tendermint/src/abci/request/query.rs b/tendermint/src/abci/request/query.rs new file mode 100644 index 000000000..07dade853 --- /dev/null +++ b/tendermint/src/abci/request/query.rs @@ -0,0 +1,63 @@ +use bytes::Bytes; + +use crate::{block, prelude::*}; + +#[doc = include_str!("../doc/request-query.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Query { + /// Raw query bytes. + /// + /// Can be used with or in lieu of `path`. + pub data: Bytes, + /// Path of the request, like an HTTP `GET` path. + /// + /// Can be used with or in lieu of `data`. + /// + /// Applications MUST interpret `/store` as a query by key on the underlying + /// store. The key SHOULD be specified in the Data field. Applications SHOULD + /// allow queries over specific types like `/accounts/...` or `/votes/...`. + pub path: String, + /// The block height for which the query should be executed. + /// + /// The default `0` returns data for the latest committed block. Note that + /// this is the height of the block containing the application's Merkle root + /// hash, which represents the state as it was after committing the block at + /// `height - 1`. + pub height: block::Height, + /// Whether to return a Merkle proof with the response, if possible. + pub prove: bool, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestQuery { + fn from(query: Query) -> Self { + Self { + data: query.data, + path: query.path, + height: query.height.into(), + prove: query.prove, + } + } +} + +impl TryFrom for Query { + type Error = crate::Error; + + fn try_from(query: pb::RequestQuery) -> Result { + Ok(Self { + data: query.data, + path: query.path, + height: query.height.try_into()?, + prove: query.prove, + }) + } +} + +impl Protobuf for Query {} diff --git a/tendermint/src/abci/request/set_option.rs b/tendermint/src/abci/request/set_option.rs new file mode 100644 index 000000000..ffb4dd9bb --- /dev/null +++ b/tendermint/src/abci/request/set_option.rs @@ -0,0 +1,38 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/request-setoption.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct SetOption { + pub key: String, + pub value: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::RequestSetOption { + fn from(message: SetOption) -> Self { + Self { + key: message.key, + value: message.value, + } + } +} + +impl TryFrom for SetOption { + type Error = crate::Error; + + fn try_from(message: pb::RequestSetOption) -> Result { + Ok(Self { + key: message.key, + value: message.value, + }) + } +} + +impl Protobuf for SetOption {} diff --git a/tendermint/src/abci/response.rs b/tendermint/src/abci/response.rs new file mode 100644 index 000000000..5b55a7116 --- /dev/null +++ b/tendermint/src/abci/response.rs @@ -0,0 +1,295 @@ +//! ABCI responses and response data. +//! +//! The [`Response`] enum records all possible ABCI responses. Responses that +//! contain data are modeled as a separate struct, to avoid duplication of field +//! definitions. + +// IMPORTANT NOTE ON DOCUMENTATION: +// +// The documentation for each request type is adapted from the ABCI Methods and +// Types spec document. However, the same logical request may appear three +// times, as a struct with the request data, as a Request variant, and as a +// CategoryRequest variant. +// +// To avoid duplication, this documentation is stored in the doc/ folder in +// individual .md files, which are pasted onto the relevant items using #[doc = +// include_str!(...)]. +// +// This is also why certain submodules have #[allow(unused)] imports to bring +// items into scope for doc links, rather than changing the doc links -- it +// allows the doc comments to be copied without editing. +use core::convert::{TryFrom, TryInto}; + +// bring into scope for doc links +#[allow(unused)] +use super::types::Snapshot; +use crate::{prelude::*, Error}; + +mod apply_snapshot_chunk; +mod begin_block; +mod check_tx; +mod commit; +mod deliver_tx; +mod echo; +mod end_block; +mod exception; +mod info; +mod init_chain; +mod list_snapshots; +mod load_snapshot_chunk; +mod offer_snapshot; +mod query; +mod set_option; + +pub use apply_snapshot_chunk::{ApplySnapshotChunk, ApplySnapshotChunkResult}; +pub use begin_block::BeginBlock; +pub use check_tx::CheckTx; +pub use commit::Commit; +pub use deliver_tx::DeliverTx; +pub use echo::Echo; +pub use end_block::EndBlock; +pub use exception::Exception; +pub use info::Info; +pub use init_chain::InitChain; +pub use list_snapshots::ListSnapshots; +pub use load_snapshot_chunk::LoadSnapshotChunk; +pub use offer_snapshot::OfferSnapshot; +pub use query::Query; +pub use set_option::SetOption; + +/// All possible ABCI responses. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum Response { + #[doc = include_str!("doc/response-exception.md")] + Exception(Exception), + #[doc = include_str!("doc/response-echo.md")] + Echo(Echo), + #[doc = include_str!("doc/response-flush.md")] + Flush, + #[doc = include_str!("doc/response-info.md")] + Info(Info), + #[doc = include_str!("doc/response-setoption.md")] + SetOption(SetOption), + #[doc = include_str!("doc/response-initchain.md")] + InitChain(InitChain), + #[doc = include_str!("doc/response-query.md")] + Query(Query), + #[doc = include_str!("doc/response-beginblock.md")] + BeginBlock(BeginBlock), + #[doc = include_str!("doc/response-checktx.md")] + CheckTx(CheckTx), + #[doc = include_str!("doc/response-delivertx.md")] + DeliverTx(DeliverTx), + #[doc = include_str!("doc/response-endblock.md")] + EndBlock(EndBlock), + #[doc = include_str!("doc/response-commit.md")] + Commit(Commit), + #[doc = include_str!("doc/response-listsnapshots.md")] + ListSnapshots(ListSnapshots), + #[doc = include_str!("doc/response-offersnapshot.md")] + OfferSnapshot(OfferSnapshot), + #[doc = include_str!("doc/response-loadsnapshotchunk.md")] + LoadSnapshotChunk(LoadSnapshotChunk), + #[doc = include_str!("doc/response-applysnapshotchunk.md")] + ApplySnapshotChunk(ApplySnapshotChunk), +} + +/// The consensus category of ABCI responses. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ConsensusResponse { + #[doc = include_str!("doc/response-initchain.md")] + InitChain(InitChain), + #[doc = include_str!("doc/response-beginblock.md")] + BeginBlock(BeginBlock), + #[doc = include_str!("doc/response-delivertx.md")] + DeliverTx(DeliverTx), + #[doc = include_str!("doc/response-endblock.md")] + EndBlock(EndBlock), + #[doc = include_str!("doc/response-commit.md")] + Commit(Commit), +} + +impl From for Response { + fn from(req: ConsensusResponse) -> Self { + match req { + ConsensusResponse::InitChain(x) => Self::InitChain(x), + ConsensusResponse::BeginBlock(x) => Self::BeginBlock(x), + ConsensusResponse::DeliverTx(x) => Self::DeliverTx(x), + ConsensusResponse::EndBlock(x) => Self::EndBlock(x), + ConsensusResponse::Commit(x) => Self::Commit(x), + } + } +} + +impl TryFrom for ConsensusResponse { + type Error = Error; + fn try_from(req: Response) -> Result { + match req { + Response::InitChain(x) => Ok(Self::InitChain(x)), + Response::BeginBlock(x) => Ok(Self::BeginBlock(x)), + Response::DeliverTx(x) => Ok(Self::DeliverTx(x)), + Response::EndBlock(x) => Ok(Self::EndBlock(x)), + Response::Commit(x) => Ok(Self::Commit(x)), + _ => Err(Error::invalid_abci_response_type()), + } + } +} + +/// The mempool category of ABCI responses. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum MempoolResponse { + #[doc = include_str!("doc/response-checktx.md")] + CheckTx(CheckTx), +} + +impl From for Response { + fn from(req: MempoolResponse) -> Self { + match req { + MempoolResponse::CheckTx(x) => Self::CheckTx(x), + } + } +} + +impl TryFrom for MempoolResponse { + type Error = Error; + fn try_from(req: Response) -> Result { + match req { + Response::CheckTx(x) => Ok(Self::CheckTx(x)), + _ => Err(Error::invalid_abci_response_type()), + } + } +} + +/// The info category of ABCI responses. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum InfoResponse { + #[doc = include_str!("doc/response-echo.md")] + Echo(Echo), + #[doc = include_str!("doc/response-info.md")] + Info(Info), + #[doc = include_str!("doc/response-query.md")] + Query(Query), + #[doc = include_str!("doc/response-setoption.md")] + SetOption(SetOption), +} + +impl From for Response { + fn from(req: InfoResponse) -> Self { + match req { + InfoResponse::Echo(x) => Self::Echo(x), + InfoResponse::Info(x) => Self::Info(x), + InfoResponse::Query(x) => Self::Query(x), + InfoResponse::SetOption(x) => Self::SetOption(x), + } + } +} + +impl TryFrom for InfoResponse { + type Error = Error; + fn try_from(req: Response) -> Result { + match req { + Response::Echo(x) => Ok(Self::Echo(x)), + Response::Info(x) => Ok(Self::Info(x)), + Response::Query(x) => Ok(Self::Query(x)), + Response::SetOption(x) => Ok(Self::SetOption(x)), + _ => Err(Error::invalid_abci_response_type()), + } + } +} + +/// The snapshot category of ABCI responses. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum SnapshotResponse { + #[doc = include_str!("doc/response-listsnapshots.md")] + ListSnapshots(ListSnapshots), + #[doc = include_str!("doc/response-offersnapshot.md")] + OfferSnapshot(OfferSnapshot), + #[doc = include_str!("doc/response-loadsnapshotchunk.md")] + LoadSnapshotChunk(LoadSnapshotChunk), + #[doc = include_str!("doc/response-applysnapshotchunk.md")] + ApplySnapshotChunk(ApplySnapshotChunk), +} + +impl From for Response { + fn from(req: SnapshotResponse) -> Self { + match req { + SnapshotResponse::ListSnapshots(x) => Self::ListSnapshots(x), + SnapshotResponse::OfferSnapshot(x) => Self::OfferSnapshot(x), + SnapshotResponse::LoadSnapshotChunk(x) => Self::LoadSnapshotChunk(x), + SnapshotResponse::ApplySnapshotChunk(x) => Self::ApplySnapshotChunk(x), + } + } +} + +impl TryFrom for SnapshotResponse { + type Error = Error; + fn try_from(req: Response) -> Result { + match req { + Response::ListSnapshots(x) => Ok(Self::ListSnapshots(x)), + Response::OfferSnapshot(x) => Ok(Self::OfferSnapshot(x)), + Response::LoadSnapshotChunk(x) => Ok(Self::LoadSnapshotChunk(x)), + Response::ApplySnapshotChunk(x) => Ok(Self::ApplySnapshotChunk(x)), + _ => Err(Error::invalid_abci_response_type()), + } + } +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::Response { + fn from(response: Response) -> pb::Response { + use pb::response::Value; + let value = match response { + Response::Exception(x) => Some(Value::Exception(x.into())), + Response::Echo(x) => Some(Value::Echo(x.into())), + Response::Flush => Some(Value::Flush(Default::default())), + Response::Info(x) => Some(Value::Info(x.into())), + Response::SetOption(x) => Some(Value::SetOption(x.into())), + Response::InitChain(x) => Some(Value::InitChain(x.into())), + Response::Query(x) => Some(Value::Query(x.into())), + Response::BeginBlock(x) => Some(Value::BeginBlock(x.into())), + Response::CheckTx(x) => Some(Value::CheckTx(x.into())), + Response::DeliverTx(x) => Some(Value::DeliverTx(x.into())), + Response::EndBlock(x) => Some(Value::EndBlock(x.into())), + Response::Commit(x) => Some(Value::Commit(x.into())), + Response::ListSnapshots(x) => Some(Value::ListSnapshots(x.into())), + Response::OfferSnapshot(x) => Some(Value::OfferSnapshot(x.into())), + Response::LoadSnapshotChunk(x) => Some(Value::LoadSnapshotChunk(x.into())), + Response::ApplySnapshotChunk(x) => Some(Value::ApplySnapshotChunk(x.into())), + }; + pb::Response { value } + } +} + +impl TryFrom for Response { + type Error = Error; + + fn try_from(response: pb::Response) -> Result { + use pb::response::Value; + match response.value { + Some(Value::Exception(x)) => Ok(Response::Exception(x.try_into()?)), + Some(Value::Echo(x)) => Ok(Response::Echo(x.try_into()?)), + Some(Value::Flush(_)) => Ok(Response::Flush), + Some(Value::Info(x)) => Ok(Response::Info(x.try_into()?)), + Some(Value::SetOption(x)) => Ok(Response::SetOption(x.try_into()?)), + Some(Value::InitChain(x)) => Ok(Response::InitChain(x.try_into()?)), + Some(Value::Query(x)) => Ok(Response::Query(x.try_into()?)), + Some(Value::BeginBlock(x)) => Ok(Response::BeginBlock(x.try_into()?)), + Some(Value::CheckTx(x)) => Ok(Response::CheckTx(x.try_into()?)), + Some(Value::DeliverTx(x)) => Ok(Response::DeliverTx(x.try_into()?)), + Some(Value::EndBlock(x)) => Ok(Response::EndBlock(x.try_into()?)), + Some(Value::Commit(x)) => Ok(Response::Commit(x.try_into()?)), + Some(Value::ListSnapshots(x)) => Ok(Response::ListSnapshots(x.try_into()?)), + Some(Value::OfferSnapshot(x)) => Ok(Response::OfferSnapshot(x.try_into()?)), + Some(Value::LoadSnapshotChunk(x)) => Ok(Response::LoadSnapshotChunk(x.try_into()?)), + Some(Value::ApplySnapshotChunk(x)) => Ok(Response::ApplySnapshotChunk(x.try_into()?)), + None => Err(crate::Error::missing_data()), + } + } +} + +impl Protobuf for Response {} diff --git a/tendermint/src/abci/response/apply_snapshot_chunk.rs b/tendermint/src/abci/response/apply_snapshot_chunk.rs new file mode 100644 index 000000000..b3502450a --- /dev/null +++ b/tendermint/src/abci/response/apply_snapshot_chunk.rs @@ -0,0 +1,88 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/response-applysnapshotchunk.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct ApplySnapshotChunk { + /// The result of applying the snapshot chunk. + pub result: ApplySnapshotChunkResult, + /// Refetch and reapply the given chunks, regardless of `result`. + /// + /// Only the listed chunks will be refetched, and reapplied in sequential + /// order. + pub refetch_chunks: Vec, + /// Reject the given P2P senders, regardless of `result`. + /// + /// Any chunks already applied will not be refetched unless explicitly + /// requested, but queued chunks from these senders will be discarded, and + /// new chunks or other snapshots rejected. + pub reject_senders: Vec, +} + +/// The result of applying a snapshot chunk. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum ApplySnapshotChunkResult { + /// Unknown result, abort all snapshot restoration. + Unknown = 0, + /// The chunk was accepted. + Accept = 1, + /// Abort snapshot restoration, and don't try any other snapshots. + Abort = 2, + /// Reapply this chunk, combine with + /// [`refetch_chunks`](ApplySnapshotChunk::refetch_chunks) and + /// [`reject_senders`](ApplySnapshotChunk::reject_senders) as appropriate. + Retry = 3, + /// Restart this snapshot from + /// [`OfferSnapshot`](super::super::request::OfferSnapshot), + /// reusing chunks unless instructed otherwise. + RetrySnapshot = 4, + /// Reject this snapshot, try a different one. + RejectSnapshot = 5, +} + +impl Default for ApplySnapshotChunkResult { + fn default() -> Self { + Self::Unknown + } +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseApplySnapshotChunk { + fn from(apply_snapshot_chunk: ApplySnapshotChunk) -> Self { + Self { + result: apply_snapshot_chunk.result as i32, + refetch_chunks: apply_snapshot_chunk.refetch_chunks, + reject_senders: apply_snapshot_chunk.reject_senders, + } + } +} + +impl TryFrom for ApplySnapshotChunk { + type Error = crate::Error; + + fn try_from(apply_snapshot_chunk: pb::ResponseApplySnapshotChunk) -> Result { + let result = match apply_snapshot_chunk.result { + 0 => ApplySnapshotChunkResult::Unknown, + 1 => ApplySnapshotChunkResult::Accept, + 2 => ApplySnapshotChunkResult::Abort, + 3 => ApplySnapshotChunkResult::Retry, + 4 => ApplySnapshotChunkResult::RetrySnapshot, + 5 => ApplySnapshotChunkResult::RejectSnapshot, + _ => return Err(crate::Error::unsupported_apply_snapshot_chunk_result()), + }; + Ok(Self { + result, + refetch_chunks: apply_snapshot_chunk.refetch_chunks, + reject_senders: apply_snapshot_chunk.reject_senders, + }) + } +} + +impl Protobuf for ApplySnapshotChunk {} diff --git a/tendermint/src/abci/response/begin_block.rs b/tendermint/src/abci/response/begin_block.rs new file mode 100644 index 000000000..b576629bc --- /dev/null +++ b/tendermint/src/abci/response/begin_block.rs @@ -0,0 +1,41 @@ +use super::super::Event; +use crate::prelude::*; + +#[doc = include_str!("../doc/response-beginblock.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct BeginBlock { + /// Events that occurred while beginning the block. + pub events: Vec, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseBeginBlock { + fn from(begin_block: BeginBlock) -> Self { + Self { + events: begin_block.events.into_iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for BeginBlock { + type Error = crate::Error; + + fn try_from(begin_block: pb::ResponseBeginBlock) -> Result { + Ok(Self { + events: begin_block + .events + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +impl Protobuf for BeginBlock {} diff --git a/tendermint/src/abci/response/check_tx.rs b/tendermint/src/abci/response/check_tx.rs new file mode 100644 index 000000000..0a1897fcb --- /dev/null +++ b/tendermint/src/abci/response/check_tx.rs @@ -0,0 +1,91 @@ +use bytes::Bytes; + +use super::super::Event; +use crate::prelude::*; + +#[doc = include_str!("../doc/response-checktx.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct CheckTx { + /// The response code. + /// + /// Transactions where `code != 0` will be rejected; these transactions will + /// not be broadcast to other nodes or included in a proposal block. + /// Tendermint attributes no other value to the response code. + pub code: u32, + /// Result bytes, if any. + pub data: Bytes, + /// The output of the application's logger. + /// + /// **May be non-deterministic**. + pub log: String, + /// Additional information. + /// + /// **May be non-deterministic**. + pub info: String, + /// Amount of gas requested for the transaction. + pub gas_wanted: i64, + /// Amount of gas consumed by the transaction. + pub gas_used: i64, + /// Events that occurred while checking the transaction. + pub events: Vec, + /// The namespace for the `code`. + pub codespace: String, + /// The transaction's sender (e.g. the signer). + pub sender: String, + /// The transaction's priority (for mempool ordering). + pub priority: i64, + // mempool_error is contained in the proto, but skipped here: + // > mempool_error is set by Tendermint. + // > ABCI applictions creating a ResponseCheckTX should not set mempool_error. +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseCheckTx { + fn from(check_tx: CheckTx) -> Self { + Self { + code: check_tx.code, + data: check_tx.data, + log: check_tx.log, + info: check_tx.info, + gas_wanted: check_tx.gas_wanted, + gas_used: check_tx.gas_used, + events: check_tx.events.into_iter().map(Into::into).collect(), + codespace: check_tx.codespace, + sender: check_tx.sender, + priority: check_tx.priority, + mempool_error: String::default(), + } + } +} + +impl TryFrom for CheckTx { + type Error = crate::Error; + + fn try_from(check_tx: pb::ResponseCheckTx) -> Result { + Ok(Self { + code: check_tx.code, + data: check_tx.data, + log: check_tx.log, + info: check_tx.info, + gas_wanted: check_tx.gas_wanted, + gas_used: check_tx.gas_used, + events: check_tx + .events + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + codespace: check_tx.codespace, + sender: check_tx.sender, + priority: check_tx.priority, + }) + } +} + +impl Protobuf for CheckTx {} diff --git a/tendermint/src/abci/response/commit.rs b/tendermint/src/abci/response/commit.rs new file mode 100644 index 000000000..5d6180da6 --- /dev/null +++ b/tendermint/src/abci/response/commit.rs @@ -0,0 +1,45 @@ +use bytes::Bytes; + +use crate::{block, prelude::*}; + +#[doc = include_str!("../doc/response-commit.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct Commit { + /// The Merkle root hash of the application state + /// + /// XXX(hdevalence) - is this different from an app hash? + /// XXX(hdevalence) - rename to app_hash ? + pub data: Bytes, + /// Blocks below this height may be removed. + pub retain_height: block::Height, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseCommit { + fn from(commit: Commit) -> Self { + Self { + data: commit.data, + retain_height: commit.retain_height.into(), + } + } +} + +impl TryFrom for Commit { + type Error = crate::Error; + + fn try_from(commit: pb::ResponseCommit) -> Result { + Ok(Self { + data: commit.data, + retain_height: commit.retain_height.try_into()?, + }) + } +} + +impl Protobuf for Commit {} diff --git a/tendermint/src/abci/response/deliver_tx.rs b/tendermint/src/abci/response/deliver_tx.rs new file mode 100644 index 000000000..60c41eea6 --- /dev/null +++ b/tendermint/src/abci/response/deliver_tx.rs @@ -0,0 +1,79 @@ +use bytes::Bytes; + +use super::super::Event; +use crate::prelude::*; + +#[doc = include_str!("../doc/response-delivertx.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct DeliverTx { + /// The response code. + /// + /// This code should be `0` only if the transaction is fully valid. However, + /// invalid transactions included in a block will still be executed against + /// the application state. + pub code: u32, + /// Result bytes, if any. + pub data: Bytes, + /// The output of the application's logger. + /// + /// **May be non-deterministic**. + pub log: String, + /// Additional information. + /// + /// **May be non-deterministic**. + pub info: String, + /// Amount of gas requested for the transaction. + pub gas_wanted: i64, + /// Amount of gas consumed by the transaction. + pub gas_used: i64, + /// Events that occurred while executing the transaction. + pub events: Vec, + /// The namespace for the `code`. + pub codespace: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseDeliverTx { + fn from(deliver_tx: DeliverTx) -> Self { + Self { + code: deliver_tx.code, + data: deliver_tx.data, + log: deliver_tx.log, + info: deliver_tx.info, + gas_wanted: deliver_tx.gas_wanted, + gas_used: deliver_tx.gas_used, + events: deliver_tx.events.into_iter().map(Into::into).collect(), + codespace: deliver_tx.codespace, + } + } +} + +impl TryFrom for DeliverTx { + type Error = crate::Error; + + fn try_from(deliver_tx: pb::ResponseDeliverTx) -> Result { + Ok(Self { + code: deliver_tx.code, + data: deliver_tx.data, + log: deliver_tx.log, + info: deliver_tx.info, + gas_wanted: deliver_tx.gas_wanted, + gas_used: deliver_tx.gas_used, + events: deliver_tx + .events + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + codespace: deliver_tx.codespace, + }) + } +} + +impl Protobuf for DeliverTx {} diff --git a/tendermint/src/abci/response/echo.rs b/tendermint/src/abci/response/echo.rs new file mode 100644 index 000000000..7e4777713 --- /dev/null +++ b/tendermint/src/abci/response/echo.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/response-echo.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct Echo { + /// The message sent in the request. + pub message: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseEcho { + fn from(echo: Echo) -> Self { + Self { + message: echo.message, + } + } +} + +impl TryFrom for Echo { + type Error = crate::Error; + + fn try_from(echo: pb::ResponseEcho) -> Result { + Ok(Self { + message: echo.message, + }) + } +} + +impl Protobuf for Echo {} diff --git a/tendermint/src/abci/response/end_block.rs b/tendermint/src/abci/response/end_block.rs new file mode 100644 index 000000000..e59fbd4e8 --- /dev/null +++ b/tendermint/src/abci/response/end_block.rs @@ -0,0 +1,62 @@ +use super::super::{types::ValidatorUpdate, Event}; +use crate::{consensus, prelude::*}; + +#[doc = include_str!("../doc/response-endblock.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct EndBlock { + /// Changes to the validator set, if any. + /// + /// Setting the voting power to 0 removes a validator. + pub validator_updates: Vec, + /// Changes to consensus parameters (optional). + pub consensus_param_updates: Option, + /// Events that occurred while ending the block. + pub events: Vec, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseEndBlock { + fn from(end_block: EndBlock) -> Self { + Self { + validator_updates: end_block + .validator_updates + .into_iter() + .map(Into::into) + .collect(), + consensus_param_updates: end_block.consensus_param_updates.map(Into::into), + events: end_block.events.into_iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for EndBlock { + type Error = crate::Error; + + fn try_from(end_block: pb::ResponseEndBlock) -> Result { + Ok(Self { + validator_updates: end_block + .validator_updates + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + consensus_param_updates: end_block + .consensus_param_updates + .map(TryInto::try_into) + .transpose()?, + events: end_block + .events + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +impl Protobuf for EndBlock {} diff --git a/tendermint/src/abci/response/exception.rs b/tendermint/src/abci/response/exception.rs new file mode 100644 index 000000000..a6e784147 --- /dev/null +++ b/tendermint/src/abci/response/exception.rs @@ -0,0 +1,36 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/response-exception.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Exception { + /// Undocumented. + pub error: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseException { + fn from(exception: Exception) -> Self { + Self { + error: exception.error, + } + } +} + +impl TryFrom for Exception { + type Error = crate::Error; + + fn try_from(exception: pb::ResponseException) -> Result { + Ok(Self { + error: exception.error, + }) + } +} + +impl Protobuf for Exception {} diff --git a/tendermint/src/abci/response/info.rs b/tendermint/src/abci/response/info.rs new file mode 100644 index 000000000..9f90630fc --- /dev/null +++ b/tendermint/src/abci/response/info.rs @@ -0,0 +1,55 @@ +use bytes::Bytes; + +use crate::{block, prelude::*, Error}; + +#[doc = include_str!("../doc/response-info.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Info { + /// Some arbitrary information. + pub data: String, + /// The application software semantic version. + pub version: String, + /// The application protocol version. + pub app_version: u64, + /// The latest block for which the app has called [`Commit`](super::super::Request::Commit). + pub last_block_height: block::Height, + /// The latest result of [`Commit`](super::super::Request::Commit). + // XXX(hdevalence): fix this, should be apphash? + pub last_block_app_hash: Bytes, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseInfo { + fn from(info: Info) -> Self { + Self { + data: info.data, + version: info.version, + app_version: info.app_version, + last_block_height: info.last_block_height.into(), + last_block_app_hash: info.last_block_app_hash, + } + } +} + +impl TryFrom for Info { + type Error = Error; + + fn try_from(info: pb::ResponseInfo) -> Result { + Ok(Self { + data: info.data, + version: info.version, + app_version: info.app_version, + last_block_height: info.last_block_height.try_into()?, + last_block_app_hash: info.last_block_app_hash, + }) + } +} + +impl Protobuf for Info {} diff --git a/tendermint/src/abci/response/init_chain.rs b/tendermint/src/abci/response/init_chain.rs new file mode 100644 index 000000000..9e5c3eae3 --- /dev/null +++ b/tendermint/src/abci/response/init_chain.rs @@ -0,0 +1,61 @@ +use bytes::Bytes; + +use super::super::types::ValidatorUpdate; +use crate::{consensus, prelude::*}; + +#[doc = include_str!("../doc/response-initchain.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct InitChain { + /// Initial consensus-critical parameters (optional). + pub consensus_params: Option, + /// Initial validator set (optional). + /// + /// If this list is empty, the initial validator set will be the one given in + /// [`request::InitChain::validators`](super::super::request::InitChain::validators). + /// + /// If this list is nonempty, it will be the initial validator set, instead + /// of the one given in + /// [`request::InitChain::validators`](super::super::request::InitChain::validators). + pub validators: Vec, + /// Initial application hash. + pub app_hash: Bytes, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseInitChain { + fn from(init_chain: InitChain) -> Self { + Self { + consensus_params: init_chain.consensus_params.map(Into::into), + validators: init_chain.validators.into_iter().map(Into::into).collect(), + app_hash: init_chain.app_hash, + } + } +} + +impl TryFrom for InitChain { + type Error = crate::Error; + + fn try_from(init_chain: pb::ResponseInitChain) -> Result { + Ok(Self { + consensus_params: init_chain + .consensus_params + .map(TryInto::try_into) + .transpose()?, + validators: init_chain + .validators + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + app_hash: init_chain.app_hash, + }) + } +} + +impl Protobuf for InitChain {} diff --git a/tendermint/src/abci/response/list_snapshots.rs b/tendermint/src/abci/response/list_snapshots.rs new file mode 100644 index 000000000..55a7a5f24 --- /dev/null +++ b/tendermint/src/abci/response/list_snapshots.rs @@ -0,0 +1,45 @@ +use super::super::types::Snapshot; +use crate::prelude::*; + +#[doc = include_str!("../doc/response-listsnapshots.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct ListSnapshots { + /// A list of local state snapshots. + pub snapshots: Vec, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseListSnapshots { + fn from(list_snapshots: ListSnapshots) -> Self { + Self { + snapshots: list_snapshots + .snapshots + .into_iter() + .map(Into::into) + .collect(), + } + } +} + +impl TryFrom for ListSnapshots { + type Error = crate::Error; + + fn try_from(list_snapshots: pb::ResponseListSnapshots) -> Result { + Ok(Self { + snapshots: list_snapshots + .snapshots + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +impl Protobuf for ListSnapshots {} diff --git a/tendermint/src/abci/response/load_snapshot_chunk.rs b/tendermint/src/abci/response/load_snapshot_chunk.rs new file mode 100644 index 000000000..fb8f2c129 --- /dev/null +++ b/tendermint/src/abci/response/load_snapshot_chunk.rs @@ -0,0 +1,41 @@ +use bytes::Bytes; + +use crate::prelude::*; + +#[doc = include_str!("../doc/response-loadsnapshotchunk.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct LoadSnapshotChunk { + /// The binary chunk contents, in an arbitrary format. + /// + /// Chunk messages cannot be larger than 16MB *including metadata*, so 10MB + /// is a good starting point. + pub chunk: Bytes, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseLoadSnapshotChunk { + fn from(load_snapshot_chunk: LoadSnapshotChunk) -> Self { + Self { + chunk: load_snapshot_chunk.chunk, + } + } +} + +impl TryFrom for LoadSnapshotChunk { + type Error = crate::Error; + + fn try_from(load_snapshot_chunk: pb::ResponseLoadSnapshotChunk) -> Result { + Ok(Self { + chunk: load_snapshot_chunk.chunk, + }) + } +} + +impl Protobuf for LoadSnapshotChunk {} diff --git a/tendermint/src/abci/response/offer_snapshot.rs b/tendermint/src/abci/response/offer_snapshot.rs new file mode 100644 index 000000000..47ee38861 --- /dev/null +++ b/tendermint/src/abci/response/offer_snapshot.rs @@ -0,0 +1,62 @@ +// bring into scope for doc links +#[allow(unused)] +use super::super::types::Snapshot; +use crate::prelude::*; + +#[doc = include_str!("../doc/response-offersnapshot.md")] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum OfferSnapshot { + /// Unknown result, abort all snapshot restoration + Unknown = 0, + /// Snapshot accepted, apply chunks + Accept = 1, + /// Abort all snapshot restoration + Abort = 2, + /// Reject this specific snapshot, try others + Reject = 3, + /// Reject all snapshots of this format, try others + RejectFormat = 4, + /// Reject all snapshots from the sender(s), try others + RejectSender = 5, +} + +impl Default for OfferSnapshot { + fn default() -> Self { + Self::Unknown + } +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseOfferSnapshot { + fn from(offer_snapshot: OfferSnapshot) -> Self { + Self { + result: offer_snapshot as i32, + } + } +} + +impl TryFrom for OfferSnapshot { + type Error = crate::Error; + + fn try_from(offer_snapshot: pb::ResponseOfferSnapshot) -> Result { + Ok(match offer_snapshot.result { + 0 => OfferSnapshot::Unknown, + 1 => OfferSnapshot::Accept, + 2 => OfferSnapshot::Abort, + 3 => OfferSnapshot::Reject, + 4 => OfferSnapshot::RejectFormat, + 5 => OfferSnapshot::RejectSender, + _ => return Err(crate::Error::unsupported_offer_snapshot_chunk_result()), + }) + } +} + +impl Protobuf for OfferSnapshot {} diff --git a/tendermint/src/abci/response/query.rs b/tendermint/src/abci/response/query.rs new file mode 100644 index 000000000..2d8279c2d --- /dev/null +++ b/tendermint/src/abci/response/query.rs @@ -0,0 +1,81 @@ +use bytes::Bytes; + +/// XXX(hdevalence): hide merkle::proof and re-export its contents from merkle? +use crate::merkle::proof as merkle; +use crate::{block, prelude::*}; + +#[doc = include_str!("../doc/response-query.md")] +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct Query { + /// The response code for the query. + pub code: u32, + /// The output of the application's logger. + /// + /// **May be non-deterministic**. + pub log: String, + /// Additional information. + /// + /// **May be non-deterministic**. + pub info: String, + /// The index of the key in the tree. + pub index: i64, + /// The key of the matching data. + pub key: Bytes, + /// The value of the matching data. + pub value: Bytes, + /// Serialized proof for the value data, if requested, to be verified against + /// the app hash for the given `height`. + pub proof: Option, + /// The block height from which data was derived. + /// + /// Note that this is the height of the block containing the application's + /// Merkle root hash, which represents the state as it was after committing + /// the block at `height - 1`. + pub height: block::Height, + /// The namespace for the `code`. + pub codespace: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::{TryFrom, TryInto}; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseQuery { + fn from(query: Query) -> Self { + Self { + code: query.code, + log: query.log, + info: query.info, + index: query.index, + key: query.key, + value: query.value, + proof_ops: query.proof.map(Into::into), + height: query.height.into(), + codespace: query.codespace, + } + } +} + +impl TryFrom for Query { + type Error = crate::Error; + + fn try_from(query: pb::ResponseQuery) -> Result { + Ok(Self { + code: query.code, + log: query.log, + info: query.info, + index: query.index, + key: query.key, + value: query.value, + proof: query.proof_ops.map(TryInto::try_into).transpose()?, + height: query.height.try_into()?, + codespace: query.codespace, + }) + } +} + +impl Protobuf for Query {} diff --git a/tendermint/src/abci/response/set_option.rs b/tendermint/src/abci/response/set_option.rs new file mode 100644 index 000000000..ce15ede66 --- /dev/null +++ b/tendermint/src/abci/response/set_option.rs @@ -0,0 +1,41 @@ +use crate::prelude::*; + +#[doc = include_str!("../doc/response-setoption.md")] +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct SetOption { + pub code: u32, + pub log: String, + pub info: String, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use core::convert::TryFrom; + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::ResponseSetOption { + fn from(message: SetOption) -> Self { + Self { + code: message.code, + log: message.log, + info: message.info, + } + } +} + +impl TryFrom for SetOption { + type Error = crate::Error; + + fn try_from(message: pb::ResponseSetOption) -> Result { + Ok(Self { + code: message.code, + log: message.log, + info: message.info, + }) + } +} + +impl Protobuf for SetOption {} diff --git a/tendermint/src/abci/responses.rs b/tendermint/src/abci/responses.rs deleted file mode 100644 index cca348473..000000000 --- a/tendermint/src/abci/responses.rs +++ /dev/null @@ -1,147 +0,0 @@ -//! ABCI response types used by the `/block_results` RPC endpoint. - -use core::fmt::{self, Display}; - -use serde::{Deserialize, Deserializer, Serialize}; - -use super::{code::Code, data::Data, gas::Gas, info::Info, log::Log, tag::Tag}; -use crate::{consensus, prelude::*, serializers, validator}; - -/// Responses for ABCI calls which occur during block processing. -/// -/// Returned from the `/block_results` RPC endpoint. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Responses { - /// Deliver TX response. - // TODO(tarcieri): remove the `alias` attribute when this lands upstream: - // - #[serde(alias = "DeliverTx")] - #[serde(default, deserialize_with = "deserialize_deliver_tx")] - pub deliver_tx: Vec, - - /// Begin block response. - // TODO(tarcieri): remove the `alias` attribute when this lands upstream: - // - #[serde(alias = "BeginBlock")] - pub begin_block: Option, - - /// End block response. - // TODO(tarcieri): remove the `alias` attribute when this lands upstream: - // - #[serde(alias = "EndBlock")] - pub end_block: Option, -} - -/// Return an empty vec in the event `deliver_tx` is `null` -fn deserialize_deliver_tx<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - Ok(Option::deserialize(deserializer)?.unwrap_or_default()) -} - -/// Deliver TX response. -/// -/// This type corresponds to the `ResponseDeliverTx` proto from: -/// -/// -// TODO(tarcieri): generate this automatically from the proto -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct DeliverTx { - /// ABCI application response code - pub code: Code, - - /// ABCI application data - #[serde(with = "serializers::nullable")] - pub data: Data, - - /// ABCI log data (nondeterministic) - pub log: Log, - - /// ABCI info (nondeterministic) - pub info: Info, - - /// Amount of gas wanted - #[serde(default)] - pub gas_wanted: Gas, - - /// Amount of gas used - #[serde(default)] - pub gas_used: Gas, - - /// Events - pub events: Vec, - - /// Codespace - pub codespace: Codespace, -} - -/// Event -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct Event { - /// Event type - #[serde(rename = "type")] - pub type_str: String, - - /// Attributes - pub attributes: Vec, -} - -/// Begin block response. -/// -/// This type corresponds to the `ResponseBeginBlock` proto from: -/// -/// -// TODO(tarcieri): generate this automatically from the proto -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct BeginBlock { - /// Tags - #[serde(default)] - pub tags: Vec, -} - -/// End block response. -/// -/// This type corresponds to the `ResponseEndBlock` proto from: -/// -/// -// TODO(tarcieri): generate this automatically from the proto -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct EndBlock { - /// Validator updates - #[serde(deserialize_with = "deserialize_validator_updates")] - pub validator_updates: Vec, - - /// New consensus params - pub consensus_param_updates: Option, - - /// Tags - #[serde(default)] - pub tags: Vec, -} - -/// Return an empty vec in the event `validator_updates` is `null` -pub fn deserialize_validator_updates<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - Ok(Option::deserialize(deserializer)?.unwrap_or_default()) -} - -/// Codespace -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct Codespace(String); - -impl AsRef for Codespace { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl Display for Codespace { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} diff --git a/tendermint/src/abci/tag.rs b/tendermint/src/abci/tag.rs deleted file mode 100644 index feb840675..000000000 --- a/tendermint/src/abci/tag.rs +++ /dev/null @@ -1,91 +0,0 @@ -//! Tags - -use core::{fmt, str::FromStr}; - -use serde::{Deserialize, Serialize}; -use tendermint_proto::serializers::bytes::base64string; - -use crate::{error::Error, prelude::*}; - -/// Tags -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct Tag { - /// Key - pub key: Key, - - /// Value - pub value: Value, -} - -/// Tag keys -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] -pub struct Key( - #[serde( - serialize_with = "base64string::serialize", - deserialize_with = "base64string::deserialize_to_string" - )] - String, -); - -impl AsRef for Key { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl FromStr for Key { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(Key(s.into())) - } -} - -impl fmt::Display for Key { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) - } -} - -/// Tag values -#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct Value( - #[serde( - serialize_with = "base64string::serialize", - deserialize_with = "base64string::deserialize_to_string" - )] - String, -); - -impl AsRef for Value { - fn as_ref(&self) -> &str { - self.0.as_ref() - } -} - -impl FromStr for Value { - type Err = Error; - - fn from_str(s: &str) -> Result { - Ok(Value(s.into())) - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", &self.0) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn tag_serde() { - let json = r#"{"key": "cGFja2V0X3RpbWVvdXRfaGVpZ2h0", "value": "MC00ODQw"}"#; - let tag: Tag = serde_json::from_str(json).unwrap(); - assert_eq!("packet_timeout_height", tag.key.0); - assert_eq!("0-4840", tag.value.0); - } -} diff --git a/tendermint/src/abci/transaction.rs b/tendermint/src/abci/transaction.rs deleted file mode 100644 index 38123a24f..000000000 --- a/tendermint/src/abci/transaction.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! Transactions - -mod hash; - -use core::{fmt, slice}; - -use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use subtle_encoding::base64; -use tendermint_proto::types::Data as RawData; - -pub use self::hash::{Hash, LENGTH as HASH_LENGTH}; -use crate::prelude::*; - -/// Transactions are arbitrary byte arrays whose contents are validated by the -/// underlying Tendermint application. -#[derive(Clone, Debug, Eq, PartialEq)] // Custom serde serialization used by RPC /broadcast_tx_async endpoint -pub struct Transaction(Vec); - -impl From> for Transaction { - fn from(value: Vec) -> Self { - Transaction(value) - } -} - -impl From for Vec { - fn from(value: Transaction) -> Self { - value.0 - } -} - -impl Transaction { - /// Borrow the contents of this transaction as a byte slice - pub fn as_bytes(&self) -> &[u8] { - self.0.as_slice() - } -} - -impl AsRef<[u8]> for Transaction { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl fmt::UpperHex for Transaction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in self.as_bytes() { - write!(f, "{:02X}", byte)?; - } - - Ok(()) - } -} - -impl<'de> Deserialize<'de> for Transaction { - fn deserialize>(deserializer: D) -> Result { - let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) - .map_err(|e| D::Error::custom(format!("{}", e)))?; - - Ok(Self::from(bytes)) - } -} - -impl Serialize for Transaction { - fn serialize(&self, serializer: S) -> Result { - String::from_utf8(base64::encode(self.as_bytes())) - .unwrap() - .serialize(serializer) - } -} - -/// Transaction data is a wrapper for a list of transactions, where -/// transactions are arbitrary byte arrays. -/// -/// -#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] -#[serde(try_from = "RawData", into = "RawData")] -pub struct Data { - txs: Option>, -} - -impl From for Data { - fn from(value: RawData) -> Self { - if value.txs.is_empty() { - return Data::default(); - } - Data { - txs: Some( - value - .txs - .iter() - .map(|tx| Transaction::from(tx.clone())) - .collect(), - ), - } - } -} - -impl From for RawData { - fn from(value: Data) -> Self { - if value.txs.is_none() { - return RawData { txs: vec![] }; - } - RawData { - txs: value - .txs - .unwrap_or_default() - .iter() - .map(|tx| tx.clone().into()) - .collect(), - } - } -} - -impl Data { - /// Iterate over the transactions in the collection - pub fn iter(&self) -> slice::Iter<'_, Transaction> { - self.as_ref().iter() - } -} - -impl AsRef<[Transaction]> for Data { - fn as_ref(&self) -> &[Transaction] { - self.txs.as_deref().unwrap_or(&[]) - } -} - -#[cfg(test)] -mod tests { - use super::Transaction; - use crate::prelude::*; - - #[test] - fn upper_hex_serialization() { - let tx = Transaction::from(vec![0xFF, 0x01, 0xFE, 0x02]); - let tx_hex = format!("{:X}", &tx); - assert_eq!(&tx_hex, "FF01FE02"); - } -} diff --git a/tendermint/src/abci/transaction/hash.rs b/tendermint/src/abci/transaction/hash.rs deleted file mode 100644 index 030547e4f..000000000 --- a/tendermint/src/abci/transaction/hash.rs +++ /dev/null @@ -1,116 +0,0 @@ -//! Transaction hashes - -use core::{ - fmt::{self, Debug, Display}, - str::FromStr, -}; - -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; -use subtle::{self, ConstantTimeEq}; -use subtle_encoding::hex; - -use crate::{error::Error, prelude::*}; - -/// Size of a transaction hash in bytes -pub const LENGTH: usize = 32; - -/// Transaction hashes -#[derive(Copy, Clone, Eq)] -pub struct Hash([u8; LENGTH]); - -impl Hash { - /// Create a new transaction hash from raw bytes - pub fn new(bytes: [u8; LENGTH]) -> Hash { - Hash(bytes) - } - - /// Borrow the transaction hash as a byte slice - pub fn as_bytes(&self) -> &[u8] { - &self.0[..] - } -} - -impl AsRef<[u8]> for Hash { - fn as_ref(&self) -> &[u8] { - self.as_bytes() - } -} - -impl ConstantTimeEq for Hash { - #[inline] - fn ct_eq(&self, other: &Hash) -> subtle::Choice { - self.as_bytes().ct_eq(other.as_bytes()) - } -} - -impl core::hash::Hash for Hash { - fn hash(&self, hasher: &mut H) - where - H: core::hash::Hasher, - { - self.0.hash(hasher) - } -} - -impl PartialEq for Hash { - fn eq(&self, other: &Hash) -> bool { - self.ct_eq(other).into() - } -} - -impl Display for Hash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - for byte in &self.0 { - write!(f, "{:02X}", byte)?; - } - Ok(()) - } -} - -impl Debug for Hash { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "transaction::Hash({})", self) - } -} - -/// Decode transaction hash from hex -impl FromStr for Hash { - type Err = Error; - - fn from_str(s: &str) -> Result { - // Accept either upper or lower case hex - let bytes = hex::decode_upper(s) - .or_else(|_| hex::decode(s)) - .map_err(Error::subtle_encoding)?; - - if bytes.len() != LENGTH { - return Err(Error::invalid_hash_size()); - } - - let mut result_bytes = [0u8; LENGTH]; - result_bytes.copy_from_slice(&bytes); - Ok(Hash(result_bytes)) - } -} - -impl<'de> Deserialize<'de> for Hash { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - Self::from_str(&s).map_err(|_| { - de::Error::custom(format!( - "expected {}-character hex string, got {:?}", - LENGTH * 2, - s - )) - }) - } -} - -impl Serialize for Hash { - fn serialize(&self, serializer: S) -> Result { - self.to_string().serialize(serializer) - } -} diff --git a/tendermint/src/abci/types.rs b/tendermint/src/abci/types.rs new file mode 100644 index 000000000..4df1a95e7 --- /dev/null +++ b/tendermint/src/abci/types.rs @@ -0,0 +1,309 @@ +//! ABCI-specific data types used in requests and responses. +//! +//! These types have changes from the core data structures to better accomodate +//! ABCI applications. +//! +//! [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#data-types) + +use core::convert::{TryFrom, TryInto}; + +use bytes::Bytes; +use chrono::{DateTime, Utc}; + +use crate::{block, prelude::*, vote, Error, PublicKey}; + +/// A validator address with voting power. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#validator) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Validator { + /// The validator's address (the first 20 bytes of `SHA256(public_key)`). + pub address: [u8; 20], + /// The voting power of the validator. + pub power: vote::Power, +} + +/// A change to the validator set. +/// +/// Used to inform Tendermint of changes to the validator set. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#validatorupdate) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct ValidatorUpdate { + /// The validator's public key. + pub pub_key: PublicKey, + /// The validator's voting power. + pub power: vote::Power, +} + +/// Information about a whether a validator signed the last block. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#voteinfo) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct VoteInfo { + /// Identifies the validator. + pub validator: Validator, + /// Whether or not the validator signed the last block. + pub signed_last_block: bool, +} + +/// The possible kinds of [`Evidence`]. +/// +/// Note: the +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#evidencetype-2) +/// calls this `EvidenceType`, but we follow the Rust convention and name it `EvidenceKind` +/// to avoid confusion with Rust types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum EvidenceKind { + /// Unknown evidence type (proto default value). + Unknown = 0, + /// Evidence that the validator voted for two different blocks in the same + /// round of the same height. + DuplicateVote = 1, + /// Evidence that a validator attacked a light client. + LightClientAttack = 2, +} + +/// Evidence of validator misbehavior. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#evidence) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Evidence { + /// The kind of evidence. + /// + /// Note: this field is called `type` in the protobuf, but we call it `kind` + /// to avoid the Rust keyword. + pub kind: EvidenceKind, + /// The offending validator. + pub validator: Validator, + /// The height when the offense occurred. + pub height: block::Height, + /// The corresponding time when the offense occurred. + pub time: DateTime, + /// Total voting power of the validator set at `height`. + /// + /// This is included in case the ABCI application does not store historical + /// validators, cf. + /// [#4581](https://github.com/tendermint/tendermint/issues/4581) + pub total_voting_power: vote::Power, +} + +/// Information on the last block commit. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#lastcommitinfo) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct LastCommitInfo { + /// The commit round. + /// + /// Reflects the total number of rounds it took to come to consensus for the + /// current block. + pub round: block::Round, + /// The list of validator addresses in the last validator set, with their + /// voting power and whether or not they signed a vote. + pub votes: Vec, +} + +/// Used for state sync snapshots. +/// +/// When sent across the network, a `Snapshot` can be at most 4 MB. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#snapshot) +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct Snapshot { + /// The height at which the snapshot was taken + pub height: block::Height, + /// The application-specific snapshot format identifier. + /// + /// This allows applications to version their snapshot data format and make + /// backwards-incompatible changes. Tendermint does not interpret this field. + pub format: u32, + /// The number of chunks in the snapshot. Must be at least 1. + pub chunks: u32, + /// An arbitrary snapshot hash. + /// + /// This hash must be equal only for identical snapshots across nodes. + /// Tendermint does not interpret the hash, only compares it with other + /// hashes. + pub hash: Bytes, + /// Arbitrary application metadata, e.g., chunk hashes or other verification data. + pub metadata: Bytes, +} + +// ============================================================================= +// Protobuf conversions +// ============================================================================= + +use tendermint_proto::{abci as pb, Protobuf}; + +impl From for pb::Validator { + fn from(v: Validator) -> Self { + Self { + address: Bytes::copy_from_slice(&v.address[..]), + power: v.power.into(), + } + } +} + +impl TryFrom for Validator { + type Error = Error; + + fn try_from(vu: pb::Validator) -> Result { + let address = if vu.address.len() == 20 { + let mut bytes = [0u8; 20]; + bytes.copy_from_slice(&vu.address); + bytes + } else { + return Err(Error::invalid_account_id_length()); + }; + + Ok(Self { + address, + power: vu.power.try_into()?, + }) + } +} + +impl Protobuf for Validator {} + +impl From for pb::ValidatorUpdate { + fn from(vu: ValidatorUpdate) -> Self { + Self { + pub_key: Some(vu.pub_key.into()), + power: vu.power.into(), + } + } +} + +impl TryFrom for ValidatorUpdate { + type Error = Error; + + fn try_from(vu: pb::ValidatorUpdate) -> Result { + Ok(Self { + pub_key: vu + .pub_key + .ok_or_else(Error::missing_public_key)? + .try_into()?, + power: vu.power.try_into()?, + }) + } +} + +impl Protobuf for ValidatorUpdate {} + +impl From for pb::VoteInfo { + fn from(vi: VoteInfo) -> Self { + Self { + validator: Some(vi.validator.into()), + signed_last_block: vi.signed_last_block, + } + } +} + +impl TryFrom for VoteInfo { + type Error = Error; + + fn try_from(vi: pb::VoteInfo) -> Result { + Ok(Self { + validator: vi + .validator + .ok_or_else(Error::missing_validator)? + .try_into()?, + signed_last_block: vi.signed_last_block, + }) + } +} + +impl Protobuf for VoteInfo {} + +impl From for pb::Evidence { + fn from(evidence: Evidence) -> Self { + Self { + r#type: evidence.kind as i32, + validator: Some(evidence.validator.into()), + height: evidence.height.into(), + time: Some(evidence.time.into()), + total_voting_power: evidence.total_voting_power.into(), + } + } +} + +impl TryFrom for Evidence { + type Error = Error; + + fn try_from(evidence: pb::Evidence) -> Result { + let kind = match evidence.r#type { + 0 => EvidenceKind::Unknown, + 1 => EvidenceKind::DuplicateVote, + 2 => EvidenceKind::LightClientAttack, + _ => return Err(Error::invalid_evidence()), + }; + + Ok(Self { + kind, + validator: evidence + .validator + .ok_or_else(Error::missing_validator)? + .try_into()?, + height: evidence.height.try_into()?, + time: evidence.time.ok_or_else(Error::missing_timestamp)?.into(), + total_voting_power: evidence.total_voting_power.try_into()?, + }) + } +} + +impl Protobuf for Evidence {} + +impl From for pb::LastCommitInfo { + fn from(lci: LastCommitInfo) -> Self { + Self { + round: lci.round.into(), + votes: lci.votes.into_iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for LastCommitInfo { + type Error = Error; + + fn try_from(lci: pb::LastCommitInfo) -> Result { + Ok(Self { + round: lci.round.try_into()?, + votes: lci + .votes + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + }) + } +} + +impl Protobuf for LastCommitInfo {} + +impl From for pb::Snapshot { + fn from(snapshot: Snapshot) -> Self { + Self { + height: snapshot.height.into(), + format: snapshot.format, + chunks: snapshot.chunks, + hash: snapshot.hash, + metadata: snapshot.metadata, + } + } +} + +impl TryFrom for Snapshot { + type Error = Error; + + fn try_from(snapshot: pb::Snapshot) -> Result { + Ok(Self { + height: snapshot.height.try_into()?, + format: snapshot.format, + chunks: snapshot.chunks, + hash: snapshot.hash, + metadata: snapshot.metadata, + }) + } +} + +impl Protobuf for Snapshot {} diff --git a/tendermint/src/block.rs b/tendermint/src/block.rs index 5c29e8440..0f961c05c 100644 --- a/tendermint/src/block.rs +++ b/tendermint/src/block.rs @@ -26,7 +26,7 @@ pub use self::{ round::*, size::Size, }; -use crate::{abci::transaction, error::Error, evidence, prelude::*}; +use crate::{error::Error, evidence, prelude::*}; /// Blocks consist of a header, transactions, votes (the commit), and a list of /// evidence of malfeasance (i.e. signing conflicting votes). @@ -35,18 +35,18 @@ use crate::{abci::transaction, error::Error, evidence, prelude::*}; // Default serialization - all fields serialize; used by /block endpoint #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] #[non_exhaustive] +#[serde(try_from = "RawBlock", into = "RawBlock")] pub struct Block { /// Block header pub header: Header, /// Transaction data - pub data: transaction::Data, + pub data: Vec>, /// Evidence of malfeasance pub evidence: evidence::Data, /// Last commit - #[serde(with = "crate::serializers::optional")] pub last_commit: Option, } @@ -75,7 +75,7 @@ impl TryFrom for Block { //} Ok(Block { header, - data: value.data.ok_or_else(Error::missing_data)?.into(), + data: value.data.ok_or_else(Error::missing_data)?.txs, evidence: value .evidence .ok_or_else(Error::missing_evidence)? @@ -87,9 +87,10 @@ impl TryFrom for Block { impl From for RawBlock { fn from(value: Block) -> Self { + use tendermint_proto::types::Data as RawData; RawBlock { header: Some(value.header.into()), - data: Some(value.data.into()), + data: Some(RawData { txs: value.data }), evidence: Some(value.evidence.into()), last_commit: value.last_commit.map(Into::into), } @@ -100,7 +101,7 @@ impl Block { /// constructor pub fn new( header: Header, - data: transaction::Data, + data: Vec>, evidence: evidence::Data, last_commit: Option, ) -> Result { @@ -128,7 +129,7 @@ impl Block { } /// Get data - pub fn data(&self) -> &transaction::Data { + pub fn data(&self) -> &Vec> { &self.data } diff --git a/tendermint/src/block/size.rs b/tendermint/src/block/size.rs index 2e9c32c50..093cd580d 100644 --- a/tendermint/src/block/size.rs +++ b/tendermint/src/block/size.rs @@ -3,7 +3,7 @@ use core::convert::{TryFrom, TryInto}; use serde::{Deserialize, Serialize}; -use tendermint_proto::{abci::BlockParams as RawSize, Protobuf}; +use tendermint_proto::{abci::BlockParams as RawAbciSize, types::BlockParams as RawSize, Protobuf}; use crate::{error::Error, serializers}; @@ -42,7 +42,7 @@ impl TryFrom for Size { .try_into() .map_err(Error::integer_overflow)?, max_gas: value.max_gas, - time_iota_ms: Self::default_time_iota_ms(), + time_iota_ms: value.time_iota_ms, }) } } @@ -53,6 +53,34 @@ impl From for RawSize { RawSize { max_bytes: value.max_bytes as i64, max_gas: value.max_gas, + time_iota_ms: value.time_iota_ms, + } + } +} + +impl Protobuf for Size {} + +impl TryFrom for Size { + type Error = Error; + + fn try_from(value: RawAbciSize) -> Result { + Ok(Self { + max_bytes: value + .max_bytes + .try_into() + .map_err(Error::integer_overflow)?, + max_gas: value.max_gas, + time_iota_ms: Self::default_time_iota_ms(), + }) + } +} + +impl From for RawAbciSize { + fn from(value: Size) -> Self { + // Todo: make the struct more robust so this can become infallible. + RawAbciSize { + max_bytes: value.max_bytes as i64, + max_gas: value.max_gas, } } } diff --git a/tendermint/src/consensus/params.rs b/tendermint/src/consensus/params.rs index b0b969c3a..e03031ec4 100644 --- a/tendermint/src/consensus/params.rs +++ b/tendermint/src/consensus/params.rs @@ -4,25 +4,28 @@ use core::convert::{TryFrom, TryInto}; use serde::{Deserialize, Serialize}; use tendermint_proto::{ - abci::ConsensusParams as RawParams, - types::{ValidatorParams as RawValidatorParams, VersionParams as RawVersionParams}, + abci::ConsensusParams as RawAbciParams, + types::{ + ConsensusParams as RawParams, ValidatorParams as RawValidatorParams, + VersionParams as RawVersionParams, + }, Protobuf, }; use crate::{block, error::Error, evidence, prelude::*, public_key}; -/// Tendermint consensus parameters +/// All consensus-relevant parameters that can be adjusted by the ABCI app. +/// +/// [ABCI documentation](https://docs.tendermint.com/master/spec/abci/abci.html#consensusparams) #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct Params { - /// Block size parameters + /// Parameters limiting the size of a block and time between consecutive blocks. pub block: block::Size, - - /// Evidence parameters + /// Parameters limiting the validity of evidence of byzantine behaviour. pub evidence: evidence::Params, - - /// Validator parameters + /// Parameters limiting the types of public keys validators can use. pub validator: ValidatorParams, - + /// The ABCI application version. /// Version parameters #[serde(skip)] // Todo: FIXME kvstore /genesis returns '{}' instead of '{app_version: "0"}' pub version: Option, @@ -63,10 +66,47 @@ impl From for RawParams { } } -/// Validator consensus parameters +impl Protobuf for Params {} + +impl TryFrom for Params { + type Error = Error; + + fn try_from(value: RawAbciParams) -> Result { + Ok(Self { + block: value + .block + .ok_or_else(|| Error::invalid_block("missing block".to_string()))? + .try_into()?, + evidence: value + .evidence + .ok_or_else(Error::invalid_evidence)? + .try_into()?, + validator: value + .validator + .ok_or_else(Error::invalid_validator_params)? + .try_into()?, + version: value.version.map(TryFrom::try_from).transpose()?, + }) + } +} + +impl From for RawAbciParams { + fn from(value: Params) -> Self { + RawAbciParams { + block: Some(value.block.into()), + evidence: Some(value.evidence.into()), + validator: Some(value.validator.into()), + version: value.version.map(From::from), + } + } +} + +/// ValidatorParams restrict the public key types validators can use. +/// +/// [Tendermint documentation](https://docs.tendermint.com/master/spec/core/data_structures.html#validatorparams) #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct ValidatorParams { - /// Allowed algorithms for validator signing + /// List of accepted public key types. pub pub_key_types: Vec, } @@ -109,10 +149,13 @@ impl From for RawValidatorParams { } /// Version Parameters +/// +/// [Tendermint documentation](https://docs.tendermint.com/master/spec/core/data_structures.html#versionparams) #[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, Default)] pub struct VersionParams { + /// The ABCI application version. #[serde(with = "crate::serializers::from_str")] - app_version: u64, + pub app_version: u64, } impl Protobuf for VersionParams {} diff --git a/tendermint/src/error.rs b/tendermint/src/error.rs index 1d9f563e2..2d6e6cf80 100644 --- a/tendermint/src/error.rs +++ b/tendermint/src/error.rs @@ -112,6 +112,27 @@ define_error! { MissingTimestamp |_| { format_args!("missing timestamp field") }, + MissingVersion + |_| { format_args!("missing version") }, + + MissingMaxAgeDuration + |_| { format_args!("missing max_age_duration") }, + + MissingPublicKey + |_| { format_args!("missing public key") }, + + MissingValidator + |_| { format_args!("missing validator") }, + + MissingLastCommitInfo + |_| { format_args!("missing last commit info") }, + + MissingGenesisTime + |_| { format_args!("missing genesis time") }, + + MissingConsensusParams + |_| { format_args!("missing consensus params") }, + InvalidTimestamp { reason: String } | e | { format_args!("invalid timestamp: {}", e.reason) }, @@ -120,9 +141,6 @@ define_error! { { reason: String } | e | { format_args!("invalid block: {}", e.reason) }, - MissingVersion - |_| { format_args!("missing version") }, - InvalidFirstHeader |_| { format_args!("last_block_id is not null on first height") }, @@ -139,6 +157,18 @@ define_error! { InvalidEvidence |_| { format_args!("invalid evidence") }, + InvalidValidatorParams + |_| { format_args!("invalid validator parameters") }, + + InvalidVersionParams + |_| { format_args!("invalid version parameters") }, + + InvalidAbciRequestType + |_| { format_args!("invalid ABCI request type") }, + + InvalidAbciResponseType + |_| { format_args!("invalid ABCI response type") }, + BlockIdFlag |_| { format_args!("invalid block id flag") }, @@ -149,26 +179,23 @@ define_error! { UnsupportedKeyType |_| { format_args!("unsupported key type" ) }, - RawVotingPowerMismatch - { raw: vote::Power, computed: vote::Power } - |e| { format_args!("mismatch between raw voting ({0:?}) and computed one ({1:?})", e.raw, e.computed) }, + UnsupportedCheckTxType + |_| { format_args!("unsupported CheckTx type" ) }, - MissingPublicKey - |_| { format_args!("missing public key") }, + UnsupportedApplySnapshotChunkResult + |_| { format_args!("unsupported ApplySnapshotChunkResult type" ) }, - InvalidValidatorParams - |_| { format_args!("invalid validator parameters") }, + UnsupportedOfferSnapshotChunkResult + |_| { format_args!("unsupported OfferSnapshotChunkResult type" ) }, - InvalidVersionParams - |_| { format_args!("invalid version parameters") }, + RawVotingPowerMismatch + { raw: vote::Power, computed: vote::Power } + |e| { format_args!("mismatch between raw voting ({0:?}) and computed one ({1:?})", e.raw, e.computed) }, NegativeMaxAgeNum [ DisplayOnly ] |_| { format_args!("negative max_age_num_blocks") }, - MissingMaxAgeDuration - |_| { format_args!("missing max_age_duration") }, - ProposerNotFound { account: account::Id } |e| { format_args!("proposer with address '{0}' no found in validator set", e.account) }, @@ -195,3 +222,9 @@ define_error! { |_| { "trust threshold too small (must be >= 1/3)" }, } } + +impl From for Error { + fn from(_never: core::convert::Infallible) -> Error { + unreachable!("Infallible can never be constructed") + } +} diff --git a/tendermint/src/evidence.rs b/tendermint/src/evidence.rs index f2dfa64e5..0594347d5 100644 --- a/tendermint/src/evidence.rs +++ b/tendermint/src/evidence.rs @@ -214,19 +214,28 @@ impl AsRef<[Evidence]> for Data { } } -/// Evidence collection parameters +/// EvidenceParams determine how we handle evidence of malfeasance. +/// +/// [Tendermint documentation](https://docs.tendermint.com/master/spec/core/data_structures.html#evidenceparams) #[derive(Deserialize, Serialize, Clone, Debug, Eq, PartialEq)] // Todo: This struct is ready to be converted through tendermint_proto::types::EvidenceParams. // https://github.com/informalsystems/tendermint-rs/issues/741 pub struct Params { - /// Maximum allowed age for evidence to be collected + /// Max age of evidence, in blocks. #[serde(with = "serializers::from_str")] pub max_age_num_blocks: u64, - /// Max age duration + /// Max age of evidence, in time. + /// + /// It should correspond with an app's "unbonding period" or other similar + /// mechanism for handling [Nothing-At-Stake attacks][nas]. + /// + /// [nas]: https://github.com/ethereum/wiki/wiki/Proof-of-Stake-FAQ#what-is-the-nothing-at-stake-problem-and-how-can-it-be-fixed pub max_age_duration: Duration, - /// Max bytes + /// This sets the maximum size of total evidence in bytes that can be + /// committed in a single block, and should fall comfortably under the max + /// block bytes. The default is 1048576 or 1MB. #[serde(with = "serializers::from_str", default)] pub max_bytes: i64, } diff --git a/tendermint/src/hash.rs b/tendermint/src/hash.rs index b6f39026c..17de5c533 100644 --- a/tendermint/src/hash.rs +++ b/tendermint/src/hash.rs @@ -233,7 +233,11 @@ impl AsRef<[u8]> for AppHash { impl Debug for AppHash { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "hash::AppHash({:?})", self.0) + write!( + f, + "AppHash({})", + Hex::upper_case().encode_to_string(&self.0).unwrap() + ) } } diff --git a/tendermint/src/prelude.rs b/tendermint/src/prelude.rs index a5b91aa13..aa9a20b65 100644 --- a/tendermint/src/prelude.rs +++ b/tendermint/src/prelude.rs @@ -1,7 +1,7 @@ // Re-export according to alloc::prelude::v1 because it is not yet stabilized // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html -pub use alloc::borrow::ToOwned; pub use alloc::{ + borrow::ToOwned, boxed::Box, format, string::{String, ToString}, From 8bc6a97e02dacc04778461a56dcc7d176defe97e Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 13:47:36 -0400 Subject: [PATCH 04/25] split A.3, abci-in-rpc --- light-client-verifier/src/prelude.rs | 2 +- light-client/src/evidence.rs | 2 +- light-client/src/tests.rs | 2 +- rpc/Cargo.toml | 1 + rpc/src/abci.rs | 34 ++++++ rpc/src/abci/code.rs | 108 +++++++++++++++++ rpc/src/abci/data.rs | 60 +++++++++ rpc/src/abci/gas.rs | 71 +++++++++++ rpc/src/abci/info.rs | 21 ++++ rpc/src/abci/log.rs | 35 ++++++ rpc/src/abci/path.rs | 29 +++++ rpc/src/abci/responses.rs | 148 +++++++++++++++++++++++ rpc/src/abci/tag.rs | 66 ++++++++++ rpc/src/abci/transaction.rs | 138 +++++++++++++++++++++ rpc/src/abci/transaction/hash.rs | 117 ++++++++++++++++++ rpc/src/client.rs | 8 +- rpc/src/client/bin/main.rs | 7 +- rpc/src/endpoint/abci_info.rs | 3 +- rpc/src/endpoint/abci_query.rs | 10 +- rpc/src/endpoint/block_results.rs | 4 +- rpc/src/endpoint/broadcast/tx_async.rs | 3 +- rpc/src/endpoint/broadcast/tx_commit.rs | 8 +- rpc/src/endpoint/broadcast/tx_sync.rs | 3 +- rpc/src/endpoint/evidence.rs | 4 +- rpc/src/endpoint/net_info.rs | 2 +- rpc/src/endpoint/tx.rs | 4 +- rpc/src/event.rs | 19 +-- rpc/src/lib.rs | 1 + rpc/src/prelude.rs | 2 +- rpc/tests/kvstore_fixtures.rs | 49 ++++---- tools/abci-test/src/main.rs | 8 +- tools/kvstore-test/tests/light-client.rs | 6 +- tools/kvstore-test/tests/tendermint.rs | 20 +-- 33 files changed, 917 insertions(+), 78 deletions(-) create mode 100644 rpc/src/abci.rs create mode 100644 rpc/src/abci/code.rs create mode 100644 rpc/src/abci/data.rs create mode 100644 rpc/src/abci/gas.rs create mode 100644 rpc/src/abci/info.rs create mode 100644 rpc/src/abci/log.rs create mode 100644 rpc/src/abci/path.rs create mode 100644 rpc/src/abci/responses.rs create mode 100644 rpc/src/abci/tag.rs create mode 100644 rpc/src/abci/transaction.rs create mode 100644 rpc/src/abci/transaction/hash.rs diff --git a/light-client-verifier/src/prelude.rs b/light-client-verifier/src/prelude.rs index 2aca81d15..57a0cedc3 100644 --- a/light-client-verifier/src/prelude.rs +++ b/light-client-verifier/src/prelude.rs @@ -1,7 +1,7 @@ // Re-export according to alloc::prelude::v1 because it is not yet stabilized // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html -pub use alloc::borrow::ToOwned; pub use alloc::{ + borrow::ToOwned, boxed::Box, format, string::{String, ToString}, diff --git a/light-client/src/evidence.rs b/light-client/src/evidence.rs index 60f76271d..df7cddb94 100644 --- a/light-client/src/evidence.rs +++ b/light-client/src/evidence.rs @@ -1,8 +1,8 @@ //! Fork evidence data structures and interfaces. use contracts::contract_trait; -use tendermint::abci::transaction::Hash; pub use tendermint::evidence::Evidence; +use tendermint_rpc::abci::transaction::Hash; use crate::{components::io::IoError, verifier::types::PeerId}; diff --git a/light-client/src/tests.rs b/light-client/src/tests.rs index 4bae5517d..435cc3855 100644 --- a/light-client/src/tests.rs +++ b/light-client/src/tests.rs @@ -5,11 +5,11 @@ use std::{collections::HashMap, time::Duration}; use contracts::contract_trait; use serde::{Deserialize, Serialize}; use tendermint::{ - abci::transaction::Hash, block::Height as HeightStr, evidence::{Duration as DurationStr, Evidence}, }; use tendermint_rpc as rpc; +use tendermint_rpc::abci::transaction::Hash; use crate::{ components::{ diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index be6cfedbb..91b44356e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -78,6 +78,7 @@ subtle-encoding = { version = "0.5", default-features = false, features = ["bech url = { version = "2.2", default-features = false } walkdir = { version = "2.3", default-features = false } flex-error = { version = "0.4.4", default-features = false } +subtle = { version = "2", default-features = false } # Optional dependencies async-trait = { version = "0.1", optional = true, default-features = false } diff --git a/rpc/src/abci.rs b/rpc/src/abci.rs new file mode 100644 index 000000000..2cbb63df4 --- /dev/null +++ b/rpc/src/abci.rs @@ -0,0 +1,34 @@ +//! Old ABCI structures, formerly defined in `tendermint::abci`. +//! +//! The original contents of `tendermint::abci` were created only to model RPC +//! responses, not to model ABCI itself: +//! +//! > NOTE: This module contains types for ABCI responses as consumed from RPC +//! endpoints. It does not contain an ABCI protocol implementation. +//! +//! The old types should be eliminated and +//! merged with the new ABCI domain types. Moving them here in the meantime +//! disentangles improving the ABCI domain modeling from changes to the RPC +//! interface. + +mod code; +mod data; +mod gas; +mod info; +mod log; +mod path; + +pub mod responses; +pub mod tag; +pub mod transaction; + +pub use self::{ + code::Code, + data::Data, + gas::Gas, + info::Info, + log::Log, + path::Path, + responses::{DeliverTx, Event, Responses}, + transaction::Transaction, +}; diff --git a/rpc/src/abci/code.rs b/rpc/src/abci/code.rs new file mode 100644 index 000000000..81c379570 --- /dev/null +++ b/rpc/src/abci/code.rs @@ -0,0 +1,108 @@ +use core::{fmt, num::NonZeroU32}; + +use serde::{ + de::{Deserialize, Deserializer, Visitor}, + Serialize, Serializer, +}; + +/// ABCI application response codes. +/// +/// These presently use 0 for success and non-zero for errors: +/// +/// +/// +/// Note that in the future there may potentially be non-zero success codes. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Code { + /// Success + Ok, + + /// Error codes + Err(NonZeroU32), +} + +impl Default for Code { + fn default() -> Code { + Code::Ok + } +} + +impl Code { + /// Was the response OK? + pub fn is_ok(self) -> bool { + match self { + Code::Ok => true, + Code::Err(_) => false, + } + } + + /// Was the response an error? + pub fn is_err(self) -> bool { + !self.is_ok() + } + + /// Get the integer error value for this code + pub fn value(self) -> u32 { + u32::from(self) + } +} + +impl From for Code { + fn from(value: u32) -> Code { + match NonZeroU32::new(value) { + Some(value) => Code::Err(value), + None => Code::Ok, + } + } +} + +impl From for u32 { + fn from(code: Code) -> u32 { + match code { + Code::Ok => 0, + Code::Err(err) => err.get(), + } + } +} + +impl Serialize for Code { + fn serialize(&self, serializer: S) -> Result { + self.value().serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for Code { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct CodeVisitor; + + impl<'de> Visitor<'de> for CodeVisitor { + type Value = Code; + + fn expecting(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str("integer or string") + } + + fn visit_u64(self, val: u64) -> Result + where + E: serde::de::Error, + { + Ok(Code::from(val as u32)) + } + + fn visit_str(self, val: &str) -> Result + where + E: serde::de::Error, + { + match val.parse::() { + Ok(val) => self.visit_u64(val), + Err(_) => Err(E::custom("failed to parse integer")), + } + } + } + + deserializer.deserialize_any(CodeVisitor) + } +} diff --git a/rpc/src/abci/data.rs b/rpc/src/abci/data.rs new file mode 100644 index 000000000..97df5e776 --- /dev/null +++ b/rpc/src/abci/data.rs @@ -0,0 +1,60 @@ +use serde::{Deserialize, Serialize}; + +use crate::prelude::*; + +/// ABCI transaction data. +/// +/// Transactions are opaque binary blobs which are validated according to +/// application-specific rules. +#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +#[serde(transparent)] +pub struct Data(#[serde(with = "tendermint::serializers::bytes::base64string")] Vec); + +impl From> for Data { + fn from(value: Vec) -> Self { + Self(value) + } +} + +impl From for Vec { + fn from(value: Data) -> Self { + value.0 + } +} + +impl Data { + /// Get value + pub fn value(&self) -> &Vec { + &self.0 + } +} + +#[cfg(test)] +mod tests { + use crate::{abci::Data, prelude::*}; + + #[test] + fn test_deserialization() { + let json = "\"ChYKFGNvbm5lY3Rpb25fb3Blbl9pbml0\""; + let mydata: Data = serde_json::from_str(json).unwrap(); + assert_eq!( + mydata.0, + vec![ + // By chance this is a protobuf struct. + 10, // Field 1 is a String + 22, // Field 1 length is 22 + 10, // Sub-field 1 is String + 20, // Sub-field 1 length is 20 + 99, 111, 110, 110, 101, 99, 116, 105, 111, 110, 95, 111, 112, 101, 110, 95, 105, + 110, 105, 116 // "connection_open_init" + ] + ); + } + + #[test] + fn test_serialization() { + let mydata: Data = vec![1, 2, 3, 4].into(); + let json = serde_json::to_string(&mydata).unwrap(); + assert_eq!(json, "\"AQIDBA==\""); + } +} diff --git a/rpc/src/abci/gas.rs b/rpc/src/abci/gas.rs new file mode 100644 index 000000000..433c86613 --- /dev/null +++ b/rpc/src/abci/gas.rs @@ -0,0 +1,71 @@ +//! Gas: abstract representation for the cost of resources used by nodes when +//! processing transactions. +//! +//! For more information, see: +//! +//! + +use core::{ + fmt::{self, Display}, + str::FromStr, +}; + +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use tendermint::error::Error; + +use crate::prelude::*; + +/// Gas: representation of transaction processing resource costs +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] +pub struct Gas(u64); + +impl Gas { + /// Get the inner integer value + pub fn value(self) -> u64 { + self.0 + } +} + +impl From for Gas { + fn from(amount: u64) -> Gas { + Gas(amount) + } +} + +impl From for u64 { + fn from(gas: Gas) -> u64 { + gas.0 + } +} + +impl Display for Gas { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl FromStr for Gas { + type Err = Error; + + fn from_str(s: &str) -> Result { + let res = s + .parse::() + .map_err(|e| Error::parse_int(s.to_string(), e))? + .into(); + + Ok(res) + } +} + +impl<'de> Deserialize<'de> for Gas { + fn deserialize>(deserializer: D) -> Result { + Self::from_str(&String::deserialize(deserializer)?) + .map_err(|e| D::Error::custom(format!("{}", e))) + } +} + +impl Serialize for Gas { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/rpc/src/abci/info.rs b/rpc/src/abci/info.rs new file mode 100644 index 000000000..c024aacd3 --- /dev/null +++ b/rpc/src/abci/info.rs @@ -0,0 +1,21 @@ +use core::fmt::{self, Display}; + +use serde::{Deserialize, Serialize}; + +use crate::prelude::*; + +/// ABCI info +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Info(String); + +impl AsRef for Info { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl Display for Info { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/rpc/src/abci/log.rs b/rpc/src/abci/log.rs new file mode 100644 index 000000000..7668affe3 --- /dev/null +++ b/rpc/src/abci/log.rs @@ -0,0 +1,35 @@ +use core::{fmt, fmt::Display}; + +use serde::{Deserialize, Serialize}; + +use crate::prelude::*; + +/// ABCI log data +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(transparent)] +pub struct Log(String); + +impl Log { + /// Convenience function: get value + pub fn value(&self) -> &String { + &self.0 + } +} + +impl From<&str> for Log { + fn from(s: &str) -> Self { + Log(s.to_owned()) + } +} + +impl AsRef for Log { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl Display for Log { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/rpc/src/abci/path.rs b/rpc/src/abci/path.rs new file mode 100644 index 000000000..040ab26d2 --- /dev/null +++ b/rpc/src/abci/path.rs @@ -0,0 +1,29 @@ +//! Paths to ABCI data + +use core::{ + fmt::{self, Display}, + str::FromStr, +}; + +use serde::{Deserialize, Serialize}; +use tendermint::error::Error; + +use crate::prelude::*; + +/// Path to ABCI data +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct Path(String); + +impl Display for Path { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +impl FromStr for Path { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Path(s.to_owned())) + } +} diff --git a/rpc/src/abci/responses.rs b/rpc/src/abci/responses.rs new file mode 100644 index 000000000..2f5c156ed --- /dev/null +++ b/rpc/src/abci/responses.rs @@ -0,0 +1,148 @@ +//! ABCI response types used by the `/block_results` RPC endpoint. + +use core::fmt::{self, Display}; + +use serde::{Deserialize, Deserializer, Serialize}; +use tendermint::{consensus, serializers, validator}; + +use super::{code::Code, data::Data, gas::Gas, info::Info, log::Log, tag::Tag}; +use crate::prelude::*; + +/// Responses for ABCI calls which occur during block processing. +/// +/// Returned from the `/block_results` RPC endpoint. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Responses { + /// Deliver TX response. + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: + // + #[serde(alias = "DeliverTx")] + #[serde(default, deserialize_with = "deserialize_deliver_tx")] + pub deliver_tx: Vec, + + /// Begin block response. + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: + // + #[serde(alias = "BeginBlock")] + pub begin_block: Option, + + /// End block response. + // TODO(tarcieri): remove the `alias` attribute when this lands upstream: + // + #[serde(alias = "EndBlock")] + pub end_block: Option, +} + +/// Return an empty vec in the event `deliver_tx` is `null` +fn deserialize_deliver_tx<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::deserialize(deserializer)?.unwrap_or_default()) +} + +/// Deliver TX response. +/// +/// This type corresponds to the `ResponseDeliverTx` proto from: +/// +/// +// TODO(tarcieri): generate this automatically from the proto +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct DeliverTx { + /// ABCI application response code + pub code: Code, + + /// ABCI application data + #[serde(with = "serializers::nullable")] + pub data: Data, + + /// ABCI log data (nondeterministic) + pub log: Log, + + /// ABCI info (nondeterministic) + pub info: Info, + + /// Amount of gas wanted + #[serde(default)] + pub gas_wanted: Gas, + + /// Amount of gas used + #[serde(default)] + pub gas_used: Gas, + + /// Events + pub events: Vec, + + /// Codespace + pub codespace: Codespace, +} + +/// Event +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Event { + /// Event type + #[serde(rename = "type")] + pub type_str: String, + + /// Attributes + pub attributes: Vec, +} + +/// Begin block response. +/// +/// This type corresponds to the `ResponseBeginBlock` proto from: +/// +/// +// TODO(tarcieri): generate this automatically from the proto +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct BeginBlock { + /// Tags + #[serde(default)] + pub tags: Vec, +} + +/// End block response. +/// +/// This type corresponds to the `ResponseEndBlock` proto from: +/// +/// +// TODO(tarcieri): generate this automatically from the proto +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct EndBlock { + /// Validator updates + #[serde(deserialize_with = "deserialize_validator_updates")] + pub validator_updates: Vec, + + /// New consensus params + pub consensus_param_updates: Option, + + /// Tags + #[serde(default)] + pub tags: Vec, +} + +/// Return an empty vec in the event `validator_updates` is `null` +pub fn deserialize_validator_updates<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + Ok(Option::deserialize(deserializer)?.unwrap_or_default()) +} + +/// Codespace +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Codespace(String); + +impl AsRef for Codespace { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl Display for Codespace { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} diff --git a/rpc/src/abci/tag.rs b/rpc/src/abci/tag.rs new file mode 100644 index 000000000..c10383890 --- /dev/null +++ b/rpc/src/abci/tag.rs @@ -0,0 +1,66 @@ +//! Tags + +use core::{fmt, str::FromStr}; + +use serde::{Deserialize, Serialize}; +use tendermint::error::Error; + +use crate::prelude::*; + +/// Tags +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct Tag { + /// Key + pub key: Key, + + /// Value + pub value: Value, +} + +/// Tag keys +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] +pub struct Key(String); + +impl AsRef for Key { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl FromStr for Key { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Key(s.into())) + } +} + +impl fmt::Display for Key { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} + +/// Tag values +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +pub struct Value(String); + +impl AsRef for Value { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + +impl FromStr for Value { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Value(s.into())) + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", &self.0) + } +} diff --git a/rpc/src/abci/transaction.rs b/rpc/src/abci/transaction.rs new file mode 100644 index 000000000..38123a24f --- /dev/null +++ b/rpc/src/abci/transaction.rs @@ -0,0 +1,138 @@ +//! Transactions + +mod hash; + +use core::{fmt, slice}; + +use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; +use subtle_encoding::base64; +use tendermint_proto::types::Data as RawData; + +pub use self::hash::{Hash, LENGTH as HASH_LENGTH}; +use crate::prelude::*; + +/// Transactions are arbitrary byte arrays whose contents are validated by the +/// underlying Tendermint application. +#[derive(Clone, Debug, Eq, PartialEq)] // Custom serde serialization used by RPC /broadcast_tx_async endpoint +pub struct Transaction(Vec); + +impl From> for Transaction { + fn from(value: Vec) -> Self { + Transaction(value) + } +} + +impl From for Vec { + fn from(value: Transaction) -> Self { + value.0 + } +} + +impl Transaction { + /// Borrow the contents of this transaction as a byte slice + pub fn as_bytes(&self) -> &[u8] { + self.0.as_slice() + } +} + +impl AsRef<[u8]> for Transaction { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl fmt::UpperHex for Transaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in self.as_bytes() { + write!(f, "{:02X}", byte)?; + } + + Ok(()) + } +} + +impl<'de> Deserialize<'de> for Transaction { + fn deserialize>(deserializer: D) -> Result { + let bytes = base64::decode(String::deserialize(deserializer)?.as_bytes()) + .map_err(|e| D::Error::custom(format!("{}", e)))?; + + Ok(Self::from(bytes)) + } +} + +impl Serialize for Transaction { + fn serialize(&self, serializer: S) -> Result { + String::from_utf8(base64::encode(self.as_bytes())) + .unwrap() + .serialize(serializer) + } +} + +/// Transaction data is a wrapper for a list of transactions, where +/// transactions are arbitrary byte arrays. +/// +/// +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(try_from = "RawData", into = "RawData")] +pub struct Data { + txs: Option>, +} + +impl From for Data { + fn from(value: RawData) -> Self { + if value.txs.is_empty() { + return Data::default(); + } + Data { + txs: Some( + value + .txs + .iter() + .map(|tx| Transaction::from(tx.clone())) + .collect(), + ), + } + } +} + +impl From for RawData { + fn from(value: Data) -> Self { + if value.txs.is_none() { + return RawData { txs: vec![] }; + } + RawData { + txs: value + .txs + .unwrap_or_default() + .iter() + .map(|tx| tx.clone().into()) + .collect(), + } + } +} + +impl Data { + /// Iterate over the transactions in the collection + pub fn iter(&self) -> slice::Iter<'_, Transaction> { + self.as_ref().iter() + } +} + +impl AsRef<[Transaction]> for Data { + fn as_ref(&self) -> &[Transaction] { + self.txs.as_deref().unwrap_or(&[]) + } +} + +#[cfg(test)] +mod tests { + use super::Transaction; + use crate::prelude::*; + + #[test] + fn upper_hex_serialization() { + let tx = Transaction::from(vec![0xFF, 0x01, 0xFE, 0x02]); + let tx_hex = format!("{:X}", &tx); + assert_eq!(&tx_hex, "FF01FE02"); + } +} diff --git a/rpc/src/abci/transaction/hash.rs b/rpc/src/abci/transaction/hash.rs new file mode 100644 index 000000000..54a7a9571 --- /dev/null +++ b/rpc/src/abci/transaction/hash.rs @@ -0,0 +1,117 @@ +//! Transaction hashes + +use core::{ + fmt::{self, Debug, Display}, + str::FromStr, +}; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use subtle::{self, ConstantTimeEq}; +use subtle_encoding::hex; +use tendermint::error::Error; + +use crate::prelude::*; + +/// Size of a transaction hash in bytes +pub const LENGTH: usize = 32; + +/// Transaction hashes +#[derive(Copy, Clone, Eq)] +pub struct Hash([u8; LENGTH]); + +impl Hash { + /// Create a new transaction hash from raw bytes + pub fn new(bytes: [u8; LENGTH]) -> Hash { + Hash(bytes) + } + + /// Borrow the transaction hash as a byte slice + pub fn as_bytes(&self) -> &[u8] { + &self.0[..] + } +} + +impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl ConstantTimeEq for Hash { + #[inline] + fn ct_eq(&self, other: &Hash) -> subtle::Choice { + self.as_bytes().ct_eq(other.as_bytes()) + } +} + +impl core::hash::Hash for Hash { + fn hash(&self, hasher: &mut H) + where + H: core::hash::Hasher, + { + self.0.hash(hasher) + } +} + +impl PartialEq for Hash { + fn eq(&self, other: &Hash) -> bool { + self.ct_eq(other).into() + } +} + +impl Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for byte in &self.0 { + write!(f, "{:02X}", byte)?; + } + Ok(()) + } +} + +impl Debug for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "transaction::Hash({})", self) + } +} + +/// Decode transaction hash from hex +impl FromStr for Hash { + type Err = Error; + + fn from_str(s: &str) -> Result { + // Accept either upper or lower case hex + let bytes = hex::decode_upper(s) + .or_else(|_| hex::decode(s)) + .map_err(Error::subtle_encoding)?; + + if bytes.len() != LENGTH { + return Err(Error::invalid_hash_size()); + } + + let mut result_bytes = [0u8; LENGTH]; + result_bytes.copy_from_slice(&bytes); + Ok(Hash(result_bytes)) + } +} + +impl<'de> Deserialize<'de> for Hash { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Self::from_str(&s).map_err(|_| { + de::Error::custom(format!( + "expected {}-character hex string, got {:?}", + LENGTH * 2, + s + )) + }) + } +} + +impl Serialize for Hash { + fn serialize(&self, serializer: S) -> Result { + self.to_string().serialize(serializer) + } +} diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 9351469ed..c163c6b9f 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -9,12 +9,7 @@ use core::{fmt, time::Duration}; use async_trait::async_trait; use serde::{de::DeserializeOwned, Serialize}; -use tendermint::{ - abci::{self, Transaction}, - block::Height, - evidence::Evidence, - Genesis, -}; +use tendermint::{block::Height, evidence::Evidence, Genesis}; use tokio::time; #[cfg(feature = "http-client")] pub use transport::http::{HttpClient, HttpClientUrl}; @@ -25,6 +20,7 @@ pub use transport::websocket::{ }; use crate::{ + abci::{self, Transaction}, endpoint::{validators::DEFAULT_VALIDATORS_PER_PAGE, *}, paging::Paging, prelude::*, diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs index 7e6733ab0..a38539f7d 100644 --- a/rpc/src/client/bin/main.rs +++ b/rpc/src/client/bin/main.rs @@ -4,10 +4,11 @@ use core::str::FromStr; use futures::StreamExt; use structopt::StructOpt; -use tendermint::abci::{transaction::Hash, Path, Transaction}; use tendermint_rpc::{ - query::Query, Client, Error, HttpClient, Order, Paging, Scheme, Subscription, - SubscriptionClient, Url, WebSocketClient, + abci::{transaction::Hash, Path, Transaction}, + query::Query, + Client, Error, HttpClient, Order, Paging, Scheme, Subscription, SubscriptionClient, Url, + WebSocketClient, }; use tokio::time::Duration; use tracing::{error, info, level_filters::LevelFilter, warn}; diff --git a/rpc/src/endpoint/abci_info.rs b/rpc/src/endpoint/abci_info.rs index 8c5b415b5..d4b9568c2 100644 --- a/rpc/src/endpoint/abci_info.rs +++ b/rpc/src/endpoint/abci_info.rs @@ -2,6 +2,7 @@ use core::convert::{TryFrom, TryInto}; +use bytes::Bytes; use serde::{Deserialize, Serialize}; use tendermint::{block, Error}; use tendermint_proto::abci::ResponseInfo; @@ -48,7 +49,7 @@ pub struct AbciInfo { pub last_block_height: block::Height, /// Last app hash for the block - pub last_block_app_hash: Vec, + pub last_block_app_hash: Bytes, } impl TryFrom for AbciInfo { diff --git a/rpc/src/endpoint/abci_query.rs b/rpc/src/endpoint/abci_query.rs index cf6cfbedc..6577fcef2 100644 --- a/rpc/src/endpoint/abci_query.rs +++ b/rpc/src/endpoint/abci_query.rs @@ -1,15 +1,13 @@ //! `/abci_query` endpoint JSON-RPC wrapper use serde::{Deserialize, Serialize}; -use tendermint::{ +use tendermint::{block, merkle::proof::Proof, serializers}; + +use crate::{ abci::{Code, Log, Path}, - block, - merkle::proof::Proof, - serializers, + prelude::*, }; -use crate::prelude::*; - /// Query the ABCI application for information #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct Request { diff --git a/rpc/src/endpoint/block_results.rs b/rpc/src/endpoint/block_results.rs index 9920f0e38..184346638 100644 --- a/rpc/src/endpoint/block_results.rs +++ b/rpc/src/endpoint/block_results.rs @@ -1,9 +1,9 @@ //! `/block_results` endpoint JSON-RPC wrapper use serde::{Deserialize, Serialize}; -use tendermint::{abci, block, consensus, validator}; +use tendermint::{block, consensus, validator}; -use crate::prelude::*; +use crate::{abci, prelude::*}; /// Get ABCI results at a given height. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/rpc/src/endpoint/broadcast/tx_async.rs b/rpc/src/endpoint/broadcast/tx_async.rs index 2ab35abe7..0de93113e 100644 --- a/rpc/src/endpoint/broadcast/tx_async.rs +++ b/rpc/src/endpoint/broadcast/tx_async.rs @@ -1,7 +1,8 @@ //! `/broadcast_tx_async`: broadcast a transaction and return immediately. use serde::{Deserialize, Serialize}; -use tendermint::abci::{transaction, Code, Data, Log, Transaction}; + +use crate::abci::{transaction, Code, Data, Log, Transaction}; /// `/broadcast_tx_async`: broadcast a transaction and return immediately. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/rpc/src/endpoint/broadcast/tx_commit.rs b/rpc/src/endpoint/broadcast/tx_commit.rs index 35352a972..6af3e82ca 100644 --- a/rpc/src/endpoint/broadcast/tx_commit.rs +++ b/rpc/src/endpoint/broadcast/tx_commit.rs @@ -2,13 +2,13 @@ //! if we timeout waiting for tx to commit. use serde::{Deserialize, Serialize}; -use tendermint::{ +use tendermint::block; + +use crate::{ abci::{responses::Codespace, transaction, Code, Data, Event, Gas, Info, Log, Transaction}, - block, + prelude::*, }; -use crate::prelude::*; - /// `/broadcast_tx_commit`: only returns error if `mempool.CheckTx()` errs or /// if we timeout waiting for tx to commit. /// diff --git a/rpc/src/endpoint/broadcast/tx_sync.rs b/rpc/src/endpoint/broadcast/tx_sync.rs index 82b1e9d0b..e188525a4 100644 --- a/rpc/src/endpoint/broadcast/tx_sync.rs +++ b/rpc/src/endpoint/broadcast/tx_sync.rs @@ -1,7 +1,8 @@ //! `/broadcast_tx_sync`: returns with the response from `CheckTx`. use serde::{Deserialize, Serialize}; -use tendermint::abci::{transaction, Code, Data, Log, Transaction}; + +use crate::abci::{transaction, Code, Data, Log, Transaction}; /// `/broadcast_tx_sync`: returns with the response from `CheckTx`. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/rpc/src/endpoint/evidence.rs b/rpc/src/endpoint/evidence.rs index 9a5f46d9c..ba1bb0e30 100644 --- a/rpc/src/endpoint/evidence.rs +++ b/rpc/src/endpoint/evidence.rs @@ -1,9 +1,9 @@ //! `/broadcast_evidence`: broadcast an evidence. use serde::{Deserialize, Serialize}; -use tendermint::{abci::transaction, evidence::Evidence}; +use tendermint::evidence::Evidence; -use crate::Method; +use crate::{abci::transaction, Method}; /// `/broadcast_evidence`: broadcast an evidence. #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] diff --git a/rpc/src/endpoint/net_info.rs b/rpc/src/endpoint/net_info.rs index da2b56920..7dc158f82 100644 --- a/rpc/src/endpoint/net_info.rs +++ b/rpc/src/endpoint/net_info.rs @@ -39,7 +39,7 @@ pub struct Response { pub n_peers: u64, /// Peer information - pub peers: Vec, + pub peers: Option>, } impl crate::Response for Response {} diff --git a/rpc/src/endpoint/tx.rs b/rpc/src/endpoint/tx.rs index 120d6881b..66aaa1b32 100644 --- a/rpc/src/endpoint/tx.rs +++ b/rpc/src/endpoint/tx.rs @@ -1,10 +1,10 @@ //! `/tx` endpoint JSON-RPC wrapper use serde::{Deserialize, Serialize}; -use tendermint::{abci, block}; +use tendermint::block; use tendermint_proto::types::TxProof; -use crate::Method; +use crate::{abci, Method}; /// Request for finding a transaction by its hash. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/rpc/src/event.rs b/rpc/src/event.rs index 2b2bc03bb..fa5c4fff7 100644 --- a/rpc/src/event.rs +++ b/rpc/src/event.rs @@ -1,15 +1,16 @@ //! RPC subscription event-related data structures. -use alloc::collections::BTreeMap as HashMap; - use serde::{Deserialize, Serialize}; -use tendermint::{ +use tendermint::Block; + +use crate::{ abci::responses::{BeginBlock, EndBlock}, - Block, + prelude::*, + query::EventType, + response::Wrapper, + Response, }; -use crate::{prelude::*, query::EventType, response::Wrapper, Response}; - /// An incoming event produced by a [`Subscription`]. /// /// [`Subscription`]: ../struct.Subscription.html @@ -19,8 +20,8 @@ pub struct Event { pub query: String, /// The data associated with the event. pub data: EventData, - /// Event type and attributes map. - pub events: Option>>, + /// Event type and attributes list. + pub events: Option>, } impl Response for Event {} @@ -76,5 +77,5 @@ pub struct TxResult { pub log: Option, pub gas_wanted: Option, pub gas_used: Option, - pub events: Vec, + pub events: Vec, } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index bd9e0eb82..1c7371edb 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -44,6 +44,7 @@ pub use client::{HttpClient, HttpClientUrl}; #[cfg(feature = "websocket-client")] pub use client::{WebSocketClient, WebSocketClientDriver, WebSocketClientUrl, WebSocketConfig}; +pub mod abci; pub mod endpoint; pub mod error; pub mod event; diff --git a/rpc/src/prelude.rs b/rpc/src/prelude.rs index a5b91aa13..aa9a20b65 100644 --- a/rpc/src/prelude.rs +++ b/rpc/src/prelude.rs @@ -1,7 +1,7 @@ // Re-export according to alloc::prelude::v1 because it is not yet stabilized // https://doc.rust-lang.org/src/alloc/prelude/v1.rs.html -pub use alloc::borrow::ToOwned; pub use alloc::{ + borrow::ToOwned, boxed::Box, format, string::{String, ToString}, diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index ba0f3f536..ed867763c 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -4,9 +4,10 @@ use core::str::FromStr; use std::{fs, path::PathBuf}; use subtle_encoding::{base64, hex}; -use tendermint::{abci::transaction::Hash, evidence::Duration, public_key}; +use tendermint::{evidence::Duration, public_key}; use tendermint_config::net::Address; use tendermint_rpc::{ + abci::transaction::Hash, endpoint, error::{Error, ErrorDetail}, request::Wrapper as RequestWrapper, @@ -549,21 +550,21 @@ fn incoming_fixtures() { }, "broadcast_tx_async" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, "broadcast_tx_commit" => { let result = endpoint::broadcast::tx_commit::Response::from_string(content).unwrap(); - assert_eq!(result.check_tx.code, tendermint::abci::Code::Ok); + assert_eq!(result.check_tx.code, tendermint_rpc::abci::Code::Ok); assert_eq!( result.check_tx.codespace, - tendermint::abci::responses::Codespace::default() + tendermint_rpc::abci::responses::Codespace::default() ); assert!(result.check_tx.data.is_none()); assert!(result.check_tx.events.is_empty()); @@ -572,10 +573,10 @@ fn incoming_fixtures() { // assert_eq!(result.check_tx.gas_wanted.value(), 1); assert!(result.check_tx.info.to_string().is_empty()); assert!(result.check_tx.log.value().is_empty()); - assert_eq!(result.deliver_tx.code, tendermint::abci::Code::Ok); + assert_eq!(result.deliver_tx.code, tendermint_rpc::abci::Code::Ok); assert_eq!( result.deliver_tx.codespace, - tendermint::abci::responses::Codespace::default() + tendermint_rpc::abci::responses::Codespace::default() ); assert!(result.deliver_tx.data.is_none()); assert_eq!(result.deliver_tx.events.len(), 1); @@ -643,16 +644,16 @@ fn incoming_fixtures() { assert!(result.deliver_tx.log.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); }, "broadcast_tx_sync" => { let result = endpoint::broadcast::tx_sync::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, @@ -1354,61 +1355,61 @@ fn incoming_fixtures() { }, "subscribe_txs_broadcast_tx_0" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, "subscribe_txs_broadcast_tx_1" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, "subscribe_txs_broadcast_tx_2" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, "subscribe_txs_broadcast_tx_3" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, "subscribe_txs_broadcast_tx_4" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, "subscribe_txs_broadcast_tx_5" => { let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap(); - assert_eq!(result.code, tendermint::abci::Code::Ok); + assert_eq!(result.code, tendermint_rpc::abci::Code::Ok); assert!(result.data.value().is_empty()); assert_ne!( result.hash, - tendermint::abci::transaction::Hash::new([0; 32]) + tendermint_rpc::abci::transaction::Hash::new([0; 32]) ); assert!(result.log.value().is_empty()); }, @@ -1429,7 +1430,7 @@ fn incoming_fixtures() { // Test a few selected attributes of the results. for tx in result.txs { assert_ne!(tx.hash.as_bytes(), [0; 32]); - assert_eq!(tx.tx_result.code, tendermint::abci::Code::Ok); + assert_eq!(tx.tx_result.code, tendermint_rpc::abci::Code::Ok); assert_eq!(tx.tx_result.events.len(), 1); assert_eq!(tx.tx_result.events[0].type_str, "app"); assert_eq!(tx.tx_result.gas_used.value(), 0); @@ -1445,7 +1446,7 @@ fn incoming_fixtures() { // Test a few selected attributes of the results. for tx in result.txs { assert_ne!(tx.hash.as_bytes(), [0; 32]); - assert_eq!(tx.tx_result.code, tendermint::abci::Code::Ok); + assert_eq!(tx.tx_result.code, tendermint_rpc::abci::Code::Ok); assert_eq!(tx.tx_result.events.len(), 1); assert_eq!(tx.tx_result.events[0].type_str, "app"); assert_eq!(tx.tx_result.gas_used.value(), 0); diff --git a/tools/abci-test/src/main.rs b/tools/abci-test/src/main.rs index eae6abe32..8469f5172 100644 --- a/tools/abci-test/src/main.rs +++ b/tools/abci-test/src/main.rs @@ -2,11 +2,11 @@ use futures::StreamExt; use structopt::StructOpt; -use tendermint::abci::Transaction; use tendermint_config::net::Address; -use tendermint_rpc::{ - event::EventData, query::EventType, Client, SubscriptionClient, WebSocketClient, -}; +use tendermint_rpc::abci::Transaction; +use tendermint_rpc::event::EventData; +use tendermint_rpc::query::EventType; +use tendermint_rpc::{Client, SubscriptionClient, WebSocketClient}; use tokio::time::Duration; use tracing::{debug, error, info, level_filters::LevelFilter}; diff --git a/tools/kvstore-test/tests/light-client.rs b/tools/kvstore-test/tests/light-client.rs index 14e25a252..6c784ad31 100644 --- a/tools/kvstore-test/tests/light-client.rs +++ b/tools/kvstore-test/tests/light-client.rs @@ -33,7 +33,11 @@ struct TestEvidenceReporter; #[contracts::contract_trait] impl EvidenceReporter for TestEvidenceReporter { - fn report(&self, evidence: Evidence, peer: PeerId) -> Result { + fn report( + &self, + evidence: Evidence, + peer: PeerId, + ) -> Result { panic!( "unexpected fork detected for peer {} with evidence: {:?}", peer, evidence diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs index 31e7606d9..4c01c375d 100644 --- a/tools/kvstore-test/tests/tendermint.rs +++ b/tools/kvstore-test/tests/tendermint.rs @@ -32,6 +32,17 @@ mod rpc { query::{EventType, Query}, Client, HttpClient, Id, Order, SubscriptionClient, WebSocketClient, WebSocketClientDriver, }; + + use futures::StreamExt; + use std::convert::TryFrom; + use std::sync::atomic::{AtomicU8, Ordering}; + use tendermint::block::Height; + use tendermint::merkle::simple_hash_from_byte_vectors; + use tendermint_rpc::abci::Log; + use tendermint_rpc::abci::{Code, Transaction}; + use tendermint_rpc::endpoint::tx::Response as ResultTx; + use tendermint_rpc::event::{Event, EventData, TxInfo}; + use tendermint_rpc::query::{EventType, Query}; use tokio::time::Duration; static LOGGING_INIT: AtomicU8 = AtomicU8::new(0); @@ -109,12 +120,7 @@ mod rpc { // Check for empty merkle root. // See: https://github.com/informalsystems/tendermint-rs/issues/562 let computed_data_hash = simple_hash_from_byte_vectors( - block_info - .block - .data - .iter() - .map(|t| t.to_owned().into()) - .collect(), + block_info.block.data.iter().map(|t| t.to_owned()).collect(), ); assert_eq!( computed_data_hash, @@ -480,7 +486,7 @@ mod rpc { http_client: &HttpClient, websocket_client: &mut WebSocketClient, tx: Transaction, - ) -> Result<(tendermint::abci::transaction::Hash, TxInfo), tendermint_rpc::Error> { + ) -> Result<(tendermint_rpc::abci::transaction::Hash, TxInfo), tendermint_rpc::Error> { let mut subs = websocket_client.subscribe(EventType::Tx.into()).await?; let r = http_client.broadcast_tx_async(tx.clone()).await?; From 309677517baf3664aff72cd8a51c6b84abe7bdf0 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 15:20:54 -0400 Subject: [PATCH 05/25] clean up imports in abci crate --- abci/src/application/kvstore.rs | 5 +---- abci/src/client.rs | 8 ++++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/abci/src/application/kvstore.rs b/abci/src/application/kvstore.rs index 14af7f335..62c117a44 100644 --- a/abci/src/application/kvstore.rs +++ b/abci/src/application/kvstore.rs @@ -12,10 +12,7 @@ use tendermint_proto::abci::{ }; use tracing::{debug, info}; -use crate::{ - codec::{encode_varint, MAX_VARINT_LENGTH}, - Application, Error, -}; +use crate::{codec::MAX_VARINT_LENGTH, Application, Error}; /// In-memory, hashmap-backed key/value store ABCI application. /// diff --git a/abci/src/client.rs b/abci/src/client.rs index 81446c596..a7a82be44 100644 --- a/abci/src/client.rs +++ b/abci/src/client.rs @@ -6,10 +6,10 @@ use tendermint_proto::abci::{ request, response, Request, RequestApplySnapshotChunk, RequestBeginBlock, RequestCheckTx, RequestCommit, RequestDeliverTx, RequestEcho, RequestEndBlock, RequestFlush, RequestInfo, RequestInitChain, RequestListSnapshots, RequestLoadSnapshotChunk, RequestOfferSnapshot, - RequestQuery, RequestSetOption, ResponseApplySnapshotChunk, ResponseBeginBlock, - ResponseCheckTx, ResponseCommit, ResponseDeliverTx, ResponseEcho, ResponseEndBlock, - ResponseFlush, ResponseInfo, ResponseInitChain, ResponseListSnapshots, - ResponseLoadSnapshotChunk, ResponseOfferSnapshot, ResponseQuery, ResponseSetOption, + RequestQuery, ResponseApplySnapshotChunk, ResponseBeginBlock, ResponseCheckTx, ResponseCommit, + ResponseDeliverTx, ResponseEcho, ResponseEndBlock, ResponseFlush, ResponseInfo, + ResponseInitChain, ResponseListSnapshots, ResponseLoadSnapshotChunk, ResponseOfferSnapshot, + ResponseQuery, }; use crate::{codec::ClientCodec, Error}; From e4039258ce952dd2fdd99c2a4f5f34a1eedd547d Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Wed, 17 Aug 2022 16:30:25 -0700 Subject: [PATCH 06/25] Return to 0.34 ABCI encoding --- abci/src/codec.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/abci/src/codec.rs b/abci/src/codec.rs index 66645a6ae..8e80736dd 100644 --- a/abci/src/codec.rs +++ b/abci/src/codec.rs @@ -130,7 +130,7 @@ where message.encode(&mut buf).map_err(Error::encode)?; let buf = buf.freeze(); - prost::encoding::encode_varint(buf.len() as u64, &mut dst); + encode_varint(buf.len() as u64, &mut dst); dst.put(buf); Ok(()) } @@ -142,11 +142,12 @@ where { let src_len = src.len(); let mut tmp = src.clone().freeze(); - let encoded_len = match prost::encoding::decode_varint(&mut tmp) { + let encoded_len = match decode_varint(&mut tmp) { Ok(len) => len, // We've potentially only received a partial length delimiter Err(_) if src_len <= MAX_VARINT_LENGTH => return Ok(None), - Err(e) => return Err(Error::decode(e)), + //Err(e) => return Err(Error::decode(e)), + Err(e) => return Err(e), }; let remaining = tmp.remaining() as u64; if remaining < encoded_len { @@ -164,3 +165,14 @@ where Ok(Some(res)) } } + +// encode_varint and decode_varint will be removed once +// https://github.com/tendermint/tendermint/issues/5783 lands in Tendermint. +pub fn encode_varint(val: u64, mut buf: &mut B) { + prost::encoding::encode_varint(val << 1, &mut buf); +} + +pub fn decode_varint(mut buf: &mut B) -> Result { + let len = prost::encoding::decode_varint(&mut buf).map_err(Error::decode)?; + Ok(len >> 1) +} From 5de942533d6ff0743b5f37ff9ef5476609c8f3df Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Tue, 7 Dec 2021 18:59:00 +0200 Subject: [PATCH 07/25] Cherry-pick chrono changes Replace chrono with time 0.3 (#1030) * pbt-gen: Converted from chrono to time 0.3 chrono has soundness issues (see RUSTSEC-2020-0159) and does not seem to be maintained. * Replace dependency on chrono with time 0.3 Change Time implementation to crate time: chrono has soundness issues (see RUSTSEC-2020-0159) and does not seem to be actively maintained. * Add Time methods checked_add and checked_sub These should be used instead of the overloaded operators that broke the operator convention by returning a Result. * proto: Don't use formatting methods of time Drop the "formatting" feature of time, as this brings in std. * pbt-gen: Add arb_datetime_for_rfc3339 With crate time, the range of supported dates stretches to the ancient past beyond the common era, which is not representable in RFC 3339. Add a generator to produce `OffsetDateTime` values that are within the RFC 3339 spec. * Ensure Time can only have values good for RFC 3339 As the time values are meant for human-readable representation in the RFC 3339 format, make it not possible to construct Time with values that fall outside this standard representation. Conversion from time::OffsetDateTime is made fallible with a TryFrom impl replacing the From impl. Conversion from Unix timestamps is also affected. * rpc: Replaced chrono with time 0.3 * testgen: Replaced chrono with time 0.3 * Less allocatey ways of formatting date-times Provide and use another helper in proto mod serializers::timestamp, one that formats into a provided fmt::Write object rather than allocating a string. * light-client: port from chrono to time * Revert to Add/Sub returning Result * light-client: changed the MBTs from chrono to time * tendermint: Remove a comment referencing chrono We use ErrorDetail::DurationOutOfRange without the source error from the time library because that is uninformative in the context. Also move the DateOutOfRange close with DurationOutOfRange since they can occur in the same operations and are generally similar. * light-client: add std feature for time The clock needs OffsetDateTime::now_utc(). * tendermint: Simplify Time::duration_since * testgen: minor code cleanup * Restrict valid Time years to 1-9999 Exclude year 0 because the spec on Google protobuf Timestamp forbids it. Add more helpers in pbt-gen to produce protobuf-safe values in both OffsetDateTime and RFC 3339 strings. Restrict the property tests to only use the protobuf-safe values where expected. * Test Time::checked_add and Time::checked_sub * Changelog entries for #1030 * Improve documentation of tendermint::Time * proto: remove the chrono type conversions The From impls are panicky and do not enforce compliance with protobuf message value restrictions. * tendermint: remove direct uses of chrono types Replace with tendermint::Time. * Harden Timestamp conversions and serde Require the timestamp to be in the validity range (years 1-9999 in UTC) and the nanosecond member value to not exceed 999_999_999. Rename ErrorDetail::TimestampOverflow to TimestampNanosOutOfRange, because the old variant was not very informative (the chained TryFromIntError did not help) and we also use it for the above-range case now which is not an overflow. * Changelog entry about changed error variants * Restore nanosecond range check in Time::from_unix_timestamp Add a unit test to exercise the check. * proto: Improve timestamp::fmt_as_rfc3339_nanos - More ergonomic signature - A non-allocating implementation * Fix component name in changelog for 1030-remove-chrono Co-authored-by: Thane Thomson * time: Use Self instead of the type name in methods Co-authored-by: Thane Thomson * Comment on the inner representation of `Time` * Don't alias crate time in testgen * Document the Time::from_utc helper Co-authored-by: Thane Thomson --- .../breaking-changes/1030-remove-chrono.md | 11 ++++ .../improvements/1030-new-time-api.md | 11 ++++ abci/src/codec.rs | 2 +- proto/Cargo.toml | 2 - proto/src/chrono.rs | 53 --------------- proto/src/lib.rs | 1 - tendermint/Cargo.toml | 4 +- tendermint/src/abci/request/init_chain.rs | 5 +- tendermint/src/abci/types.rs | 10 +-- tendermint/src/time.rs | 66 +++++++++---------- 10 files changed, 65 insertions(+), 100 deletions(-) create mode 100644 .changelog/unreleased/breaking-changes/1030-remove-chrono.md create mode 100644 .changelog/unreleased/improvements/1030-new-time-api.md delete mode 100644 proto/src/chrono.rs diff --git a/.changelog/unreleased/breaking-changes/1030-remove-chrono.md b/.changelog/unreleased/breaking-changes/1030-remove-chrono.md new file mode 100644 index 000000000..7d8385ee5 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1030-remove-chrono.md @@ -0,0 +1,11 @@ +- `[tendermint]` Reform `tendermint::Time` + ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)): + * The struct content is made private. + * The range of acceptable values is restricted to years 1-9999 + (as reckoned in UTC). + * Removed conversions from/to `chrono::DateTime`. + * Changes in error variants: removed `TimestampOverflow`, replaced with + `TimestampNanosOutOfRange`; removed `ChronoParse`, replaced with `TimeParse`. +- `[tendermint-rpc]` Use `OffsetDateTime` and `Date` types provided by the `time` crate + in query operands instead of their `chrono` counterparts. + ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)) diff --git a/.changelog/unreleased/improvements/1030-new-time-api.md b/.changelog/unreleased/improvements/1030-new-time-api.md new file mode 100644 index 000000000..a9c269f0d --- /dev/null +++ b/.changelog/unreleased/improvements/1030-new-time-api.md @@ -0,0 +1,11 @@ +- Remove dependencies on the `chrono` crate. + ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)) +- `[tendermint]` Improve `tendermint::Time` + ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)): + * Restrict the validity range of `Time` to dates with years in the range + 1-9999, to match the specification of protobuf message `Timestamp`. + Add an `ErrorDetail` variant `DateOutOfRange` to report when this + restriction is not met. + * Added a conversion to, and a fallible conversion from, + `OffsetDateTime` of the `time` crate. + * Added `Time` methods `checked_add` and `checked_sub`. diff --git a/abci/src/codec.rs b/abci/src/codec.rs index 8e80736dd..d9fd649c6 100644 --- a/abci/src/codec.rs +++ b/abci/src/codec.rs @@ -146,7 +146,7 @@ where Ok(len) => len, // We've potentially only received a partial length delimiter Err(_) if src_len <= MAX_VARINT_LENGTH => return Ok(None), - //Err(e) => return Err(Error::decode(e)), + // Err(e) => return Err(Error::decode(e)), Err(e) => return Err(e), }; let remaining = tmp.remaining() as u64; diff --git a/proto/Cargo.toml b/proto/Cargo.toml index afd8e8023..aab957435 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -28,8 +28,6 @@ num-derive = { version = "0.3", default-features = false } # TODO(thane): Remove restrictions once js-sys issue is resolved (causes the Substrate no_std check to fail) time = { version = ">=0.3, <0.3.12", default-features = false, features = ["macros", "parsing"] } flex-error = { version = "0.4.4", default-features = false } -# TODO(hdevalence): re-added while rebasing the ABCI domain types PR again -chrono = { version = "0.4", default-features = false, features = ["serde", "alloc"] } [dev-dependencies] serde_json = { version = "1.0", default-features = false, features = ["alloc"] } diff --git a/proto/src/chrono.rs b/proto/src/chrono.rs deleted file mode 100644 index 3b969f0f0..000000000 --- a/proto/src/chrono.rs +++ /dev/null @@ -1,53 +0,0 @@ -use core::convert::TryInto; - -use chrono::{DateTime, Duration, TimeZone, Utc}; - -use crate::google::protobuf as pb; - -impl From> for pb::Timestamp { - fn from(dt: DateTime) -> pb::Timestamp { - pb::Timestamp { - seconds: dt.timestamp(), - // This can exceed 1_000_000_000 in the case of a leap second, but - // even with a leap second it should be under 2_147_483_647. - nanos: dt - .timestamp_subsec_nanos() - .try_into() - .expect("timestamp_subsec_nanos bigger than i32::MAX"), - } - } -} - -impl From for DateTime { - fn from(ts: pb::Timestamp) -> DateTime { - Utc.timestamp(ts.seconds, ts.nanos as u32) - } -} - -// Note: we convert a protobuf::Duration into a chrono::Duration, not a -// std::time::Duration, because std::time::Durations are unsigned, but the -// protobuf duration is signed. - -impl From for pb::Duration { - fn from(d: Duration) -> pb::Duration { - // chrono's Duration stores the fractional part as `nanos: i32` - // internally but doesn't provide a way to access it, only a way to get - // the *total* number of nanoseconds. so we have to do this cool and fun - // hoop-jumping maneuver - let seconds = d.num_seconds(); - let nanos = (d - Duration::seconds(seconds)) - .num_nanoseconds() - .expect("we computed the fractional part, so there's no overflow") - .try_into() - .expect("the fractional part fits in i32"); - - pb::Duration { seconds, nanos } - } -} - -impl From for Duration { - fn from(d: pb::Duration) -> Duration { - // there's no constructor that supplies both at once - Duration::seconds(d.seconds) + Duration::nanoseconds(d.nanos as i64) - } -} diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 312403de6..49e41fde6 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -19,7 +19,6 @@ pub mod google { } } -mod chrono; mod error; #[allow(warnings)] mod tendermint; diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index cc50f5cbe..cb4fe8046 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -31,7 +31,7 @@ rustdoc-args = ["--cfg", "docsrs"] [dependencies] async-trait = { version = "0.1", default-features = false } -bytes = { version = "1.0", default-features = false } +bytes = { version = "1.0", default-features = false, features = ["serde"] } ed25519 = { version = "1.3", default-features = false } ed25519-dalek = { version = "1", default-features = false, features = ["u64_backend"] } futures = { version = "0.3", default-features = false } @@ -54,8 +54,6 @@ zeroize = { version = "1.1", default-features = false, features = ["zeroize_deri flex-error = { version = "0.4.4", default-features = false } k256 = { version = "0.11", optional = true, default-features = false, features = ["ecdsa", "sha256"] } ripemd160 = { version = "0.9", default-features = false, optional = true } -# TODO(hdevalence): re-added while rebasing the ABCI domain types PR again -chrono = { version = "0.4", default-features = false, features = ["serde", "alloc"] } [features] default = ["std"] diff --git a/tendermint/src/abci/request/init_chain.rs b/tendermint/src/abci/request/init_chain.rs index 8072c2f6e..bb50b553f 100644 --- a/tendermint/src/abci/request/init_chain.rs +++ b/tendermint/src/abci/request/init_chain.rs @@ -1,8 +1,7 @@ use bytes::Bytes; -use chrono::{DateTime, Utc}; use super::super::types::ValidatorUpdate; -use crate::{block, consensus, prelude::*}; +use crate::{block, consensus, prelude::*, Time}; /// Called on genesis to initialize chain state. /// @@ -10,7 +9,7 @@ use crate::{block, consensus, prelude::*}; #[derive(Clone, PartialEq, Eq, Debug)] pub struct InitChain { /// The genesis time. - pub time: DateTime, + pub time: Time, /// The ID of the blockchain. pub chain_id: String, /// Initial consensus-critical parameters. diff --git a/tendermint/src/abci/types.rs b/tendermint/src/abci/types.rs index 4df1a95e7..54344d692 100644 --- a/tendermint/src/abci/types.rs +++ b/tendermint/src/abci/types.rs @@ -8,9 +8,8 @@ use core::convert::{TryFrom, TryInto}; use bytes::Bytes; -use chrono::{DateTime, Utc}; -use crate::{block, prelude::*, vote, Error, PublicKey}; +use crate::{block, prelude::*, vote, Error, PublicKey, Time}; /// A validator address with voting power. /// @@ -80,7 +79,7 @@ pub struct Evidence { /// The height when the offense occurred. pub height: block::Height, /// The corresponding time when the offense occurred. - pub time: DateTime, + pub time: Time, /// Total voting power of the validator set at `height`. /// /// This is included in case the ABCI application does not store historical @@ -246,7 +245,10 @@ impl TryFrom for Evidence { .ok_or_else(Error::missing_validator)? .try_into()?, height: evidence.height.try_into()?, - time: evidence.time.ok_or_else(Error::missing_timestamp)?.into(), + time: evidence + .time + .ok_or_else(Error::missing_timestamp)? + .try_into()?, total_voting_power: evidence.total_voting_power.try_into()?, }) } diff --git a/tendermint/src/time.rs b/tendermint/src/time.rs index 00114efb0..0cb6e330f 100644 --- a/tendermint/src/time.rs +++ b/tendermint/src/time.rs @@ -34,7 +34,7 @@ use crate::{error::Error, prelude::*}; // For memory efficiency, the inner member is `PrimitiveDateTime`, with assumed // UTC offset. The `assume_utc` method is used to get the operational // `OffsetDateTime` value. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] #[serde(try_from = "Timestamp", into = "Timestamp")] pub struct Time(PrimitiveDateTime); @@ -117,6 +117,20 @@ impl Time { pub fn to_rfc3339(&self) -> String { timestamp::to_rfc3339_nanos(self.0.assume_utc()) } + + /// Computes `self + duration`, returning `None` if an overflow occurred. + pub fn checked_add(self, duration: Duration) -> Option { + let duration = duration.try_into().ok()?; + let t = self.0.checked_add(duration)?; + Self::from_utc(t.assume_utc()).ok() + } + + /// Computes `self - duration`, returning `None` if an overflow occurred. + pub fn checked_sub(self, duration: Duration) -> Option { + let duration = duration.try_into().ok()?; + let t = self.0.checked_sub(duration)?; + Self::from_utc(t.assume_utc()).ok() + } } impl fmt::Display for Time { @@ -151,19 +165,12 @@ impl Add for Time { type Output = Result; fn add(self, rhs: Duration) -> Self::Output { - // Work around not being able to depend on time 0.3.5 - // https://github.com/informalsystems/tendermint-rs/issues/1047 - let lhs_nanos = self.0.assume_utc().unix_timestamp_nanos(); - let rhs_nanos: i128 = rhs - .as_nanos() - .try_into() - .map_err(|_| Error::duration_out_of_range())?; - let res_nanos = lhs_nanos - .checked_add(rhs_nanos) + let duration = rhs.try_into().map_err(|_| Error::duration_out_of_range())?; + let t = self + .0 + .checked_add(duration) .ok_or_else(Error::duration_out_of_range)?; - let t = OffsetDateTime::from_unix_timestamp_nanos(res_nanos) - .map_err(|_| Error::duration_out_of_range())?; - Self::from_utc(t) + Self::from_utc(t.assume_utc()) } } @@ -171,19 +178,12 @@ impl Sub for Time { type Output = Result; fn sub(self, rhs: Duration) -> Self::Output { - // Work around not being able to depend on time 0.3.5 - // https://github.com/informalsystems/tendermint-rs/issues/1047 - let lhs_nanos = self.0.assume_utc().unix_timestamp_nanos(); - let rhs_nanos: i128 = rhs - .as_nanos() - .try_into() - .map_err(|_| Error::duration_out_of_range())?; - let res_nanos = lhs_nanos - .checked_sub(rhs_nanos) + let duration = rhs.try_into().map_err(|_| Error::duration_out_of_range())?; + let t = self + .0 + .checked_sub(duration) .ok_or_else(Error::duration_out_of_range)?; - let t = OffsetDateTime::from_unix_timestamp_nanos(res_nanos) - .map_err(|_| Error::duration_out_of_range())?; - Self::from_utc(t) + Self::from_utc(t.assume_utc()) } } @@ -376,31 +376,31 @@ mod tests { proptest! { #[test] - fn add_regular((dt, d) in args_for_regular_add()) { + fn checked_add_regular((dt, d) in args_for_regular_add()) { let t: Time = dt.try_into().unwrap(); - let t = (t + d).unwrap(); + let t = t.checked_add(d).unwrap(); let res: OffsetDateTime = t.into(); assert_eq!(res, dt + d); } #[test] - fn sub_regular((dt, d) in args_for_regular_sub()) { + fn checked_sub_regular((dt, d) in args_for_regular_sub()) { let t: Time = dt.try_into().unwrap(); - let t = (t - d).unwrap(); + let t = t.checked_sub(d).unwrap(); let res: OffsetDateTime = t.into(); assert_eq!(res, dt - d); } #[test] - fn add_overflow((dt, d) in args_for_overflowed_add()) { + fn checked_add_overflow((dt, d) in args_for_overflowed_add()) { let t: Time = dt.try_into().unwrap(); - assert!((t + d).is_err()); + assert_eq!(t.checked_add(d), None); } #[test] - fn sub_overflow((dt, d) in args_for_overflowed_sub()) { + fn checked_sub_overflow((dt, d) in args_for_overflowed_sub()) { let t: Time = dt.try_into().unwrap(); - assert!((t - d).is_err()); + assert_eq!(t.checked_sub(d), None); } } } From b9b6596df8a2668e06819c98cae1ccaa9bea8a37 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 14:49:19 -0400 Subject: [PATCH 08/25] fixup! split A.3, abci-in-rpc --- rpc/src/endpoint/tx.rs | 2 +- rpc/src/lib.rs | 1 + rpc/src/serializers.rs | 10 ++++++++++ {tendermint => rpc}/src/serializers/hash_base64.rs | 0 tendermint/src/serializers.rs | 1 - 5 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 rpc/src/serializers.rs rename {tendermint => rpc}/src/serializers/hash_base64.rs (100%) diff --git a/rpc/src/endpoint/tx.rs b/rpc/src/endpoint/tx.rs index 66aaa1b32..71729bc03 100644 --- a/rpc/src/endpoint/tx.rs +++ b/rpc/src/endpoint/tx.rs @@ -13,7 +13,7 @@ pub struct Request { /// /// Serialized internally into a base64-encoded string before sending to /// the RPC server. - #[serde(with = "tendermint::serializers::hash_base64")] + #[serde(with = "crate::serializers::hash_base64")] pub hash: abci::transaction::Hash, /// Whether or not to include the proofs of the transaction's inclusion in /// the block. diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 1c7371edb..b10f14f50 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -57,6 +57,7 @@ pub mod request; pub mod response; pub mod response_error; mod rpc_url; +pub mod serializers; mod utils; mod version; diff --git a/rpc/src/serializers.rs b/rpc/src/serializers.rs new file mode 100644 index 000000000..025f02876 --- /dev/null +++ b/rpc/src/serializers.rs @@ -0,0 +1,10 @@ +//! Serde serializers +//! +//! Serializers and deserializers for a transparent developer experience. +//! +//! CAUTION: There are no guarantees for backwards compatibility, this module should be considered +//! an internal implementation detail which can vanish without further warning. Use at your own +//! risk. +pub use tendermint_proto::serializers::*; + +pub mod hash_base64; diff --git a/tendermint/src/serializers/hash_base64.rs b/rpc/src/serializers/hash_base64.rs similarity index 100% rename from tendermint/src/serializers/hash_base64.rs rename to rpc/src/serializers/hash_base64.rs diff --git a/tendermint/src/serializers.rs b/tendermint/src/serializers.rs index 697ceaa1c..8a0efc8b9 100644 --- a/tendermint/src/serializers.rs +++ b/tendermint/src/serializers.rs @@ -9,6 +9,5 @@ pub use tendermint_proto::serializers::*; pub mod apphash; pub mod hash; -pub mod hash_base64; pub mod option_hash; pub mod time; From c02739a2952c9b23f843d64e257aa24100fe5e89 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 14:56:50 -0400 Subject: [PATCH 09/25] split A.4.1.1, check_event_attrs --- rpc/tests/kvstore_fixtures.rs | 97 ++++++++++++----------------------- 1 file changed, 32 insertions(+), 65 deletions(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index ed867763c..010148dd3 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -1162,19 +1162,7 @@ fn incoming_fixtures() { } else { panic!("not a tx"); } - for (k, v) in result.events.unwrap() { - assert_eq!(v.len(), 1); - match k.as_str() { - "app.creator" => assert_eq!(v[0], "Cosmoshi Netowoko"), - "app.index_key" => assert_eq!(v[0], "index is working"), - "app.key" => assert_eq!(v[0], "tx0"), - "app.noindex_key" => assert_eq!(v[0], "index is working"), - "tm.event" => assert_eq!(v[0], "Tx"), - "tx.hash" => assert_eq!(v[0].len(), 64), - "tx.height" => assert_eq!(v[0], height.to_string()), - _ => panic!("unknown event found {}", k), - } - } + check_event_attrs(&result.events.unwrap(), "tx0", height); assert_eq!(result.query, "tm.event = 'Tx'"); }, "subscribe_txs_1" => { @@ -1206,19 +1194,8 @@ fn incoming_fixtures() { } else { panic!("not a tx"); } - for (k, v) in result.events.unwrap() { - assert_eq!(v.len(), 1); - match k.as_str() { - "app.creator" => assert_eq!(v[0], "Cosmoshi Netowoko"), - "app.index_key" => assert_eq!(v[0], "index is working"), - "app.key" => assert_eq!(v[0], "tx1"), - "app.noindex_key" => assert_eq!(v[0], "index is working"), - "tm.event" => assert_eq!(v[0], "Tx"), - "tx.hash" => assert_eq!(v[0].len(), 64), - "tx.height" => assert_eq!(v[0], height.to_string()), - _ => panic!("unknown event found {}", k), - } - } + + check_event_attrs(&result.events.unwrap(), "tx1", height); assert_eq!(result.query, "tm.event = 'Tx'"); }, "subscribe_txs_2" => { @@ -1250,19 +1227,7 @@ fn incoming_fixtures() { } else { panic!("not a tx"); } - for (k, v) in result.events.unwrap() { - assert_eq!(v.len(), 1); - match k.as_str() { - "app.creator" => assert_eq!(v[0], "Cosmoshi Netowoko"), - "app.index_key" => assert_eq!(v[0], "index is working"), - "app.key" => assert_eq!(v[0], "tx2"), - "app.noindex_key" => assert_eq!(v[0], "index is working"), - "tm.event" => assert_eq!(v[0], "Tx"), - "tx.hash" => assert_eq!(v[0].len(), 64), - "tx.height" => assert_eq!(v[0], height.to_string()), - _ => panic!("unknown event found {}", k), - } - } + check_event_attrs(&result.events.unwrap(), "tx2", height); assert_eq!(result.query, "tm.event = 'Tx'"); }, "subscribe_txs_3" => { @@ -1294,19 +1259,7 @@ fn incoming_fixtures() { } else { panic!("not a tx"); } - for (k, v) in result.events.unwrap() { - assert_eq!(v.len(), 1); - match k.as_str() { - "app.creator" => assert_eq!(v[0], "Cosmoshi Netowoko"), - "app.index_key" => assert_eq!(v[0], "index is working"), - "app.key" => assert_eq!(v[0], "tx3"), - "app.noindex_key" => assert_eq!(v[0], "index is working"), - "tm.event" => assert_eq!(v[0], "Tx"), - "tx.hash" => assert_eq!(v[0].len(), 64), - "tx.height" => assert_eq!(v[0], height.to_string()), - _ => panic!("unknown event found {}", k), - } - } + check_event_attrs(&result.events.unwrap(), "tx3", height); assert_eq!(result.query, "tm.event = 'Tx'"); }, "subscribe_txs_4" => { @@ -1338,19 +1291,7 @@ fn incoming_fixtures() { } else { panic!("not a tx"); } - for (k, v) in result.events.unwrap() { - assert_eq!(v.len(), 1); - match k.as_str() { - "app.creator" => assert_eq!(v[0], "Cosmoshi Netowoko"), - "app.index_key" => assert_eq!(v[0], "index is working"), - "app.key" => assert_eq!(v[0], "tx4"), - "app.noindex_key" => assert_eq!(v[0], "index is working"), - "tm.event" => assert_eq!(v[0], "Tx"), - "tx.hash" => assert_eq!(v[0].len(), 64), - "tx.height" => assert_eq!(v[0], height.to_string()), - _ => panic!("unknown event found {}", k), - } - } + check_event_attrs(&result.events.unwrap(), "tx4", height); assert_eq!(result.query, "tm.event = 'Tx'"); }, "subscribe_txs_broadcast_tx_0" => { @@ -1465,3 +1406,29 @@ fn incoming_fixtures() { } } } + +fn check_event_attrs(events: &[tendermint_rpc::abci::Event], app_key: &str, height: i64) { + for event in events { + for attr in &event.attributes { + match event.type_str.as_ref() { + "app" => match attr.key.as_ref() { + "creator" => assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko"), + "index_key" => assert_eq!(attr.value.as_ref(), "index is working"), + "key" => assert_eq!(attr.value.as_ref(), app_key), + "noindex_key" => assert_eq!(attr.value.as_ref(), "index is working"), + _ => panic!("unrecognized app attribute found \"{}\"", attr.key), + }, + "tx" => match attr.key.as_ref() { + "hash" => assert_eq!(attr.value.as_ref().len(), 64), + "height" => assert_eq!(attr.value.as_ref(), height.to_string()), + _ => panic!("unrecognized tx attribute found \"{}\"", attr.key), + }, + "tm" => match attr.key.as_ref() { + "event" => assert_eq!(attr.value.as_ref(), "Tx"), + _ => panic!("unrecognized tm attribute found \"{}\"", attr.key), + }, + _ => panic!("unrecognized event type found \"{}\"", event.type_str), + } + } + } +} From 2e429e7e778eea6c54cd531a34587c2c13589953 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 14:58:15 -0400 Subject: [PATCH 10/25] split A.4.1.2, some slice change? --- rpc/tests/kvstore_fixtures.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 010148dd3..0fd8b650e 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -318,7 +318,7 @@ fn incoming_fixtures() { let result = endpoint::abci_info::Response::from_string(content).unwrap(); assert_eq!(result.response.app_version, 1); assert_eq!(result.response.data, "{\"size\":0}"); - assert_eq!(result.response.last_block_app_hash, b"AAAAAAAAAAA="); + assert_eq!(result.response.last_block_app_hash, b"AAAAAAAAAAA="[..]); assert_eq!(result.response.version, "0.17.0"); }, "abci_query_with_existing_key" => { From 064fb82c9b4e83df0c8ee2524d96e7c1c744a741 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 15:01:03 -0400 Subject: [PATCH 11/25] split A.4.1.3, b.data.get(0).is_none() --- rpc/tests/kvstore_fixtures.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 0fd8b650e..92ee6258c 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -364,7 +364,7 @@ fn incoming_fixtures() { }, "block_at_height_1" => { let result = endpoint::block::Response::from_string(content).unwrap(); - assert!(result.block.data.iter().next().is_none()); + assert!(result.block.data.get(0).is_none()); assert!(result.block.evidence.iter().next().is_none()); assert!(result.block.header.app_hash.value().is_empty()); assert_eq!(result.block.header.chain_id.as_str(), CHAIN_ID); @@ -405,7 +405,7 @@ fn incoming_fixtures() { }, "block_at_height_10" => { let result = endpoint::block::Response::from_string(content).unwrap(); - assert!(result.block.data.iter().next().is_none()); + assert!(result.block.data.get(0).is_none()); assert!(result.block.evidence.iter().next().is_none()); assert_eq!(result.block.header.app_hash.value(), [0u8; 8]); assert_eq!(result.block.header.chain_id.as_str(), CHAIN_ID); @@ -864,7 +864,7 @@ fn incoming_fixtures() { } = result.data { let b = block.unwrap(); - assert!(b.data.iter().next().is_none()); + assert!(b.data.get(0).is_none()); assert!(b.evidence.iter().next().is_none()); assert!(!b.header.app_hash.value().is_empty()); assert_eq!(b.header.chain_id.as_str(), CHAIN_ID); @@ -919,7 +919,7 @@ fn incoming_fixtures() { } = result.data { let b = block.unwrap(); - assert!(b.data.iter().next().is_none()); + assert!(b.data.get(0).is_none()); assert!(b.evidence.iter().next().is_none()); assert!(!b.header.app_hash.value().is_empty()); assert_eq!(b.header.chain_id.as_str(), CHAIN_ID); @@ -974,7 +974,7 @@ fn incoming_fixtures() { } = result.data { let b = block.unwrap(); - assert!(b.data.iter().next().is_none()); + assert!(b.data.get(0).is_none()); assert!(b.evidence.iter().next().is_none()); assert!(!b.header.app_hash.value().is_empty()); assert_eq!(b.header.chain_id.as_str(), CHAIN_ID); @@ -1029,7 +1029,7 @@ fn incoming_fixtures() { } = result.data { let b = block.unwrap(); - assert!(b.data.iter().next().is_none()); + assert!(b.data.get(0).is_none()); assert!(b.evidence.iter().next().is_none()); assert!(!b.header.app_hash.value().is_empty()); assert_eq!(b.header.chain_id.as_str(), CHAIN_ID); @@ -1084,7 +1084,7 @@ fn incoming_fixtures() { } = result.data { let b = block.unwrap(); - assert!(b.data.iter().next().is_none()); + assert!(b.data.get(0).is_none()); assert!(b.evidence.iter().next().is_none()); assert!(!b.header.app_hash.value().is_empty()); assert_eq!(b.header.chain_id.as_str(), CHAIN_ID); From 39f721032321da0bf4e06b08b404c23cfce40045 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 15:01:49 -0400 Subject: [PATCH 12/25] split A.4.1.4, remove test attrs --- rpc/tests/kvstore_fixtures.rs | 39 ++--------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 92ee6258c..8e2f2313c 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -462,43 +462,8 @@ fn incoming_fixtures() { "block_search" => { let result = endpoint::block_search::Response::from_string(content).unwrap(); assert_eq!(result.total_count as usize, result.blocks.len()); - // Test a few selected attributes of the results. - for block in result.blocks { - assert!(block.block.data.iter().next().is_none()); - assert!(block.block.evidence.iter().next().is_none()); - assert_eq!(block.block.header.app_hash.value(), [0u8; 8]); - assert_eq!(block.block.header.chain_id.as_str(), CHAIN_ID); - assert!(!block.block.header.consensus_hash.is_empty()); - assert_eq!(block.block.header.data_hash, empty_merkle_root_hash); - assert_eq!(block.block.header.evidence_hash, empty_merkle_root_hash); - assert!(block.block.header.height.value() > 1); - assert!(block.block.header.last_block_id.is_some()); - assert!(block.block.header.last_commit_hash.is_some()); - assert!(block.block.header.last_results_hash.is_some()); - assert!(!block.block.header.next_validators_hash.is_empty()); - assert_ne!( - block.block.header.proposer_address.as_bytes(), - [0u8; tendermint::account::LENGTH] - ); - assert!( - block - .block - .header - .time - .duration_since(informal_epoch) - .unwrap() - .as_secs() - > 0 - ); - assert!(!block.block.header.validators_hash.is_empty()); - assert_eq!( - block.block.header.version, - tendermint::block::header::Version { block: 11, app: 1 } - ); - assert!(block.block.last_commit.is_some()); - assert!(!block.block_id.hash.is_empty()); - assert!(!block.block_id.part_set_header.hash.is_empty()); - assert_eq!(block.block_id.part_set_header.total, 1); + for response in result.blocks { + assert!(response.block.header.height.value() > 1); } }, "blockchain_from_1_to_10" => { From 60dafb48860dd7d79d3a9fde37ecd6ea5a919f60 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 15:02:14 -0400 Subject: [PATCH 13/25] split A.4.1.5, someting optiony --- rpc/tests/kvstore_fixtures.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 8e2f2313c..4ddd0a25e 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -753,7 +753,7 @@ fn incoming_fixtures() { assert_eq!(result.listeners[0].to_string(), "Listener(@)"); assert!(result.listening); assert_eq!(result.n_peers, 0); - assert!(result.peers.is_empty()); + assert!(result.peers.is_none()); }, "status" => { let result = endpoint::status::Response::from_string(content).unwrap(); From 61e6bf5792bf7e6ed3bffe7aef9a03bf29f312b5 Mon Sep 17 00:00:00 2001 From: Henry de Valence Date: Tue, 20 Sep 2022 16:20:15 -0400 Subject: [PATCH 14/25] clippy fix --- tendermint/src/abci/request.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tendermint/src/abci/request.rs b/tendermint/src/abci/request.rs index ea15e0bc0..990b567d9 100644 --- a/tendermint/src/abci/request.rs +++ b/tendermint/src/abci/request.rs @@ -155,7 +155,7 @@ impl TryFrom for ConsensusRequest { } /// The mempool category of ABCI requests. -#[derive(Clone, PartialEq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub enum MempoolRequest { #[doc = include_str!("doc/request-checktx.md")] CheckTx(CheckTx), From 08a6a1ecbc32c374b86e325b3cdabb24c33808a9 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 22 Sep 2022 19:13:05 +0300 Subject: [PATCH 15/25] Remove unnessarily generated files --- proto/src/prost/gogoproto.rs | 0 proto/src/prost/google.protobuf.rs | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 proto/src/prost/gogoproto.rs delete mode 100644 proto/src/prost/google.protobuf.rs diff --git a/proto/src/prost/gogoproto.rs b/proto/src/prost/gogoproto.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/proto/src/prost/google.protobuf.rs b/proto/src/prost/google.protobuf.rs deleted file mode 100644 index e69de29bb..000000000 From ce30fe6c15cc0044abfe962036176fc0b58a82c2 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 22 Sep 2022 20:08:15 +0300 Subject: [PATCH 16/25] Fix deliver_tx.events KV tests --- rpc/tests/kvstore_fixtures.rs | 56 ++++++++++------------------------- 1 file changed, 16 insertions(+), 40 deletions(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 4ddd0a25e..d7746225e 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -547,60 +547,36 @@ fn incoming_fixtures() { assert_eq!(result.deliver_tx.events.len(), 1); assert_eq!(result.deliver_tx.events[0].attributes.len(), 4); assert_eq!( - result.deliver_tx.events[0].attributes[0] - .key - .to_string() - .as_bytes(), - base64::decode("Y3JlYXRvcg==").unwrap() + result.deliver_tx.events[0].attributes[0].key.as_ref(), + "Y3JlYXRvcg==" ); assert_eq!( - result.deliver_tx.events[0].attributes[0] - .value - .to_string() - .as_bytes(), - base64::decode("Q29zbW9zaGkgTmV0b3dva28=").unwrap() + result.deliver_tx.events[0].attributes[0].value.as_ref(), + "Q29zbW9zaGkgTmV0b3dva28=" ); assert_eq!( - result.deliver_tx.events[0].attributes[1] - .key - .to_string() - .as_bytes(), - base64::decode("a2V5").unwrap() + result.deliver_tx.events[0].attributes[1].key.as_ref(), + "a2V5" ); assert_eq!( - result.deliver_tx.events[0].attributes[1] - .value - .to_string() - .as_bytes(), - base64::decode("Y29tbWl0LWtleQ==").unwrap() + result.deliver_tx.events[0].attributes[1].value.as_ref(), + "Y29tbWl0LWtleQ==" ); assert_eq!( - result.deliver_tx.events[0].attributes[2] - .key - .to_string() - .as_bytes(), - base64::decode("aW5kZXhfa2V5").unwrap() + result.deliver_tx.events[0].attributes[2].key.as_ref(), + "aW5kZXhfa2V5" ); assert_eq!( - result.deliver_tx.events[0].attributes[2] - .value - .to_string() - .as_bytes(), - base64::decode("aW5kZXggaXMgd29ya2luZw==").unwrap() + result.deliver_tx.events[0].attributes[2].value.as_ref(), + "aW5kZXggaXMgd29ya2luZw==" ); assert_eq!( - result.deliver_tx.events[0].attributes[3] - .key - .to_string() - .as_bytes(), - base64::decode("bm9pbmRleF9rZXk=").unwrap() + result.deliver_tx.events[0].attributes[3].key.as_ref(), + "bm9pbmRleF9rZXk=" ); assert_eq!( - result.deliver_tx.events[0].attributes[3] - .value - .to_string() - .as_bytes(), - base64::decode("aW5kZXggaXMgd29ya2luZw==").unwrap() + result.deliver_tx.events[0].attributes[3].value.as_ref(), + "aW5kZXggaXMgd29ya2luZw==" ); assert_eq!(result.deliver_tx.events[0].type_str, "app"); assert_eq!(result.deliver_tx.gas_used.value(), 0); From 35b75fc92e15a64c003db74719eb8663402bd7d5 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Thu, 22 Sep 2022 20:09:18 +0300 Subject: [PATCH 17/25] Remove Option wart in net_info::Response --- rpc/src/endpoint/net_info.rs | 2 +- rpc/tests/kvstore_fixtures.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpc/src/endpoint/net_info.rs b/rpc/src/endpoint/net_info.rs index 7dc158f82..da2b56920 100644 --- a/rpc/src/endpoint/net_info.rs +++ b/rpc/src/endpoint/net_info.rs @@ -39,7 +39,7 @@ pub struct Response { pub n_peers: u64, /// Peer information - pub peers: Option>, + pub peers: Vec, } impl crate::Response for Response {} diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index d7746225e..1337ab761 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -729,7 +729,7 @@ fn incoming_fixtures() { assert_eq!(result.listeners[0].to_string(), "Listener(@)"); assert!(result.listening); assert_eq!(result.n_peers, 0); - assert!(result.peers.is_none()); + assert!(result.peers.is_empty()); }, "status" => { let result = endpoint::status::Response::from_string(content).unwrap(); From 96671480aeaea3751021659934e0935aa35a837f Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 23 Sep 2022 15:22:34 +0300 Subject: [PATCH 18/25] rpc: Revert Event member events to a KV map The event list change did not make it into tendermint 0.37. --- rpc/src/event.rs | 6 +- rpc/tests/kvstore_fixtures.rs | 120 ++++++++++++++++------------------ 2 files changed, 62 insertions(+), 64 deletions(-) diff --git a/rpc/src/event.rs b/rpc/src/event.rs index fa5c4fff7..49055de82 100644 --- a/rpc/src/event.rs +++ b/rpc/src/event.rs @@ -1,5 +1,7 @@ //! RPC subscription event-related data structures. +use alloc::collections::BTreeMap as HashMap; + use serde::{Deserialize, Serialize}; use tendermint::Block; @@ -20,8 +22,8 @@ pub struct Event { pub query: String, /// The data associated with the event. pub data: EventData, - /// Event type and attributes list. - pub events: Option>, + /// Event type and attributes map. + pub events: Option>>, } impl Response for Event {} diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 1337ab761..1a7423c7a 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -1,6 +1,7 @@ //! Tendermint kvstore RPC endpoint testing. use core::str::FromStr; +use std::collections::BTreeMap as HashMap; use std::{fs, path::PathBuf}; use subtle_encoding::{base64, hex}; @@ -1085,16 +1086,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_ref() { - "creator" => { - assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") + match &base64::decode(attr.key.as_ref()).unwrap()[..] { + b"creator" => { + check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value.as_ref(), "tx0"), - "index_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"key" => check_base64_str(attr.value.as_ref(), "tx0"), + b"index_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, - "noindex_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"noindex_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1117,16 +1118,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_ref() { - "creator" => { - assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") + match &base64::decode(attr.key.as_ref()).unwrap()[..] { + b"creator" => { + check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value.as_ref(), "tx1"), - "index_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"key" => check_base64_str(attr.value.as_ref(), "tx1"), + b"index_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, - "noindex_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"noindex_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1150,16 +1151,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_ref() { - "creator" => { - assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") + match &base64::decode(attr.key.as_ref()).unwrap()[..] { + b"creator" => { + check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value.as_ref(), "tx2"), - "index_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"key" => check_base64_str(attr.value.as_ref(), "tx2"), + b"index_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, - "noindex_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"noindex_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1182,16 +1183,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_ref() { - "creator" => { - assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") + match &base64::decode(attr.key.as_ref()).unwrap()[..] { + b"creator" => { + check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value.as_ref(), "tx3"), - "index_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"key" => check_base64_str(attr.value.as_ref(), "tx3"), + b"index_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, - "noindex_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"noindex_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1214,16 +1215,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_ref() { - "creator" => { - assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") + match &base64::decode(attr.key.as_ref()).unwrap()[..] { + b"creator" => { + check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value.as_ref(), "tx4"), - "index_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"key" => check_base64_str(attr.value.as_ref(), "tx4"), + b"index_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, - "noindex_key" => { - assert_eq!(attr.value.as_ref(), "index is working") + b"noindex_key" => { + check_base64_str(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1348,28 +1349,23 @@ fn incoming_fixtures() { } } -fn check_event_attrs(events: &[tendermint_rpc::abci::Event], app_key: &str, height: i64) { - for event in events { - for attr in &event.attributes { - match event.type_str.as_ref() { - "app" => match attr.key.as_ref() { - "creator" => assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko"), - "index_key" => assert_eq!(attr.value.as_ref(), "index is working"), - "key" => assert_eq!(attr.value.as_ref(), app_key), - "noindex_key" => assert_eq!(attr.value.as_ref(), "index is working"), - _ => panic!("unrecognized app attribute found \"{}\"", attr.key), - }, - "tx" => match attr.key.as_ref() { - "hash" => assert_eq!(attr.value.as_ref().len(), 64), - "height" => assert_eq!(attr.value.as_ref(), height.to_string()), - _ => panic!("unrecognized tx attribute found \"{}\"", attr.key), - }, - "tm" => match attr.key.as_ref() { - "event" => assert_eq!(attr.value.as_ref(), "Tx"), - _ => panic!("unrecognized tm attribute found \"{}\"", attr.key), - }, - _ => panic!("unrecognized event type found \"{}\"", event.type_str), - } +fn check_event_attrs(events: &HashMap>, app_key: &str, height: i64) { + for (k, v) in events { + assert_eq!(v.len(), 1); + match k.as_str() { + "app.creator" => assert_eq!(v[0], "Cosmoshi Netowoko"), + "app.index_key" => assert_eq!(v[0], "index is working"), + "app.key" => assert_eq!(v[0], app_key), + "app.noindex_key" => assert_eq!(v[0], "index is working"), + "tm.event" => assert_eq!(v[0], "Tx"), + "tx.hash" => assert_eq!(v[0].len(), 64), + "tx.height" => assert_eq!(v[0], height.to_string()), + _ => panic!("unknown event found {}", k), } } } + +fn check_base64_str(encoded: &str, expected: &str) { + let decoded = base64::decode(encoded).unwrap(); + assert_eq!(decoded, expected.as_bytes()) +} From 3aba2d2f263b24d246ddd3c438620846d025279e Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 23 Sep 2022 19:19:23 +0300 Subject: [PATCH 19/25] Revert tests to checking decoded KVs --- rpc/tests/kvstore_fixtures.rs | 82 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index ab5196dd1..923057411 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -1122,16 +1122,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match &base64::decode(attr.key.as_ref()).unwrap()[..] { - b"creator" => { - check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") + match attr.key.as_ref() { + "creator" => { + assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") }, - b"key" => check_base64_str(attr.value.as_ref(), "tx0"), - b"index_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "key" => assert_eq!(attr.value.as_ref(), "tx0"), + "index_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, - b"noindex_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "noindex_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1154,16 +1154,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match &base64::decode(attr.key.as_ref()).unwrap()[..] { - b"creator" => { - check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") + match attr.key.as_ref() { + "creator" => { + assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") }, - b"key" => check_base64_str(attr.value.as_ref(), "tx1"), - b"index_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "key" => assert_eq!(attr.value.as_ref(), "tx1"), + "index_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, - b"noindex_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "noindex_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1187,16 +1187,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match &base64::decode(attr.key.as_ref()).unwrap()[..] { - b"creator" => { - check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") + match attr.key.as_ref() { + "creator" => { + assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") }, - b"key" => check_base64_str(attr.value.as_ref(), "tx2"), - b"index_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "key" => assert_eq!(attr.value.as_ref(), "tx2"), + "index_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, - b"noindex_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "noindex_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1219,16 +1219,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match &base64::decode(attr.key.as_ref()).unwrap()[..] { - b"creator" => { - check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") + match attr.key.as_ref() { + "creator" => { + assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") }, - b"key" => check_base64_str(attr.value.as_ref(), "tx3"), - b"index_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "key" => assert_eq!(attr.value.as_ref(), "tx3"), + "index_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, - b"noindex_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "noindex_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1251,16 +1251,16 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].type_str, "app"); for attr in &tx_result.result.events[0].attributes { - match &base64::decode(attr.key.as_ref()).unwrap()[..] { - b"creator" => { - check_base64_str(attr.value.as_ref(), "Cosmoshi Netowoko") + match attr.key.as_ref() { + "creator" => { + assert_eq!(attr.value.as_ref(), "Cosmoshi Netowoko") }, - b"key" => check_base64_str(attr.value.as_ref(), "tx4"), - b"index_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "key" => assert_eq!(attr.value.as_ref(), "tx4"), + "index_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, - b"noindex_key" => { - check_base64_str(attr.value.as_ref(), "index is working") + "noindex_key" => { + assert_eq!(attr.value.as_ref(), "index is working") }, _ => panic!("unknown attribute found {}", attr.key), } @@ -1397,9 +1397,9 @@ fn check_event_attrs(events: &HashMap>, app_key: &str, heigh "tx.hash" => assert_eq!(v[0].len(), 64), "tx.height" => assert_eq!(v[0], height.to_string()), _ => panic!("unknown event found {}", k), + } } } -} fn check_base64_str(encoded: &str, expected: &str) { let decoded = base64::decode(encoded).unwrap(); From 6c3813194ac80ecf7fc45445f2885b020546476c Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 23 Sep 2022 19:41:08 +0300 Subject: [PATCH 20/25] rpc: Restore base64 serialization of event KV tags --- rpc/src/abci/tag.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/rpc/src/abci/tag.rs b/rpc/src/abci/tag.rs index c10383890..e4558d679 100644 --- a/rpc/src/abci/tag.rs +++ b/rpc/src/abci/tag.rs @@ -4,6 +4,7 @@ use core::{fmt, str::FromStr}; use serde::{Deserialize, Serialize}; use tendermint::error::Error; +use tendermint_proto::serializers::bytes::base64string; use crate::prelude::*; @@ -19,7 +20,13 @@ pub struct Tag { /// Tag keys #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize)] -pub struct Key(String); +pub struct Key( + #[serde( + serialize_with = "base64string::serialize", + deserialize_with = "base64string::deserialize_to_string" + )] + String, +); impl AsRef for Key { fn as_ref(&self) -> &str { @@ -43,7 +50,13 @@ impl fmt::Display for Key { /// Tag values #[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] -pub struct Value(String); +pub struct Value( + #[serde( + serialize_with = "base64string::serialize", + deserialize_with = "base64string::deserialize_to_string" + )] + String, +); impl AsRef for Value { fn as_ref(&self) -> &str { From 0ca7299bb60287ba578a9eba5b36da5644b7ed42 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 23 Sep 2022 19:42:10 +0300 Subject: [PATCH 21/25] Post-merge fixes for rpc tests --- rpc/tests/kvstore_fixtures.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 923057411..fbabb5c00 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -5,7 +5,11 @@ use std::collections::BTreeMap as HashMap; use std::{fs, path::PathBuf}; use subtle_encoding::{base64, hex}; -use tendermint::{evidence::Duration, public_key}; +use tendermint::{ + evidence::{Duration, Evidence}, + public_key, + vote::Vote, +}; use tendermint_config::net::Address; use tendermint_rpc::{ abci::transaction::Hash, @@ -538,35 +542,35 @@ fn incoming_fixtures() { assert_eq!(result.deliver_tx.events[0].attributes.len(), 4); assert_eq!( result.deliver_tx.events[0].attributes[0].key.as_ref(), - "Y3JlYXRvcg==" + "creator" ); assert_eq!( result.deliver_tx.events[0].attributes[0].value.as_ref(), - "Q29zbW9zaGkgTmV0b3dva28=" + "Cosmoshi Netowoko" ); assert_eq!( result.deliver_tx.events[0].attributes[1].key.as_ref(), - "a2V5" + "key" ); assert_eq!( result.deliver_tx.events[0].attributes[1].value.as_ref(), - "Y29tbWl0LWtleQ==" + "commit-key" ); assert_eq!( result.deliver_tx.events[0].attributes[2].key.as_ref(), - "aW5kZXhfa2V5" + "index_key" ); assert_eq!( result.deliver_tx.events[0].attributes[2].value.as_ref(), - "aW5kZXggaXMgd29ya2luZw==" + "index is working" ); assert_eq!( result.deliver_tx.events[0].attributes[3].key.as_ref(), - "bm9pbmRleF9rZXk=" + "noindex_key" ); assert_eq!( result.deliver_tx.events[0].attributes[3].value.as_ref(), - "aW5kZXggaXMgd29ya2luZw==" + "index is working" ); assert_eq!(result.deliver_tx.events[0].type_str, "app"); assert_eq!(result.deliver_tx.gas_used.value(), 0); @@ -1397,11 +1401,6 @@ fn check_event_attrs(events: &HashMap>, app_key: &str, heigh "tx.hash" => assert_eq!(v[0].len(), 64), "tx.height" => assert_eq!(v[0], height.to_string()), _ => panic!("unknown event found {}", k), - } } } - -fn check_base64_str(encoded: &str, expected: &str) { - let decoded = base64::decode(encoded).unwrap(); - assert_eq!(decoded, expected.as_bytes()) } From 3a92d77d3bcedaedb4cce319bcb79f6d88164c18 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 23 Sep 2022 19:53:13 +0300 Subject: [PATCH 22/25] rpc: Use the re-exported serializers mod --- rpc/src/abci/tag.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/src/abci/tag.rs b/rpc/src/abci/tag.rs index e4558d679..c791688eb 100644 --- a/rpc/src/abci/tag.rs +++ b/rpc/src/abci/tag.rs @@ -4,9 +4,9 @@ use core::{fmt, str::FromStr}; use serde::{Deserialize, Serialize}; use tendermint::error::Error; -use tendermint_proto::serializers::bytes::base64string; use crate::prelude::*; +use crate::serializers::bytes::base64string; /// Tags #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] From 8d31c4e9686bed6d901f464851eba2ffee035926 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Fri, 23 Sep 2022 23:55:46 +0300 Subject: [PATCH 23/25] Remove duplicate changelog entries --- .../unreleased/breaking-changes/1030-remove-chrono.md | 11 ----------- .../unreleased/improvements/1030-new-time-api.md | 11 ----------- 2 files changed, 22 deletions(-) delete mode 100644 .changelog/unreleased/breaking-changes/1030-remove-chrono.md delete mode 100644 .changelog/unreleased/improvements/1030-new-time-api.md diff --git a/.changelog/unreleased/breaking-changes/1030-remove-chrono.md b/.changelog/unreleased/breaking-changes/1030-remove-chrono.md deleted file mode 100644 index 7d8385ee5..000000000 --- a/.changelog/unreleased/breaking-changes/1030-remove-chrono.md +++ /dev/null @@ -1,11 +0,0 @@ -- `[tendermint]` Reform `tendermint::Time` - ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)): - * The struct content is made private. - * The range of acceptable values is restricted to years 1-9999 - (as reckoned in UTC). - * Removed conversions from/to `chrono::DateTime`. - * Changes in error variants: removed `TimestampOverflow`, replaced with - `TimestampNanosOutOfRange`; removed `ChronoParse`, replaced with `TimeParse`. -- `[tendermint-rpc]` Use `OffsetDateTime` and `Date` types provided by the `time` crate - in query operands instead of their `chrono` counterparts. - ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)) diff --git a/.changelog/unreleased/improvements/1030-new-time-api.md b/.changelog/unreleased/improvements/1030-new-time-api.md deleted file mode 100644 index a9c269f0d..000000000 --- a/.changelog/unreleased/improvements/1030-new-time-api.md +++ /dev/null @@ -1,11 +0,0 @@ -- Remove dependencies on the `chrono` crate. - ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)) -- `[tendermint]` Improve `tendermint::Time` - ([#1030](https://github.com/informalsystems/tendermint-rs/issues/1030)): - * Restrict the validity range of `Time` to dates with years in the range - 1-9999, to match the specification of protobuf message `Timestamp`. - Add an `ErrorDetail` variant `DateOutOfRange` to report when this - restriction is not met. - * Added a conversion to, and a fallible conversion from, - `OffsetDateTime` of the `time` crate. - * Added `Time` methods `checked_add` and `checked_sub`. From f9ae6a476472005324590e546051bcffcd10ad29 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Sat, 24 Sep 2022 00:07:29 +0300 Subject: [PATCH 24/25] Fix kvstore-test build --- tools/kvstore-test/tests/light-client.rs | 1 - tools/kvstore-test/tests/tendermint.rs | 13 +------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/tools/kvstore-test/tests/light-client.rs b/tools/kvstore-test/tests/light-client.rs index 6c784ad31..0abe2b713 100644 --- a/tools/kvstore-test/tests/light-client.rs +++ b/tools/kvstore-test/tests/light-client.rs @@ -14,7 +14,6 @@ use std::{convert::TryFrom, time::Duration}; -use tendermint::abci::transaction::Hash as TxHash; use tendermint_light_client::{ builder::{LightClientBuilder, SupervisorBuilder}, components::io::{AtHeight, Io, IoError, ProdIo}, diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs index 4c01c375d..a9c450231 100644 --- a/tools/kvstore-test/tests/tendermint.rs +++ b/tools/kvstore-test/tests/tendermint.rs @@ -22,27 +22,16 @@ mod rpc { use futures::StreamExt; use tendermint::{ - abci::{Code, Log, Transaction}, block::Height, merkle::simple_hash_from_byte_vectors, }; use tendermint_rpc::{ + abci::{Code, Log, Transaction}, endpoint::tx::Response as ResultTx, event::{Event, EventData, TxInfo}, query::{EventType, Query}, Client, HttpClient, Id, Order, SubscriptionClient, WebSocketClient, WebSocketClientDriver, }; - - use futures::StreamExt; - use std::convert::TryFrom; - use std::sync::atomic::{AtomicU8, Ordering}; - use tendermint::block::Height; - use tendermint::merkle::simple_hash_from_byte_vectors; - use tendermint_rpc::abci::Log; - use tendermint_rpc::abci::{Code, Transaction}; - use tendermint_rpc::endpoint::tx::Response as ResultTx; - use tendermint_rpc::event::{Event, EventData, TxInfo}; - use tendermint_rpc::query::{EventType, Query}; use tokio::time::Duration; static LOGGING_INIT: AtomicU8 = AtomicU8::new(0); From 308d060ce5b0d32a7f255640cd6a270f2dd6aaa6 Mon Sep 17 00:00:00 2001 From: Mikhail Zabaluev Date: Mon, 26 Sep 2022 17:53:24 +0300 Subject: [PATCH 25/25] Changelog entry for switching to Bytes --- .../unreleased/breaking-changes/1203-bytes-for-abci-proto.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/breaking-changes/1203-bytes-for-abci-proto.md diff --git a/.changelog/unreleased/breaking-changes/1203-bytes-for-abci-proto.md b/.changelog/unreleased/breaking-changes/1203-bytes-for-abci-proto.md new file mode 100644 index 000000000..c06a80b7f --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1203-bytes-for-abci-proto.md @@ -0,0 +1,2 @@ +- `[tendermint-proto]` Use `Bytes` for byte array fields of ABCI protobuf types. + ([#1203](https://github.com/informalsystems/tendermint-rs/pull/1203))