sc-statement-store 28.0.0

Substrate statement store.
Documentation

Substrate statement store implementation

License: GPL-3.0-or-later WITH Classpath-exception-2.0

The statement store is an off-chain, decentralized data store for cryptographically signed statements. It enables accounts to publish arbitrary data that can be queried and propagated across the network without consuming on-chain storage. Statement store designed around two fundamental pillars: Scalability and Graceful Degradation. It expressly avoids the properties of centralized data structures. Instead, it prioritizes privacy, scalability, and decentralization by deliberately reducing other guarantees.

Unlike centralized services, the Statement Store is a weakly coherent system and does not guarantee message delivery or specific delivery times.

How do I run a local statement-store node for development?

This starts a standalone Substrate node with StatementStore turned on. Quota is set directly in storage in the next step, without an individuality runtime.

  1. Build Substrate node

    cargo build --profile production --locked --bin substrate-node --target x86_64-unknown-linux-gnu
    
  2. Run it with:

    RUST_LOG=info,statement-store=trace ./target/x86_64-unknown-linux-gnu/debug/substrate-node
    
  3. To set a quota using sudo, you can use the following

  • 3.1. Identify the account you use, it should be an account public key.
  • 3.2. Obtain the storage key by running this Python code. You would need your account ID from the previous step.
      >>> statement_allowance_key = lambda account_id_hex: "0x" + (b":statement_allowance:" + bytes.fromhex(account_id_hex.removeprefix ("0x"))).hex()
      >>> statement_allowance_key("YOUR_ACCOUNT_BYTES_IN_HEX")
    
  • 3.3. Using https://polkadot.js.org/apps/ call Developer->sudo->system->set_storage with obtained account key and StatementAllowance, SCALE encoded. For example, to allow an account to store 10 statements and a maximum size of 20 KiB you can use 0x0a00000000500000. 0x0a00000000500000 is SCALE for: StatementAllowance { max_count: 10, max_size: 20480 } 0a000000 -> 10 (max_count) 00500000 -> 20480 bytes (max_size, i.e. 20 KiB)

    LLMs know how to answer this question if you want to use a different quota.

Warning: Use carefully. Do not set big quotas on test environments with SUDO because then they will not match production quotas.

How to use SS with subxt

Submission

fn submit(&self, encoded: Bytes) -> RpcResult<()>;

let result: SubmitResult = rpc_client
    .request("statement_submit", rpc_params![encoded])
    .await
    .with_context(|| format!("Client {client_id}: Failed to submit statement"))?;

Subscription

fn subscribe_statement(&self, topic_filter: TopicFilter);

Parameters:

  • topic_filter: Which topics to match.

  • Use TopicFilter::Any to match all topics, TopicFilter::MatchAll(vec) to match statements that include all provided topics, or TopicFilter::MatchAny(vec) to match statements that include any of the provided topics.

let mut subscription: Subscription<StatementEvent> = rpc_client
    .subscribe(
        "statement_subscribeStatement",
        rpc_params![TopicFilter::MatchAny(bounded_topics)],
        "statement_unsubscribeStatement",
    )
    .await
    .with_context(|| format!("Client {client_id}: Failed to subscribe"))?;

Returns

When there are no matching statements in the store, you first receive an empty array. As new matching statements arrive in the node they are forwarded to the client.

{
  "jsonrpc": "2.0",
  "method": "statement_statement",
  "params": {
    "subscription": 4851578855668545,
    "result": {
      "event": "newStatements",
      "data": {
        "statements": [],
        "remaining": 0
      }
    }
  }
}

If there are matching statements in the store you receive them in batches of newStatements events, with remaining telling you how many statements remain. This guarantees that the subscription will receive at least this amount of statements.

{
  "jsonrpc": "2.0",
  "method": "statement_statement",
  "params": {
    "subscription": 1710164133533157,
    "result": {
      "event": "newStatements",
      "data": {
        "statements": [
          "0x1000010000",
          "0x100001000000"
        ],
        "remaining": 10
      }
    }
  }
}

If new statements arrive in the store they are delivered as they are, without any remaining information.

{
  "jsonrpc": "2.0",
  "method": "statement_statement",
  "params": {
    "subscription": 2661920166788434,
    "result": {
      "event": "newStatements",
      "data": {
        "statements": [
          "0x100001000...000"
        ]
      }
    }
  }
}

Expiration and maintenance

Message expiration is based on the expiry field. Please refer to the in-code description.