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..284df5f58 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/1405-change-event-attribute-value.md @@ -0,0 +1,3 @@ +- `[tendermint]` Change `EventAttribute`'s `key` and `value` fields from `String` to `Vec` for Tendermint v0.34, as enforced by the Protobuf schema for Tendermint v0.34. + `tendermint::abci::EventAttribute` is now an enum, to account for version 0.34 and 0.37+, therefore the `key`, `value` and `index` fields now have to be retrieved through the `key_str()`/`key_bytes`, `value_str()`/`value_bytes()` and `index()` methods. + ([\#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..12a9e6a2b 100644 --- a/rpc/src/dialect/v0_34.rs +++ b/rpc/src/dialect/v0_34.rs @@ -44,15 +44,17 @@ pub struct EventAttribute { /// The event key. #[serde( serialize_with = "base64string::serialize", - deserialize_with = "base64string::deserialize_to_string" + deserialize_with = "base64string::deserialize" )] - pub key: String, + pub key: Vec, + /// 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 +63,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_bytes().to_vec(), + value: msg.value_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..68b6435cb 100644 --- a/rpc/tests/kvstore_fixtures/v0_34.rs +++ b/rpc/tests/kvstore_fixtures/v0_34.rs @@ -509,21 +509,41 @@ 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].value, + result.tx_result.events[0].attributes[0].key_bytes(), + b"creator" + ); + assert_eq!( + 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_bytes(), b"key"); + assert_eq!( + result.tx_result.events[0].attributes[1] + .value_str() + .unwrap(), + "commit-key" + ); assert_eq!( - result.tx_result.events[0].attributes[2].value, + result.tx_result.events[0].attributes[2].key_bytes(), + b"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_bytes(), + b"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 +928,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_bytes(), b"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_bytes(), b"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_bytes(), b"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 +1135,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgwPXZhbHVl").unwrap()); @@ -1147,18 +1167,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgxPXZhbHVl").unwrap()); @@ -1180,18 +1200,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgyPXZhbHVl").unwrap()); @@ -1212,18 +1232,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgzPXZhbHVl").unwrap()); @@ -1244,18 +1264,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } 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..5efd62e78 100644 --- a/rpc/tests/kvstore_fixtures/v0_37.rs +++ b/rpc/tests/kvstore_fixtures/v0_37.rs @@ -503,21 +503,41 @@ 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].value, + result.tx_result.events[0].attributes[0].key_bytes(), + b"creator" + ); + assert_eq!( + 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_bytes(), b"key"); + assert_eq!( + result.tx_result.events[0].attributes[1] + .value_str() + .unwrap(), + "commit-key" + ); assert_eq!( - result.tx_result.events[0].attributes[2].value, + result.tx_result.events[0].attributes[2].key_bytes(), + b"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_bytes(), + b"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 +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_bytes(), b"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_bytes(), b"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_bytes(), b"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 +1129,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgwPXZhbHVl").unwrap()); @@ -1141,18 +1161,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgxPXZhbHVl").unwrap()); @@ -1174,18 +1194,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgyPXZhbHVl").unwrap()); @@ -1206,18 +1226,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgzPXZhbHVl").unwrap()); @@ -1238,18 +1258,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 dbg!(attr).key_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } 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..0a8a6318b 100644 --- a/rpc/tests/kvstore_fixtures/v0_38.rs +++ b/rpc/tests/kvstore_fixtures/v0_38.rs @@ -503,21 +503,41 @@ 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].value, + result.tx_result.events[0].attributes[0].key_bytes(), + b"creator" + ); + assert_eq!( + 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_bytes(), b"key"); + assert_eq!( + result.tx_result.events[0].attributes[1] + .value_str() + .unwrap(), + "commit-key" + ); assert_eq!( - result.tx_result.events[0].attributes[2].value, + result.tx_result.events[0].attributes[2].key_bytes(), + b"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_bytes(), + b"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 +1121,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgwPXZhbHVl").unwrap()); @@ -1133,18 +1153,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgxPXZhbHVl").unwrap()); @@ -1166,18 +1186,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgyPXZhbHVl").unwrap()); @@ -1198,18 +1218,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } assert_eq!(tx_result.tx, base64::decode("dHgzPXZhbHVl").unwrap()); @@ -1230,18 +1250,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_str().unwrap() { "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), + other => panic!("unknown attribute found {other}"), } } 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 2ea77b1bc..aafb66c27 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. /// @@ -21,10 +20,90 @@ pub struct Event { /// with Rust types and follow Rust conventions. #[serde(rename = "type")] pub kind: String, + /// A list of [`EventAttribute`]s describing the event. pub attributes: Vec, } +/// The attributes of an Event consist of a key, a value, and an index flag. +/// The index flag notifies the Tendermint indexer to index the attribute. +/// The value of the index flag is non-deterministic and may vary across different nodes in the network. +/// +/// Before Tendermint v0.37, the key and value can contain arbitrary byte arrays. +/// Since Tendermint v0.37, the key and value are defined to be valid UTF-8 encoded strings. +/// +/// IMPORTANT: The order of the two variants below is significant and must not be changed. +/// The `EventAttribute` enum is serialized and deserialized using the `untagged` attribute, +/// meaning that the first variant is tried first when deserializing, if that fails +/// then the second variant is tried. This allows us to deserialize v0.37+ events which +/// are vald UTF-8 strings, and fall back to deserializing v0.34 events which are arbitrary byte arrays. +#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize, Hash)] +#[serde(untagged)] +pub enum EventAttribute { + /// EventAttribute keys and values in TM37 and later are plain strings. + V037(v0_37::EventAttribute), + + /// EventAttribute keys and values in TM34 are base64-encoded strings. + V034(v0_34::EventAttribute), +} + +impl EventAttribute { + /// Access the `key` field common to all variants of the enum as a string. + /// Will return error if the value is malformed UTF8. + pub fn key_str(&self) -> Result<&str, crate::Error> { + match self { + EventAttribute::V034(attr) => { + core::str::from_utf8(&attr.key).map_err(|e| crate::Error::parse(e.to_string())) + }, + EventAttribute::V037(attr) => Ok(&attr.key), + } + } + + /// Access the `key` field common to all variants of the enum as bytes. + pub fn key_bytes(&self) -> &[u8] { + match self { + EventAttribute::V034(attr) => &attr.key, + EventAttribute::V037(attr) => attr.key.as_bytes(), + } + } + + /// Access the `value` field common to all variants of the enum as a string. + /// Will return error if the value is malformed UTF8. + pub fn value_str(&self) -> Result<&str, crate::Error> { + match self { + EventAttribute::V034(attr) => { + core::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 as bytes. + /// This is useful if you have binary values for TM34. + pub fn value_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 +190,43 @@ 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 { + Self::V037(v0_37::EventAttribute { key: key.into(), value: value.into(), index, - } + }) } } @@ -201,13 +276,37 @@ 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 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::bytes::base64string")] + pub key: Vec, + + /// 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 { @@ -222,12 +321,9 @@ mod v0_34 { 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()))?, + key: event.value.to_vec(), + value: event.value.to_vec(), index: event.index, }) } @@ -239,7 +335,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 +359,10 @@ mod v0_34 { .attributes .into_iter() .map(TryInto::try_into) - .collect::>()?, + .collect::, _>>()? + .into_iter() + .map(super::EventAttribute::V034) + .collect(), }) } } @@ -263,12 +371,36 @@ mod v0_34 { } mod v0_37 { - use super::{Event, EventAttribute}; + use super::Event; use crate::prelude::*; + use crate::serializers; + 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 +429,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 +453,10 @@ mod v0_37 { .attributes .into_iter() .map(TryInto::try_into) - .collect::>()?, + .collect::, _>>()? + .into_iter() + .map(super::EventAttribute::V037) + .collect(), }) } } @@ -321,12 +465,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 +501,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 +525,10 @@ mod v0_38 { .attributes .into_iter() .map(TryInto::try_into) - .collect::>()?, + .collect::, _>>()? + .into_iter() + .map(super::EventAttribute::V037) + .collect(), }) } } @@ -432,15 +590,15 @@ mod tests { let a = event .attributes .iter() - .find(|attr| attr.key == "a") + .find(|attr| attr.key_bytes() == b"a") .ok_or(()) - .and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?; + .and_then(|attr| serde_json::from_slice(attr.value_bytes()).map_err(|_| ()))?; let b = event .attributes .iter() - .find(|attr| attr.key == "b") + .find(|attr| attr.key_bytes() == b"b") .ok_or(()) - .and_then(|attr| serde_json::from_str(&attr.value).map_err(|_| ()))?; + .and_then(|attr| serde_json::from_slice(attr.value_bytes()).map_err(|_| ()))?; Ok(MyEvent { a, b }) } @@ -459,8 +617,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 };