Skip to content

Commit

Permalink
rpc: add support for /tx endpoint
Browse files Browse the repository at this point in the history
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: tendermint/tendermint#6359
  • Loading branch information
tarcieri committed Apr 16, 2021
1 parent 4fa065b commit 03860fb
Show file tree
Hide file tree
Showing 9 changed files with 356 additions and 14 deletions.
5 changes: 5 additions & 0 deletions rpc/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<tx::Response> {
self.perform(tx::Request::new(hash, prove)).await
}

/// `/tx_search`: search for transactions with their results.
async fn tx_search(
&self,
Expand Down
1 change: 1 addition & 0 deletions rpc/src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
46 changes: 46 additions & 0 deletions rpc/src/endpoint/tx.rs
Original file line number Diff line number Diff line change
@@ -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<TxProof>,
}

impl crate::Response for Response {}
19 changes: 6 additions & 13 deletions rpc/src/endpoint/tx_search.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down Expand Up @@ -48,20 +48,13 @@ impl crate::SimpleRequest for Request {}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Response {
pub txs: Vec<ResultTx>,
pub txs: Vec<tx::Response>,
#[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<TxProof>,
}
// TODO: remove this after the next breaking release
#[deprecated(note = "use endpoint::tx::Response instead")]
pub type ResultTx = tx::Response;
5 changes: 5 additions & 0 deletions rpc/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ pub enum Method {
/// Get node status
Status,

/// Find transaction by hash
Tx,

/// Search for transactions with their results
TxSearch,

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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,
Expand Down
80 changes: 80 additions & 0 deletions rpc/tests/parse_response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -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]
Expand Down
101 changes: 101 additions & 0 deletions rpc/tests/support/tx_no_prove.json
Original file line number Diff line number Diff line change
@@ -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="
}
}
Loading

0 comments on commit 03860fb

Please sign in to comment.