From 0bd449a75b2e4025bef1a0064d30accf26afe120 Mon Sep 17 00:00:00 2001 From: ronkq Date: Tue, 30 Jun 2026 09:16:04 +0200 Subject: [PATCH 1/2] Add testnet configuration --- .../testnet-config.toml | 33 +++++++++++++++++++ zebra-chain/src/block/genesis.rs | 15 ++++++++- .../block/genesis/block-testnet-0-000-000.txt | 1 + zebra-network/src/config.rs | 6 +++- zebrad/src/commands/start.rs | 23 +++++++++---- 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 testnet-single-node-deploy/testnet-config.toml create mode 100644 zebra-chain/src/block/genesis/block-testnet-0-000-000.txt diff --git a/testnet-single-node-deploy/testnet-config.toml b/testnet-single-node-deploy/testnet-config.toml new file mode 100644 index 00000000000..fa0607651df --- /dev/null +++ b/testnet-single-node-deploy/testnet-config.toml @@ -0,0 +1,33 @@ +[mining] +miner_address = "tmLTZegcJN5zaufWQBARHkvqC62mTumm3jR" + +[network] +network = "Testnet" +listen_addr = "0.0.0.0:18233" +initial_testnet_peers = [] + +[network.testnet_parameters] +network_name = "QEDTestnet" +network_magic = [0x51, 0x45, 0x44, 0x54] +disable_pow = true +# extend_funding_stream_addresses_as_required prevents a startup panic from the default +# testnet funding stream address-count validation. The funding_streams = [] below clears +# them at runtime, but the extend flag is needed to survive the earlier validation pass. +extend_funding_stream_addresses_as_required = true +funding_streams = [] +lockbox_disbursements = [] + +[network.testnet_parameters.activation_heights] +NU5 = 1 +NU6 = 1 +NU7 = 1 + +[state] +ephemeral = true + +[mempool] +debug_enable_at_height = 0 + +[rpc] +listen_addr = "0.0.0.0:18232" +enable_cookie_auth = false diff --git a/zebra-chain/src/block/genesis.rs b/zebra-chain/src/block/genesis.rs index 271d791420a..94169195423 100644 --- a/zebra-chain/src/block/genesis.rs +++ b/zebra-chain/src/block/genesis.rs @@ -1,4 +1,4 @@ -//! Regtest genesis block +//! Regtest and Testnet genesis blocks use std::sync::Arc; @@ -17,3 +17,16 @@ pub fn regtest_genesis_block() -> Arc { .map(Arc::new) .expect("hard-coded Regtest genesis block data must deserialize successfully") } + +/// Genesis block for Testnet (and configured testnets that share the Testnet genesis hash), +/// copied from zcashd via `getblock 0 0 -testnet` RPC method +pub fn testnet_genesis_block() -> Arc { + let testnet_genesis_block_bytes = + >::from_hex(include_str!("genesis/block-testnet-0-000-000.txt").trim()) + .expect("Block bytes are in valid hex representation"); + + testnet_genesis_block_bytes + .zcash_deserialize_into() + .map(Arc::new) + .expect("hard-coded Testnet genesis block data must deserialize successfully") +} diff --git a/zebra-chain/src/block/genesis/block-testnet-0-000-000.txt b/zebra-chain/src/block/genesis/block-testnet-0-000-000.txt new file mode 100644 index 00000000000..a04d27d0c54 --- /dev/null +++ b/zebra-chain/src/block/genesis/block-testnet-0-000-000.txt @@ -0,0 +1 @@ +040000000000000000000000000000000000000000000000000000000000000000000000db4d7a85b768123f1dff1d4c4cece70083b2d27e117b4ac2e31d087988a5eac40000000000000000000000000000000000000000000000000000000000000000a11e1358ffff07200600000000000000000000000000000000000000000000000000000000000000fd400500a6a51259c3f6732481e2d035197218b7a69504461d04335503cd69759b2d02bd2b53a9653f42cb33c608511c953673fa9da76170958115fe92157ad3bb5720d927f18e09459bf5c6072973e143e20f9bdf0584058c96b7c2234c7565f100d5eea083ba5d3dbaff9f0681799a113e7beff4a611d2b49590563109962baa149b628aae869af791f2f70bb041bd7ebfa658570917f6654a142b05e7ec0289a4f46470be7be5f693b90173eaaa6e84907170f32602204f1f4e1c04b1830116ffd0c54f0b1caa9a5698357bd8aa1f5ac8fc93b405265d824ba0e49f69dab5446653927298e6b7bdc61ee86ff31c07bde86331b4e500d42e4e50417e285502684b7966184505b885b42819a88469d1e9cf55072d7f3510f85580db689302eab377e4e11b14a91fdd0df7627efc048934f0aff8e7eb77eb17b3a95de13678004f2512293891d8baf8dde0ef69be520a58bbd6038ce899c9594cf3e30b8c3d9c7ecc832d4c19a6212747b50724e6f70f6451f78fd27b58ce43ca33b1641304a916186cfbe7dbca224f55d08530ba851e4df22baf7ab7078e9cbea46c0798b35a750f54103b0cdd08c81a6505c4932f6bfbd492a9fced31d54e98b6370d4c96600552fcf5b37780ed18c8787d03200963600db297a8f05dfa551321d17b9917edadcda51e274830749d133ad226f8bb6b94f13b4f77e67b35b71f52112ce9ba5da706ad9573584a2570a4ff25d29ab9761a06bdcf2c33638bf9baf2054825037881c14adf3816ba0cbd0fca689aad3ce16f2fe362c98f48134a9221765d939f0b49677d1c2447e56b46859f1810e2cf23e82a53e0d44f34dae932581b3b7f49eaec59af872cf9de757a964f7b33d143a36c270189508fcafe19398e4d2966948164d40556b05b7ff532f66f5d1edc41334ef742f78221dfe0c7ae2275bb3f24c89ae35f00afeea4e6ed187b866b209dc6e83b660593fce7c40e143beb07ac86c56f39e895385924667efe3a3f031938753c7764a2dbeb0a643fd359c46e614873fd0424e435fa7fac083b9a41a9d6bf7e284eee537ea7c50dd239f359941a43dc982745184bf3ee31a8dc850316aa9c6b66d6985acee814373be3458550659e1a06287c3b3b76a185c5cb93e38c1eebcf34ff072894b6430aed8d34122dafd925c46a515cca79b0269c92b301890ca6b0dc8b679cdac0f23318c105de73d7a46d16d2dad988d49c22e9963c117960bdc70ef0db6b091cf09445a516176b7f6d58ec29539166cc8a38bbff387acefffab2ea5faad0e8bb70625716ef0edf61940733c25993ea3de9f0be23d36e7cb8da10505f9dc426cd0e6e5b173ab4fff8c37e1f1fb56d1ea372013d075e0934c6919393cfc21395eea20718fad03542a4162a9ded66c814ad8320b2d7c2da3ecaf206da34c502db2096d1c46699a91dd1c432f019ad434e2c1ce507f91104f66f491fed37b225b8e0b2888c37276cfa0468fc13b8d593fd9a2675f0f5b20b8a15f8fa7558176a530d6865738ddb25d3426dab905221681cf9da0e0200eea5b2eba3ad3a5237d2a391f9074bf1779a2005cee43eec2b058511532635e0fea61664f531ac2b356f40db5c5d275a4cf5c82d468976455af4e3362cc8f71aa95e71d394aff3ead6f7101279f95bcd8a0fedce1d21cb3c9f6dd3b182fce0db5d6712981b651f29178a24119968b14783cafa713bc5f2a65205a42e4ce9dc7ba462bdb1f3e4553afc15f5f39998fdb53e7e231e3e520a46943734a007c2daa1eda9f495791657eefcac5c32833936e568d06187857ed04d7b97167ae207c5c5ae54e528c36016a984235e9c5b2f0718d7b3aa93c7822ccc772580b6599671b3c02ece8a21399abd33cfd3028790133167d0a97e7de53dc8ff0101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff071f0104455a6361736830623963346565663862376363343137656535303031653335303039383462366665613335363833613763616331343161303433633432303634383335643334ffffffff010000000000000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000 diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index ab8909d4444..341a77ad7f1 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -858,6 +858,10 @@ impl<'de> Deserialize<'de> for Config { } // Set configured funding streams after setting any parameters that affect the funding stream address period. + // Distinguish "not configured" (None → keep defaults) from "explicitly cleared" (Some([]) → clear). + let explicitly_configured = funding_streams.is_some() + || post_nu6_funding_streams.is_some() + || pre_nu6_funding_streams.is_some(); let mut funding_streams_vec = funding_streams.unwrap_or_default(); if let Some(funding_streams) = post_nu6_funding_streams { @@ -868,7 +872,7 @@ impl<'de> Deserialize<'de> for Config { funding_streams_vec.insert(0, funding_streams); } - if !funding_streams_vec.is_empty() { + if explicitly_configured { params_builder = params_builder.with_funding_streams(funding_streams_vec); } diff --git a/zebrad/src/commands/start.rs b/zebrad/src/commands/start.rs index de1c550ac23..659b3315302 100644 --- a/zebrad/src/commands/start.rs +++ b/zebrad/src/commands/start.rs @@ -82,7 +82,7 @@ use tokio::{pin, select, sync::oneshot}; use tower::{builder::ServiceBuilder, util::BoxService, ServiceExt}; use tracing_futures::Instrument; -use zebra_chain::block::genesis::regtest_genesis_block; +use zebra_chain::block::genesis::{regtest_genesis_block, testnet_genesis_block}; use zebra_consensus::router::BackgroundTaskHandles; use zebra_rpc::{methods::RpcImpl, server::RpcServer, SubmitBlockChannel}; @@ -115,8 +115,14 @@ impl StartCmd { async fn start(&self) -> Result<(), Report> { let config = APPLICATION.config(); let is_regtest = config.network.network.is_regtest(); - - let config = if is_regtest { + // A configured testnet with PoW disabled and no peers is a private isolated network + // (similar to Regtest). It needs the same bootstrap treatment: commit the genesis block + // locally and skip the P2P syncer. + let is_isolated_configured_testnet = !is_regtest + && config.network.network.disable_pow() + && config.network.initial_testnet_peers.is_empty(); + + let config = if is_regtest || is_isolated_configured_testnet { Arc::new(ZebradConfig { mempool: mempool::Config { debug_enable_at_height: Some(0), @@ -370,16 +376,21 @@ impl StartCmd { ); info!("spawning syncer task"); - let syncer_task_handle = if is_regtest { + let syncer_task_handle = if is_regtest || is_isolated_configured_testnet { if !syncer .state_contains(config.network.network.genesis_hash()) .await? { + let genesis_block = if is_regtest { + regtest_genesis_block() + } else { + testnet_genesis_block() + }; let genesis_hash = block_verifier_router .clone() - .oneshot(zebra_consensus::Request::Commit(regtest_genesis_block())) + .oneshot(zebra_consensus::Request::Commit(genesis_block)) .await - .expect("should validate Regtest genesis block"); + .expect("should validate genesis block"); assert_eq!( genesis_hash, From 8133d7f3649a2a6c171d9a9971c0582c2807fd4b Mon Sep 17 00:00:00 2001 From: ronkq Date: Tue, 30 Jun 2026 09:27:10 +0200 Subject: [PATCH 2/2] Update config.rs --- zebra-network/src/config.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zebra-network/src/config.rs b/zebra-network/src/config.rs index 341a77ad7f1..415163df847 100644 --- a/zebra-network/src/config.rs +++ b/zebra-network/src/config.rs @@ -858,7 +858,7 @@ impl<'de> Deserialize<'de> for Config { } // Set configured funding streams after setting any parameters that affect the funding stream address period. - // Distinguish "not configured" (None → keep defaults) from "explicitly cleared" (Some([]) → clear). + // Distinguish "not configured" (None, keep defaults) from "explicitly cleared" (Some([]), clear). let explicitly_configured = funding_streams.is_some() || post_nu6_funding_streams.is_some() || pre_nu6_funding_streams.is_some();