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:
parent
16168d7623
commit
82ded59a31
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
///
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:?}")]
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>),
|
||||
|
||||
|
|
|
@ -115,6 +115,7 @@ impl From<SemanticallyVerifiedBlock> for ChainTipBlock {
|
|||
height,
|
||||
new_outputs: _,
|
||||
transaction_hashes,
|
||||
deferred_balance: _,
|
||||
} = prepared;
|
||||
|
||||
Self {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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",
|
||||
),
|
||||
]
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue