Skip to content

Commit 31b591e

Browse files
committed
Add subspace-farmerless-dev-node package with initial implementation and dependencies
1 parent acc42da commit 31b591e

File tree

4 files changed

+196
-0
lines changed

4 files changed

+196
-0
lines changed

Cargo.lock

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

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ members = [
2020
"test/subspace-test-fuzzer",
2121
"test/subspace-test-runtime",
2222
"test/subspace-test-service",
23+
"test/subspace-farmerless-dev-node",
2324
]
2425

2526
[workspace.dependencies]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "subspace-farmerless-dev-node"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "GPL-3.0-or-later"
6+
publish = false
7+
8+
[[bin]]
9+
name = "subspace-farmerless-dev-node"
10+
path = "src/main.rs"
11+
12+
[dependencies]
13+
clap = { workspace = true, features = ["derive"] }
14+
sc-cli = { workspace = true, default-features = false }
15+
sc-service = { workspace = true, default-features = false }
16+
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
17+
tempfile = { workspace = true }
18+
tracing = { workspace = true }
19+
sp-keyring = { workspace = true }
20+
21+
subspace-test-service = { workspace = true }
22+
domain-test-service = { workspace = true }
23+
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
use clap::Parser;
2+
use domain_test_service::{DomainNodeBuilder, EcdsaKeyring};
3+
use sc_cli::LoggerBuilder;
4+
use sc_service::{BasePath, Role};
5+
use sp_keyring::Sr25519Keyring;
6+
use std::net::{IpAddr, SocketAddr};
7+
use std::path::PathBuf;
8+
use std::time::Duration;
9+
use subspace_test_service::{MockConsensusNode, MockConsensusNodeRpcConfig};
10+
use tempfile::TempDir;
11+
use tokio::runtime::Builder as TokioBuilder;
12+
13+
#[derive(Debug, Parser)]
14+
#[command(
15+
name = "subspace-farmerless-testnet",
16+
about = "Starts consensus and domain test nodes together (farmerless)"
17+
)]
18+
struct Cli {
19+
/// Finalization depth (K). Omit to disable finalization enforcement.
20+
#[arg(long)]
21+
finalize_depth: Option<u32>,
22+
23+
/// Whether to start the EVM domain node
24+
#[arg(long, default_value_t = false)]
25+
domain: bool,
26+
27+
/// Base path for node data. Defaults to a unique temporary directory per run.
28+
#[arg(long)]
29+
base_path: Option<PathBuf>,
30+
31+
/// Consensus RPC host/interface
32+
#[arg(long, default_value = "127.0.0.1")]
33+
rpc_host: IpAddr,
34+
35+
/// Consensus RPC port
36+
#[arg(long, default_value_t = 9944)]
37+
rpc_port: u16,
38+
39+
/// Domain RPC host/interface
40+
#[arg(long, default_value = "127.0.0.1")]
41+
domain_rpc_host: IpAddr,
42+
43+
/// Domain RPC port
44+
#[arg(long, default_value_t = 9945)]
45+
domain_rpc_port: u16,
46+
47+
/// Block production interval in milliseconds (consensus). Use 0 to disable.
48+
#[arg(long, default_value_t = 6000)]
49+
block_interval_ms: u64,
50+
}
51+
52+
fn init_logger() {
53+
let mut logger = LoggerBuilder::new("");
54+
logger.with_colors(false);
55+
let _ = logger.init();
56+
}
57+
58+
fn compute_base_path(cli: &Cli) -> (PathBuf, Option<TempDir>) {
59+
match cli.base_path.clone() {
60+
Some(p) => (p, None),
61+
None => {
62+
let tmp = TempDir::new().expect("Must be able to create temporary directory");
63+
(tmp.path().to_path_buf(), Some(tmp))
64+
}
65+
}
66+
}
67+
68+
fn build_runtime() -> tokio::runtime::Runtime {
69+
TokioBuilder::new_multi_thread()
70+
.enable_all()
71+
.build()
72+
.expect("Tokio runtime must build")
73+
}
74+
75+
fn start_consensus_node(
76+
tokio_handle: tokio::runtime::Handle,
77+
base_path: PathBuf,
78+
finalize_depth: Option<u32>,
79+
rpc_host: IpAddr,
80+
rpc_port: u16,
81+
) -> MockConsensusNode {
82+
let private_evm = false;
83+
let consensus_key = Sr25519Keyring::Alice;
84+
let rpc_addr = SocketAddr::new(rpc_host, rpc_port);
85+
let rpc_config = MockConsensusNodeRpcConfig {
86+
base_path: BasePath::new(base_path),
87+
finalize_block_depth: finalize_depth,
88+
private_evm,
89+
evm_owner: None,
90+
rpc_addr: Some(rpc_addr),
91+
rpc_port: Some(rpc_port),
92+
};
93+
let mut node = MockConsensusNode::run_with_rpc_options(tokio_handle, consensus_key, rpc_config);
94+
node.start_network();
95+
node
96+
}
97+
98+
fn main() {
99+
init_logger();
100+
101+
let cli = Cli::parse();
102+
let (base_path, _temp_dir_guard) = compute_base_path(&cli);
103+
let block_interval_ms = cli.block_interval_ms;
104+
105+
let runtime = build_runtime();
106+
let _enter = runtime.enter();
107+
let tokio_handle = runtime.handle().clone();
108+
109+
// Start consensus
110+
let consensus_base = base_path.join("consensus");
111+
let mut consensus = start_consensus_node(
112+
tokio_handle.clone(),
113+
consensus_base,
114+
cli.finalize_depth,
115+
cli.rpc_host,
116+
cli.rpc_port,
117+
);
118+
119+
// Optionally start domain (EVM)
120+
let domain = if cli.domain {
121+
let domain_base = BasePath::new(base_path.join("auto-evm"));
122+
let domain_addr = SocketAddr::new(cli.domain_rpc_host, cli.domain_rpc_port);
123+
Some(runtime.block_on(async {
124+
DomainNodeBuilder::new(tokio_handle.clone(), domain_base)
125+
.rpc_addr(domain_addr)
126+
.rpc_port(cli.domain_rpc_port)
127+
.build_evm_node(Role::Authority, EcdsaKeyring::Alice, &mut consensus)
128+
.await
129+
}))
130+
} else {
131+
None
132+
};
133+
consensus.start_cross_domain_gossip_message_worker();
134+
135+
if block_interval_ms > 0 {
136+
// Keep domain node alive - move it into the async block but don't move consensus
137+
let _domain_guard = domain;
138+
runtime.block_on(async {
139+
loop {
140+
tokio::select! {
141+
_ = tokio::signal::ctrl_c() => break,
142+
_ = 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+
147+
}
148+
}
149+
}
150+
});
151+
return;
152+
}
153+
154+
runtime.block_on(async {
155+
let _ = tokio::signal::ctrl_c().await;
156+
});
157+
}

0 commit comments

Comments
 (0)