From 03860fbdfdaacc50ecc36c7cb05e61a3f0e6b195 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Fri, 16 Apr 2021 09:30:18 -0700 Subject: [PATCH] rpc: add support for `/tx` endpoint Support for finding a transaction directly by its hash. In theory this endpoint should have read-your-writes (RYW) consistency whereas `/tx_search` does not. See: https://github.com/tendermint/tendermint/issues/6359 --- rpc/src/client.rs | 5 ++ rpc/src/endpoint.rs | 1 + rpc/src/endpoint/tx.rs | 46 ++++++++++ rpc/src/endpoint/tx_search.rs | 19 ++--- rpc/src/method.rs | 5 ++ rpc/tests/parse_response.rs | 80 ++++++++++++++++++ rpc/tests/support/tx_no_prove.json | 101 ++++++++++++++++++++++ rpc/tests/support/tx_with_prove.json | 111 +++++++++++++++++++++++++ tools/kvstore-test/tests/tendermint.rs | 2 +- 9 files changed, 356 insertions(+), 14 deletions(-) create mode 100644 rpc/src/endpoint/tx.rs create mode 100644 rpc/tests/support/tx_no_prove.json create mode 100644 rpc/tests/support/tx_with_prove.json diff --git a/rpc/src/client.rs b/rpc/src/client.rs index bcab4946e..1e61980ae 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -211,6 +211,11 @@ pub trait Client { self.perform(evidence::Request::new(e)).await } + /// `/tx`: find transaction by hash. + async fn tx(&self, hash: abci::transaction::Hash, prove: bool) -> Result { + self.perform(tx::Request::new(hash, prove)).await + } + /// `/tx_search`: search for transactions with their results. async fn tx_search( &self, diff --git a/rpc/src/endpoint.rs b/rpc/src/endpoint.rs index 9b0da8213..caa85a052 100644 --- a/rpc/src/endpoint.rs +++ b/rpc/src/endpoint.rs @@ -14,6 +14,7 @@ pub mod health; pub mod net_info; pub mod status; pub mod subscribe; +pub mod tx; pub mod tx_search; pub mod unsubscribe; pub mod validators; diff --git a/rpc/src/endpoint/tx.rs b/rpc/src/endpoint/tx.rs new file mode 100644 index 000000000..c2e9224bf --- /dev/null +++ b/rpc/src/endpoint/tx.rs @@ -0,0 +1,46 @@ +//! `/tx` endpoint JSON-RPC wrapper + +use crate::Method; +use serde::{Deserialize, Serialize}; +use tendermint::{abci, block}; +use tendermint_proto::types::TxProof; + +/// Request for finding a transaction by its hash. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct Request { + pub hash: String, + pub prove: bool, +} + +impl Request { + /// Constructor. + pub fn new(hash: abci::transaction::Hash, prove: bool) -> Self { + Self { + hash: format!("0x{}", &hash), + prove, + } + } +} + +impl crate::Request for Request { + type Response = Response; + + fn method(&self) -> Method { + Method::Tx + } +} + +impl crate::SimpleRequest for Request {} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Response { + pub hash: abci::transaction::Hash, + pub height: block::Height, + pub index: u32, + pub tx_result: abci::DeliverTx, + pub tx: abci::Transaction, + #[serde(skip_serializing_if = "Option::is_none")] + pub proof: Option, +} + +impl crate::Response for Response {} diff --git a/rpc/src/endpoint/tx_search.rs b/rpc/src/endpoint/tx_search.rs index b31a7a2f2..ba823a369 100644 --- a/rpc/src/endpoint/tx_search.rs +++ b/rpc/src/endpoint/tx_search.rs @@ -1,9 +1,9 @@ //! `/tx_search` endpoint JSON-RPC wrapper +pub use super::tx; + use crate::{Method, Order}; use serde::{Deserialize, Serialize}; -use tendermint::{abci, block}; -use tendermint_proto::types::TxProof; /// Request for searching for transactions with their results. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] @@ -48,20 +48,13 @@ impl crate::SimpleRequest for Request {} #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Response { - pub txs: Vec, + pub txs: Vec, #[serde(with = "tendermint_proto::serializers::from_str")] pub total_count: u32, } impl crate::Response for Response {} -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct ResultTx { - pub hash: abci::transaction::Hash, - pub height: block::Height, - pub index: u32, - pub tx_result: abci::DeliverTx, - pub tx: abci::Transaction, - #[serde(skip_serializing_if = "Option::is_none")] - pub proof: Option, -} +// TODO: remove this after the next breaking release +#[deprecated(note = "use endpoint::tx::Response instead")] +pub type ResultTx = tx::Response; diff --git a/rpc/src/method.rs b/rpc/src/method.rs index c20db535f..bc44eb72e 100644 --- a/rpc/src/method.rs +++ b/rpc/src/method.rs @@ -54,6 +54,9 @@ pub enum Method { /// Get node status Status, + /// Find transaction by hash + Tx, + /// Search for transactions with their results TxSearch, @@ -90,6 +93,7 @@ impl Method { Method::NetInfo => "net_info", Method::Status => "status", Method::Subscribe => "subscribe", + Method::Tx => "tx", Method::TxSearch => "tx_search", Method::Unsubscribe => "unsubscribe", Method::Validators => "validators", @@ -118,6 +122,7 @@ impl FromStr for Method { "net_info" => Method::NetInfo, "status" => Method::Status, "subscribe" => Method::Subscribe, + "tx" => Method::Tx, "tx_search" => Method::TxSearch, "unsubscribe" => Method::Unsubscribe, "validators" => Method::Validators, diff --git a/rpc/tests/parse_response.rs b/rpc/tests/parse_response.rs index 61d82af76..8a9ecd402 100644 --- a/rpc/tests/parse_response.rs +++ b/rpc/tests/parse_response.rs @@ -306,6 +306,80 @@ fn jsonrpc_error() { } } +#[test] +fn tx_no_prove() { + let tx = endpoint::tx::Response::from_string(&read_json_fixture("tx_no_prove")).unwrap(); + + assert_eq!( + "291B44C883803751917D547238EAC419E968C0171A3154D777B2EA8EA5039C57", + tx.hash.to_string() + ); + assert_eq!(2, tx.height.value()); + + let events = &tx.tx_result.events; + assert_eq!(events.len(), 6); + assert_eq!(events[0].attributes.len(), 3); + assert_eq!(events[0].attributes[0].key.as_ref(), "recipient"); + assert_eq!( + events[0].attributes[0].value.as_ref(), + "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta" + ); + + assert!(tx.proof.is_none()); +} + +#[test] +fn tx_with_prove() { + let tx = endpoint::tx::Response::from_string(&read_json_fixture("tx_with_prove")).unwrap(); + + assert_eq!( + "291B44C883803751917D547238EAC419E968C0171A3154D777B2EA8EA5039C57", + tx.hash.to_string() + ); + assert_eq!(2, tx.height.value()); + + let events = &tx.tx_result.events; + assert_eq!(events.len(), 6); + assert_eq!(events[0].attributes.len(), 3); + assert_eq!(events[0].attributes[0].key.as_ref(), "recipient"); + assert_eq!( + events[0].attributes[0].value.as_ref(), + "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta" + ); + + let proof = tx.proof.as_ref().unwrap(); + assert_eq!( + vec![ + 10, 159, 1, 10, 142, 1, 10, 28, 47, 99, 111, 115, 109, 111, 115, 46, 98, 97, 110, 107, + 46, 118, 49, 98, 101, 116, 97, 49, 46, 77, 115, 103, 83, 101, 110, 100, 18, 110, 10, + 45, 99, 111, 115, 109, 111, 115, 49, 115, 50, 116, 119, 52, 53, 99, 55, 115, 116, 115, + 97, 102, 107, 52, 50, 118, 115, 122, 57, 115, 106, 48, 57, 106, 109, 48, 57, 121, 54, + 116, 107, 52, 113, 101, 101, 114, 104, 18, 45, 99, 111, 115, 109, 111, 115, 49, 110, + 118, 51, 117, 102, 55, 104, 112, 117, 118, 107, 52, 101, 109, 51, 57, 118, 120, 114, + 57, 52, 52, 104, 112, 104, 117, 106, 116, 117, 113, 97, 50, 120, 108, 55, 54, 56, 56, + 26, 14, 10, 9, 115, 97, 109, 111, 108, 101, 97, 110, 115, 18, 1, 49, 18, 9, 116, 101, + 115, 116, 32, 109, 101, 109, 111, 24, 169, 70, 18, 102, 10, 78, 10, 70, 10, 31, 47, 99, + 111, 115, 109, 111, 115, 46, 99, 114, 121, 112, 116, 111, 46, 115, 101, 99, 112, 50, + 53, 54, 107, 49, 46, 80, 117, 98, 75, 101, 121, 18, 35, 10, 33, 3, 98, 211, 158, 175, + 190, 7, 170, 66, 0, 20, 131, 204, 81, 56, 214, 191, 143, 101, 195, 149, 126, 234, 114, + 55, 58, 237, 26, 39, 95, 114, 111, 164, 18, 4, 10, 2, 8, 1, 18, 20, 10, 14, 10, 9, 115, + 97, 109, 111, 108, 101, 97, 110, 115, 18, 1, 49, 16, 160, 141, 6, 26, 64, 185, 213, + 205, 42, 231, 20, 240, 14, 103, 188, 41, 94, 116, 55, 181, 30, 185, 212, 221, 131, 145, + 132, 32, 83, 223, 255, 85, 10, 220, 211, 124, 172, 29, 152, 55, 91, 199, 85, 165, 186, + 68, 87, 22, 14, 235, 208, 43, 62, 93, 129, 228, 237, 222, 77, 146, 245, 107, 123, 173, + 19, 73, 154, 174, 249 + ], + proof.data + ); + assert_eq!( + vec![ + 105, 196, 2, 216, 75, 198, 114, 80, 111, 27, 54, 17, 4, 107, 139, 37, 40, 156, 38, 0, + 253, 122, 0, 118, 137, 197, 148, 154, 51, 32, 101, 87 + ], + proof.root_hash + ); +} + #[test] fn tx_search_no_prove() { let response = @@ -320,6 +394,12 @@ fn tx_search_no_prove() { ); assert_eq!(11, response.txs[0].height.value()); assert!(response.txs[0].proof.is_none()); + + let events = &response.txs[0].tx_result.events; + assert_eq!(events.len(), 1); + assert_eq!(events[0].attributes.len(), 4); + assert_eq!(events[0].attributes[0].key.as_ref(), "creator"); + assert_eq!(events[0].attributes[0].value.as_ref(), "Cosmoshi Netowoko"); } #[test] diff --git a/rpc/tests/support/tx_no_prove.json b/rpc/tests/support/tx_no_prove.json new file mode 100644 index 000000000..de000f369 --- /dev/null +++ b/rpc/tests/support/tx_no_prove.json @@ -0,0 +1,101 @@ +{ + "jsonrpc": "2.0", + "id": -1, + "result": { + "hash": "291B44C883803751917D547238EAC419E968C0171A3154D777B2EA8EA5039C57", + "height": "2", + "index": 0, + "tx_result": { + "code": 0, + "data": "CgYKBHNlbmQ=", + "log": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1s2tw45c7stsafk42vsz9sj09jm09y6tk4qeerh\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1nv3uf7hpuvk4em39vxr944hphujtuqa2xl7688\"},{\"key\":\"sender\",\"value\":\"cosmos1s2tw45c7stsafk42vsz9sj09jm09y6tk4qeerh\"},{\"key\":\"amount\",\"value\":\"1samoleans\"}]}]}]", + "info": "", + "gas_wanted": "100000", + "gas_used": "75848", + "events": [ + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "Y29zbW9zMTd4cGZ2YWttMmFtZzk2MnlsczZmODR6M2tlbGw4YzVsc2VycXRh", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MXNhbW9sZWFucw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "Y29zbW9zMW52M3VmN2hwdXZrNGVtMzl2eHI5NDRocGh1anR1cWEyeGw3Njg4", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MXNhbW9sZWFucw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + } + ], + "codespace": "" + }, + "tx": "Cp8BCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLWNvc21vczFzMnR3NDVjN3N0c2FmazQydnN6OXNqMDlqbTA5eTZ0azRxZWVyaBItY29zbW9zMW52M3VmN2hwdXZrNGVtMzl2eHI5NDRocGh1anR1cWEyeGw3Njg4Gg4KCXNhbW9sZWFucxIBMRIJdGVzdCBtZW1vGKlGEmYKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNi056vvgeqQgAUg8xRONa/j2XDlX7qcjc67RonX3JvpBIECgIIARIUCg4KCXNhbW9sZWFucxIBMRCgjQYaQLnVzSrnFPAOZ7wpXnQ3tR651N2DkYQgU9//VQrc03ysHZg3W8dVpbpEVxYO69ArPl2B5O3eTZL1a3utE0marvk=" + } +} diff --git a/rpc/tests/support/tx_with_prove.json b/rpc/tests/support/tx_with_prove.json new file mode 100644 index 000000000..0b6ba25ae --- /dev/null +++ b/rpc/tests/support/tx_with_prove.json @@ -0,0 +1,111 @@ +{ + "jsonrpc": "2.0", + "id": -1, + "result": { + "hash": "291B44C883803751917D547238EAC419E968C0171A3154D777B2EA8EA5039C57", + "height": "2", + "index": 0, + "tx_result": { + "code": 0, + "data": "CgYKBHNlbmQ=", + "log": "[{\"events\":[{\"type\":\"message\",\"attributes\":[{\"key\":\"action\",\"value\":\"send\"},{\"key\":\"sender\",\"value\":\"cosmos1s2tw45c7stsafk42vsz9sj09jm09y6tk4qeerh\"},{\"key\":\"module\",\"value\":\"bank\"}]},{\"type\":\"transfer\",\"attributes\":[{\"key\":\"recipient\",\"value\":\"cosmos1nv3uf7hpuvk4em39vxr944hphujtuqa2xl7688\"},{\"key\":\"sender\",\"value\":\"cosmos1s2tw45c7stsafk42vsz9sj09jm09y6tk4qeerh\"},{\"key\":\"amount\",\"value\":\"1samoleans\"}]}]}]", + "info": "", + "gas_wanted": "100000", + "gas_used": "75848", + "events": [ + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "Y29zbW9zMTd4cGZ2YWttMmFtZzk2MnlsczZmODR6M2tlbGw4YzVsc2VycXRh", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MXNhbW9sZWFucw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "YWN0aW9u", + "value": "c2VuZA==", + "index": true + } + ] + }, + { + "type": "transfer", + "attributes": [ + { + "key": "cmVjaXBpZW50", + "value": "Y29zbW9zMW52M3VmN2hwdXZrNGVtMzl2eHI5NDRocGh1anR1cWEyeGw3Njg4", + "index": true + }, + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + }, + { + "key": "YW1vdW50", + "value": "MXNhbW9sZWFucw==", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "c2VuZGVy", + "value": "Y29zbW9zMXMydHc0NWM3c3RzYWZrNDJ2c3o5c2owOWptMDl5NnRrNHFlZXJo", + "index": true + } + ] + }, + { + "type": "message", + "attributes": [ + { + "key": "bW9kdWxl", + "value": "YmFuaw==", + "index": true + } + ] + } + ], + "codespace": "" + }, + "tx": "Cp8BCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLWNvc21vczFzMnR3NDVjN3N0c2FmazQydnN6OXNqMDlqbTA5eTZ0azRxZWVyaBItY29zbW9zMW52M3VmN2hwdXZrNGVtMzl2eHI5NDRocGh1anR1cWEyeGw3Njg4Gg4KCXNhbW9sZWFucxIBMRIJdGVzdCBtZW1vGKlGEmYKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNi056vvgeqQgAUg8xRONa/j2XDlX7qcjc67RonX3JvpBIECgIIARIUCg4KCXNhbW9sZWFucxIBMRCgjQYaQLnVzSrnFPAOZ7wpXnQ3tR651N2DkYQgU9//VQrc03ysHZg3W8dVpbpEVxYO69ArPl2B5O3eTZL1a3utE0marvk=", + "proof": { + "root_hash": "69C402D84BC672506F1B3611046B8B25289C2600FD7A007689C5949A33206557", + "data": "Cp8BCo4BChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEm4KLWNvc21vczFzMnR3NDVjN3N0c2FmazQydnN6OXNqMDlqbTA5eTZ0azRxZWVyaBItY29zbW9zMW52M3VmN2hwdXZrNGVtMzl2eHI5NDRocGh1anR1cWEyeGw3Njg4Gg4KCXNhbW9sZWFucxIBMRIJdGVzdCBtZW1vGKlGEmYKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQNi056vvgeqQgAUg8xRONa/j2XDlX7qcjc67RonX3JvpBIECgIIARIUCg4KCXNhbW9sZWFucxIBMRCgjQYaQLnVzSrnFPAOZ7wpXnQ3tR651N2DkYQgU9//VQrc03ysHZg3W8dVpbpEVxYO69ArPl2B5O3eTZL1a3utE0marvk=", + "proof": { + "total": "1", + "index": "0", + "leaf_hash": "acQC2EvGclBvGzYRBGuLJSicJgD9egB2icWUmjMgZVc=", + "aunts": [] + } + } + } +} diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs index 5f2161d05..79781295c 100644 --- a/tools/kvstore-test/tests/tendermint.rs +++ b/tools/kvstore-test/tests/tendermint.rs @@ -28,7 +28,7 @@ mod rpc { use tendermint::abci::{Code, Transaction}; use tendermint::block::Height; use tendermint::merkle::simple_hash_from_byte_vectors; - use tendermint_rpc::endpoint::tx_search::ResultTx; + 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;