|
| 1 | +//! Statement-store JSON-RPC shapes mirrored from `sp_statement_store`. |
| 2 | +//! |
| 3 | +//! See the upstream RPC methods plus `TopicFilter` / `StatementEvent` types: |
| 4 | +//! <https://github.com/paritytech/polkadot-sdk/blob/f2f3aa6a8fda8ea52282da9711b3c5da4ba82529/substrate/client/rpc-api/src/statement/mod.rs#L19-L117> |
| 5 | +//! <https://github.com/paritytech/polkadot-sdk/blob/f2f3aa6a8fda8ea52282da9711b3c5da4ba82529/substrate/primitives/statement-store/src/store_api.rs#L41-L54> |
| 6 | +//! <https://github.com/paritytech/polkadot-sdk/blob/f2f3aa6a8fda8ea52282da9711b3c5da4ba82529/substrate/primitives/statement-store/src/store_api.rs#L204-L221> |
| 7 | +
|
| 8 | +use serde_json::Value; |
| 9 | + |
| 10 | +use super::StatementStoreParseError; |
| 11 | + |
| 12 | +/// Statement-store RPC method used to open a topic subscription. |
| 13 | +pub const SUBSCRIBE_STATEMENT_METHOD: &str = "statement_subscribeStatement"; |
| 14 | +/// Statement-store RPC method used to close a topic subscription. |
| 15 | +pub const UNSUBSCRIBE_STATEMENT_METHOD: &str = "statement_unsubscribeStatement"; |
| 16 | +/// Statement-store RPC method used to submit a signed statement. |
| 17 | +pub const SUBMIT_STATEMENT_METHOD: &str = "statement_submit"; |
| 18 | +/// Maximum `matchAll` topic count accepted by the statement-store RPC. |
| 19 | +pub const MAX_MATCH_ALL_TOPICS: usize = 4; |
| 20 | +/// Maximum `matchAny` topic count accepted by the statement-store RPC. |
| 21 | +pub const MAX_MATCH_ANY_TOPICS: usize = 128; |
| 22 | + |
| 23 | +/// Decoded `newStatements` subscription notification. |
| 24 | +#[derive(Debug, Clone, PartialEq, Eq)] |
| 25 | +pub struct NewStatements { |
| 26 | + /// Remote subscription id included in the notification. |
| 27 | + pub remote_subscription_id: String, |
| 28 | + /// SCALE-encoded signed statements carried by the notification. |
| 29 | + pub statements: Vec<Vec<u8>>, |
| 30 | + /// Optional server-side backlog count. |
| 31 | + pub remaining: Option<u64>, |
| 32 | +} |
| 33 | + |
| 34 | +/// Topic filter flavor used by statement-store subscribe requests. |
| 35 | +#[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 36 | +pub enum TopicFilterKind { |
| 37 | + /// Require every listed topic to match. |
| 38 | + MatchAll, |
| 39 | + /// Accept any listed topic match. |
| 40 | + MatchAny, |
| 41 | +} |
| 42 | + |
| 43 | +/// Parse a statement-store subscription result value. |
| 44 | +pub fn parse_new_statements_result( |
| 45 | + remote_subscription_id: String, |
| 46 | + result: &Value, |
| 47 | +) -> Result<NewStatements, StatementStoreParseError> { |
| 48 | + if result.get("event").and_then(Value::as_str) != Some("newStatements") { |
| 49 | + return Err(StatementStoreParseError::Malformed( |
| 50 | + "result is not a newStatements event".to_string(), |
| 51 | + )); |
| 52 | + } |
| 53 | + let data = result |
| 54 | + .get("data") |
| 55 | + .ok_or_else(|| StatementStoreParseError::Malformed("missing data".to_string()))?; |
| 56 | + let statement_values = data |
| 57 | + .get("statements") |
| 58 | + .and_then(Value::as_array) |
| 59 | + .ok_or_else(|| StatementStoreParseError::Malformed("missing statements".to_string()))?; |
| 60 | + let statements = statement_values |
| 61 | + .iter() |
| 62 | + .map(|value| { |
| 63 | + let Some(hex) = value.as_str() else { |
| 64 | + return Err(StatementStoreParseError::Malformed( |
| 65 | + "statement is not a hex string".to_string(), |
| 66 | + )); |
| 67 | + }; |
| 68 | + decode_hex(hex) |
| 69 | + }) |
| 70 | + .collect::<Result<Vec<_>, _>>()?; |
| 71 | + let remaining = data |
| 72 | + .get("remaining") |
| 73 | + .map(|value| { |
| 74 | + value.as_u64().ok_or_else(|| { |
| 75 | + StatementStoreParseError::Malformed("remaining is not an integer".to_string()) |
| 76 | + }) |
| 77 | + }) |
| 78 | + .transpose()?; |
| 79 | + |
| 80 | + Ok(NewStatements { |
| 81 | + remote_subscription_id, |
| 82 | + statements, |
| 83 | + remaining, |
| 84 | + }) |
| 85 | +} |
| 86 | + |
| 87 | +fn decode_hex(value: &str) -> Result<Vec<u8>, StatementStoreParseError> { |
| 88 | + hex::decode(value.strip_prefix("0x").unwrap_or(value)) |
| 89 | + .map_err(|error| StatementStoreParseError::InvalidStatementHex(error.to_string())) |
| 90 | +} |
| 91 | + |
| 92 | +#[cfg(test)] |
| 93 | +mod tests { |
| 94 | + use super::*; |
| 95 | + |
| 96 | + #[test] |
| 97 | + fn parses_dotli_sdk_new_statements_result() { |
| 98 | + let result = serde_json::json!({ |
| 99 | + "event": "newStatements", |
| 100 | + "data": { |
| 101 | + "statements": ["0xdeadbeef", "0xcafe"], |
| 102 | + "remaining": 0, |
| 103 | + }, |
| 104 | + }); |
| 105 | + |
| 106 | + assert_eq!( |
| 107 | + parse_new_statements_result("remote-sub".to_string(), &result).unwrap(), |
| 108 | + NewStatements { |
| 109 | + remote_subscription_id: "remote-sub".to_string(), |
| 110 | + statements: vec![vec![0xde, 0xad, 0xbe, 0xef], vec![0xca, 0xfe]], |
| 111 | + remaining: Some(0), |
| 112 | + } |
| 113 | + ); |
| 114 | + } |
| 115 | +} |
0 commit comments