From c3f20de73b51fea7894a58618dcb5360f995dcef Mon Sep 17 00:00:00 2001 From: Fabien Penso Date: Fri, 29 Mar 2024 19:05:21 +0100 Subject: [PATCH] tendermint: Add `Vec` for `EventAttribute` for 0.34 (#1405) 0.34 doesn't enforce UTF8 for event attribute values. Adding a Vec for those, and keeping String for later versions. --- .../1405-change-event-attribute-value.md | 4 + rpc/src/dialect/v0_34.rs | 14 +- rpc/tests/kvstore_fixtures/v0_34.rs | 106 +++++---- rpc/tests/kvstore_fixtures/v0_37.rs | 106 +++++---- rpc/tests/kvstore_fixtures/v0_38.rs | 90 ++++--- tendermint/src/abci.rs | 1 + tendermint/src/abci/event.rs | 225 ++++++++++++++---- 7 files changed, 366 insertions(+), 180 deletions(-) create mode 100644 .changelog/unreleased/breaking-changes/1405-change-event-attribute-value.md diff --git a/.changelog/unreleased/breaking-changes/1405-change-event-attribute-value.md b/.changelog/unreleased/breaking-changes/1405-change-event-attribute-value.md new file mode 100644 index 000000000..9ccdc2555 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1405-change-event-attribute-value.md @@ -0,0 +1,4 @@ +- `[tendermint]` Change `EventAttribute` `value` from `String` to `Vec` for + TM34. `key`, `value` and `index` now have to be called through `key()`, + `value_str()` and `index()` to support both `Vec` and `String`. + ([\#1400](https://github.com/informalsystems/tendermint-rs/issues/1400)). diff --git a/rpc/src/dialect/v0_34.rs b/rpc/src/dialect/v0_34.rs index 2102b71fa..0c0f8b621 100644 --- a/rpc/src/dialect/v0_34.rs +++ b/rpc/src/dialect/v0_34.rs @@ -50,9 +50,9 @@ pub struct EventAttribute { /// The event value. #[serde( serialize_with = "base64string::serialize", - deserialize_with = "base64string::deserialize_to_string" + deserialize_with = "base64string::deserialize" )] - pub value: String, + pub value: Vec, /// Whether Tendermint's indexer should index this event. /// /// **This field is nondeterministic**. @@ -61,20 +61,20 @@ pub struct EventAttribute { impl From for abci::EventAttribute { fn from(msg: EventAttribute) -> Self { - Self { + Self::V034(abci::v0_34::EventAttribute { key: msg.key, value: msg.value, index: msg.index, - } + }) } } impl From for EventAttribute { fn from(msg: abci::EventAttribute) -> Self { Self { - key: msg.key, - value: msg.value, - index: msg.index, + key: msg.key().clone(), + value: msg.value_as_bytes().to_vec(), + index: msg.index(), } } } diff --git a/rpc/tests/kvstore_fixtures/v0_34.rs b/rpc/tests/kvstore_fixtures/v0_34.rs index 1b845b544..c307bf790 100644 --- a/rpc/tests/kvstore_fixtures/v0_34.rs +++ b/rpc/tests/kvstore_fixtures/v0_34.rs @@ -509,21 +509,35 @@ fn incoming_fixtures() { assert!(result.tx_result.data.is_empty()); assert_eq!(result.tx_result.events.len(), 1); assert_eq!(result.tx_result.events[0].attributes.len(), 4); - assert_eq!(result.tx_result.events[0].attributes[0].key, "creator"); + assert_eq!(result.tx_result.events[0].attributes[0].key(), "creator"); assert_eq!( - result.tx_result.events[0].attributes[0].value, + result.tx_result.events[0].attributes[0] + .value_str() + .unwrap(), "Cosmoshi Netowoko" ); - assert_eq!(result.tx_result.events[0].attributes[1].key, "key"); - assert_eq!(result.tx_result.events[0].attributes[1].value, "commit-key"); - assert_eq!(result.tx_result.events[0].attributes[2].key, "index_key"); + assert_eq!(result.tx_result.events[0].attributes[1].key(), "key"); assert_eq!( - result.tx_result.events[0].attributes[2].value, + result.tx_result.events[0].attributes[1] + .value_str() + .unwrap(), + "commit-key" + ); + assert_eq!(result.tx_result.events[0].attributes[2].key(), "index_key"); + assert_eq!( + result.tx_result.events[0].attributes[2] + .value_str() + .unwrap(), "index is working" ); - assert_eq!(result.tx_result.events[0].attributes[3].key, "noindex_key"); assert_eq!( - result.tx_result.events[0].attributes[3].value, + result.tx_result.events[0].attributes[3].key(), + "noindex_key" + ); + assert_eq!( + result.tx_result.events[0].attributes[3] + .value_str() + .unwrap(), "index is working" ); assert_eq!(result.tx_result.events[0].kind, "app"); @@ -908,23 +922,23 @@ fn incoming_fixtures() { assert_eq!(rbb.events.len(), 2); assert_eq!(rbb.events[0].kind, "transfer"); assert_eq!(rbb.events[0].attributes.len(), 2); - assert_eq!(rbb.events[0].attributes[0].key, "recipient"); + assert_eq!(rbb.events[0].attributes[0].key(), "recipient"); assert_eq!( - rbb.events[0].attributes[0].value, + rbb.events[0].attributes[0].value_str().unwrap(), "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta" ); - assert!(rbb.events[0].attributes[0].index); - assert_eq!(rbb.events[0].attributes[1].key, "sender"); + assert!(rbb.events[0].attributes[0].index()); + assert_eq!(rbb.events[0].attributes[1].key(), "sender"); assert_eq!( - rbb.events[0].attributes[1].value, + rbb.events[0].attributes[1].value_str().unwrap(), "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q" ); - assert!(!rbb.events[0].attributes[1].index); + assert!(!rbb.events[0].attributes[1].index()); assert_eq!(rbb.events[1].kind, "message"); assert_eq!(rbb.events[1].attributes.len(), 1); - assert_eq!(rbb.events[1].attributes[0].key, "sender"); + assert_eq!(rbb.events[1].attributes[0].key(), "sender"); assert_eq!( - rbb.events[1].attributes[0].value, + rbb.events[1].attributes[0].value_str().unwrap(), "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q" ); let reb = result_end_block.unwrap(); @@ -1115,18 +1129,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx0"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx0"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgwPXZhbHVl").unwrap()); @@ -1147,18 +1161,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx1"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx1"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgxPXZhbHVl").unwrap()); @@ -1180,18 +1194,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx2"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx2"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgyPXZhbHVl").unwrap()); @@ -1212,18 +1226,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx3"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx3"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgzPXZhbHVl").unwrap()); @@ -1244,18 +1258,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 1); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx4"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx4"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHg0PXZhbHVl").unwrap()); diff --git a/rpc/tests/kvstore_fixtures/v0_37.rs b/rpc/tests/kvstore_fixtures/v0_37.rs index b3fd3eac8..3bdf8b43d 100644 --- a/rpc/tests/kvstore_fixtures/v0_37.rs +++ b/rpc/tests/kvstore_fixtures/v0_37.rs @@ -503,21 +503,35 @@ fn incoming_fixtures() { assert!(result.tx_result.data.is_empty()); assert_eq!(result.tx_result.events.len(), 2); assert_eq!(result.tx_result.events[0].attributes.len(), 4); - assert_eq!(result.tx_result.events[0].attributes[0].key, "creator"); + assert_eq!(result.tx_result.events[0].attributes[0].key(), "creator"); assert_eq!( - result.tx_result.events[0].attributes[0].value, + result.tx_result.events[0].attributes[0] + .value_str() + .unwrap(), "Cosmoshi Netowoko" ); - assert_eq!(result.tx_result.events[0].attributes[1].key, "key"); - assert_eq!(result.tx_result.events[0].attributes[1].value, "commit-key"); - assert_eq!(result.tx_result.events[0].attributes[2].key, "index_key"); + assert_eq!(result.tx_result.events[0].attributes[1].key(), "key"); assert_eq!( - result.tx_result.events[0].attributes[2].value, + result.tx_result.events[0].attributes[1] + .value_str() + .unwrap(), + "commit-key" + ); + assert_eq!(result.tx_result.events[0].attributes[2].key(), "index_key"); + assert_eq!( + result.tx_result.events[0].attributes[2] + .value_str() + .unwrap(), "index is working" ); - assert_eq!(result.tx_result.events[0].attributes[3].key, "noindex_key"); assert_eq!( - result.tx_result.events[0].attributes[3].value, + result.tx_result.events[0].attributes[3].key(), + "noindex_key" + ); + assert_eq!( + result.tx_result.events[0].attributes[3] + .value_str() + .unwrap(), "index is working" ); assert_eq!(result.tx_result.events[0].kind, "app"); @@ -902,23 +916,23 @@ fn incoming_fixtures() { assert_eq!(rbb.events.len(), 2); assert_eq!(rbb.events[0].kind, "transfer"); assert_eq!(rbb.events[0].attributes.len(), 2); - assert_eq!(rbb.events[0].attributes[0].key, "recipient"); + assert_eq!(rbb.events[0].attributes[0].key(), "recipient"); assert_eq!( - rbb.events[0].attributes[0].value, + rbb.events[0].attributes[0].value_str().unwrap(), "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta" ); - assert!(rbb.events[0].attributes[0].index); - assert_eq!(rbb.events[0].attributes[1].key, "sender"); + assert!(rbb.events[0].attributes[0].index()); + assert_eq!(rbb.events[0].attributes[1].key(), "sender"); assert_eq!( - rbb.events[0].attributes[1].value, + rbb.events[0].attributes[1].value_str().unwrap(), "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q" ); - assert!(!rbb.events[0].attributes[1].index); + assert!(!rbb.events[0].attributes[1].index()); assert_eq!(rbb.events[1].kind, "message"); assert_eq!(rbb.events[1].attributes.len(), 1); - assert_eq!(rbb.events[1].attributes[0].key, "sender"); + assert_eq!(rbb.events[1].attributes[0].key(), "sender"); assert_eq!( - rbb.events[1].attributes[0].value, + rbb.events[1].attributes[0].value_str().unwrap(), "cosmos1m3h30wlvsf8llruxtpukdvsy0km2kum8g38c8q" ); let reb = result_end_block.unwrap(); @@ -1109,18 +1123,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx0"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx0"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgwPXZhbHVl").unwrap()); @@ -1141,18 +1155,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx1"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx1"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgxPXZhbHVl").unwrap()); @@ -1174,18 +1188,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx2"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx2"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgyPXZhbHVl").unwrap()); @@ -1206,18 +1220,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx3"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx3"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgzPXZhbHVl").unwrap()); @@ -1238,18 +1252,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx4"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx4"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHg0PXZhbHVl").unwrap()); diff --git a/rpc/tests/kvstore_fixtures/v0_38.rs b/rpc/tests/kvstore_fixtures/v0_38.rs index 6885e5ba7..e014f4735 100644 --- a/rpc/tests/kvstore_fixtures/v0_38.rs +++ b/rpc/tests/kvstore_fixtures/v0_38.rs @@ -503,21 +503,35 @@ fn incoming_fixtures() { assert!(result.tx_result.data.is_empty()); assert_eq!(result.tx_result.events.len(), 2); assert_eq!(result.tx_result.events[0].attributes.len(), 4); - assert_eq!(result.tx_result.events[0].attributes[0].key, "creator"); + assert_eq!(result.tx_result.events[0].attributes[0].key(), "creator"); assert_eq!( - result.tx_result.events[0].attributes[0].value, + result.tx_result.events[0].attributes[0] + .value_str() + .unwrap(), "Cosmoshi Netowoko" ); - assert_eq!(result.tx_result.events[0].attributes[1].key, "key"); - assert_eq!(result.tx_result.events[0].attributes[1].value, "commit-key"); - assert_eq!(result.tx_result.events[0].attributes[2].key, "index_key"); + assert_eq!(result.tx_result.events[0].attributes[1].key(), "key"); assert_eq!( - result.tx_result.events[0].attributes[2].value, + result.tx_result.events[0].attributes[1] + .value_str() + .unwrap(), + "commit-key" + ); + assert_eq!(result.tx_result.events[0].attributes[2].key(), "index_key"); + assert_eq!( + result.tx_result.events[0].attributes[2] + .value_str() + .unwrap(), "index is working" ); - assert_eq!(result.tx_result.events[0].attributes[3].key, "noindex_key"); assert_eq!( - result.tx_result.events[0].attributes[3].value, + result.tx_result.events[0].attributes[3].key(), + "noindex_key" + ); + assert_eq!( + result.tx_result.events[0].attributes[3] + .value_str() + .unwrap(), "index is working" ); assert_eq!(result.tx_result.events[0].kind, "app"); @@ -1101,18 +1115,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx0"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx0"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgwPXZhbHVl").unwrap()); @@ -1133,18 +1147,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx1"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx1"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgxPXZhbHVl").unwrap()); @@ -1166,18 +1180,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx2"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx2"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgyPXZhbHVl").unwrap()); @@ -1198,18 +1212,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx3"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx3"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHgzPXZhbHVl").unwrap()); @@ -1230,18 +1244,18 @@ fn incoming_fixtures() { assert_eq!(tx_result.result.events.len(), 2); assert_eq!(tx_result.result.events[0].kind, "app"); for attr in &tx_result.result.events[0].attributes { - match attr.key.as_str() { + match attr.key().as_str() { "creator" => { - assert_eq!(attr.value, "Cosmoshi Netowoko") + assert_eq!(attr.value_str().unwrap(), "Cosmoshi Netowoko") }, - "key" => assert_eq!(attr.value, "tx4"), + "key" => assert_eq!(attr.value_str().unwrap(), "tx4"), "index_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, "noindex_key" => { - assert_eq!(attr.value, "index is working") + assert_eq!(attr.value_str().unwrap(), "index is working") }, - _ => panic!("unknown attribute found {}", attr.key), + _ => panic!("unknown attribute found {}", attr.key()), } } assert_eq!(tx_result.tx, base64::decode("dHg0PXZhbHVl").unwrap()); diff --git a/tendermint/src/abci.rs b/tendermint/src/abci.rs index 2fae2e88d..379284ba8 100644 --- a/tendermint/src/abci.rs +++ b/tendermint/src/abci.rs @@ -53,6 +53,7 @@ pub use crate::v0_38::abci::response::{ ConsensusResponse, InfoResponse, MempoolResponse, SnapshotResponse, }; +pub use event::v0_34; pub use event::{Event, EventAttribute, EventAttributeIndexExt, TypedEvent}; #[doc(inline)] diff --git a/tendermint/src/abci/event.rs b/tendermint/src/abci/event.rs index 7a3cd3301..97905be31 100644 --- a/tendermint/src/abci/event.rs +++ b/tendermint/src/abci/event.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; use crate::prelude::*; -use crate::serializers; /// An event that occurred while processing a request. /// @@ -25,6 +24,62 @@ pub struct Event { pub attributes: Vec, } +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, Hash)] +#[serde(untagged)] +pub enum EventAttribute { + /// EventAttribute value in TM34 is a byte array. + V034(v0_34::EventAttribute), + /// EventAttribute value in TM37 and later is a string. + V037(v0_37::EventAttribute), +} + +impl EventAttribute { + /// Access the `key` field common to all variants of the enum. + pub fn key(&self) -> &String { + match self { + EventAttribute::V034(attr) => &attr.key, + EventAttribute::V037(attr) => &attr.key, + } + } + + /// Access the `value` field common to all variants of the enum. Will return error if the value + /// is malformed UTF8. + pub fn value_str(&self) -> Result<&str, crate::Error> { + match self { + EventAttribute::V034(attr) => { + Ok(std::str::from_utf8(&attr.value) + .map_err(|e| crate::Error::parse(e.to_string()))?) + }, + EventAttribute::V037(attr) => Ok(&attr.value), + } + } + + /// Access the `value` field common to all variants of the enum. This is useful if you have + /// binary values for TM34. + pub fn value_as_bytes(&self) -> &[u8] { + match self { + EventAttribute::V034(attr) => &attr.value, + EventAttribute::V037(attr) => &attr.value.as_bytes(), + } + } + + /// Access the `index` field common to all variants of the enum. + pub fn index(&self) -> bool { + match self { + EventAttribute::V034(attr) => attr.index, + EventAttribute::V037(attr) => attr.index, + } + } + + /// Set `index` field common to all variants of the enum. + pub fn set_index(&mut self, index: bool) { + match self { + EventAttribute::V034(attr) => attr.index = index, + EventAttribute::V037(attr) => attr.index = index, + } + } +} + impl Event { /// Construct an event from generic data. /// @@ -111,47 +166,44 @@ where } } -/// 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, Serialize, Deserialize, Hash)] -pub struct EventAttribute { - /// The event key. - #[serde(with = "serializers::allow_null")] - pub key: String, - /// The event value. - #[serde(with = "serializers::allow_null")] - pub value: String, - /// Whether Tendermint's indexer should index this event. - /// - /// **This field is nondeterministic**. - pub index: bool, -} - impl EventAttribute { /// Checks whether `&self` is equal to `other`, ignoring the `index` field. pub fn eq_ignoring_index(&self, other: &Self) -> bool { - self.key == other.key && self.value == other.value + match (self, other) { + (EventAttribute::V034(a), EventAttribute::V034(b)) => { + a.key == b.key && a.value == b.value + }, + (EventAttribute::V037(a), EventAttribute::V037(b)) => { + a.key == b.key && a.value == b.value + }, + // Shouldn't happen, comparing event attributes from different versions + _ => false, + } } /// A variant of [`core::hash::Hash::hash`] that ignores the `index` field. pub fn hash_ignoring_index(&self, state: &mut H) { use core::hash::Hash; // Call the `Hash` impl on the (k,v) tuple to avoid prefix collision issues. - (&self.key, &self.value).hash(state); + match self { + EventAttribute::V034(attr) => { + (&attr.key, &attr.value).hash(state); + }, + EventAttribute::V037(attr) => { + (&attr.key, &attr.value).hash(state); + }, + } } } impl, V: Into> From<(K, V, bool)> for EventAttribute { fn from((key, value, index): (K, V, bool)) -> Self { - EventAttribute { + // TODO: support all versions + Self::V037(v0_37::EventAttribute { key: key.into(), value: value.into(), index, - } + }) } } @@ -201,13 +253,36 @@ impl, V: Into> From<(K, V)> for EventAttribute { // Protobuf conversions // ============================================================================= -mod v0_34 { - use super::{Event, EventAttribute}; +pub mod v0_34 { + use super::Event; use crate::prelude::*; + use crate::serializers; + use core::convert::{TryFrom, TryInto}; + use serde::{Deserialize, Serialize}; use tendermint_proto::v0_34::abci as pb; use tendermint_proto::Protobuf; + /// 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, Serialize, Deserialize, Hash)] + pub struct EventAttribute { + /// The event key. + #[serde(with = "serializers::allow_null")] + pub key: String, + /// The event value. + #[serde(with = "serializers::bytes::base64string")] + pub value: Vec, + /// Whether Tendermint's indexer should index this event. + /// + /// **This field is nondeterministic**. + pub index: bool, + } + impl From for pb::EventAttribute { fn from(event: EventAttribute) -> Self { Self { @@ -226,8 +301,7 @@ mod v0_34 { 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()))?, + value: event.value.to_vec(), index: event.index, }) } @@ -239,7 +313,16 @@ mod v0_34 { fn from(event: Event) -> Self { Self { r#type: event.kind, - attributes: event.attributes.into_iter().map(Into::into).collect(), + attributes: event + .attributes + .into_iter() + .filter_map(|t| { + let super::EventAttribute::V034(ea) = t else { + return None; + }; + Some(ea.into()) + }) + .collect(), } } } @@ -254,7 +337,10 @@ mod v0_34 { .attributes .into_iter() .map(TryInto::try_into) - .collect::>()?, + .collect::, _>>()? + .into_iter() + .map(super::EventAttribute::V034) + .collect(), }) } } @@ -263,12 +349,35 @@ mod v0_34 { } mod v0_37 { - use super::{Event, EventAttribute}; + use super::Event; use crate::prelude::*; + use crate::serializers; + use core::convert::{TryFrom, TryInto}; + use serde::{Deserialize, Serialize}; use tendermint_proto::v0_37::abci as pb; use tendermint_proto::Protobuf; + /// 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, Serialize, Deserialize, Hash)] + pub struct EventAttribute { + /// The event key. + #[serde(with = "serializers::allow_null")] + pub key: String, + /// The event value. + #[serde(with = "serializers::allow_null")] + pub value: String, + /// Whether Tendermint's indexer should index this event. + /// + /// **This field is nondeterministic**. + pub index: bool, + } + impl From for pb::EventAttribute { fn from(event: EventAttribute) -> Self { Self { @@ -297,7 +406,16 @@ mod v0_37 { fn from(event: Event) -> Self { Self { r#type: event.kind, - attributes: event.attributes.into_iter().map(Into::into).collect(), + attributes: event + .attributes + .into_iter() + .filter_map(|t| { + let super::EventAttribute::V037(ea) = t else { + return None; + }; + Some(ea.into()) + }) + .collect(), } } } @@ -312,7 +430,10 @@ mod v0_37 { .attributes .into_iter() .map(TryInto::try_into) - .collect::>()?, + .collect::, _>>()? + .into_iter() + .map(super::EventAttribute::V037) + .collect(), }) } } @@ -321,12 +442,14 @@ mod v0_37 { } mod v0_38 { - use super::{Event, EventAttribute}; + use super::Event; use crate::prelude::*; use tendermint_proto::v0_38::abci as pb; use tendermint_proto::Protobuf; + pub use super::v0_37::EventAttribute; + impl From for pb::EventAttribute { fn from(event: EventAttribute) -> Self { Self { @@ -355,7 +478,16 @@ mod v0_38 { fn from(event: Event) -> Self { Self { r#type: event.kind, - attributes: event.attributes.into_iter().map(Into::into).collect(), + attributes: event + .attributes + .into_iter() + .filter_map(|t| { + let super::EventAttribute::V037(ea) = t else { + return None; + }; + Some(ea.into()) + }) + .collect(), } } } @@ -370,7 +502,10 @@ mod v0_38 { .attributes .into_iter() .map(TryInto::try_into) - .collect::>()?, + .collect::, _>>()? + .into_iter() + .map(super::EventAttribute::V037) + .collect(), }) } } @@ -434,15 +569,19 @@ mod tests { let a = event .attributes .iter() - .find(|attr| attr.key == "a") + .find(|attr| attr.key() == "a") .ok_or(()) - .and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?; + .and_then(|attr| { + serde_json::from_str(&attr.value_str().unwrap()).map_err(|_| ()) + })?; let b = event .attributes .iter() - .find(|attr| attr.key == "b") + .find(|attr| attr.key() == "b") .ok_or(()) - .and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?; + .and_then(|attr| { + serde_json::from_str(&attr.value_str().unwrap()).map_err(|_| ()) + })?; Ok(MyEvent { a, b }) } @@ -459,8 +598,8 @@ mod tests { // e2 is like e1 but with different indexing. let e2 = { let mut e = e1.clone(); - e.attributes[0].index = false; - e.attributes[1].index = false; + e.attributes[0].set_index(false); + e.attributes[1].set_index(false); e };