Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7720,6 +7720,7 @@ dependencies = [
"proptest",
"proptest-derive",
"rand 0.8.6",
"rand_core 0.6.4",
"rayon",
"sapling-crypto",
"serde",
Expand All @@ -7733,8 +7734,10 @@ dependencies = [
"tracing-error",
"tracing-futures",
"tracing-subscriber",
"zcash_primitives",
"zcash_proofs",
"zcash_protocol",
"zcash_transparent",
"zebra-chain",
"zebra-node-services",
"zebra-script",
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ debug = false
# The linter should ignore these expected config flags/values
unexpected_cfgs = { level = "warn", check-cfg = [
'cfg(tokio_unstable)', # Used by tokio-console
'cfg(zcash_unstable, values("zfuture", "nu6.1", "nu7", "zip235"))' # Used in Zebra and librustzcash
'cfg(zcash_unstable, values("zfuture", "nu6.1", "nu7", "zip235", "nsm"))' # Used in Zebra and librustzcash
] }

# High-risk code
Expand Down
23 changes: 16 additions & 7 deletions zebra-chain/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,22 +225,31 @@ impl Block {
/// UTXOs, which are ignored.
///
/// Note that the chain value pool has the opposite sign to the transaction value pool.
///
/// The Long-Term Support (NSM / ZIP-234) pool delta is **not** set here.
/// The `lts` leg of the returned [`ValueBalance`] is left at zero;
/// callers that track the LTS pool must derive the signed implicit
/// coinbase claim and call [`set_lts_amount`] on the result.
///
/// [`set_lts_amount`]: crate::value_balance::ValueBalance::set_lts_amount
pub fn chain_value_pool_change(
&self,
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
deferred_pool_balance_change: Option<DeferredPoolBalanceChange>,
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
Ok(*self
let mut value_balance = self
.transactions
.iter()
.flat_map(|t| t.value_balance(utxos))
.sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
.neg()
.set_deferred_amount(
deferred_pool_balance_change
.map(DeferredPoolBalanceChange::value)
.unwrap_or_default(),
))
.neg();
value_balance.set_deferred_amount(
deferred_pool_balance_change
.map(DeferredPoolBalanceChange::value)
.unwrap_or_default(),
);

Ok(value_balance)
}

/// Compute the root of the authorizing data Merkle tree,
Expand Down
8 changes: 8 additions & 0 deletions zebra-chain/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
// Required by bitvec! macro
#![recursion_limit = "256"]

#[cfg(all(
zcash_unstable = "nsm",
not(all(zcash_unstable = "nu7", zcash_unstable = "zip235"))
))]
compile_error!(
"zcash_unstable=\"nsm\" requires zcash_unstable=\"nu7\" and zcash_unstable=\"zip235\""
);

#[macro_use]
extern crate bitflags;

Expand Down
8 changes: 8 additions & 0 deletions zebra-chain/src/parameters/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,14 @@ impl Network {
.expect("Sapling activation height needs to be set")
}

/// Returns the height where V4 transactions stop being accepted.
pub fn v4_deprecation_height(&self) -> Option<Height> {
match self {
Self::Mainnet => None,
Self::Testnet(params) => params.v4_deprecation_height(),
}
}

/// Returns the expected total value of the sum of all NU6.1 one-time lockbox disbursement output values for this network at
/// the provided height.
pub fn lockbox_disbursement_total_amount(&self, height: Height) -> Amount<NonNegative> {
Expand Down
4 changes: 4 additions & 0 deletions zebra-chain/src/parameters/network/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ pub enum ParametersBuilderError {
#[non_exhaustive]
Nu7RequiresUnstableCfg,

#[error("V4 deprecation height must be after NU7 activation height")]
#[non_exhaustive]
InvalidV4DeprecationHeight,

#[error("difficulty limits are valid expanded values")]
#[non_exhaustive]
InvaildDifficultyLimits,
Expand Down
86 changes: 86 additions & 0 deletions zebra-chain/src/parameters/network/subsidy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,92 @@ pub fn founders_reward_address(net: &Network, height: Height) -> Option<transpar
.and_then(|a| a.parse().ok())
}

/// First block at which Long-Term Support (LTS / NSM) payouts begin: two
/// halvenings after NU7 activation. Returns `None` on networks where NU7
/// isn't configured, or where the resulting halving index can't be mapped to
/// a height (e.g. far-future overflow).
///
/// `lts_disbursement_start = height_for_halving(halving(NU7) + 2, network)`
///
/// Before this height the LTS pool only accumulates (via the ZIP-235 60% fee
/// floor); from this height onward, each block's coinbase may claim an extra
/// `lts_payout` amount on top of `block_subsidy + miner_fees`.
///
/// Build-cfg note: every NSM site is gated by `cfg(zcash_unstable = "nsm")`.
/// NSM sits on top of the ZIP-235 LTS pool plumbing, so `zcash_unstable =
/// "nsm"` implies `zcash_unstable = "zip235"` is also set — the build script
/// / xtask sets both. The two cfgs are kept separate so a future build can
/// enable ZIP-235 without NSM, but never the other way around.
#[cfg(zcash_unstable = "nsm")]
pub fn lts_disbursement_start(network: &Network) -> Option<Height> {
let nu7 = NetworkUpgrade::Nu7.activation_height(network)?;
let target_halving = halving(nu7, network).checked_add(2)?;
height_for_halving(target_halving, network)
}

/// Numerator of the per-block LTS payout fraction (ZIP-234 smooth issuance).
#[cfg(zcash_unstable = "nsm")]
const LTS_PAYOUT_FRACTION_NUMERATOR: u64 = 4_126;

/// Denominator of the per-block LTS payout fraction (ZIP-234 smooth issuance).
#[cfg(zcash_unstable = "nsm")]
const LTS_PAYOUT_FRACTION_DENOMINATOR: u64 = 10_000_000_000;

/// Per-block LTS payout for `height`, given the LTS pool snapshot at the
/// parent block.
///
/// Returns `Amount::zero()` before [`lts_disbursement_start`], when NU7 is
/// unconfigured, or when `parent_pool` is empty.
///
/// Inside the disbursement window the payout is recomputed every block as a
/// ZIP-234 smooth-issuance ceiling fraction of the parent LTS pool:
///
/// ```text
/// payout = ceil(parent_pool * LTS_PAYOUT_FRACTION_NUMERATOR
/// / LTS_PAYOUT_FRACTION_DENOMINATOR)
/// payout = min(payout, parent_pool)
/// ```
///
/// The current block's own LTS contributions don't affect the current block's
/// payout — they enter the pool in this block and only affect the next
/// block. This parent-pool rule avoids a circular dependency between the
/// coinbase output, implicit ZIP-235 deposits, transaction fees, and the
/// state transition for the same block. Halving boundaries have no special
/// effect on the LTS payout rate after disbursement begins.
///
/// Ceiling division mirrors ZIP-234 and drains a one-zatoshi pool in one
/// block, so no separate dust rule is needed.
#[cfg(zcash_unstable = "nsm")]
pub fn lts_payout(
height: Height,
network: &Network,
parent_pool: Amount<NonNegative>,
) -> Amount<NonNegative> {
let Some(start) = lts_disbursement_start(network) else {
return Amount::zero();
};

if height < start {
return Amount::zero();
}

let parent_pool_u = u64::from(parent_pool);
if parent_pool_u == 0 {
return Amount::zero();
}

// u128 keeps the consensus math obvious: parent_pool fits in u64, and the
// numerator multiplication can't overflow u128 for any plausible pool.
let numerator = u128::from(parent_pool_u) * u128::from(LTS_PAYOUT_FRACTION_NUMERATOR);
let payout = numerator.div_ceil(u128::from(LTS_PAYOUT_FRACTION_DENOMINATOR));
let capped = payout.min(u128::from(parent_pool_u));

// capped ≤ parent_pool ≤ u64::MAX, so the conversion through u64 and into
// Amount<NonNegative> can't fail.
Amount::try_from(u64::try_from(capped).expect("capped ≤ parent_pool ≤ u64::MAX"))
.expect("capped LTS payout fits in Amount because it is at most parent_pool")
}

/// `FoundersReward(height)` as described in [§7.8].
///
/// [§7.8]: <https://zips.z.cash/protocol/protocol.pdf#subsidies>
Expand Down
51 changes: 51 additions & 0 deletions zebra-chain/src/parameters/network/testnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ pub struct ParametersBuilder {
genesis_hash: block::Hash,
/// The network upgrade activation heights for this network, see [`Parameters::activation_heights`] for more details.
activation_heights: BTreeMap<Height, NetworkUpgrade>,
/// The height at which V4 transactions are no longer accepted.
v4_deprecation_height: Option<Height>,
/// Slow start interval for this network
slow_start_interval: Height,
/// Funding streams for this network
Expand Down Expand Up @@ -492,6 +494,7 @@ impl Default for ParametersBuilder {
//
// `Genesis` network upgrade activation height must always be 0
activation_heights: TESTNET_ACTIVATION_HEIGHTS.iter().cloned().collect(),
v4_deprecation_height: None,
genesis_hash: TESTNET_GENESIS_HASH
.parse()
.expect("hard-coded hash parses"),
Expand Down Expand Up @@ -678,6 +681,17 @@ impl ParametersBuilder {
Ok(self)
}

/// Sets the height where V4 transactions stop being accepted.
pub fn with_v4_deprecation_height(mut self, height: Height) -> Self {
self.v4_deprecation_height = Some(height);
self
}

fn with_optional_v4_deprecation_height(mut self, height: Option<Height>) -> Self {
self.v4_deprecation_height = height;
self
}

/// Sets the slow start interval to be used in the [`Parameters`] being built.
pub fn with_slow_start_interval(mut self, slow_start_interval: Height) -> Self {
self.slow_start_interval = slow_start_interval;
Expand Down Expand Up @@ -832,6 +846,7 @@ impl ParametersBuilder {
network_magic,
genesis_hash,
activation_heights,
v4_deprecation_height,
slow_start_interval,
funding_streams,
should_lock_funding_stream_address_period: _,
Expand All @@ -849,6 +864,7 @@ impl ParametersBuilder {
network_magic,
genesis_hash,
activation_heights,
v4_deprecation_height,
slow_start_interval,
slow_start_shift: Height(slow_start_interval.0 / 2),
funding_streams,
Expand All @@ -868,10 +884,29 @@ impl ParametersBuilder {
Network::new_configured_testnet(self.clone().finish())
}

fn validate_v4_deprecation_height(&self) -> Result<(), ParametersBuilderError> {
let Some(v4_deprecation_height) = self.v4_deprecation_height else {
return Ok(());
};

let network = self.to_network_unchecked();
let Some(nu7_activation_height) = NetworkUpgrade::Nu7.activation_height(&network) else {
return Err(ParametersBuilderError::InvalidV4DeprecationHeight);
};

if v4_deprecation_height <= nu7_activation_height {
return Err(ParametersBuilderError::InvalidV4DeprecationHeight);
}

Ok(())
}

/// Checks funding streams and converts the builder to a configured [`Network::Testnet`]
pub fn to_network(self) -> Result<Network, ParametersBuilderError> {
let network = self.to_network_unchecked();

self.validate_v4_deprecation_height()?;

// Final check that the configured funding streams will be valid for these Testnet parameters.
for fs in &self.funding_streams {
// Check that the funding streams are valid for the configured Testnet parameters.
Expand All @@ -896,6 +931,7 @@ impl ParametersBuilder {
network_magic,
genesis_hash,
activation_heights,
v4_deprecation_height,
slow_start_interval,
funding_streams,
should_lock_funding_stream_address_period: _,
Expand All @@ -910,6 +946,7 @@ impl ParametersBuilder {
} = Self::default();

self.activation_heights == activation_heights
&& self.v4_deprecation_height == v4_deprecation_height
&& self.network_magic == network_magic
&& self.genesis_hash == genesis_hash
&& self.slow_start_interval == slow_start_interval
Expand All @@ -929,6 +966,8 @@ impl ParametersBuilder {
pub struct RegtestParameters {
/// The configured network upgrade activation heights to use on Regtest
pub activation_heights: ConfiguredActivationHeights,
/// The height at which V4 transactions are no longer accepted on Regtest.
pub v4_deprecation_height: Option<Height>,
/// Configured funding streams
pub funding_streams: Option<Vec<ConfiguredFundingStreams>>,
/// Expected one-time lockbox disbursement outputs in NU6.1 activation block coinbase for Regtest
Expand Down Expand Up @@ -959,6 +998,8 @@ pub struct Parameters {
genesis_hash: block::Hash,
/// The network upgrade activation heights for this network.
activation_heights: BTreeMap<Height, NetworkUpgrade>,
/// The height at which V4 transactions are no longer accepted.
v4_deprecation_height: Option<Height>,
/// Slow start interval for this network
slow_start_interval: Height,
/// Slow start shift for this network, always half the slow start interval
Expand Down Expand Up @@ -1020,6 +1061,7 @@ impl Parameters {
pub fn new_regtest(
RegtestParameters {
activation_heights,
v4_deprecation_height,
funding_streams,
lockbox_disbursements,
checkpoints,
Expand All @@ -1036,11 +1078,14 @@ impl Parameters {
// Removes default Testnet activation heights if not configured,
// most network upgrades are disabled by default for Regtest in zcashd
.with_activation_heights(activation_heights.for_regtest())?
.with_optional_v4_deprecation_height(v4_deprecation_height)
.with_halving_interval(PRE_BLOSSOM_REGTEST_HALVING_INTERVAL)?
.with_funding_streams(funding_streams.unwrap_or_default())
.with_lockbox_disbursements(lockbox_disbursements.unwrap_or_default())
.with_checkpoints(checkpoints.unwrap_or_default())?;

parameters.validate_v4_deprecation_height()?;

if Some(true) == extend_funding_stream_addresses_as_required {
parameters = parameters.extend_funding_streams();
}
Expand Down Expand Up @@ -1070,6 +1115,7 @@ impl Parameters {
genesis_hash,
// Activation heights are configurable on Regtest
activation_heights: _,
v4_deprecation_height: _,
slow_start_interval,
slow_start_shift,
funding_streams: _,
Expand Down Expand Up @@ -1115,6 +1161,11 @@ impl Parameters {
&self.activation_heights
}

/// Returns the height where V4 transactions stop being accepted.
pub fn v4_deprecation_height(&self) -> Option<Height> {
self.v4_deprecation_height
}

/// Returns slow start interval for this network
pub fn slow_start_interval(&self) -> Height {
self.slow_start_interval
Expand Down
2 changes: 2 additions & 0 deletions zebra-chain/src/parameters/network/tests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#![allow(clippy::unwrap_in_result)]

#[cfg(zcash_unstable = "nsm")]
mod lts;
mod prop;
mod vectors;

Expand Down
Loading
Loading