Repository: cometbft/tendermint-rs (Rust)

Issue:
Incorrect deserialization of `ResultBroadcastTx`
When broadcasting a transaction using CometBFT RPC, it returns a `ResultBroadcastTx` where the `Data` field is encoded in Hex:

https://github.com/cometbft/cometbft/blob/v0.38.17/rpc/core/types/responses.go#L177

```go
// CheckTx result
type ResultBroadcastTx struct {
	Code      uint32         `json:"code"`
	Data      bytes.HexBytes `json:"data"`  // <------ here!!!
	Log       string         `json:"log"`
	Codespace string         `json:"codespace"`

	Hash bytes.HexBytes `json:"hash"`
}
```

However, tendermint-rs attempts to deserialize this as Base64:

https://github.com/informalsystems/tendermint-rs/blob/main/rpc/src/endpoint/broadcast/tx_sync.rs#L48

```rust
/// Response from either an async or sync transaction broadcast request.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Response {
    /// Code space
    pub codespace: String,

    /// Code
    pub code: Code,

    /// Data
    #[serde(with = "serializers::bytes::base64string")]  // <------ here!!!
    pub data: Bytes,

    /// Log
    pub log: String,

    /// Transaction hash
    pub hash: Hash,
}
```

This causes deserialization to return gibberish data.


Discussion:
Seems like a duplicate of https://github.com/informalsystems/tendermint-rs/issues/1251
Here's a tentative fix that works for us. But we only use `tx_sync`; we're not familiar with `tx_async` and `tx_commit`.

https://github.com/informalsystems/tendermint-rs/compare/v0.40.3...left-curve:tendermint-rs:v0.40.3-grug



Patch:
diff --git a/.changelog/unreleased/bug-fixes/1500-result-broadcast.md b/.changelog/unreleased/bug-fixes/1500-result-broadcast.md
new file mode 100644
index 000000000..11d73ec6e
--- /dev/null
+++ b/.changelog/unreleased/bug-fixes/1500-result-broadcast.md
@@ -0,0 +1,2 @@
+- Fix deserialization of `data` field in `ResultBroadcastTx`
+  ([\#1500](https://github.com/informalsystems/tendermint-rs/issues/1500))
\ No newline at end of file
diff --git a/proto/src/serializers/bytes.rs b/proto/src/serializers/bytes.rs
index b17a79392..29204d605 100644
--- a/proto/src/serializers/bytes.rs
+++ b/proto/src/serializers/bytes.rs
@@ -9,13 +9,15 @@ pub mod hexstring {
     use crate::serializers::cow_str::CowStr;
 
     /// Deserialize a hex-encoded string into `Vec<u8>`
-    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
+    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
     where
         D: Deserializer<'de>,
+        T: From<Vec<u8>>,
     {
         let string = Option::<CowStr<'_>>::deserialize(deserializer)?.unwrap_or_default();
         hex::decode_upper(&string)
             .or_else(|_| hex::decode(&string))
+            .map(Into::into)
             .map_err(serde::de::Error::custom)
     }
 
diff --git a/rpc/src/endpoint/broadcast/tx_async.rs b/rpc/src/endpoint/broadcast/tx_async.rs
index 9ea161f8e..7fe404e0c 100644
--- a/rpc/src/endpoint/broadcast/tx_async.rs
+++ b/rpc/src/endpoint/broadcast/tx_async.rs
@@ -45,7 +45,7 @@ pub struct Response {
     pub code: Code,
 
     /// Data
-    #[serde(with = "serializers::bytes::base64string")]
+    #[serde(with = "serializers::bytes::hexstring")]
     pub data: Bytes,
 
     /// Log
diff --git a/rpc/src/endpoint/broadcast/tx_sync.rs b/rpc/src/endpoint/broadcast/tx_sync.rs
index 350148653..f03d934f4 100644
--- a/rpc/src/endpoint/broadcast/tx_sync.rs
+++ b/rpc/src/endpoint/broadcast/tx_sync.rs
@@ -45,7 +45,7 @@ pub struct Response {
     pub code: Code,
 
     /// Data
-    #[serde(with = "serializers::bytes::base64string")]
+    #[serde(with = "serializers::bytes::hexstring")]
     pub data: Bytes,
 
     /// Log


Test patch:
diff --git a/rpc/tests/kvstore_fixtures/v0_34.rs b/rpc/tests/kvstore_fixtures/v0_34.rs
index 68b6435cb..4ebc63fae 100644
--- a/rpc/tests/kvstore_fixtures/v0_34.rs
+++ b/rpc/tests/kvstore_fixtures/v0_34.rs
@@ -483,7 +483,13 @@ fn incoming_fixtures() {
             "broadcast_tx_async" => {
                 let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap();
                 assert_eq!(result.code, abci::Code::Ok);
-                assert!(result.data.is_empty());
+                // "broadcast_tx_async result code" string
+                // "62726f6164636173745f74785f6173796e6320726573756c7420636f6465" hex encoded
+                assert_eq!(
+                    result.data,
+                    hex::decode("62726f6164636173745f74785f6173796e6320726573756c7420636f6465")
+                        .unwrap()
+                );
                 assert_ne!(
                     result.hash,
                     Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap()
@@ -559,7 +565,13 @@ fn incoming_fixtures() {
             "broadcast_tx_sync" => {
                 let result = endpoint::broadcast::tx_sync::Response::from_string(content).unwrap();
                 assert_eq!(result.code, abci::Code::Ok);
-                assert!(result.data.is_empty());
+                // "broadcast_tx_sync result code" string
+                // "62726f6164636173745f74785f73796e6320726573756c7420636f6465" hex encoded
+                assert_eq!(
+                    result.data,
+                    hex::decode("62726f6164636173745f74785f73796e6320726573756c7420636f6465")
+                        .unwrap()
+                );
                 assert_ne!(
                     result.hash,
                     Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap()
diff --git a/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_async.json b/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_async.json
index c7e0da79d..ff443a3e3 100644
--- a/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_async.json
+++ b/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_async.json
@@ -4,7 +4,7 @@
   "result": {
     "code": 0,
     "codespace": "",
-    "data": "",
+    "data": "62726f6164636173745f74785f6173796e6320726573756c7420636f6465",
     "hash": "9F28904F9C0F3AB74A81CBA48E39124DA1C680B47FBFCBA0126870DB722BCC30",
     "log": ""
   }
diff --git a/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_sync.json b/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_sync.json
index 88104ec77..f153b218b 100644
--- a/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_sync.json
+++ b/rpc/tests/kvstore_fixtures/v0_34/incoming/broadcast_tx_sync.json
@@ -4,7 +4,7 @@
   "result": {
     "code": 0,
     "codespace": "",
-    "data": "",
+    "data": "62726f6164636173745f74785f73796e6320726573756c7420636f6465",
     "hash": "57018296EE0919C9D351F2FFEA82A8D28DE223724D79965FC8D00A7477ED48BC",
     "log": ""
   }
diff --git a/rpc/tests/kvstore_fixtures/v0_38.rs b/rpc/tests/kvstore_fixtures/v0_38.rs
index b8827fea8..915ce6068 100644
--- a/rpc/tests/kvstore_fixtures/v0_38.rs
+++ b/rpc/tests/kvstore_fixtures/v0_38.rs
@@ -484,7 +484,11 @@ fn incoming_fixtures() {
             "broadcast_tx_async" => {
                 let result = endpoint::broadcast::tx_async::Response::from_string(content).unwrap();
                 assert_eq!(result.code, abci::Code::Ok);
-                assert!(result.data.is_empty());
+                assert_eq!(
+                    result.data,
+                    hex::decode("62726f6164636173745f74785f6173796e6320726573756c7420636f6465")
+                        .unwrap()
+                );
                 assert_ne!(
                     result.hash,
                     Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap()
@@ -558,7 +562,11 @@ fn incoming_fixtures() {
             "broadcast_tx_sync" => {
                 let result = endpoint::broadcast::tx_sync::Response::from_string(content).unwrap();
                 assert_eq!(result.code, abci::Code::Ok);
-                assert!(result.data.is_empty());
+                assert_eq!(
+                    result.data,
+                    hex::decode("62726f6164636173745f74785f73796e6320726573756c7420636f6465")
+                        .unwrap()
+                );
                 assert_ne!(
                     result.hash,
                     Hash::from_bytes(Algorithm::Sha256, &[0; 32]).unwrap()
diff --git a/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_async.json b/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_async.json
index f36c70747..039262d0f 100644
--- a/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_async.json
+++ b/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_async.json
@@ -4,7 +4,7 @@
   "result": {
     "code": 0,
     "codespace": "",
-    "data": "",
+    "data": "62726f6164636173745f74785f6173796e6320726573756c7420636f6465",
     "hash": "9F28904F9C0F3AB74A81CBA48E39124DA1C680B47FBFCBA0126870DB722BCC30",
     "log": ""
   }
diff --git a/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_sync.json b/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_sync.json
index 408b83ebb..92c1cd2aa 100644
--- a/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_sync.json
+++ b/rpc/tests/kvstore_fixtures/v0_38/incoming/broadcast_tx_sync.json
@@ -4,7 +4,7 @@
   "result": {
     "code": 0,
     "codespace": "",
-    "data": "",
+    "data": "62726f6164636173745f74785f73796e6320726573756c7420636f6465",
     "hash": "57018296EE0919C9D351F2FFEA82A8D28DE223724D79965FC8D00A7477ED48BC",
     "log": ""
   }