change: Track the balance of the deferred chain value pool (#8729)

* Addresses clippy lints

* checks network magic and returns early from `is_regtest()`

* Moves  `subsidy.rs` to `zebra-chain`, refactors funding streams into structs, splits them into pre/post NU6 funding streams, and adds them as a field on `testnet::Parameters`

* Replaces Vec with HashMap, adds `ConfiguredFundingStreams` type and conversion logic with constraints.

Minor refactors

* Empties recipients list

* Adds a comment on num_addresses calculation being invalid for configured Testnets, but that being okay since configured testnet parameters are checked when they're being built

* Documentation fixes, minor cleanup, renames a test, adds TODOs, and fixes test logic

* Removes unnecessary `ParameterSubsidy` impl for &Network, adds docs and TODOs

* Adds a "deferred" FundingStreamReceiver, adds a post-NU6 funding streams, updates the `miner_fees_are_valid()` and `subsidy_is_valid()` functions to check that the deferred pool contribution is valid and that there are no unclaimed block subsidies after NU6 activation, and adds some TODOs

* adds `lockbox_input_value()` fn

* Adds TODOs for linking to relevant ZIPs and updating height ranges

* Adds `nu6_lockbox_funding_stream` acceptance test

* updates funding stream values test to check post-NU6 funding streams too, adds Mainnet/Testnet NU6 activation heights, fixes lints/compilation issue

* Reverts Mainnet/Testnet NU6 activation height definitions, updates `test_funding_stream_values()` to use a configured testnet with the post-NU6 Mainnet funding streams height range

* reverts unnecessary refactor

* appease clippy

* Adds a test for `lockbox_input_value()`

* Applies suggestions from code review

* Fixes potential panic

* Fixes bad merge

* Update zebra-chain/src/parameters/network_upgrade.rs

* Updates acceptance test to check that invalid blocks are rejected

* Checks that the original valid block template at height 2 is accepted as a block submission

* Reverts changes for coinbase should balance exactly ZIP

* Add `Deferred` to `ValueBalance`

* Update snapshots

* Unrelated: Revise docs

* Add TODOs

* Stop recalculating the block subsidy

* Track deferred balances

* Support heights below slow start shift in halvings

* Fix `CheckpointVerifiedBlock` conversion in tests

* Allow deserialization of legacy `ValueBalance`s

* Simplify docs

* Fix warnings raised by Clippy

* Fix warnings raised by `cargo fmt`

* Update zebra-chain/src/block.rs

Co-authored-by: Arya <aryasolhi@gmail.com>

* Refactor docs around chain value pool changes

* updates test name

* Updates deferred pool funding stream name to "Lockbox", moves post-NU6 height ranges to constants, updates TODO

* Updates `get_block_subsidy()` RPC method to exclude lockbox funding stream from `fundingstreams` field

* Adds a TODO for updating `FundingStreamReceiver::name()` method docs

* Updates `FundingStreamRecipient::new()` to accept an iterator of items instead of an option of an iterator, updates a comment quoting the coinbase transaction balance consensus rule to note that the current code is inconsistent with the protocol spec, adds a TODO for updating the quote there once the protocol spec has been updated.

* Update zebra-consensus/src/checkpoint.rs

Co-authored-by: Arya <aryasolhi@gmail.com>

* Update docs for value balances

* Cleanup: Simplify getting info for FS receivers

* Avoid a panic when deserializing value balances

* Uses FPF Testnet address for post-NU6 testnet funding streams

* Updates the NU6 consensus branch id

* Update zebra-consensus/src/checkpoint.rs

* Bump the major database format version

* Add a database upgrade mark

* Fix tests after merge

* trigger GitHub actions

---------

Co-authored-by: Arya <aryasolhi@gmail.com>
Co-authored-by: Pili Guerra <mpguerra@users.noreply.github.com>
This commit is contained in:
Marek 2024-08-05 22:30:47 +02:00 committed by GitHub
parent 16168d7623
commit 82ded59a31
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 676 additions and 401 deletions

View File

@ -5,7 +5,7 @@ use std::{collections::HashMap, fmt, ops::Neg, sync::Arc};
use halo2::pasta::pallas;
use crate::{
amount::NegativeAllowed,
amount::{Amount, NegativeAllowed, NonNegative},
block::merkle::AuthDataRoot,
fmt::DisplayToDebug,
orchard,
@ -205,34 +205,39 @@ impl Block {
.expect("number of transactions must fit u64")
}
/// Get the overall chain value pool change in this block,
/// the negative sum of the transaction value balances in this block.
/// Returns the overall chain value pool change in this block---the negative sum of the
/// transaction value balances in this block.
///
/// These are the changes in the transparent, sprout, sapling, and orchard
/// chain value pools, as a result of this block.
/// These are the changes in the transparent, Sprout, Sapling, Orchard, and
/// Deferred chain value pools, as a result of this block.
///
/// Positive values are added to the corresponding chain value pool.
/// Negative values are removed from the corresponding pool.
/// Positive values are added to the corresponding chain value pool and negative values are
/// removed from the corresponding pool.
///
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
///
/// `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
/// including UTXOs created by earlier transactions in this block.
/// (It can also contain unrelated UTXOs, which are ignored.)
/// The given `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
/// including UTXOs created by earlier transactions in this block. It can also contain unrelated
/// UTXOs, which are ignored.
///
/// Note: the chain value pool has the opposite sign to the transaction
/// value pool.
/// Note that the chain value pool has the opposite sign to the transaction value pool.
pub fn chain_value_pool_change(
&self,
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
deferred_balance: Option<Amount<NonNegative>>,
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
let transaction_value_balance_total = self
Ok(*self
.transactions
.iter()
.flat_map(|t| t.value_balance(utxos))
.sum::<Result<ValueBalance<NegativeAllowed>, _>>()?;
Ok(transaction_value_balance_total.neg())
.sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
.neg()
.set_deferred_amount(
deferred_balance
.unwrap_or(Amount::zero())
.constrain::<NegativeAllowed>()
.map_err(ValueBalanceError::Deferred)?,
))
}
/// Compute the root of the authorizing data Merkle tree,

View File

@ -68,20 +68,24 @@ pub enum FundingStreamReceiver {
}
impl FundingStreamReceiver {
/// The name for each funding stream receiver, as described in [ZIP-1014] and [`zcashd`].
/// Returns a human-readable name and a specification URL for the receiver, as described in
/// [ZIP-1014] and [`zcashd`].
///
/// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract
/// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32
// TODO: Update method documentation with a reference to https://zips.z.cash/draft-nuttycom-funding-allocation once its
// status is updated to 'Proposed'.
pub fn name(self) -> &'static str {
match self {
FundingStreamReceiver::Ecc => "Electric Coin Company",
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
FundingStreamReceiver::MajorGrants => "Major Grants",
// TODO: Find out what this should be called and update the funding stream name.
FundingStreamReceiver::Deferred => "Lockbox",
}
pub fn info(&self) -> (&'static str, &'static str) {
(
match self {
FundingStreamReceiver::Ecc => "Electric Coin Company",
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
FundingStreamReceiver::MajorGrants => "Major Grants",
// TODO: Find out what this should be called and update the funding stream name
FundingStreamReceiver::Deferred => "Lockbox",
},
FUNDING_STREAM_SPECIFICATION,
)
}
}
@ -90,6 +94,7 @@ impl FundingStreamReceiver {
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100;
// TODO: Update the link for post-NU6 funding streams.
/// The specification for all current funding stream receivers, a URL that links to [ZIP-214].
///
/// [ZIP-214]: https://zips.z.cash/zip-0214

View File

@ -1390,10 +1390,7 @@ impl Transaction {
.map(|shielded_data| &mut shielded_data.value_balance)
}
/// Get the value balances for this transaction,
/// using the transparent outputs spent in this transaction.
///
/// See `value_balance` for details.
/// Returns the value balances for this transaction using the provided transparent outputs.
pub(crate) fn value_balance_from_outputs(
&self,
outputs: &HashMap<transparent::OutPoint, transparent::Output>,
@ -1404,25 +1401,26 @@ impl Transaction {
+ self.orchard_value_balance()
}
/// Get the value balances for this transaction.
/// These are the changes in the transaction value pool,
/// split up into transparent, sprout, sapling, and orchard values.
/// Returns the value balances for this transaction.
///
/// Calculated as the sum of the inputs and outputs from each pool,
/// or the sum of the value balances from each pool.
/// These are the changes in the transaction value pool, split up into transparent, Sprout,
/// Sapling, and Orchard values.
///
/// Positive values are added to this transaction's value pool,
/// and removed from the corresponding chain value pool.
/// Negative values are removed from this transaction,
/// and added to the corresponding pool.
/// Calculated as the sum of the inputs and outputs from each pool, or the sum of the value
/// balances from each pool.
///
/// Positive values are added to this transaction's value pool, and removed from the
/// corresponding chain value pool. Negative values are removed from this transaction, and added
/// to the corresponding pool.
///
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
///
/// `utxos` must contain the utxos of every input in the transaction,
/// including UTXOs created by earlier transactions in this block.
/// `utxos` must contain the utxos of every input in the transaction, including UTXOs created by
/// earlier transactions in this block.
///
/// Note: the chain value pool has the opposite sign to the transaction
/// value pool.
/// ## Note
///
/// The chain value pool has the opposite sign to the transaction value pool.
pub fn value_balance(
&self,
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,

View File

@ -1,16 +1,14 @@
//! A type that can hold the four types of Zcash value pools.
//! Balances in chain value pools and transaction value pools.
use crate::{
amount::{self, Amount, Constraint, NegativeAllowed, NonNegative},
block::Block,
transparent,
};
use crate::amount::{self, Amount, Constraint, NegativeAllowed, NonNegative};
use core::fmt;
#[cfg(any(test, feature = "proptest-impl"))]
use std::{borrow::Borrow, collections::HashMap};
#[cfg(any(test, feature = "proptest-impl"))]
use crate::{amount::MAX_MONEY, transaction::Transaction};
use crate::{amount::MAX_MONEY, transaction::Transaction, transparent};
#[cfg(any(test, feature = "proptest-impl"))]
mod arbitrary;
@ -20,13 +18,14 @@ mod tests;
use ValueBalanceError::*;
/// An amount spread between different Zcash pools.
/// A balance in each chain value pool or transaction value pool.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
pub struct ValueBalance<C> {
transparent: Amount<C>,
sprout: Amount<C>,
sapling: Amount<C>,
orchard: Amount<C>,
deferred: Amount<C>,
}
impl<C> ValueBalance<C>
@ -116,6 +115,17 @@ where
self
}
/// Returns the deferred amount.
pub fn deferred_amount(&self) -> Amount<C> {
self.deferred
}
/// Sets the deferred amount without affecting other amounts.
pub fn set_deferred_amount(&mut self, deferred_amount: Amount<C>) -> &Self {
self.deferred = deferred_amount;
self
}
/// Creates a [`ValueBalance`] where all the pools are zero.
pub fn zero() -> Self {
let zero = Amount::zero();
@ -124,6 +134,7 @@ where
sprout: zero,
sapling: zero,
orchard: zero,
deferred: zero,
}
}
@ -138,6 +149,7 @@ where
sprout: self.sprout.constrain().map_err(Sprout)?,
sapling: self.sapling.constrain().map_err(Sapling)?,
orchard: self.orchard.constrain().map_err(Orchard)?,
deferred: self.deferred.constrain().map_err(Deferred)?,
})
}
}
@ -166,60 +178,6 @@ impl ValueBalance<NegativeAllowed> {
}
impl ValueBalance<NonNegative> {
/// Returns the sum of this value balance, and the chain value pool changes in `block`.
///
/// `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
/// including UTXOs created by earlier transactions in this block.
///
/// Note: the chain value pool has the opposite sign to the transaction
/// value pool.
///
/// See [`Block::chain_value_pool_change`] for details.
///
/// # Consensus
///
/// > If the Sprout chain value pool balance would become negative in the block chain
/// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitbalance>
///
/// > If the Sapling chain value pool balance would become negative in the block chain
/// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingbalance>
///
/// > If the Orchard chain value pool balance would become negative in the block chain
/// > created as a result of accepting a block , then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
///
/// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
/// > "Orchard chain value pool balance" would become negative in the block chain created
/// > as a result of accepting a block, then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/zip-0209#specification>
///
/// Zebra also checks that the transparent value pool is non-negative.
/// In Zebra, we define this pool as the sum of all unspent transaction outputs.
/// (Despite their encoding as an `int64`, transparent output values must be non-negative.)
///
/// This is a consensus rule derived from Bitcoin:
///
/// > because a UTXO can only be spent once,
/// > the full value of the included UTXOs must be spent or given to a miner as a transaction fee.
///
/// <https://developer.bitcoin.org/devguide/transactions.html#transaction-fees-and-change>
pub fn add_block(
self,
block: impl Borrow<Block>,
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
let chain_value_pool_change = block.borrow().chain_value_pool_change(utxos)?;
// This will error if the chain value pool balance gets negative with the change.
self.add_chain_value_pool_change(chain_value_pool_change)
}
/// Returns the sum of this value balance, and the chain value pool changes in `transaction`.
///
/// `outputs` must contain the [`transparent::Output`]s of every input in this transaction,
@ -228,9 +186,6 @@ impl ValueBalance<NonNegative> {
/// Note: the chain value pool has the opposite sign to the transaction
/// value pool.
///
/// See [`Block::chain_value_pool_change`] and [`Transaction::value_balance`]
/// for details.
///
/// # Consensus
///
/// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
@ -269,9 +224,6 @@ impl ValueBalance<NonNegative> {
///
/// Note: the chain value pool has the opposite sign to the transaction
/// value pool. Inputs remove value from the chain value pool.
///
/// See [`Block::chain_value_pool_change`] and [`Transaction::value_balance`]
/// for details.
#[cfg(any(test, feature = "proptest-impl"))]
pub fn add_transparent_input(
self,
@ -289,12 +241,46 @@ impl ValueBalance<NonNegative> {
self.add_chain_value_pool_change(transparent_value_pool_change)
}
/// Returns the sum of this value balance, and the `chain_value_pool_change`.
/// Returns the sum of this value balance, and the given `chain_value_pool_change`.
///
/// Note: the chain value pool has the opposite sign to the transaction
/// value pool.
/// Note that the chain value pool has the opposite sign to the transaction value pool.
///
/// See `add_block` for details.
/// # Consensus
///
/// > If the Sprout chain value pool balance would become negative in the block chain
/// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/protocol/protocol.pdf#joinsplitbalance>
///
/// > If the Sapling chain value pool balance would become negative in the block chain
/// > created as a result of accepting a block, then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/protocol/protocol.pdf#saplingbalance>
///
/// > If the Orchard chain value pool balance would become negative in the block chain
/// > created as a result of accepting a block , then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/protocol/protocol.pdf#orchardbalance>
///
/// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
/// > "Orchard chain value pool balance" would become negative in the block chain created
/// > as a result of accepting a block, then all nodes MUST reject the block as invalid.
///
/// <https://zips.z.cash/zip-0209#specification>
///
/// Zebra also checks that the transparent value pool is non-negative.
/// In Zebra, we define this pool as the sum of all unspent transaction outputs.
/// (Despite their encoding as an `int64`, transparent output values must be non-negative.)
///
/// This is a consensus rule derived from Bitcoin:
///
/// > because a UTXO can only be spent once,
/// > the full value of the included UTXOs must be spent or given to a miner as a transaction fee.
///
/// <https://developer.bitcoin.org/devguide/transactions.html#transaction-fees-and-change>
///
/// We implement the consensus rules above by constraining the returned value balance to
/// [`ValueBalance<NonNegative>`].
#[allow(clippy::unwrap_in_result)]
pub fn add_chain_value_pool_change(
self,
@ -333,15 +319,20 @@ impl ValueBalance<NonNegative> {
}
/// To byte array
pub fn to_bytes(self) -> [u8; 32] {
let transparent = self.transparent.to_bytes();
let sprout = self.sprout.to_bytes();
let sapling = self.sapling.to_bytes();
let orchard = self.orchard.to_bytes();
match [transparent, sprout, sapling, orchard].concat().try_into() {
pub fn to_bytes(self) -> [u8; 40] {
match [
self.transparent.to_bytes(),
self.sprout.to_bytes(),
self.sapling.to_bytes(),
self.orchard.to_bytes(),
self.deferred.to_bytes(),
]
.concat()
.try_into()
{
Ok(bytes) => bytes,
_ => unreachable!(
"Four [u8; 8] should always concat with no error into a single [u8; 32]"
"five [u8; 8] should always concat with no error into a single [u8; 40]"
),
}
}
@ -349,39 +340,59 @@ impl ValueBalance<NonNegative> {
/// From byte array
#[allow(clippy::unwrap_in_result)]
pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
let bytes_length = bytes.len();
// Return an error early if bytes don't have the right lenght instead of panicking later.
match bytes_length {
32 | 40 => {}
_ => return Err(Unparsable),
};
let transparent = Amount::from_bytes(
bytes[0..8]
.try_into()
.expect("Extracting the first quarter of a [u8; 32] should always succeed"),
.expect("transparent amount should be parsable"),
)
.map_err(Transparent)?;
let sprout = Amount::from_bytes(
bytes[8..16]
.try_into()
.expect("Extracting the second quarter of a [u8; 32] should always succeed"),
.expect("sprout amount should be parsable"),
)
.map_err(Sprout)?;
let sapling = Amount::from_bytes(
bytes[16..24]
.try_into()
.expect("Extracting the third quarter of a [u8; 32] should always succeed"),
.expect("sapling amount should be parsable"),
)
.map_err(Sapling)?;
let orchard = Amount::from_bytes(
bytes[24..32]
.try_into()
.expect("Extracting the last quarter of a [u8; 32] should always succeed"),
.expect("orchard amount should be parsable"),
)
.map_err(Orchard)?;
let deferred = match bytes_length {
32 => Amount::zero(),
40 => Amount::from_bytes(
bytes[32..40]
.try_into()
.expect("deferred amount should be parsable"),
)
.map_err(Deferred)?,
_ => return Err(Unparsable),
};
Ok(ValueBalance {
transparent,
sprout,
sapling,
orchard,
deferred,
})
}
}
@ -400,6 +411,12 @@ pub enum ValueBalanceError {
/// orchard amount error {0}
Orchard(amount::Error),
/// deferred amount error {0}
Deferred(amount::Error),
/// ValueBalance is unparsable
Unparsable,
}
impl fmt::Display for ValueBalanceError {
@ -409,6 +426,8 @@ impl fmt::Display for ValueBalanceError {
Sprout(e) => format!("sprout amount err: {e}"),
Sapling(e) => format!("sapling amount err: {e}"),
Orchard(e) => format!("orchard amount err: {e}"),
Deferred(e) => format!("deferred amount err: {e}"),
Unparsable => "value balance is unparsable".to_string(),
})
}
}
@ -424,6 +443,7 @@ where
sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
deferred: (self.deferred + rhs.deferred).map_err(Deferred)?,
})
}
}
@ -472,6 +492,7 @@ where
sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
deferred: (self.deferred - rhs.deferred).map_err(Deferred)?,
})
}
}
@ -540,6 +561,7 @@ where
sprout: self.sprout.neg(),
sapling: self.sapling.neg(),
orchard: self.orchard.neg(),
deferred: self.deferred.neg(),
}
}
}

View File

@ -10,12 +10,14 @@ impl Arbitrary for ValueBalance<NegativeAllowed> {
any::<Amount<NegativeAllowed>>(),
any::<Amount<NegativeAllowed>>(),
any::<Amount<NegativeAllowed>>(),
any::<Amount<NegativeAllowed>>(),
)
.prop_map(|(transparent, sprout, sapling, orchard)| Self {
.prop_map(|(transparent, sprout, sapling, orchard, deferred)| Self {
transparent,
sprout,
sapling,
orchard,
deferred,
})
.boxed()
}
@ -32,12 +34,14 @@ impl Arbitrary for ValueBalance<NonNegative> {
any::<Amount<NonNegative>>(),
any::<Amount<NonNegative>>(),
any::<Amount<NonNegative>>(),
any::<Amount<NonNegative>>(),
)
.prop_map(|(transparent, sprout, sapling, orchard)| Self {
.prop_map(|(transparent, sprout, sapling, orchard, deferred)| Self {
transparent,
sprout,
sapling,
orchard,
deferred,
})
.boxed()
}

View File

@ -16,15 +16,17 @@ proptest! {
let sprout = value_balance1.sprout + value_balance2.sprout;
let sapling = value_balance1.sapling + value_balance2.sapling;
let orchard = value_balance1.orchard + value_balance2.orchard;
let deferred = value_balance1.deferred + value_balance2.deferred;
match (transparent, sprout, sapling, orchard) {
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard)) => prop_assert_eq!(
match (transparent, sprout, sapling, orchard, deferred) {
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard), Ok(deferred)) => prop_assert_eq!(
value_balance1 + value_balance2,
Ok(ValueBalance {
transparent,
sprout,
sapling,
orchard,
deferred
})
),
_ => prop_assert!(
@ -33,7 +35,8 @@ proptest! {
Err(ValueBalanceError::Transparent(_)
| ValueBalanceError::Sprout(_)
| ValueBalanceError::Sapling(_)
| ValueBalanceError::Orchard(_))
| ValueBalanceError::Orchard(_)
| ValueBalanceError::Deferred(_))
)
),
}
@ -49,26 +52,27 @@ proptest! {
let sprout = value_balance1.sprout - value_balance2.sprout;
let sapling = value_balance1.sapling - value_balance2.sapling;
let orchard = value_balance1.orchard - value_balance2.orchard;
let deferred = value_balance1.deferred - value_balance2.deferred;
match (transparent, sprout, sapling, orchard) {
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard)) => prop_assert_eq!(
match (transparent, sprout, sapling, orchard, deferred) {
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard), Ok(deferred)) => prop_assert_eq!(
value_balance1 - value_balance2,
Ok(ValueBalance {
transparent,
sprout,
sapling,
orchard,
deferred
})
),
_ => prop_assert!(
matches!(
_ => prop_assert!(matches!(
value_balance1 - value_balance2,
Err(ValueBalanceError::Transparent(_)
| ValueBalanceError::Sprout(_)
| ValueBalanceError::Sapling(_)
| ValueBalanceError::Orchard(_))
)
),
| ValueBalanceError::Orchard(_)
| ValueBalanceError::Deferred(_))
)),
}
}
@ -85,23 +89,27 @@ proptest! {
let sprout = value_balance1.sprout + value_balance2.sprout;
let sapling = value_balance1.sapling + value_balance2.sapling;
let orchard = value_balance1.orchard + value_balance2.orchard;
let deferred = value_balance1.deferred + value_balance2.deferred;
match (transparent, sprout, sapling, orchard) {
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard)) => prop_assert_eq!(
match (transparent, sprout, sapling, orchard, deferred) {
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard), Ok(deferred)) => prop_assert_eq!(
collection.iter().sum::<Result<ValueBalance<NegativeAllowed>, ValueBalanceError>>(),
Ok(ValueBalance {
transparent,
sprout,
sapling,
orchard,
deferred
})
),
_ => prop_assert!(matches!(collection.iter().sum(),
Err(ValueBalanceError::Transparent(_)
| ValueBalanceError::Sprout(_)
| ValueBalanceError::Sapling(_)
| ValueBalanceError::Orchard(_))
))
_ => prop_assert!(matches!(
collection.iter().sum(),
Err(ValueBalanceError::Transparent(_)
| ValueBalanceError::Sprout(_)
| ValueBalanceError::Sapling(_)
| ValueBalanceError::Orchard(_)
| ValueBalanceError::Deferred(_))
))
}
}
@ -115,11 +123,27 @@ proptest! {
}
#[test]
fn value_balance_deserialization(bytes in any::<[u8; 32]>()) {
fn value_balance_deserialization(bytes in any::<[u8; 40]>()) {
let _init_guard = zebra_test::init();
if let Ok(deserialized) = ValueBalance::<NonNegative>::from_bytes(&bytes) {
prop_assert_eq!(bytes, deserialized.to_bytes());
}
}
/// The legacy version of [`ValueBalance`] had 32 bytes compared to the current 40 bytes,
/// but it's possible to correctly instantiate the current version of [`ValueBalance`] from
/// the legacy format, so we test if Zebra can still deserialiaze the legacy format.
#[test]
fn legacy_value_balance_deserialization(bytes in any::<[u8; 32]>()) {
let _init_guard = zebra_test::init();
if let Ok(deserialized) = ValueBalance::<NonNegative>::from_bytes(&bytes) {
let deserialized = deserialized.to_bytes();
let mut extended_bytes = [0u8; 40];
extended_bytes[..32].copy_from_slice(&bytes);
prop_assert_eq!(extended_bytes, deserialized);
}
}
}

View File

@ -21,7 +21,13 @@ use thiserror::Error;
use tower::{Service, ServiceExt};
use tracing::Instrument;
use zebra_chain::{amount::Amount, block, parameters::Network, transparent, work::equihash};
use zebra_chain::{
amount::Amount,
block,
parameters::{subsidy::FundingStreamReceiver, Network},
transparent,
work::equihash,
};
use zebra_state as zs;
use crate::{error::*, transaction as tx, BoxError};
@ -78,6 +84,9 @@ pub enum VerifyBlockError {
#[error("invalid transaction")]
Transaction(#[from] TransactionError),
#[error("invalid block subsidy")]
Subsidy(#[from] zebra_chain::amount::Error),
}
impl VerifyBlockError {
@ -205,7 +214,10 @@ where
check::time_is_valid_at(&block.header, now, &height, &hash)
.map_err(VerifyBlockError::Time)?;
let coinbase_tx = check::coinbase_is_first(&block)?;
check::subsidy_is_valid(&block, &network)?;
let expected_block_subsidy = subsidy::general::block_subsidy(height, &network)?;
check::subsidy_is_valid(&block, &network, expected_block_subsidy)?;
// Now do the slower checks
@ -271,13 +283,29 @@ where
})?;
}
// TODO: Add link to lockbox stream ZIP
let expected_deferred_amount = subsidy::funding_streams::funding_stream_values(
height,
&network,
expected_block_subsidy,
)
.expect("we always expect a funding stream hashmap response even if empty")
.remove(&FundingStreamReceiver::Deferred)
.unwrap_or_default();
let block_miner_fees =
block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
height,
hash,
source: amount_error,
})?;
check::miner_fees_are_valid(&block, &network, block_miner_fees)?;
check::miner_fees_are_valid(
&block,
block_miner_fees,
expected_block_subsidy,
expected_deferred_amount,
)?;
// Finally, submit the block for contextual verification.
let new_outputs = Arc::into_inner(known_utxos)
@ -289,6 +317,7 @@ where
height,
new_outputs,
transaction_hashes,
deferred_balance: Some(expected_deferred_amount),
};
// Return early for proposal requests when getblocktemplate-rpcs feature is enabled

View File

@ -144,7 +144,11 @@ pub fn equihash_solution_is_valid(header: &Header) -> Result<(), equihash::Error
/// Returns `Ok(())` if the block subsidy in `block` is valid for `network`
///
/// [3.9]: https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts
pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockError> {
pub fn subsidy_is_valid(
block: &Block,
network: &Network,
expected_block_subsidy: Amount<NonNegative>,
) -> Result<(), BlockError> {
let height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
@ -182,8 +186,12 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
// Note: Canopy activation is at the first halving on mainnet, but not on testnet
// ZIP-1014 only applies to mainnet, ZIP-214 contains the specific rules for testnet
// funding stream amount values
let funding_streams = subsidy::funding_streams::funding_stream_values(height, network)
.expect("We always expect a funding stream hashmap response even if empty");
let funding_streams = subsidy::funding_streams::funding_stream_values(
height,
network,
expected_block_subsidy,
)
.expect("We always expect a funding stream hashmap response even if empty");
// # Consensus
//
@ -227,10 +235,10 @@ pub fn subsidy_is_valid(block: &Block, network: &Network) -> Result<(), BlockErr
/// [7.1.2]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus
pub fn miner_fees_are_valid(
block: &Block,
network: &Network,
block_miner_fees: Amount<NonNegative>,
expected_block_subsidy: Amount<NonNegative>,
expected_deferred_amount: Amount<NonNegative>,
) -> Result<(), BlockError> {
let height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
let transparent_value_balance: Amount = subsidy::general::output_amounts(coinbase)
@ -242,15 +250,6 @@ pub fn miner_fees_are_valid(
let sapling_value_balance = coinbase.sapling_value_balance().sapling_amount();
let orchard_value_balance = coinbase.orchard_value_balance().orchard_amount();
let block_subsidy = subsidy::general::block_subsidy(height, network)
.expect("a valid block subsidy for this height and network");
// TODO: Add link to lockbox stream ZIP
let expected_deferred_amount = subsidy::funding_streams::funding_stream_values(height, network)
.expect("we always expect a funding stream hashmap response even if empty")
.remove(&FundingStreamReceiver::Deferred)
.unwrap_or_default();
// # Consensus
//
// > The total value in zatoshi of transparent outputs from a coinbase transaction,
@ -266,7 +265,7 @@ pub fn miner_fees_are_valid(
// https://zips.z.cash/draft-nuttycom-funding-allocation and https://zips.z.cash/draft-hopwood-coinbase-balance.
let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance)
.map_err(|_| SubsidyError::SumOverflow)?;
let right = (block_subsidy + block_miner_fees - expected_deferred_amount)
let right = (expected_block_subsidy + block_miner_fees - expected_deferred_amount)
.map_err(|_| SubsidyError::SumOverflow)?;
if left > right {

View File

@ -12,8 +12,6 @@ use zebra_chain::{
transparent::{self, Script},
};
use crate::block::subsidy::general::block_subsidy;
#[cfg(test)]
mod tests;
@ -24,6 +22,7 @@ mod tests;
pub fn funding_stream_values(
height: Height,
network: &Network,
expected_block_subsidy: Amount<NonNegative>,
) -> Result<HashMap<FundingStreamReceiver, Amount<NonNegative>>, Error> {
let canopy_height = Canopy.activation_height(network).unwrap();
let mut results = HashMap::new();
@ -31,14 +30,13 @@ pub fn funding_stream_values(
if height >= canopy_height {
let funding_streams = network.funding_streams(height);
if funding_streams.height_range().contains(&height) {
let block_subsidy = block_subsidy(height, network)?;
for (&receiver, recipient) in funding_streams.recipients() {
// - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`:
// https://zips.z.cash/protocol/protocol.pdf#subsidies
// - In Rust, "integer division rounds towards zero":
// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
// This is the same as `floor()`, because these numbers are all positive.
let amount_value = ((block_subsidy * recipient.numerator())?
let amount_value = ((expected_block_subsidy * recipient.numerator())?
/ FUNDING_STREAM_RECEIVER_DENOMINATOR)?;
results.insert(receiver, amount_value);
@ -93,13 +91,6 @@ pub fn funding_stream_address(
funding_streams.recipient(receiver)?.addresses().get(index)
}
/// Return a human-readable name and a specification URL for the funding stream `receiver`.
pub fn funding_stream_recipient_info(
receiver: FundingStreamReceiver,
) -> (&'static str, &'static str) {
(receiver.name(), FUNDING_STREAM_SPECIFICATION)
}
/// Given a funding stream P2SH address, create a script and check if it is the same
/// as the given lock_script as described in [protocol specification §7.10][7.10]
///

View File

@ -10,6 +10,8 @@ use zebra_chain::parameters::{
NetworkKind,
};
use crate::block::subsidy::general::block_subsidy;
use super::*;
/// Check mainnet funding stream values are correct for the entire period.
@ -19,13 +21,19 @@ fn test_funding_stream_values() -> Result<(), Report> {
let network = &Network::Mainnet;
// funding streams not active
let canopy_height_minus1 = Canopy.activation_height(network).unwrap() - 1;
assert!(funding_stream_values(canopy_height_minus1.unwrap(), network)?.is_empty());
let canopy_height_minus1 = (Canopy.activation_height(network).unwrap() - 1).unwrap();
assert!(funding_stream_values(
canopy_height_minus1,
network,
block_subsidy(canopy_height_minus1, network)?
)?
.is_empty());
// funding stream is active
let canopy_height = Canopy.activation_height(network);
let canopy_height_plus1 = Canopy.activation_height(network).unwrap() + 1;
let canopy_height_plus2 = Canopy.activation_height(network).unwrap() + 2;
let canopy_height = Canopy.activation_height(network).unwrap();
let canopy_height_plus1 = (Canopy.activation_height(network).unwrap() + 1).unwrap();
let canopy_height_plus2 = (Canopy.activation_height(network).unwrap() + 2).unwrap();
let mut hash_map = HashMap::new();
hash_map.insert(FundingStreamReceiver::Ecc, Amount::try_from(21_875_000)?);
@ -39,28 +47,46 @@ fn test_funding_stream_values() -> Result<(), Report> {
);
assert_eq!(
funding_stream_values(canopy_height.unwrap(), network).unwrap(),
funding_stream_values(
canopy_height,
network,
block_subsidy(canopy_height, network)?
)
.unwrap(),
hash_map
);
assert_eq!(
funding_stream_values(canopy_height_plus1.unwrap(), network).unwrap(),
funding_stream_values(
canopy_height_plus1,
network,
block_subsidy(canopy_height_plus1, network)?
)
.unwrap(),
hash_map
);
assert_eq!(
funding_stream_values(canopy_height_plus2.unwrap(), network).unwrap(),
funding_stream_values(
canopy_height_plus2,
network,
block_subsidy(canopy_height_plus2, network)?
)
.unwrap(),
hash_map
);
// funding stream period is ending
let range = network.pre_nu6_funding_streams().height_range();
let end = range.end;
let last = end - 1;
let last = (end - 1).unwrap();
assert_eq!(
funding_stream_values(last.unwrap(), network).unwrap(),
funding_stream_values(last, network, block_subsidy(last, network)?).unwrap(),
hash_map
);
assert!(funding_stream_values(end, network)?.is_empty());
assert!(funding_stream_values(end, network, block_subsidy(end, network)?)?.is_empty());
// TODO: Replace this with Mainnet once there's an NU6 activation height defined for Mainnet
let network = testnet::Parameters::build()
@ -110,7 +136,64 @@ fn test_funding_stream_values() -> Result<(), Report> {
Height(nu6_height.0 + 1),
Height(nu6_height.0 + 1),
] {
assert_eq!(funding_stream_values(height, &network).unwrap(), hash_map);
assert_eq!(
funding_stream_values(height, &network, block_subsidy(height, &network)?).unwrap(),
hash_map
);
}
// TODO: Replace this with Mainnet once there's an NU6 activation height defined for Mainnet
let network = testnet::Parameters::build()
.with_activation_heights(ConfiguredActivationHeights {
blossom: Some(Blossom.activation_height(&network).unwrap().0),
nu6: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().start.0),
..Default::default()
})
.with_post_nu6_funding_streams(ConfiguredFundingStreams {
// Start checking funding streams from block height 1
height_range: Some(POST_NU6_FUNDING_STREAMS_MAINNET.height_range().clone()),
// Use default post-NU6 recipients
recipients: Some(
POST_NU6_FUNDING_STREAMS_TESTNET
.recipients()
.iter()
.map(|(&receiver, recipient)| ConfiguredFundingStreamRecipient {
receiver,
numerator: recipient.numerator(),
addresses: Some(
recipient
.addresses()
.iter()
.map(|addr| addr.to_string())
.collect(),
),
})
.collect(),
),
})
.to_network();
let mut hash_map = HashMap::new();
hash_map.insert(
FundingStreamReceiver::Deferred,
Amount::try_from(18_750_000)?,
);
hash_map.insert(
FundingStreamReceiver::MajorGrants,
Amount::try_from(12_500_000)?,
);
let nu6_height = Nu6.activation_height(&network).unwrap();
for height in [
nu6_height,
Height(nu6_height.0 + 1),
Height(nu6_height.0 + 1),
] {
assert_eq!(
funding_stream_values(height, &network, block_subsidy(height, &network)?).unwrap(),
hash_map
);
}
Ok(())

View File

@ -26,10 +26,7 @@ pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> {
.expect("blossom activation height should be available");
if height < network.slow_start_shift() {
panic!(
"unsupported block height {height:?}: checkpoints should handle blocks below {:?}",
network.slow_start_shift()
)
None
} else if height < blossom_height {
let pre_blossom_height = height - network.slow_start_shift();
let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL;
@ -101,11 +98,17 @@ pub fn block_subsidy(height: Height, network: &Network) -> Result<Amount<NonNega
/// `MinerSubsidy(height)` as described in [protocol specification §7.8][7.8]
///
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
pub fn miner_subsidy(height: Height, network: &Network) -> Result<Amount<NonNegative>, Error> {
pub fn miner_subsidy(
height: Height,
network: &Network,
expected_block_subsidy: Amount<NonNegative>,
) -> Result<Amount<NonNegative>, Error> {
let total_funding_stream_amount: Result<Amount<NonNegative>, _> =
funding_stream_values(height, network)?.values().sum();
funding_stream_values(height, network, expected_block_subsidy)?
.values()
.sum();
block_subsidy(height, network)? - total_funding_stream_amount?
expected_block_subsidy - total_funding_stream_amount?
}
/// Returns all output amounts in `Transaction`.
@ -129,10 +132,13 @@ fn lockbox_input_value(network: &Network, height: Height) -> Amount<NonNegative>
return Amount::zero();
};
let &deferred_amount_per_block = funding_stream_values(nu6_activation_height, network)
.expect("we always expect a funding stream hashmap response even if empty")
.get(&FundingStreamReceiver::Deferred)
.expect("we expect a lockbox funding stream after NU5");
let expected_block_subsidy = block_subsidy(nu6_activation_height, network)
.expect("block at NU6 activation height must have valid expected subsidy");
let &deferred_amount_per_block =
funding_stream_values(nu6_activation_height, network, expected_block_subsidy)
.expect("we always expect a funding stream hashmap response even if empty")
.get(&FundingStreamReceiver::Deferred)
.expect("we expect a lockbox funding stream after NU5");
let post_nu6_funding_stream_height_range = network.post_nu6_funding_streams().height_range();

View File

@ -20,7 +20,7 @@ use zebra_chain::{
use zebra_script::CachedFfiTransaction;
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
use crate::transaction;
use crate::{block_subsidy, transaction};
use super::*;
@ -292,6 +292,7 @@ fn subsidy_is_valid_for_network(network: Network) -> Result<(), Report> {
let block_iter = network.block_iter();
for (&height, block) in block_iter {
let height = block::Height(height);
let block = block
.zcash_deserialize_into::<Block>()
.expect("block is structurally valid");
@ -301,8 +302,11 @@ fn subsidy_is_valid_for_network(network: Network) -> Result<(), Report> {
.expect("Canopy activation height is known");
// TODO: first halving, second halving, third halving, and very large halvings
if block::Height(height) >= canopy_activation_height {
check::subsidy_is_valid(&block, &network)
if height >= canopy_activation_height {
let expected_block_subsidy =
subsidy::general::block_subsidy(height, &network).expect("valid block subsidy");
check::subsidy_is_valid(&block, &network, expected_block_subsidy)
.expect("subsidies should pass for this block");
}
}
@ -322,6 +326,14 @@ fn coinbase_validation_failure() -> Result<(), Report> {
.expect("block should deserialize");
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
let expected_block_subsidy = subsidy::general::block_subsidy(
block
.coinbase_height()
.expect("block should have coinbase height"),
&network,
)
.expect("valid block subsidy");
// Remove coinbase transaction
block.transactions.remove(0);
@ -330,8 +342,7 @@ fn coinbase_validation_failure() -> Result<(), Report> {
let expected = BlockError::NoTransactions;
assert_eq!(expected, result);
// Validate the block using subsidy_is_valid
let result = check::subsidy_is_valid(&block, &network).unwrap_err();
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy).unwrap_err();
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
assert_eq!(expected, result);
@ -341,6 +352,14 @@ fn coinbase_validation_failure() -> Result<(), Report> {
.expect("block should deserialize");
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
let expected_block_subsidy = subsidy::general::block_subsidy(
block
.coinbase_height()
.expect("block should have coinbase height"),
&network,
)
.expect("valid block subsidy");
// Remove coinbase transaction
block.transactions.remove(0);
@ -349,8 +368,7 @@ fn coinbase_validation_failure() -> Result<(), Report> {
let expected = BlockError::Transaction(TransactionError::CoinbasePosition);
assert_eq!(expected, result);
// Validate the block using subsidy_is_valid
let result = check::subsidy_is_valid(&block, &network).unwrap_err();
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy).unwrap_err();
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
assert_eq!(expected, result);
@ -374,8 +392,15 @@ fn coinbase_validation_failure() -> Result<(), Report> {
let expected = BlockError::Transaction(TransactionError::CoinbaseAfterFirst);
assert_eq!(expected, result);
// Validate the block using subsidy_is_valid, which does not detect this error
check::subsidy_is_valid(&block, &network)
let expected_block_subsidy = subsidy::general::block_subsidy(
block
.coinbase_height()
.expect("block should have coinbase height"),
&network,
)
.expect("valid block subsidy");
check::subsidy_is_valid(&block, &network, expected_block_subsidy)
.expect("subsidy does not check for extra coinbase transactions");
Ok(())
@ -399,11 +424,15 @@ fn funding_stream_validation_for_network(network: Network) -> Result<(), Report>
.expect("Canopy activation height is known");
for (&height, block) in block_iter {
if Height(height) >= canopy_activation_height {
let height = Height(height);
if height >= canopy_activation_height {
let block = Block::zcash_deserialize(&block[..]).expect("block should deserialize");
let expected_block_subsidy =
subsidy::general::block_subsidy(height, &network).expect("valid block subsidy");
// Validate
let result = check::subsidy_is_valid(&block, &network);
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy);
assert!(result.is_ok());
}
}
@ -447,7 +476,15 @@ fn funding_stream_validation_failure() -> Result<(), Report> {
};
// Validate it
let result = check::subsidy_is_valid(&block, &network);
let expected_block_subsidy = subsidy::general::block_subsidy(
block
.coinbase_height()
.expect("block should have coinbase height"),
&network,
)
.expect("valid block subsidy");
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy);
let expected = Err(BlockError::Transaction(TransactionError::Subsidy(
SubsidyError::FundingStreamNotFound,
)));
@ -470,14 +507,30 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> {
let block_iter = network.block_iter();
for (&height, block) in block_iter {
if Height(height) > network.slow_start_shift() {
let height = Height(height);
if height > network.slow_start_shift() {
let block = Block::zcash_deserialize(&block[..]).expect("block should deserialize");
let expected_block_subsidy = block_subsidy(height, &network)?;
// TODO: Add link to lockbox stream ZIP
let expected_deferred_amount = subsidy::funding_streams::funding_stream_values(
height,
&network,
expected_block_subsidy,
)
.expect("we always expect a funding stream hashmap response even if empty")
.remove(&FundingStreamReceiver::Deferred)
.unwrap_or_default();
// fake the miner fee to a big amount
let miner_fees = Amount::try_from(MAX_MONEY / 2).unwrap();
// Validate
let result = check::miner_fees_are_valid(&block, &network, miner_fees);
let result = check::miner_fees_are_valid(
&block,
miner_fees,
expected_block_subsidy,
expected_deferred_amount,
);
assert!(result.is_ok());
}
}
@ -489,16 +542,28 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> {
fn miner_fees_validation_failure() -> Result<(), Report> {
let _init_guard = zebra_test::init();
let network = Network::Mainnet;
let block =
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_347499_BYTES[..])
.expect("block should deserialize");
let height = block.coinbase_height().expect("valid coinbase height");
let expected_block_subsidy = block_subsidy(height, &network)?;
// TODO: Add link to lockbox stream ZIP
let expected_deferred_amount =
subsidy::funding_streams::funding_stream_values(height, &network, expected_block_subsidy)
.expect("we always expect a funding stream hashmap response even if empty")
.remove(&FundingStreamReceiver::Deferred)
.unwrap_or_default();
// fake the miner fee to a small amount
let miner_fees = Amount::zero();
// Validate
let result = check::miner_fees_are_valid(&block, &network, miner_fees);
let result = check::miner_fees_are_valid(
&block,
miner_fees,
expected_block_subsidy,
expected_deferred_amount,
);
let expected = Err(BlockError::Transaction(TransactionError::Subsidy(
SubsidyError::InvalidMinerFees,

View File

@ -28,21 +28,22 @@ use tower::{Service, ServiceExt};
use tracing::instrument;
use zebra_chain::{
amount,
block::{self, Block},
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
parameters::{subsidy::FundingStreamReceiver, Network, GENESIS_PREVIOUS_BLOCK_HASH},
work::equihash,
};
use zebra_state::{self as zs, CheckpointVerifiedBlock};
use crate::{
block::VerifyBlockError,
block_subsidy,
checkpoint::types::{
Progress,
Progress::*,
Progress::{self, *},
TargetHeight::{self, *},
},
error::BlockError,
BoxError, ParameterCheckpoint as _,
funding_stream_values, BoxError, ParameterCheckpoint as _,
};
pub(crate) mod list;
@ -607,8 +608,16 @@ where
crate::block::check::equihash_solution_is_valid(&block.header)?;
}
let expected_deferred_amount = if height > self.network.slow_start_interval() {
// TODO: Add link to lockbox stream ZIP
funding_stream_values(height, &self.network, block_subsidy(height, &self.network)?)?
.remove(&FundingStreamReceiver::Deferred)
} else {
None
};
// don't do precalculation until the block passes basic difficulty checks
let block = CheckpointVerifiedBlock::with_hash(block, hash);
let block = CheckpointVerifiedBlock::new(block, Some(hash), expected_deferred_amount);
crate::block::check::merkle_root_validity(
&self.network,
@ -981,6 +990,8 @@ pub enum VerifyCheckpointError {
CheckpointList(BoxError),
#[error(transparent)]
VerifyBlock(VerifyBlockError),
#[error("invalid block subsidy")]
SubsidyError(#[from] amount::Error),
#[error("too many queued blocks at this height")]
QueuedLimit,
#[error("the block hash does not match the chained checkpoint hash, expected {expected:?} found {found:?}")]

View File

@ -49,11 +49,8 @@ pub use block::check::difficulty_is_valid;
pub use block::{
subsidy::{
funding_streams::{
funding_stream_address, funding_stream_recipient_info, funding_stream_values,
new_coinbase_script,
},
general::miner_subsidy,
funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script},
general::{block_subsidy, miner_subsidy},
},
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
};

View File

@ -22,7 +22,9 @@ use zebra_chain::{
},
work::difficulty::{ParameterDifficulty as _, U256},
};
use zebra_consensus::{funding_stream_address, funding_stream_values, miner_subsidy, RouterError};
use zebra_consensus::{
block_subsidy, funding_stream_address, funding_stream_values, miner_subsidy, RouterError,
};
use zebra_network::AddressBookPeers;
use zebra_node_services::mempool;
use zebra_state::{ReadRequest, ReadResponse};
@ -1176,20 +1178,28 @@ where
});
}
let miner = miner_subsidy(height, &network).map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
// Always zero for post-halving blocks
let founders = Amount::zero();
let funding_streams =
funding_stream_values(height, &network).map_err(|error| Error {
let expected_block_subsidy =
block_subsidy(height, &network).map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
let miner_subsidy =
miner_subsidy(height, &network, expected_block_subsidy).map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
// Always zero for post-halving blocks
let founders = Amount::zero();
let funding_streams = funding_stream_values(height, &network, expected_block_subsidy)
.map_err(|error| Error {
code: ErrorCode::ServerError(0),
message: error.to_string(),
data: None,
})?;
let mut funding_streams: Vec<_> = funding_streams
.iter()
.filter_map(|(receiver, value)| {
@ -1208,7 +1218,7 @@ where
let (_receivers, funding_streams): (Vec<_>, _) = funding_streams.into_iter().unzip();
Ok(BlockSubsidy {
miner: miner.into(),
miner: miner_subsidy.into(),
founders: founders.into(),
funding_streams,
})

View File

@ -19,7 +19,9 @@ use zebra_chain::{
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
transparent,
};
use zebra_consensus::{funding_stream_address, funding_stream_values, miner_subsidy};
use zebra_consensus::{
block_subsidy, funding_stream_address, funding_stream_values, miner_subsidy,
};
use zebra_node_services::mempool;
use zebra_state::GetBlockTemplateChainInfo;
@ -375,7 +377,8 @@ pub fn standard_coinbase_outputs(
miner_fee: Amount<NonNegative>,
like_zcashd: bool,
) -> Vec<(Amount<NonNegative>, transparent::Script)> {
let funding_streams = funding_stream_values(height, network)
let expected_block_subsidy = block_subsidy(height, network).expect("valid block subsidy");
let funding_streams = funding_stream_values(height, network, expected_block_subsidy)
.expect("funding stream value calculations are valid for reasonable chain heights");
// Optional TODO: move this into a zebra_consensus function?
@ -392,7 +395,7 @@ pub fn standard_coinbase_outputs(
})
.collect();
let miner_reward = miner_subsidy(height, network)
let miner_reward = miner_subsidy(height, network, expected_block_subsidy)
.expect("reward calculations are valid for reasonable chain heights")
+ miner_fee;
let miner_reward =

View File

@ -5,7 +5,6 @@ use zebra_chain::{
parameters::subsidy::FundingStreamReceiver,
transparent,
};
use zebra_consensus::funding_stream_recipient_info;
use crate::methods::get_block_template_rpcs::types::zec::Zec;
@ -69,10 +68,10 @@ impl FundingStream {
value: Amount<NonNegative>,
address: &transparent::Address,
) -> FundingStream {
let (recipient, specification) = funding_stream_recipient_info(receiver);
let (name, specification) = receiver.info();
FundingStream {
recipient: recipient.to_string(),
recipient: name.to_string(),
specification: specification.to_string(),
value: value.into(),
value_zat: value,

View File

@ -3,7 +3,7 @@
use std::sync::Arc;
use zebra_chain::{
amount::{Amount, NegativeAllowed},
amount::Amount,
block::{self, Block},
transaction::Transaction,
transparent,
@ -11,7 +11,7 @@ use zebra_chain::{
};
use crate::{
request::ContextuallyVerifiedBlock, service::chain_tip::ChainTipBlock, CheckpointVerifiedBlock,
request::ContextuallyVerifiedBlock, service::chain_tip::ChainTipBlock,
SemanticallyVerifiedBlock,
};
@ -37,6 +37,7 @@ impl Prepare for Arc<Block> {
height,
new_outputs,
transaction_hashes,
deferred_balance: None,
}
}
}
@ -60,18 +61,6 @@ impl SemanticallyVerifiedBlock {
ContextuallyVerifiedBlock::test_with_zero_spent_utxos(self)
}
/// Returns a [`ContextuallyVerifiedBlock`] created from this block,
/// using a fake chain value pool change.
///
/// Only for use in tests.
#[cfg(test)]
pub fn test_with_chain_pool_change(
&self,
fake_chain_value_pool_change: ValueBalance<NegativeAllowed>,
) -> ContextuallyVerifiedBlock {
ContextuallyVerifiedBlock::test_with_chain_pool_change(self, fake_chain_value_pool_change)
}
/// Returns a [`ContextuallyVerifiedBlock`] created from this block,
/// with no chain value pool change.
///
@ -112,19 +101,17 @@ impl ContextuallyVerifiedBlock {
}
/// Create a [`ContextuallyVerifiedBlock`] from a [`Block`] or [`SemanticallyVerifiedBlock`],
/// using a fake chain value pool change.
/// with no chain value pool change.
///
/// Only for use in tests.
pub fn test_with_chain_pool_change(
block: impl Into<SemanticallyVerifiedBlock>,
fake_chain_value_pool_change: ValueBalance<NegativeAllowed>,
) -> Self {
pub fn test_with_zero_chain_pool_change(block: impl Into<SemanticallyVerifiedBlock>) -> Self {
let SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_balance: _,
} = block.into();
Self {
@ -137,40 +124,7 @@ impl ContextuallyVerifiedBlock {
// TODO: fix the tests, and stop adding unrelated inputs and outputs.
spent_outputs: new_outputs,
transaction_hashes,
chain_value_pool_change: fake_chain_value_pool_change,
chain_value_pool_change: ValueBalance::zero(),
}
}
/// Create a [`ContextuallyVerifiedBlock`] from a [`Block`] or [`SemanticallyVerifiedBlock`],
/// with no chain value pool change.
///
/// Only for use in tests.
pub fn test_with_zero_chain_pool_change(block: impl Into<SemanticallyVerifiedBlock>) -> Self {
Self::test_with_chain_pool_change(block, ValueBalance::zero())
}
}
impl CheckpointVerifiedBlock {
/// Create a block that's ready to be committed to the finalized state,
/// using a precalculated [`block::Hash`] and [`block::Height`].
///
/// This is a test-only method, prefer [`CheckpointVerifiedBlock::with_hash`].
#[cfg(any(test, feature = "proptest-impl"))]
pub fn with_hash_and_height(
block: Arc<Block>,
hash: block::Hash,
height: block::Height,
) -> Self {
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs =
transparent::new_ordered_outputs_with_height(&block, height, &transaction_hashes);
Self(SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
})
}
}

View File

@ -46,7 +46,7 @@ pub const STATE_DATABASE_KIND: &str = "state";
///
/// Instead of using this constant directly, use [`constants::state_database_format_version_in_code()`]
/// or [`config::database_format_version_on_disk()`] to get the full semantic format version.
const DATABASE_FORMAT_VERSION: u64 = 25;
const DATABASE_FORMAT_VERSION: u64 = 26;
/// The database format minor version, incremented each time the on-disk database format has a
/// significant data format change.
@ -55,7 +55,7 @@ const DATABASE_FORMAT_VERSION: u64 = 25;
/// - adding new column families,
/// - changing the format of a column family in a compatible way, or
/// - breaking changes with compatibility code in all supported Zebra versions.
const DATABASE_FORMAT_MINOR_VERSION: u64 = 3;
const DATABASE_FORMAT_MINOR_VERSION: u64 = 0;
/// The database format patch version, incremented each time the on-disk database format has a
/// significant format compatibility fix.

View File

@ -7,7 +7,7 @@ use std::{
};
use zebra_chain::{
amount::NegativeAllowed,
amount::{Amount, NegativeAllowed, NonNegative},
block::{self, Block},
history_tree::HistoryTree,
orchard,
@ -161,6 +161,8 @@ pub struct SemanticallyVerifiedBlock {
/// A precomputed list of the hashes of the transactions in this block,
/// in the same order as `block.transactions`.
pub transaction_hashes: Arc<[transaction::Hash]>,
/// This block's contribution to the deferred pool.
pub deferred_balance: Option<Amount<NonNegative>>,
}
/// A block ready to be committed directly to the finalized state with
@ -289,6 +291,8 @@ pub struct FinalizedBlock {
pub(super) transaction_hashes: Arc<[transaction::Hash]>,
/// The tresstate associated with the block.
pub(super) treestate: Treestate,
/// This block's contribution to the deferred pool.
pub(super) deferred_balance: Option<Amount<NonNegative>>,
}
impl FinalizedBlock {
@ -314,6 +318,7 @@ impl FinalizedBlock {
new_outputs: block.new_outputs,
transaction_hashes: block.transaction_hashes,
treestate,
deferred_balance: block.deferred_balance,
}
}
}
@ -386,6 +391,7 @@ impl ContextuallyVerifiedBlock {
height,
new_outputs,
transaction_hashes,
deferred_balance,
} = semantically_verified;
// This is redundant for the non-finalized state,
@ -401,32 +407,27 @@ impl ContextuallyVerifiedBlock {
new_outputs,
spent_outputs: spent_outputs.clone(),
transaction_hashes,
chain_value_pool_change: block
.chain_value_pool_change(&utxos_from_ordered_utxos(spent_outputs))?,
chain_value_pool_change: block.chain_value_pool_change(
&utxos_from_ordered_utxos(spent_outputs),
deferred_balance,
)?,
})
}
}
impl SemanticallyVerifiedBlock {
fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
let height = block
.coinbase_height()
.expect("coinbase height was already checked");
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
SemanticallyVerifiedBlock {
block,
hash,
height,
new_outputs,
transaction_hashes,
}
}
}
impl CheckpointVerifiedBlock {
/// Create a block that's ready to be committed to the finalized state,
/// Creates a [`CheckpointVerifiedBlock`] from [`Block`] with optional deferred balance and
/// optional pre-computed hash.
pub fn new(
block: Arc<Block>,
hash: Option<block::Hash>,
deferred_balance: Option<Amount<NonNegative>>,
) -> Self {
let mut block = Self::with_hash(block.clone(), hash.unwrap_or(block.hash()));
block.deferred_balance = deferred_balance;
block
}
/// Creates a block that's ready to be committed to the finalized state,
/// using a precalculated [`block::Hash`].
///
/// Note: a [`CheckpointVerifiedBlock`] isn't actually finalized
@ -436,33 +437,14 @@ impl CheckpointVerifiedBlock {
}
}
impl From<Arc<Block>> for CheckpointVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
let hash = block.hash();
CheckpointVerifiedBlock::with_hash(block, hash)
}
}
impl From<Arc<Block>> for SemanticallyVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
let hash = block.hash();
SemanticallyVerifiedBlock::with_hash(block, hash)
}
}
impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
fn from(contextually_valid: ContextuallyVerifiedBlock) -> Self {
let ContextuallyVerifiedBlock {
block,
hash,
height,
new_outputs,
spent_outputs: _,
transaction_hashes,
chain_value_pool_change: _,
} = contextually_valid;
impl SemanticallyVerifiedBlock {
/// Creates [`SemanticallyVerifiedBlock`] from [`Block`] and [`block::Hash`].
pub fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
let height = block
.coinbase_height()
.expect("semantically verified block should have a coinbase height");
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
@ -470,6 +452,71 @@ impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
height,
new_outputs,
transaction_hashes,
deferred_balance: None,
}
}
/// Sets the deferred balance in the block.
pub fn with_deferred_balance(mut self, deferred_balance: Option<Amount<NonNegative>>) -> Self {
self.deferred_balance = deferred_balance;
self
}
}
impl From<Arc<Block>> for CheckpointVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
CheckpointVerifiedBlock(SemanticallyVerifiedBlock::from(block))
}
}
impl From<Arc<Block>> for SemanticallyVerifiedBlock {
fn from(block: Arc<Block>) -> Self {
let hash = block.hash();
let height = block
.coinbase_height()
.expect("semantically verified block should have a coinbase height");
let transaction_hashes: Arc<[_]> = block.transactions.iter().map(|tx| tx.hash()).collect();
let new_outputs = transparent::new_ordered_outputs(&block, &transaction_hashes);
Self {
block,
hash,
height,
new_outputs,
transaction_hashes,
deferred_balance: None,
}
}
}
impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
fn from(valid: ContextuallyVerifiedBlock) -> Self {
Self {
block: valid.block,
hash: valid.hash,
height: valid.height,
new_outputs: valid.new_outputs,
transaction_hashes: valid.transaction_hashes,
deferred_balance: Some(
valid
.chain_value_pool_change
.deferred_amount()
.constrain::<NonNegative>()
.expect("deferred balance in a block must me non-negative"),
),
}
}
}
impl From<FinalizedBlock> for SemanticallyVerifiedBlock {
fn from(finalized: FinalizedBlock) -> Self {
Self {
block: finalized.block,
hash: finalized.hash,
height: finalized.height,
new_outputs: finalized.new_outputs,
transaction_hashes: finalized.transaction_hashes,
deferred_balance: finalized.deferred_balance,
}
}
}
@ -937,7 +984,7 @@ pub enum ReadRequest {
/// Looks up the balance of a set of transparent addresses.
///
/// Returns an [`Amount`](zebra_chain::amount::Amount) with the total
/// Returns an [`Amount`] with the total
/// balance of the set of addresses.
AddressBalance(HashSet<transparent::Address>),

View File

@ -115,6 +115,7 @@ impl From<SemanticallyVerifiedBlock> for ChainTipBlock {
height,
new_outputs: _,
transaction_hashes,
deferred_balance: _,
} = prepared;
Self {

View File

@ -21,7 +21,7 @@ use zebra_chain::{
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
impl IntoDisk for ValueBalance<NonNegative> {
type Bytes = [u8; 32];
type Bytes = [u8; 40];
fn as_bytes(&self) -> Self::Bytes {
self.to_bytes()

View File

@ -1,12 +1,10 @@
---
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
assertion_line: 125
expression: cf_data
---
[
KV(
k: "",
v: "24f4000000000000000000000000000000000000000000000000000000000000",
v: "24f40000000000000000000000000000000000000000000000000000000000000000000000000000",
),
]

View File

@ -1,12 +1,10 @@
---
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
assertion_line: 125
expression: cf_data
---
[
KV(
k: "",
v: "6cdc020000000000000000000000000000000000000000000000000000000000",
v: "6cdc0200000000000000000000000000000000000000000000000000000000000000000000000000",
),
]

View File

@ -1,12 +1,10 @@
---
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
assertion_line: 125
expression: cf_data
---
[
KV(
k: "",
v: "24f4000000000000000000000000000000000000000000000000000000000000",
v: "24f40000000000000000000000000000000000000000000000000000000000000000000000000000",
),
]

View File

@ -1,12 +1,10 @@
---
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
assertion_line: 125
expression: cf_data
---
[
KV(
k: "",
v: "6cdc020000000000000000000000000000000000000000000000000000000000",
v: "6cdc0200000000000000000000000000000000000000000000000000000000000000000000000000",
),
]

View File

@ -541,6 +541,14 @@ impl DbFormatChange {
timer.finish(module_path!(), line!(), "tree keys and caches upgrade");
}
let version_for_upgrading_value_balance_format =
Version::parse("26.0.0").expect("hard-coded version string should be valid");
// Check if we need to do the upgrade.
if older_disk_version < &version_for_upgrading_value_balance_format {
Self::mark_as_upgraded_to(db, &version_for_upgrading_value_balance_format)
}
// # New Upgrades Usually Go Here
//
// New code goes above this comment!

View File

@ -22,6 +22,7 @@ use zebra_chain::{
},
parameters::Network::{self, *},
serialization::{ZcashDeserializeInto, ZcashSerialize},
transparent::new_ordered_outputs_with_height,
};
use zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS};
@ -29,7 +30,7 @@ use crate::{
constants::{state_database_format_version_in_code, STATE_DATABASE_KIND},
request::{FinalizedBlock, Treestate},
service::finalized_state::{disk_db::DiskWriteBatch, ZebraDb, STATE_COLUMN_FAMILIES_IN_CODE},
CheckpointVerifiedBlock, Config,
CheckpointVerifiedBlock, Config, SemanticallyVerifiedBlock,
};
/// Storage round-trip test for block and transaction data in the finalized state database.
@ -117,14 +118,26 @@ fn test_block_db_round_trip_with(
// Now, use the database
let original_block = Arc::new(original_block);
let checkpoint_verified = if original_block.coinbase_height().is_some() {
original_block.clone().into()
CheckpointVerifiedBlock::from(original_block.clone())
} else {
// Fake a zero height
CheckpointVerifiedBlock::with_hash_and_height(
original_block.clone(),
original_block.hash(),
Height(0),
)
let hash = original_block.hash();
let transaction_hashes: Arc<[_]> = original_block
.transactions
.iter()
.map(|tx| tx.hash())
.collect();
let new_outputs =
new_ordered_outputs_with_height(&original_block, Height(0), &transaction_hashes);
CheckpointVerifiedBlock(SemanticallyVerifiedBlock {
block: original_block.clone(),
hash,
height: Height(0),
new_outputs,
transaction_hashes,
deferred_balance: None,
})
};
let dummy_treestate = Treestate::default();

View File

@ -12,7 +12,6 @@
//! each time the database format (column, serialization, etc) changes.
use std::{
borrow::Borrow,
collections::{BTreeMap, HashMap},
sync::Arc,
};
@ -203,16 +202,24 @@ impl DiskWriteBatch {
// Value pool methods
/// Prepare a database batch containing the chain value pool update from `finalized.block`,
/// and return it (without actually writing anything).
/// Prepares a database batch containing the chain value pool update from `finalized.block`, and
/// returns it without actually writing anything.
///
/// If this method returns an error, it will be propagated,
/// and the batch should not be written to the database.
/// The batch is modified by this method and written by the caller. The caller should not write
/// the batch if this method returns an error.
///
/// The parameter `utxos_spent_by_block` must contain the [`transparent::Utxo`]s of every input
/// in this block, including UTXOs created by earlier transactions in this block.
///
/// Note that the chain value pool has the opposite sign to the transaction value pool. See the
/// [`chain_value_pool_change`] and [`add_chain_value_pool_change`] methods for more details.
///
/// # Errors
///
/// - Propagates any errors from updating value pools
#[allow(clippy::unwrap_in_result)]
///
/// [`chain_value_pool_change`]: zebra_chain::block::Block::chain_value_pool_change
/// [`add_chain_value_pool_change`]: ValueBalance::add_chain_value_pool_change
pub fn prepare_chain_value_pools_batch(
&mut self,
db: &ZebraDb,
@ -220,14 +227,18 @@ impl DiskWriteBatch {
utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
value_pool: ValueBalance<NonNegative>,
) -> Result<(), BoxError> {
let chain_value_pools_cf = db.chain_value_pools_cf().with_batch_for_writing(self);
let FinalizedBlock { block, .. } = finalized;
let new_pool = value_pool.add_block(block.borrow(), &utxos_spent_by_block)?;
// The batch is modified by this method and written by the caller.
let _ = chain_value_pools_cf.zs_insert(&(), &new_pool);
let _ = db
.chain_value_pools_cf()
.with_batch_for_writing(self)
.zs_insert(
&(),
&value_pool.add_chain_value_pool_change(
finalized.block.chain_value_pool_change(
&utxos_spent_by_block,
finalized.deferred_balance,
)?,
)?,
);
Ok(())
}

View File

@ -53,17 +53,18 @@ pub struct Chain {
//
/// The last height this chain forked at. Diagnostics only.
///
/// This field is only used for metrics, it is not consensus-critical, and it is not checked
/// for equality.
/// This field is only used for metrics. It is not consensus-critical, and it is not checked for
/// equality.
///
/// We keep the same last fork height in both sides of a clone, because every new block clones
/// a chain, even if it's just growing that chain.
/// We keep the same last fork height in both sides of a clone, because every new block clones a
/// chain, even if it's just growing that chain.
///
/// # Note
///
/// Most diagnostics are implemented on the `NonFinalizedState`, rather than each chain. Some
/// diagnostics only use the best chain, and others need to modify the Chain state, but that's
/// difficult with `Arc<Chain>`s.
pub(super) last_fork_height: Option<Height>,
// # Note
//
// Most diagnostics are implemented on the NonFinalizedState, rather than each chain.
// Some diagnostics only use the best chain, and others need to modify the Chain state,
// but that's difficult with `Arc<Chain>`s.
}
/// The internal state of [`Chain`].
@ -199,12 +200,11 @@ pub struct ChainInner {
// Chain Pools
//
/// The chain value pool balances of the tip of this [`Chain`],
/// including the block value pool changes from all finalized blocks,
/// and the non-finalized blocks in this chain.
/// The chain value pool balances of the tip of this [`Chain`], including the block value pool
/// changes from all finalized blocks, and the non-finalized blocks in this chain.
///
/// When a new chain is created from the finalized tip,
/// it is initialized with the finalized tip chain value pool balances.
/// When a new chain is created from the finalized tip, it is initialized with the finalized tip
/// chain value pool balances.
pub(crate) chain_value_pools: ValueBalance<NonNegative>,
}

View File

@ -420,7 +420,7 @@ proptest! {
// which is not included in the UTXO set
if block.height > block::Height(0) {
let utxos = &block.new_outputs.iter().map(|(k, ordered_utxo)| (*k, ordered_utxo.utxo.clone())).collect();
let block_value_pool = &block.block.chain_value_pool_change(utxos)?;
let block_value_pool = &block.block.chain_value_pool_change(utxos, None)?;
expected_finalized_value_pool += *block_value_pool;
}
@ -447,7 +447,7 @@ proptest! {
let mut expected_non_finalized_value_pool = Ok(expected_finalized_value_pool?);
for block in non_finalized_blocks {
let utxos = block.new_outputs.clone();
let block_value_pool = &block.block.chain_value_pool_change(&transparent::utxos_from_ordered_utxos(utxos))?;
let block_value_pool = &block.block.chain_value_pool_change(&transparent::utxos_from_ordered_utxos(utxos), None)?;
expected_non_finalized_value_pool += *block_value_pool;
let result_receiver = state_service.queue_and_commit_to_non_finalized_state(block.clone());
@ -586,10 +586,8 @@ fn continuous_empty_blocks_from_test_vectors() -> impl Strategy<
})
.prop_map(|(network, mut blocks, finalized_blocks_count)| {
let non_finalized_blocks = blocks.split_off(finalized_blocks_count);
let finalized_blocks: Vec<_> = blocks
.into_iter()
.map(|prepared_block| CheckpointVerifiedBlock::from(prepared_block.block))
.collect();
let finalized_blocks: Vec<_> =
blocks.into_iter().map(CheckpointVerifiedBlock).collect();
(
network,