Skip to content

Commit 0adad18

Browse files
authored
feat: introduce a Substrate source (#913)
1 parent 6b62e13 commit 0adad18

File tree

15 files changed

+1887
-71
lines changed

15 files changed

+1887
-71
lines changed

Cargo.lock

Lines changed: 1295 additions & 62 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mithril = ["mithril-client"]
2222
hydra = ["tungstenite", "tokio-tungstenite", "futures-util", "bytes"]
2323
eth = ["futures-util", "alloy"]
2424
btc = ["bitcoind-async-client", "corepc-types"]
25+
substrate = ["futures-util", "subxt"]
2526
# elasticsearch = auto feature flag
2627
# kafka = auto feature flag
2728

@@ -87,6 +88,7 @@ zmq = { version = "0.10.0", optional = true }
8788
bitcoind-async-client = { version = "0.6.0", optional = true }
8889
corepc-types = { version = "0.10.1", optional = true }
8990
alloy = { version = "1.0.38", features = ["provider-ws"], optional = true }
91+
subxt = { version = "0.44.0", optional = true }
9092

9193
[dev-dependencies]
9294
goldenfile = "1.7.3"

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ The data pipeline is implemented by the [Gasket](https://github.com/construkts/g
4242
You can run:
4343
- `oura watch cardano <socket>` to print Cardano transaction data into the terminal from the tip of a local or remote node
4444
- `oura watch bitcoin <rpc_host>` to watch Bitcoin blockchain events
45-
- `oura watch ethereum <url>` to watch Ethereum blockchain events via WebSocket.
45+
- `oura watch ethereum <url>` to watch Ethereum blockchain events via WebSocket
46+
- `oura watch substrate <url>` to watch Substrate blockchain events from any Substrate-based network
4647

4748
These commands are useful as debugging tools for developers or if you're just curious to see what's going on in the network (for example, to see airdrops as they happen or oracles posting new information).
4849

docs/v3/index.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ The data pipeline makes heavy use (maybe a bit too much) of multi-threading and
2727
You can run:
2828
- `oura watch cardano <socket>` to print Cardano transaction data into the terminal from the tip of a local or remote node
2929
- `oura watch bitcoin <rpc_host>` to watch Bitcoin blockchain events
30-
- `oura watch ethereum <url>` to watch Ethereum blockchain events via WebSocket.
30+
- `oura watch ethereum <url>` to watch Ethereum blockchain events via WebSocket
31+
- `oura watch substrate <url>` to watch Substrate blockchain events from any Substrate-based network
3132

3233
These commands are useful as debugging tools for developers or if you're just curious to see what's going on in the network (for example, to see airdrops as they happen or oracles posting new information).
3334

docs/v3/sources/substrate.mdx

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
title: Substrate RPC
3+
---
4+
5+
The Substrate RPC source connects to a Substrate-based blockchain node via WebSocket RPC to subscribe to new blocks and retrieve complete block data. It works with any Substrate-based network including Polkadot, Kusama, and parachains.
6+
7+
## Configuration
8+
9+
The following snippet shows an example of how to set up a Substrate WebSocket source:
10+
11+
```toml
12+
[source]
13+
type = "substrate-rpc"
14+
url = "ws://localhost:9944"
15+
```
16+
17+
### Section `source`:
18+
19+
- `type`: this field must be set to the literal value `substrate-rpc`.
20+
- `url`: the WebSocket URL of the Substrate RPC endpoint (required). Must use `ws://` or `wss://` protocol.
21+
22+
## Supported Networks
23+
24+
The Substrate source works with any Substrate-based blockchain, including:
25+
26+
- **Polkadot** - The main relay chain
27+
- **Kusama** - Polkadot's canary network
28+
- **Westend** - Polkadot's testnet
29+
- **Parachains** - Any parachain connected to Polkadot or Kusama
30+
- **Solo chains** - Independent Substrate-based blockchains
31+
- **Local development nodes** - For testing and development
32+
33+
## Examples
34+
35+
### Local Substrate Node
36+
37+
```toml
38+
[source]
39+
type = "substrate-rpc"
40+
url = "ws://localhost:9944"
41+
```
42+
43+
### Polkadot Mainnet via OnFinality
44+
45+
```toml
46+
[source]
47+
type = "substrate-rpc"
48+
url = "wss://polkadot.api.onfinality.io/public-ws"
49+
```
50+
51+
### Kusama Network
52+
53+
```toml
54+
[source]
55+
type = "substrate-rpc"
56+
url = "wss://kusama-rpc.polkadot.io"
57+
```
58+
59+
### Westend Testnet
60+
61+
```toml
62+
[source]
63+
type = "substrate-rpc"
64+
url = "wss://westend-rpc.polkadot.io"
65+
```
66+
67+
### Astar Parachain
68+
69+
```toml
70+
[source]
71+
type = "substrate-rpc"
72+
url = "wss://rpc.astar.network"
73+
```
74+
75+
## How It Works
76+
77+
The Substrate source operates using Subxt library for Substrate interaction:
78+
79+
1. **Connection**: Connects to the Substrate node via WebSocket using the Subxt client.
80+
81+
2. **Subscription**: Subscribes to the best block stream to receive notifications when new blocks are finalized.
82+
83+
3. **Block Processing**: For each new block:
84+
- Retrieves the complete block header information
85+
- Parses all extrinsics (transactions) in the block
86+
- Extracts signature information for signed extrinsics
87+
- Calculates block and extrinsic hashes
88+
89+
4. **Event Generation**: Converts the block data into Oura's internal `ChainEvent::Apply` format for processing by filters and sinks.
90+
91+
## Output Events
92+
93+
The Substrate source produces `ChainEvent::Apply` events containing parsed Substrate block data. Each event includes:
94+
95+
### Block Header
96+
- Block number and hash
97+
- Parent block hash
98+
- State root and extrinsics root
99+
- Block timestamp and other metadata
100+
101+
### Extrinsics
102+
For each extrinsic in the block:
103+
- Extrinsic index and hash
104+
- Raw bytes (hex-encoded)
105+
- Signature information (for signed extrinsics):
106+
- Signer address
107+
- Signature data
108+
- Signed extensions metadata
109+
110+
### Additional Information
111+
- Total extrinsics count
112+
- Block processing metrics
113+
- Chain tip information
114+
115+
## Data Structure
116+
117+
The parsed block follows this structure:
118+
119+
```rust
120+
ParsedBlock {
121+
header: BlockHeader {
122+
number: u64,
123+
hash: String,
124+
parent_hash: String,
125+
state_root: String,
126+
extrinsics_root: String,
127+
},
128+
extrinsics: Vec<Extrinsic>,
129+
extrinsics_count: usize,
130+
}
131+
132+
Extrinsic {
133+
index: u32,
134+
hash: String,
135+
bytes: Option<String>,
136+
signature: Option<SignatureInfo>,
137+
}
138+
139+
SignatureInfo {
140+
address: String,
141+
signature: String,
142+
extra: Option<String>,
143+
}
144+
```
145+
146+
## Substrate Runtime Compatibility
147+
148+
The source is designed to work with any Substrate runtime without requiring specific runtime metadata knowledge. It focuses on the core block structure that is consistent across all Substrate chains:
149+
150+
- Block headers follow the standard Substrate format
151+
- Extrinsics use the SCALE codec for encoding
152+
- Signature extraction works with standard Substrate transaction formats
153+
154+
This makes the source compatible with both current and future Substrate networks without requiring updates for new runtime versions.
155+
156+
## Performance Considerations
157+
158+
- The source subscribes to the best blocks only, ensuring reliable block ordering
159+
- Block processing is done asynchronously to maintain good throughput
160+
- Extrinsic parsing includes detailed signature extraction which may add some processing overhead
161+
- WebSocket connections are automatically managed and reconnected if needed
162+
163+
The events follow the same pattern as other Oura sources, allowing them to be processed by standard filters and sinks in the pipeline.

docs/v3/usage/watch.mdx

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
title: Watch
33
---
44

5-
The `watch` mode provides a quick way to tail the latest events from blockchain networks. It supports Cardano, Bitcoin, and Ethereum blockchains, each with their own subcommands and connection methods.
5+
The `watch` mode provides a quick way to tail the latest events from blockchain networks. It supports Cardano, Bitcoin, Ethereum, and Substrate blockchains, each with their own subcommands and connection methods.
66

77
The output is colorized by type of event and dynamically truncated to fit the width of the terminal. The speed of the output lines is throttled to facilitate visual inspection of each event, otherwise, all events for a block would be output simultaneously.
88

@@ -61,6 +61,21 @@ oura watch ethereum [OPTIONS] <url>
6161
- `--throttle`: milliseconds to wait between output lines (for easier reading).
6262
- `--wrap`: indicates that long output text should break and continue in the following line.
6363

64+
### Substrate
65+
66+
To watch Substrate blockchain events:
67+
68+
```
69+
oura watch substrate [OPTIONS] <url>
70+
```
71+
72+
- `<url>`: the Substrate WebSocket RPC URL to connect to.
73+
74+
#### Substrate Options
75+
76+
- `--throttle`: milliseconds to wait between output lines (for easier reading).
77+
- `--wrap`: indicates that long output text should break and continue in the following line.
78+
6479
## Examples
6580

6681
### Cardano Examples
@@ -148,3 +163,35 @@ oura watch ethereum ws://localhost:8546 --throttle 1000 --wrap
148163
```sh
149164
oura watch ethereum wss://ethereum-sepolia-rpc.publicnode.com
150165
```
166+
167+
### Substrate Examples
168+
169+
#### Watch Live Data From A Local Polkadot Node
170+
171+
```sh
172+
oura watch substrate ws://localhost:9944
173+
```
174+
175+
#### Watch Polkadot Mainnet via OnFinality
176+
177+
```sh
178+
oura watch substrate wss://polkadot.api.onfinality.io/public-ws
179+
```
180+
181+
#### Watch Substrate With Output Throttling
182+
183+
```sh
184+
oura watch substrate ws://localhost:9944 --throttle 1000 --wrap
185+
```
186+
187+
#### Watch Kusama Network
188+
189+
```sh
190+
oura watch substrate wss://kusama-rpc.polkadot.io
191+
```
192+
193+
#### Watch Westend Testnet
194+
195+
```sh
196+
oura watch substrate wss://westend-rpc.polkadot.io
197+
```

examples/substrate/daemon.toml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[source]
2+
type = "substrate-rpc"
3+
url = "wss://polkadot-rpc.publicnode.com"
4+
5+
[intersect]
6+
type = "Tip"
7+
8+
[sink]
9+
# type = "Stdout"
10+
type = "FileRotate"
11+
output_path = "/tmp/oura-substrate/mainnet"
12+
output_format = "JSONL"
13+
max_bytes_per_file = 1_000_000
14+
max_total_files = 10
15+
compress_files = false

src/bin/oura/watch.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ use oura::watch::btc;
66
#[cfg(feature = "eth")]
77
use oura::watch::eth;
88

9+
#[cfg(feature = "substrate")]
10+
use oura::watch::substrate;
11+
912
use clap::{Parser, Subcommand};
1013
use oura::framework::Error;
1114

@@ -18,6 +21,9 @@ pub fn run(args: &Args) -> Result<(), Error> {
1821

1922
#[cfg(feature = "eth")]
2023
WatchCommand::Ethereum(eth_args) => eth::run(eth_args),
24+
25+
#[cfg(feature = "substrate")]
26+
WatchCommand::Substrate(substrate_args) => substrate::run(substrate_args),
2127
}
2228
}
2329

@@ -40,4 +46,8 @@ pub enum WatchCommand {
4046
/// Watch Ethereum blockchain
4147
#[cfg(feature = "eth")]
4248
Ethereum(eth::Args),
49+
50+
/// Watch Substrate blockchain
51+
#[cfg(feature = "substrate")]
52+
Substrate(substrate::Args),
4353
}

src/framework/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ pub mod bitcoin;
2323

2424
#[cfg(feature = "eth")]
2525
pub mod ethereum;
26+
27+
#[cfg(feature = "substrate")]
2628
pub mod substrate;
2729

2830
pub mod errors;
@@ -93,6 +95,8 @@ pub enum Record {
9395

9496
#[cfg(feature = "btc")]
9597
Bitcoin(bitcoin::Record),
98+
99+
#[cfg(feature = "substrate")]
96100
Substrate(substrate::Record),
97101
}
98102
impl From<Record> for JsonValue {
@@ -104,6 +108,7 @@ impl From<Record> for JsonValue {
104108
Record::Ethereum(record) => record.into(),
105109
#[cfg(feature = "btc")]
106110
Record::Bitcoin(record) => record.into(),
111+
#[cfg(feature = "substrate")]
107112
Record::Substrate(record) => record.into(),
108113
}
109114
}

0 commit comments

Comments
 (0)