Today
zcash_local_net::validator::regtest_test_activation_heights puts NU6.1 at height 1000 to dodge zebrad's "missing lockbox disbursements for NU6.1 activation block" rejection. The downstream effect: any test whose chain reaches NU6.1 fails, so regtest fixtures effectively never activate it.
NU6.1 is mainnet-active. A regtest fixture that can't reach it is poor coverage.
Root cause (verified against Zebra source)
zebra-consensus/src/block/check.rs::subsidy_is_valid at the NU6.1 activation block:
if Some(height) == NetworkUpgrade::Nu6_1.activation_height(net) {
let lockbox_disbursements = net.lockbox_disbursements(height);
if lockbox_disbursements.is_empty() {
Err(BlockError::Other(
"missing lockbox disbursements for NU6.1 activation block".to_string(),
))?;
}
...
}
Network::lockbox_disbursements(height) returns:
- Mainnet / default Testnet: hardcoded ZIP-271 disbursement list.
- Regtest: whatever was set via
ParametersBuilder::with_lockbox_disbursements. Defaults to Vec::new() in Parameters::new_regtest.
The check is height-independent — there is no "minimum gap from NU6" rule. Heights 2, 3, 5, 10, 50 all fail the same way against the empty-default. zcashd has no equivalent check today, hence the validator divergence (probed empirically: launch_zcashd_with_nu6_1_at_height_2 passes; every launch_zebrad_with_nu6_1_at_height_* fails with submitblock returning "rejected").
Proposed change
A LockboxDisbursement value type and a ZebradConfig.lockbox_disbursements: Vec<LockboxDisbursement> field, serialized into Zebra's regtest config TOML at [[network.testnet_parameters.lockbox_disbursements]]. Default empty preserves today's behavior; a single dummy disbursement (1 zat to the standard regtest miner address) suffices to pass is_empty().
Schema, matching Zebra's ConfiguredLockboxDisbursement (zebra-chain/src/parameters/network/testnet.rs:124):
[[network.testnet_parameters.lockbox_disbursements]]
address = "tmF..."
amount = 1
Acceptance criteria
Open question
Whether a single dummy disbursement satisfies subsidy_is_valid end-to-end, or whether the activation-block coinbase must also emit matching outputs paying each disbursement address. Settled empirically by the proof-of-life test in the linked PR.
Out of scope
- Coordinating the matching change in zaino (
zaino-common::ZEBRAD_DEFAULT_ACTIVATION_HEIGHTS). Tracked as a downstream issue — see cross-reference.
Cross-reference
Today
zcash_local_net::validator::regtest_test_activation_heightsputs NU6.1 at height 1000 to dodge zebrad's"missing lockbox disbursements for NU6.1 activation block"rejection. The downstream effect: any test whose chain reaches NU6.1 fails, so regtest fixtures effectively never activate it.NU6.1 is mainnet-active. A regtest fixture that can't reach it is poor coverage.
Root cause (verified against Zebra source)
zebra-consensus/src/block/check.rs::subsidy_is_validat the NU6.1 activation block:Network::lockbox_disbursements(height)returns:ParametersBuilder::with_lockbox_disbursements. Defaults toVec::new()inParameters::new_regtest.The check is height-independent — there is no "minimum gap from NU6" rule. Heights 2, 3, 5, 10, 50 all fail the same way against the empty-default. zcashd has no equivalent check today, hence the validator divergence (probed empirically:
launch_zcashd_with_nu6_1_at_height_2passes; everylaunch_zebrad_with_nu6_1_at_height_*fails withsubmitblockreturning"rejected").Proposed change
A
LockboxDisbursementvalue type and aZebradConfig.lockbox_disbursements: Vec<LockboxDisbursement>field, serialized into Zebra's regtest config TOML at[[network.testnet_parameters.lockbox_disbursements]]. Default empty preserves today's behavior; a single dummy disbursement (1 zat to the standard regtest miner address) suffices to passis_empty().Schema, matching Zebra's
ConfiguredLockboxDisbursement(zebra-chain/src/parameters/network/testnet.rs:124):Acceptance criteria
LockboxDisbursementtype andZebradConfig.lockbox_disbursementsfield exposed byzcash_local_net.write_zebrad_configserializes the disbursement list when building the regtest config.Zebrad::launch+generate_blockspast NU6.1 activation when the field is populated.Open question
Whether a single dummy disbursement satisfies
subsidy_is_validend-to-end, or whether the activation-block coinbase must also emit matching outputs paying each disbursement address. Settled empirically by the proof-of-life test in the linked PR.Out of scope
zaino-common::ZEBRAD_DEFAULT_ACTIVATION_HEIGHTS). Tracked as a downstream issue — see cross-reference.Cross-reference
ZEBRAD_DEFAULT_ACTIVATION_HEIGHTSonce this lands.