Skip to content

Commit 75a174e

Browse files
committed
Added export-chain-spec command in order to generate chainspec file dinamically. Use this file in order to start the substrate-node
1 parent f521661 commit 75a174e

File tree

1 file changed

+95
-157
lines changed

1 file changed

+95
-157
lines changed

crates/node/src/kitchensink.rs

Lines changed: 95 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use std::{
2-
io::{BufRead, Write},
2+
fs::create_dir_all,
3+
io::{BufRead, Read},
34
path::PathBuf,
45
process::{Child, Command, Stdio},
56
sync::atomic::{AtomicU32, Ordering},
67
time::Duration,
78
};
89

9-
use sc_chain_spec::{ChainSpecBuilder, ChainType};
10-
use serde_json::{Value, json};
10+
use serde_json::{Value as JsonValue, json};
1111
use sp_core::crypto::Ss58Codec;
1212
use sp_runtime::AccountId32;
1313
use std::fs;
@@ -22,7 +22,7 @@ use alloy::{
2222
},
2323
};
2424

25-
use crate::{Node, chainspec::ChainSpec};
25+
use crate::Node;
2626
use revive_dt_config::Arguments;
2727
use revive_dt_node_interaction::{
2828
EthereumNode, trace::trace_transaction, transaction::execute_transaction,
@@ -37,23 +37,80 @@ pub struct KitchensinkNode {
3737
eth_proxy_binary: PathBuf,
3838
rpc_url: String,
3939
wallet: EthereumWallet,
40+
base_directory: PathBuf,
4041
process_substrate: Option<Child>,
4142
process_proxy: Option<Child>,
4243
}
4344

4445
impl KitchensinkNode {
46+
const BASE_DIRECTORY: &str = "kitchensink";
4547
const SUBSTRATE_READY_MARKER: &str = "Running JSON-RPC server";
4648
const ETH_PROXY_READY_MARKER: &str = "Running JSON-RPC server";
49+
const CHAIN_SPEC_JSON_FILE: &str = "template_chainspec.json";
4750
const BASE_SUBSTRATE_RPC_PORT: u16 = 9944;
4851
const BASE_PROXY_RPC_PORT: u16 = 8545;
4952

50-
fn spawn_process(&mut self, genesis: String) -> anyhow::Result<()> {
53+
fn init(&mut self, genesis_path: &str) -> anyhow::Result<&mut Self> {
54+
create_dir_all(&self.base_directory)?;
55+
56+
let template_chainspec_path = self.base_directory.join(Self::CHAIN_SPEC_JSON_FILE);
57+
58+
let status = Command::new(&self.substrate_binary)
59+
.arg("export-chain-spec")
60+
.arg("--chain")
61+
.arg("dev")
62+
.arg("--output")
63+
.arg(&template_chainspec_path)
64+
.status()?;
65+
66+
if !status.success() {
67+
anyhow::bail!("substrate-node export-chain-spec failed");
68+
}
69+
70+
let mut file = std::fs::File::open(&template_chainspec_path)?;
71+
let mut content = String::new();
72+
file.read_to_string(&mut content)?;
73+
let mut chainspec_json: JsonValue = serde_json::from_str(&content)?;
74+
75+
let existing_chainspec_balances =
76+
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"]
77+
.as_array()
78+
.cloned()
79+
.unwrap_or_default();
80+
81+
let mut merged_balances: Vec<(String, u128)> = existing_chainspec_balances
82+
.into_iter()
83+
.filter_map(|val| {
84+
if let Some(arr) = val.as_array() {
85+
if arr.len() == 2 {
86+
let account = arr[0].as_str()?.to_string();
87+
let balance = arr[1].as_f64()? as u128;
88+
return Some((account, balance));
89+
}
90+
}
91+
None
92+
})
93+
.collect();
94+
let mut eth_balances = self.extract_balance_from_genesis_file(genesis_path)?;
95+
merged_balances.append(&mut eth_balances);
96+
97+
chainspec_json["genesis"]["runtimeGenesis"]["patch"]["balances"]["balances"] =
98+
json!(merged_balances);
99+
100+
serde_json::to_writer_pretty(
101+
std::fs::File::create(&template_chainspec_path)?,
102+
&chainspec_json,
103+
)?;
104+
Ok(self)
105+
}
106+
107+
fn spawn_process(&mut self) -> anyhow::Result<()> {
51108
let substrate_rpc_port = Self::BASE_SUBSTRATE_RPC_PORT + self.id as u16;
52109
let proxy_rpc_port = Self::BASE_PROXY_RPC_PORT + self.id as u16;
53110

54111
self.rpc_url = format!("http://127.0.0.1:{proxy_rpc_port}");
55112

56-
let chainspec_path = self.generate_chainspec_file(&genesis)?;
113+
let chainspec_path = self.base_directory.join(Self::CHAIN_SPEC_JSON_FILE);
57114

58115
// Start Substrate node
59116
let mut substrate_process = Command::new(&self.substrate_binary)
@@ -103,141 +160,12 @@ impl KitchensinkNode {
103160
Ok(())
104161
}
105162

106-
fn generate_chainspec_file(&self, genesis_path: &str) -> anyhow::Result<PathBuf> {
107-
let mut merged_balances: Vec<(String, u128)> = vec![
108-
(
109-
"5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo".to_string(),
110-
1_000_000_000_000_000_000_000,
111-
),
112-
(
113-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy".to_string(),
114-
1_000_000_000_000_000_000_000,
115-
),
116-
];
117-
let mut eth_balances = self.extract_balance_from_genesis_file(genesis_path)?;
118-
merged_balances.append(&mut eth_balances);
119-
120-
let chainspec: ChainSpec = serde_json::from_str(include_str!("default_chainspec.json"))?;
121-
122-
let wasm_binary = hex::decode(
123-
chainspec
124-
.genesis
125-
.runtime_genesis
126-
.code
127-
.trim_start_matches("0x"),
128-
)?;
129-
130-
let builder: ChainSpecBuilder<(), ()> = ChainSpecBuilder::new(&wasm_binary, ())
131-
.with_name("Custom Testnet")
132-
.with_id("custom_testnet")
133-
.with_chain_type(ChainType::Development)
134-
.with_genesis_config_patch(json!({
135-
"balances": { "balances": merged_balances },
136-
137-
"staking": {
138-
"invulnerables": [
139-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy"
140-
],
141-
"minimumValidatorCount": 1,
142-
"slashRewardFraction": 100000000,
143-
"stakers": [
144-
[
145-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy",
146-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy",
147-
1000000000000000000u128,
148-
"Validator"
149-
]
150-
],
151-
"validatorCount": 1
152-
},
153-
154-
"session": {
155-
"keys": [
156-
[
157-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy",
158-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy",
159-
{
160-
"authority_discovery": "5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8",
161-
"babe": "5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8",
162-
"beefy": "KWAeFsyPkaVyGuWbZN9uDANzndXXtR5N5haFCTQZ9Ym2U3wzu",
163-
"grandpa": "5Fb9ayurnxnaXj56CjmyQLBiadfRCqUbL2VWNbbe1nZU6wiC",
164-
"im_online": "5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8",
165-
"mixnet": "5EZaeQ8djPcq9pheJUhgerXQZt9YaHnMJpiHMRhwQeinqUW8"
166-
}
167-
]
168-
]
169-
},
170-
171-
"sudo": {
172-
"key": "5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo"
173-
},
174-
175-
"elections": {
176-
"members": [
177-
[
178-
"5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo",
179-
1000000000000000000u128
180-
],
181-
[
182-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy",
183-
1000000000000000000u128
184-
]
185-
]
186-
},
187-
188-
"revive": {
189-
"mappedAccounts": [
190-
"5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo",
191-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy"
192-
]
193-
},
194-
195-
"technicalCommittee": {
196-
"members": [
197-
"5Ff3iXP75ruzroPWRP2FYBHWnmGGBSb63857BgnzCoXNxfPo",
198-
"5Fbsd6WXDGiLTxunqeK5BATNiocfCqu9bS1yArVjCgeBLkVy"
199-
]
200-
},
201-
202-
"nominationPools": {
203-
"minCreateBond": 1000000000000000u128,
204-
"minJoinBond": 100000000000000u128
205-
},
206-
207-
"society": {
208-
"pot": 0
209-
},
210-
211-
"babe": {
212-
"epochConfig": {
213-
"allowed_slots": "PrimaryAndSecondaryPlainSlots",
214-
"c": [1, 4]
215-
}
216-
}
217-
}))
218-
.with_code(&wasm_binary.to_vec());
219-
220-
let chainspec = builder.build();
221-
let json_spec = chainspec
222-
.as_json(false)
223-
.map_err(|e| anyhow::anyhow!("Failed to generate JSON: {}", e))?;
224-
225-
let temp_path =
226-
std::env::temp_dir().join(format!("chainspec-{}.json", uuid::Uuid::new_v4()));
227-
228-
let mut temp_file = std::fs::File::create(&temp_path)?;
229-
temp_file.write_all(json_spec.as_bytes())?;
230-
temp_file.flush()?;
231-
232-
Ok(temp_path)
233-
}
234-
235163
fn extract_balance_from_genesis_file(
236164
&self,
237165
genesis_path: &str,
238166
) -> anyhow::Result<Vec<(String, u128)>> {
239167
let genesis_str = fs::read_to_string(genesis_path)?;
240-
let genesis_json: Value = serde_json::from_str(&genesis_str)?;
168+
let genesis_json: JsonValue = serde_json::from_str(&genesis_str)?;
241169
let alloc = genesis_json
242170
.get("alloc")
243171
.and_then(|a| a.as_object())
@@ -354,14 +282,17 @@ impl EthereumNode for KitchensinkNode {
354282

355283
impl Node for KitchensinkNode {
356284
fn new(config: &Arguments) -> Self {
285+
let kitchensink_directory = config.directory().join(Self::BASE_DIRECTORY);
357286
let id = NODE_COUNT.fetch_add(1, Ordering::SeqCst);
287+
let base_directory = kitchensink_directory.join(id.to_string());
358288

359289
Self {
360290
id,
361291
substrate_binary: config.kitchensink.clone(),
362292
eth_proxy_binary: config.eth_proxy.clone(),
363293
rpc_url: String::new(),
364294
wallet: config.wallet(),
295+
base_directory,
365296
process_substrate: None,
366297
process_proxy: None,
367298
}
@@ -382,7 +313,7 @@ impl Node for KitchensinkNode {
382313
}
383314

384315
fn spawn(&mut self, genesis: String) -> anyhow::Result<()> {
385-
self.spawn_process(genesis)
316+
self.init(&genesis)?.spawn_process()
386317
}
387318

388319
fn state_diff(&self, transaction: TransactionReceipt) -> anyhow::Result<DiffMode> {
@@ -444,7 +375,7 @@ mod tests {
444375
}
445376

446377
#[test]
447-
fn test_generate_chainspec() {
378+
fn test_init_generates_chainspec_with_balances() {
448379
// Setup: Write a minimal Geth-style genesis.json
449380
let test_genesis_path = std::env::temp_dir().join("test_genesis_file.json");
450381
let genesis_content = r#"
@@ -460,30 +391,37 @@ mod tests {
460391
}
461392
"#;
462393
fs::write(&test_genesis_path, genesis_content).expect("Failed to write test genesis");
394+
let mut dummy_node = KitchensinkNode::new(&test_config().0);
463395

464-
let dummy_node = KitchensinkNode::new(&Arguments::default());
396+
// Call `init()`
397+
dummy_node
398+
.init(test_genesis_path.to_str().unwrap())
399+
.expect("init failed");
465400

466-
let result = dummy_node.generate_chainspec_file(test_genesis_path.to_str().unwrap());
401+
// Check that the patched chainspec file was generated
402+
let final_chainspec_path = dummy_node
403+
.base_directory
404+
.join(KitchensinkNode::CHAIN_SPEC_JSON_FILE);
405+
assert!(final_chainspec_path.exists(), "Chainspec file should exist");
467406

468-
match result {
469-
Ok(path) => {
470-
println!("Chainspec file generated at: {:?}", path);
471-
let contents = fs::read_to_string(&path).expect("Failed to read chainspec");
407+
let contents = fs::read_to_string(&final_chainspec_path).expect("Failed to read chainspec");
472408

473-
let first_eth_addr = dummy_node
474-
.eth_to_substrate_address("90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")
475-
.unwrap();
476-
let second_eth_addr = dummy_node
477-
.eth_to_substrate_address("Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2")
478-
.unwrap();
409+
// Validate that the Substrate addresses derived from the Ethereum addresses are in the file
410+
let first_eth_addr = dummy_node
411+
.eth_to_substrate_address("90F8bf6A479f320ead074411a4B0e7944Ea8c9C1")
412+
.unwrap();
413+
let second_eth_addr = dummy_node
414+
.eth_to_substrate_address("Ab8483F64d9C6d1EcF9b849Ae677dD3315835cb2")
415+
.unwrap();
479416

480-
assert!(contents.contains(&first_eth_addr));
481-
assert!(contents.contains(&second_eth_addr));
482-
}
483-
Err(e) => {
484-
panic!("Failed to generate chainspec: {:?}", e);
485-
}
486-
}
417+
assert!(
418+
contents.contains(&first_eth_addr),
419+
"Chainspec should contain Substrate address for first Ethereum account"
420+
);
421+
assert!(
422+
contents.contains(&second_eth_addr),
423+
"Chainspec should contain Substrate address for second Ethereum account"
424+
);
487425
}
488426

489427
#[test]

0 commit comments

Comments
 (0)