Skip to content

Commit 00eb294

Browse files
committed
Add manual block production RPCs to the farmerless-dev-node
1 parent 4bfb551 commit 00eb294

File tree

5 files changed

+311
-14
lines changed

5 files changed

+311
-14
lines changed

Cargo.lock

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

test/subspace-farmerless-dev-node/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
1717
tempfile = { workspace = true }
1818
tracing = { workspace = true }
1919
sp-keyring = { workspace = true }
20+
jsonrpsee = { workspace = true, features = ["macros", "server-core"] }
21+
async-trait = { workspace = true }
2022

2123
subspace-test-service = { workspace = true }
2224
domain-test-service = { workspace = true }

test/subspace-farmerless-dev-node/README.md

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,49 @@ cargo run -p subspace-farmerless-dev-node -- --help
3535
- **Quick smoke test:** `cargo run -p subspace-farmerless-dev-node` (produces consensus blocks every 6s using temp storage).
3636
- **Run with domain node:** `cargo run -p subspace-farmerless-dev-node -- --domain`.
3737
- **Fast integration testing:** `cargo run -p subspace-farmerless-dev-node -- --block-interval-ms 500 --domain`.
38-
- **Manual block production:** `cargo run -p subspace-farmerless-dev-node -- --block-interval-ms 0` and trigger blocks via RPC helpers.
38+
- **Manual block production:** `cargo run -p subspace-farmerless-dev-node -- --block-interval-ms 0 --domain` and trigger blocks via RPC helpers.
39+
40+
## Manual Block Production RPCs
41+
42+
When `--block-interval-ms 0` is set, the node exposes JSON-RPC endpoints for manual block production:
43+
44+
### `dev_produceBlock`
45+
46+
Produce a single consensus block.
47+
48+
**Parameters:**
49+
50+
- `wait_for_bundle` (optional, boolean): If `true`, wait for domain bundle submission before producing the block. Defaults to `false`.
51+
52+
**Example:**
53+
54+
```bash
55+
curl -H "Content-Type: application/json" \
56+
--data '{"jsonrpc":"2.0","id":1,"method":"dev_produceBlock","params":[true]}' \
57+
http://127.0.0.1:9944
58+
```
59+
60+
### `dev_produceBlocks`
61+
62+
Produce multiple consensus blocks.
63+
64+
**Parameters:**
65+
66+
- `count` (required, number): Number of blocks to produce.
67+
- `wait_for_bundle` (optional, boolean): If `true`, wait for domain bundle submission before each block. Defaults to `false`.
68+
69+
**Example:**
70+
71+
```bash
72+
# Produce 5 blocks without waiting for bundles
73+
curl -H "Content-Type: application/json" \
74+
--data '{"jsonrpc":"2.0","id":1,"method":"dev_produceBlocks","params":[5]}' \
75+
http://127.0.0.1:9944
76+
77+
# Produce 5 blocks, waiting for bundles
78+
curl -H "Content-Type: application/json" \
79+
--data '{"jsonrpc":"2.0","id":1,"method":"dev_produceBlocks","params":[5,true]}' \
80+
http://127.0.0.1:9944
81+
```
82+
83+
**Note:** When `wait_for_bundle` is `true`, the domain node must be running (`--domain` flag) or the RPC call will timeout waiting for bundle submission.

test/subspace-farmerless-dev-node/src/main.rs

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1+
mod manual_rpc;
2+
13
use clap::Parser;
24
use domain_test_service::{DomainNodeBuilder, EcdsaKeyring};
5+
use manual_rpc::{
6+
ConsensusControl, consensus_control_channel, manual_block_production_rpc,
7+
spawn_consensus_worker,
8+
};
39
use sc_cli::LoggerBuilder;
410
use sc_service::{BasePath, Role};
511
use sp_keyring::Sr25519Keyring;
@@ -9,6 +15,7 @@ use std::time::Duration;
915
use subspace_test_service::{MockConsensusNode, MockConsensusNodeRpcConfig};
1016
use tempfile::TempDir;
1117
use tokio::runtime::Builder as TokioBuilder;
18+
use tracing::error;
1219

1320
#[derive(Debug, Parser)]
1421
#[command(
@@ -78,6 +85,7 @@ fn start_consensus_node(
7885
finalize_depth: Option<u32>,
7986
rpc_host: IpAddr,
8087
rpc_port: u16,
88+
consensus_control: ConsensusControl,
8189
) -> MockConsensusNode {
8290
let private_evm = false;
8391
let consensus_key = Sr25519Keyring::Alice;
@@ -90,7 +98,13 @@ fn start_consensus_node(
9098
rpc_addr: Some(rpc_addr),
9199
rpc_port: Some(rpc_port),
92100
};
93-
let mut node = MockConsensusNode::run_with_rpc_options(tokio_handle, consensus_key, rpc_config);
101+
102+
let mut node = MockConsensusNode::run_with_rpc_builder(
103+
tokio_handle,
104+
consensus_key,
105+
rpc_config,
106+
Box::new(move || Ok(manual_block_production_rpc(consensus_control.clone()))),
107+
);
94108
node.start_network();
95109
node
96110
}
@@ -106,6 +120,8 @@ fn main() {
106120
let _enter = runtime.enter();
107121
let tokio_handle = runtime.handle().clone();
108122

123+
let (consensus_control, command_rx) = consensus_control_channel();
124+
109125
// Start consensus
110126
let consensus_base = base_path.join("consensus");
111127
let mut consensus = start_consensus_node(
@@ -114,6 +130,7 @@ fn main() {
114130
cli.finalize_depth,
115131
cli.rpc_host,
116132
cli.rpc_port,
133+
consensus_control.clone(),
117134
);
118135

119136
// Optionally start domain (EVM)
@@ -130,28 +147,35 @@ fn main() {
130147
} else {
131148
None
132149
};
150+
133151
consensus.start_cross_domain_gossip_message_worker();
134152

135-
if block_interval_ms > 0 {
136-
// Keep domain node alive - move it into the async block but don't move consensus
153+
let worker_handle = spawn_consensus_worker(consensus, command_rx);
154+
155+
let consensus_for_loop = consensus_control.clone();
156+
runtime.block_on(async move {
137157
let _domain_guard = domain;
138-
runtime.block_on(async {
158+
if block_interval_ms > 0 {
139159
loop {
140160
tokio::select! {
141161
_ = tokio::signal::ctrl_c() => break,
142162
_ = tokio::time::sleep(Duration::from_millis(block_interval_ms)) => {
143-
let slot = consensus.produce_slot();
144-
let _ = consensus.notify_new_slot_and_wait_for_bundle(slot).await;
145-
let _ = consensus.produce_block_with_slot(slot).await;
146-
163+
if let Err(err) = consensus_for_loop.produce_block(true).await {
164+
error!(%err, "Failed to auto-produce block");
165+
}
147166
}
148167
}
149168
}
150-
});
151-
return;
169+
} else {
170+
let _ = tokio::signal::ctrl_c().await;
171+
}
172+
});
173+
174+
if let Err(err) = runtime.block_on(consensus_control.shutdown()) {
175+
error!(%err, "Failed to shut down consensus control");
152176
}
153177

154-
runtime.block_on(async {
155-
let _ = tokio::signal::ctrl_c().await;
156-
});
178+
worker_handle
179+
.join()
180+
.expect("Failed to join consensus control thread");
157181
}

0 commit comments

Comments
 (0)