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 halo2::pasta::pallas;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
amount::NegativeAllowed,
|
amount::{Amount, NegativeAllowed, NonNegative},
|
||||||
block::merkle::AuthDataRoot,
|
block::merkle::AuthDataRoot,
|
||||||
fmt::DisplayToDebug,
|
fmt::DisplayToDebug,
|
||||||
orchard,
|
orchard,
|
||||||
|
@ -205,34 +205,39 @@ impl Block {
|
||||||
.expect("number of transactions must fit u64")
|
.expect("number of transactions must fit u64")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the overall chain value pool change in this block,
|
/// Returns the overall chain value pool change in this block---the negative sum of the
|
||||||
/// the negative sum of the transaction value balances in this block.
|
/// transaction value balances in this block.
|
||||||
///
|
///
|
||||||
/// These are the changes in the transparent, sprout, sapling, and orchard
|
/// These are the changes in the transparent, Sprout, Sapling, Orchard, and
|
||||||
/// chain value pools, as a result of this block.
|
/// Deferred chain value pools, as a result of this block.
|
||||||
///
|
///
|
||||||
/// Positive values are added to the corresponding chain value pool.
|
/// Positive values are added to the corresponding chain value pool and negative values are
|
||||||
/// Negative values are removed from the corresponding pool.
|
/// removed from the corresponding pool.
|
||||||
///
|
///
|
||||||
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
||||||
///
|
///
|
||||||
/// `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
|
/// The given `utxos` must contain the [`transparent::Utxo`]s of every input in this block,
|
||||||
/// including UTXOs created by earlier transactions in this block.
|
/// including UTXOs created by earlier transactions in this block. It can also contain unrelated
|
||||||
/// (It can also contain unrelated UTXOs, which are ignored.)
|
/// UTXOs, which are ignored.
|
||||||
///
|
///
|
||||||
/// Note: the chain value pool has the opposite sign to the transaction
|
/// Note that the chain value pool has the opposite sign to the transaction value pool.
|
||||||
/// value pool.
|
|
||||||
pub fn chain_value_pool_change(
|
pub fn chain_value_pool_change(
|
||||||
&self,
|
&self,
|
||||||
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
|
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
|
||||||
|
deferred_balance: Option<Amount<NonNegative>>,
|
||||||
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
|
) -> Result<ValueBalance<NegativeAllowed>, ValueBalanceError> {
|
||||||
let transaction_value_balance_total = self
|
Ok(*self
|
||||||
.transactions
|
.transactions
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|t| t.value_balance(utxos))
|
.flat_map(|t| t.value_balance(utxos))
|
||||||
.sum::<Result<ValueBalance<NegativeAllowed>, _>>()?;
|
.sum::<Result<ValueBalance<NegativeAllowed>, _>>()?
|
||||||
|
.neg()
|
||||||
Ok(transaction_value_balance_total.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,
|
/// Compute the root of the authorizing data Merkle tree,
|
||||||
|
|
|
@ -68,20 +68,24 @@ pub enum FundingStreamReceiver {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
/// [ZIP-1014]: https://zips.z.cash/zip-1014#abstract
|
||||||
/// [`zcashd`]: https://github.com/zcash/zcash/blob/3f09cfa00a3c90336580a127e0096d99e25a38d6/src/consensus/funding.cpp#L13-L32
|
/// [`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
|
// TODO: Update method documentation with a reference to https://zips.z.cash/draft-nuttycom-funding-allocation once its
|
||||||
// status is updated to 'Proposed'.
|
// status is updated to 'Proposed'.
|
||||||
pub fn name(self) -> &'static str {
|
pub fn info(&self) -> (&'static str, &'static str) {
|
||||||
|
(
|
||||||
match self {
|
match self {
|
||||||
FundingStreamReceiver::Ecc => "Electric Coin Company",
|
FundingStreamReceiver::Ecc => "Electric Coin Company",
|
||||||
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
|
FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
|
||||||
FundingStreamReceiver::MajorGrants => "Major Grants",
|
FundingStreamReceiver::MajorGrants => "Major Grants",
|
||||||
// TODO: Find out what this should be called and update the funding stream name.
|
// TODO: Find out what this should be called and update the funding stream name
|
||||||
FundingStreamReceiver::Deferred => "Lockbox",
|
FundingStreamReceiver::Deferred => "Lockbox",
|
||||||
}
|
},
|
||||||
|
FUNDING_STREAM_SPECIFICATION,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +94,7 @@ impl FundingStreamReceiver {
|
||||||
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
|
/// [7.10.1]: https://zips.z.cash/protocol/protocol.pdf#zip214fundingstreams
|
||||||
pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100;
|
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].
|
/// The specification for all current funding stream receivers, a URL that links to [ZIP-214].
|
||||||
///
|
///
|
||||||
/// [ZIP-214]: https://zips.z.cash/zip-0214
|
/// [ZIP-214]: https://zips.z.cash/zip-0214
|
||||||
|
|
|
@ -1390,10 +1390,7 @@ impl Transaction {
|
||||||
.map(|shielded_data| &mut shielded_data.value_balance)
|
.map(|shielded_data| &mut shielded_data.value_balance)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value balances for this transaction,
|
/// Returns the value balances for this transaction using the provided transparent outputs.
|
||||||
/// using the transparent outputs spent in this transaction.
|
|
||||||
///
|
|
||||||
/// See `value_balance` for details.
|
|
||||||
pub(crate) fn value_balance_from_outputs(
|
pub(crate) fn value_balance_from_outputs(
|
||||||
&self,
|
&self,
|
||||||
outputs: &HashMap<transparent::OutPoint, transparent::Output>,
|
outputs: &HashMap<transparent::OutPoint, transparent::Output>,
|
||||||
|
@ -1404,25 +1401,26 @@ impl Transaction {
|
||||||
+ self.orchard_value_balance()
|
+ self.orchard_value_balance()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the value balances for this transaction.
|
/// Returns the value balances for this transaction.
|
||||||
/// These are the changes in the transaction value pool,
|
|
||||||
/// split up into transparent, sprout, sapling, and orchard values.
|
|
||||||
///
|
///
|
||||||
/// Calculated as the sum of the inputs and outputs from each pool,
|
/// These are the changes in the transaction value pool, split up into transparent, Sprout,
|
||||||
/// or the sum of the value balances from each pool.
|
/// Sapling, and Orchard values.
|
||||||
///
|
///
|
||||||
/// Positive values are added to this transaction's value pool,
|
/// Calculated as the sum of the inputs and outputs from each pool, or the sum of the value
|
||||||
/// and removed from the corresponding chain value pool.
|
/// balances from each pool.
|
||||||
/// Negative values are removed from this transaction,
|
///
|
||||||
/// and added to the corresponding 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>
|
/// <https://zebra.zfnd.org/dev/rfcs/0012-value-pools.html#definitions>
|
||||||
///
|
///
|
||||||
/// `utxos` must contain the utxos of every input in the transaction,
|
/// `utxos` must contain the utxos of every input in the transaction, including UTXOs created by
|
||||||
/// including UTXOs created by earlier transactions in this block.
|
/// earlier transactions in this block.
|
||||||
///
|
///
|
||||||
/// Note: the chain value pool has the opposite sign to the transaction
|
/// ## Note
|
||||||
/// value pool.
|
///
|
||||||
|
/// The chain value pool has the opposite sign to the transaction value pool.
|
||||||
pub fn value_balance(
|
pub fn value_balance(
|
||||||
&self,
|
&self,
|
||||||
utxos: &HashMap<transparent::OutPoint, transparent::Utxo>,
|
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::{
|
use crate::amount::{self, Amount, Constraint, NegativeAllowed, NonNegative};
|
||||||
amount::{self, Amount, Constraint, NegativeAllowed, NonNegative},
|
|
||||||
block::Block,
|
|
||||||
transparent,
|
|
||||||
};
|
|
||||||
|
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
use std::{borrow::Borrow, collections::HashMap};
|
use std::{borrow::Borrow, collections::HashMap};
|
||||||
|
|
||||||
#[cfg(any(test, feature = "proptest-impl"))]
|
#[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"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
mod arbitrary;
|
mod arbitrary;
|
||||||
|
@ -20,13 +18,14 @@ mod tests;
|
||||||
|
|
||||||
use ValueBalanceError::*;
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
||||||
pub struct ValueBalance<C> {
|
pub struct ValueBalance<C> {
|
||||||
transparent: Amount<C>,
|
transparent: Amount<C>,
|
||||||
sprout: Amount<C>,
|
sprout: Amount<C>,
|
||||||
sapling: Amount<C>,
|
sapling: Amount<C>,
|
||||||
orchard: Amount<C>,
|
orchard: Amount<C>,
|
||||||
|
deferred: Amount<C>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C> ValueBalance<C>
|
impl<C> ValueBalance<C>
|
||||||
|
@ -116,6 +115,17 @@ where
|
||||||
self
|
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.
|
/// Creates a [`ValueBalance`] where all the pools are zero.
|
||||||
pub fn zero() -> Self {
|
pub fn zero() -> Self {
|
||||||
let zero = Amount::zero();
|
let zero = Amount::zero();
|
||||||
|
@ -124,6 +134,7 @@ where
|
||||||
sprout: zero,
|
sprout: zero,
|
||||||
sapling: zero,
|
sapling: zero,
|
||||||
orchard: zero,
|
orchard: zero,
|
||||||
|
deferred: zero,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +149,7 @@ where
|
||||||
sprout: self.sprout.constrain().map_err(Sprout)?,
|
sprout: self.sprout.constrain().map_err(Sprout)?,
|
||||||
sapling: self.sapling.constrain().map_err(Sapling)?,
|
sapling: self.sapling.constrain().map_err(Sapling)?,
|
||||||
orchard: self.orchard.constrain().map_err(Orchard)?,
|
orchard: self.orchard.constrain().map_err(Orchard)?,
|
||||||
|
deferred: self.deferred.constrain().map_err(Deferred)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -166,60 +178,6 @@ impl ValueBalance<NegativeAllowed> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ValueBalance<NonNegative> {
|
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`.
|
/// 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,
|
/// `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
|
/// Note: the chain value pool has the opposite sign to the transaction
|
||||||
/// value pool.
|
/// value pool.
|
||||||
///
|
///
|
||||||
/// See [`Block::chain_value_pool_change`] and [`Transaction::value_balance`]
|
|
||||||
/// for details.
|
|
||||||
///
|
|
||||||
/// # Consensus
|
/// # Consensus
|
||||||
///
|
///
|
||||||
/// > If any of the "Sprout chain value pool balance", "Sapling chain value pool balance", or
|
/// > 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
|
/// Note: the chain value pool has the opposite sign to the transaction
|
||||||
/// value pool. Inputs remove value from the chain value pool.
|
/// 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"))]
|
#[cfg(any(test, feature = "proptest-impl"))]
|
||||||
pub fn add_transparent_input(
|
pub fn add_transparent_input(
|
||||||
self,
|
self,
|
||||||
|
@ -289,12 +241,46 @@ impl ValueBalance<NonNegative> {
|
||||||
self.add_chain_value_pool_change(transparent_value_pool_change)
|
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
|
/// Note that the chain value pool has the opposite sign to the transaction value pool.
|
||||||
/// 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)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn add_chain_value_pool_change(
|
pub fn add_chain_value_pool_change(
|
||||||
self,
|
self,
|
||||||
|
@ -333,15 +319,20 @@ impl ValueBalance<NonNegative> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// To byte array
|
/// To byte array
|
||||||
pub fn to_bytes(self) -> [u8; 32] {
|
pub fn to_bytes(self) -> [u8; 40] {
|
||||||
let transparent = self.transparent.to_bytes();
|
match [
|
||||||
let sprout = self.sprout.to_bytes();
|
self.transparent.to_bytes(),
|
||||||
let sapling = self.sapling.to_bytes();
|
self.sprout.to_bytes(),
|
||||||
let orchard = self.orchard.to_bytes();
|
self.sapling.to_bytes(),
|
||||||
match [transparent, sprout, sapling, orchard].concat().try_into() {
|
self.orchard.to_bytes(),
|
||||||
|
self.deferred.to_bytes(),
|
||||||
|
]
|
||||||
|
.concat()
|
||||||
|
.try_into()
|
||||||
|
{
|
||||||
Ok(bytes) => bytes,
|
Ok(bytes) => bytes,
|
||||||
_ => unreachable!(
|
_ => 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
|
/// From byte array
|
||||||
#[allow(clippy::unwrap_in_result)]
|
#[allow(clippy::unwrap_in_result)]
|
||||||
pub fn from_bytes(bytes: &[u8]) -> Result<ValueBalance<NonNegative>, ValueBalanceError> {
|
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(
|
let transparent = Amount::from_bytes(
|
||||||
bytes[0..8]
|
bytes[0..8]
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("Extracting the first quarter of a [u8; 32] should always succeed"),
|
.expect("transparent amount should be parsable"),
|
||||||
)
|
)
|
||||||
.map_err(Transparent)?;
|
.map_err(Transparent)?;
|
||||||
|
|
||||||
let sprout = Amount::from_bytes(
|
let sprout = Amount::from_bytes(
|
||||||
bytes[8..16]
|
bytes[8..16]
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("Extracting the second quarter of a [u8; 32] should always succeed"),
|
.expect("sprout amount should be parsable"),
|
||||||
)
|
)
|
||||||
.map_err(Sprout)?;
|
.map_err(Sprout)?;
|
||||||
|
|
||||||
let sapling = Amount::from_bytes(
|
let sapling = Amount::from_bytes(
|
||||||
bytes[16..24]
|
bytes[16..24]
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("Extracting the third quarter of a [u8; 32] should always succeed"),
|
.expect("sapling amount should be parsable"),
|
||||||
)
|
)
|
||||||
.map_err(Sapling)?;
|
.map_err(Sapling)?;
|
||||||
|
|
||||||
let orchard = Amount::from_bytes(
|
let orchard = Amount::from_bytes(
|
||||||
bytes[24..32]
|
bytes[24..32]
|
||||||
.try_into()
|
.try_into()
|
||||||
.expect("Extracting the last quarter of a [u8; 32] should always succeed"),
|
.expect("orchard amount should be parsable"),
|
||||||
)
|
)
|
||||||
.map_err(Orchard)?;
|
.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 {
|
Ok(ValueBalance {
|
||||||
transparent,
|
transparent,
|
||||||
sprout,
|
sprout,
|
||||||
sapling,
|
sapling,
|
||||||
orchard,
|
orchard,
|
||||||
|
deferred,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -400,6 +411,12 @@ pub enum ValueBalanceError {
|
||||||
|
|
||||||
/// orchard amount error {0}
|
/// orchard amount error {0}
|
||||||
Orchard(amount::Error),
|
Orchard(amount::Error),
|
||||||
|
|
||||||
|
/// deferred amount error {0}
|
||||||
|
Deferred(amount::Error),
|
||||||
|
|
||||||
|
/// ValueBalance is unparsable
|
||||||
|
Unparsable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ValueBalanceError {
|
impl fmt::Display for ValueBalanceError {
|
||||||
|
@ -409,6 +426,8 @@ impl fmt::Display for ValueBalanceError {
|
||||||
Sprout(e) => format!("sprout amount err: {e}"),
|
Sprout(e) => format!("sprout amount err: {e}"),
|
||||||
Sapling(e) => format!("sapling amount err: {e}"),
|
Sapling(e) => format!("sapling amount err: {e}"),
|
||||||
Orchard(e) => format!("orchard 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)?,
|
sprout: (self.sprout + rhs.sprout).map_err(Sprout)?,
|
||||||
sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
|
sapling: (self.sapling + rhs.sapling).map_err(Sapling)?,
|
||||||
orchard: (self.orchard + rhs.orchard).map_err(Orchard)?,
|
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)?,
|
sprout: (self.sprout - rhs.sprout).map_err(Sprout)?,
|
||||||
sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
|
sapling: (self.sapling - rhs.sapling).map_err(Sapling)?,
|
||||||
orchard: (self.orchard - rhs.orchard).map_err(Orchard)?,
|
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(),
|
sprout: self.sprout.neg(),
|
||||||
sapling: self.sapling.neg(),
|
sapling: self.sapling.neg(),
|
||||||
orchard: self.orchard.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>>(),
|
||||||
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,
|
transparent,
|
||||||
sprout,
|
sprout,
|
||||||
sapling,
|
sapling,
|
||||||
orchard,
|
orchard,
|
||||||
|
deferred,
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -32,12 +34,14 @@ impl Arbitrary for ValueBalance<NonNegative> {
|
||||||
any::<Amount<NonNegative>>(),
|
any::<Amount<NonNegative>>(),
|
||||||
any::<Amount<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,
|
transparent,
|
||||||
sprout,
|
sprout,
|
||||||
sapling,
|
sapling,
|
||||||
orchard,
|
orchard,
|
||||||
|
deferred,
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,15 +16,17 @@ proptest! {
|
||||||
let sprout = value_balance1.sprout + value_balance2.sprout;
|
let sprout = value_balance1.sprout + value_balance2.sprout;
|
||||||
let sapling = value_balance1.sapling + value_balance2.sapling;
|
let sapling = value_balance1.sapling + value_balance2.sapling;
|
||||||
let orchard = value_balance1.orchard + value_balance2.orchard;
|
let orchard = value_balance1.orchard + value_balance2.orchard;
|
||||||
|
let deferred = value_balance1.deferred + value_balance2.deferred;
|
||||||
|
|
||||||
match (transparent, sprout, sapling, orchard) {
|
match (transparent, sprout, sapling, orchard, deferred) {
|
||||||
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard)) => prop_assert_eq!(
|
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard), Ok(deferred)) => prop_assert_eq!(
|
||||||
value_balance1 + value_balance2,
|
value_balance1 + value_balance2,
|
||||||
Ok(ValueBalance {
|
Ok(ValueBalance {
|
||||||
transparent,
|
transparent,
|
||||||
sprout,
|
sprout,
|
||||||
sapling,
|
sapling,
|
||||||
orchard,
|
orchard,
|
||||||
|
deferred
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
_ => prop_assert!(
|
_ => prop_assert!(
|
||||||
|
@ -33,7 +35,8 @@ proptest! {
|
||||||
Err(ValueBalanceError::Transparent(_)
|
Err(ValueBalanceError::Transparent(_)
|
||||||
| ValueBalanceError::Sprout(_)
|
| ValueBalanceError::Sprout(_)
|
||||||
| ValueBalanceError::Sapling(_)
|
| ValueBalanceError::Sapling(_)
|
||||||
| ValueBalanceError::Orchard(_))
|
| ValueBalanceError::Orchard(_)
|
||||||
|
| ValueBalanceError::Deferred(_))
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -49,26 +52,27 @@ proptest! {
|
||||||
let sprout = value_balance1.sprout - value_balance2.sprout;
|
let sprout = value_balance1.sprout - value_balance2.sprout;
|
||||||
let sapling = value_balance1.sapling - value_balance2.sapling;
|
let sapling = value_balance1.sapling - value_balance2.sapling;
|
||||||
let orchard = value_balance1.orchard - value_balance2.orchard;
|
let orchard = value_balance1.orchard - value_balance2.orchard;
|
||||||
|
let deferred = value_balance1.deferred - value_balance2.deferred;
|
||||||
|
|
||||||
match (transparent, sprout, sapling, orchard) {
|
match (transparent, sprout, sapling, orchard, deferred) {
|
||||||
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard)) => prop_assert_eq!(
|
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard), Ok(deferred)) => prop_assert_eq!(
|
||||||
value_balance1 - value_balance2,
|
value_balance1 - value_balance2,
|
||||||
Ok(ValueBalance {
|
Ok(ValueBalance {
|
||||||
transparent,
|
transparent,
|
||||||
sprout,
|
sprout,
|
||||||
sapling,
|
sapling,
|
||||||
orchard,
|
orchard,
|
||||||
|
deferred
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
_ => prop_assert!(
|
_ => prop_assert!(matches!(
|
||||||
matches!(
|
|
||||||
value_balance1 - value_balance2,
|
value_balance1 - value_balance2,
|
||||||
Err(ValueBalanceError::Transparent(_)
|
Err(ValueBalanceError::Transparent(_)
|
||||||
| ValueBalanceError::Sprout(_)
|
| ValueBalanceError::Sprout(_)
|
||||||
| ValueBalanceError::Sapling(_)
|
| ValueBalanceError::Sapling(_)
|
||||||
| ValueBalanceError::Orchard(_))
|
| ValueBalanceError::Orchard(_)
|
||||||
)
|
| ValueBalanceError::Deferred(_))
|
||||||
),
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,22 +89,26 @@ proptest! {
|
||||||
let sprout = value_balance1.sprout + value_balance2.sprout;
|
let sprout = value_balance1.sprout + value_balance2.sprout;
|
||||||
let sapling = value_balance1.sapling + value_balance2.sapling;
|
let sapling = value_balance1.sapling + value_balance2.sapling;
|
||||||
let orchard = value_balance1.orchard + value_balance2.orchard;
|
let orchard = value_balance1.orchard + value_balance2.orchard;
|
||||||
|
let deferred = value_balance1.deferred + value_balance2.deferred;
|
||||||
|
|
||||||
match (transparent, sprout, sapling, orchard) {
|
match (transparent, sprout, sapling, orchard, deferred) {
|
||||||
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard)) => prop_assert_eq!(
|
(Ok(transparent), Ok(sprout), Ok(sapling), Ok(orchard), Ok(deferred)) => prop_assert_eq!(
|
||||||
collection.iter().sum::<Result<ValueBalance<NegativeAllowed>, ValueBalanceError>>(),
|
collection.iter().sum::<Result<ValueBalance<NegativeAllowed>, ValueBalanceError>>(),
|
||||||
Ok(ValueBalance {
|
Ok(ValueBalance {
|
||||||
transparent,
|
transparent,
|
||||||
sprout,
|
sprout,
|
||||||
sapling,
|
sapling,
|
||||||
orchard,
|
orchard,
|
||||||
|
deferred
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
_ => prop_assert!(matches!(collection.iter().sum(),
|
_ => prop_assert!(matches!(
|
||||||
|
collection.iter().sum(),
|
||||||
Err(ValueBalanceError::Transparent(_)
|
Err(ValueBalanceError::Transparent(_)
|
||||||
| ValueBalanceError::Sprout(_)
|
| ValueBalanceError::Sprout(_)
|
||||||
| ValueBalanceError::Sapling(_)
|
| ValueBalanceError::Sapling(_)
|
||||||
| ValueBalanceError::Orchard(_))
|
| ValueBalanceError::Orchard(_)
|
||||||
|
| ValueBalanceError::Deferred(_))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,11 +123,27 @@ proptest! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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();
|
let _init_guard = zebra_test::init();
|
||||||
|
|
||||||
if let Ok(deserialized) = ValueBalance::<NonNegative>::from_bytes(&bytes) {
|
if let Ok(deserialized) = ValueBalance::<NonNegative>::from_bytes(&bytes) {
|
||||||
prop_assert_eq!(bytes, deserialized.to_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 tower::{Service, ServiceExt};
|
||||||
use tracing::Instrument;
|
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 zebra_state as zs;
|
||||||
|
|
||||||
use crate::{error::*, transaction as tx, BoxError};
|
use crate::{error::*, transaction as tx, BoxError};
|
||||||
|
@ -78,6 +84,9 @@ pub enum VerifyBlockError {
|
||||||
|
|
||||||
#[error("invalid transaction")]
|
#[error("invalid transaction")]
|
||||||
Transaction(#[from] TransactionError),
|
Transaction(#[from] TransactionError),
|
||||||
|
|
||||||
|
#[error("invalid block subsidy")]
|
||||||
|
Subsidy(#[from] zebra_chain::amount::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VerifyBlockError {
|
impl VerifyBlockError {
|
||||||
|
@ -205,7 +214,10 @@ where
|
||||||
check::time_is_valid_at(&block.header, now, &height, &hash)
|
check::time_is_valid_at(&block.header, now, &height, &hash)
|
||||||
.map_err(VerifyBlockError::Time)?;
|
.map_err(VerifyBlockError::Time)?;
|
||||||
let coinbase_tx = check::coinbase_is_first(&block)?;
|
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
|
// 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 =
|
let block_miner_fees =
|
||||||
block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
|
block_miner_fees.map_err(|amount_error| BlockError::SummingMinerFees {
|
||||||
height,
|
height,
|
||||||
hash,
|
hash,
|
||||||
source: amount_error,
|
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.
|
// Finally, submit the block for contextual verification.
|
||||||
let new_outputs = Arc::into_inner(known_utxos)
|
let new_outputs = Arc::into_inner(known_utxos)
|
||||||
|
@ -289,6 +317,7 @@ where
|
||||||
height,
|
height,
|
||||||
new_outputs,
|
new_outputs,
|
||||||
transaction_hashes,
|
transaction_hashes,
|
||||||
|
deferred_balance: Some(expected_deferred_amount),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return early for proposal requests when getblocktemplate-rpcs feature is enabled
|
// 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`
|
/// Returns `Ok(())` if the block subsidy in `block` is valid for `network`
|
||||||
///
|
///
|
||||||
/// [3.9]: https://zips.z.cash/protocol/protocol.pdf#subsidyconcepts
|
/// [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 height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
|
||||||
let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
|
let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
|
||||||
|
|
||||||
|
@ -182,7 +186,11 @@ 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
|
// 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
|
// ZIP-1014 only applies to mainnet, ZIP-214 contains the specific rules for testnet
|
||||||
// funding stream amount values
|
// funding stream amount values
|
||||||
let funding_streams = subsidy::funding_streams::funding_stream_values(height, network)
|
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");
|
.expect("We always expect a funding stream hashmap response even if empty");
|
||||||
|
|
||||||
// # Consensus
|
// # 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
|
/// [7.1.2]: https://zips.z.cash/protocol/protocol.pdf#txnconsensus
|
||||||
pub fn miner_fees_are_valid(
|
pub fn miner_fees_are_valid(
|
||||||
block: &Block,
|
block: &Block,
|
||||||
network: &Network,
|
|
||||||
block_miner_fees: Amount<NonNegative>,
|
block_miner_fees: Amount<NonNegative>,
|
||||||
|
expected_block_subsidy: Amount<NonNegative>,
|
||||||
|
expected_deferred_amount: Amount<NonNegative>,
|
||||||
) -> Result<(), BlockError> {
|
) -> Result<(), BlockError> {
|
||||||
let height = block.coinbase_height().ok_or(SubsidyError::NoCoinbase)?;
|
|
||||||
let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
|
let coinbase = block.transactions.first().ok_or(SubsidyError::NoCoinbase)?;
|
||||||
|
|
||||||
let transparent_value_balance: Amount = subsidy::general::output_amounts(coinbase)
|
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 sapling_value_balance = coinbase.sapling_value_balance().sapling_amount();
|
||||||
let orchard_value_balance = coinbase.orchard_value_balance().orchard_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
|
// # Consensus
|
||||||
//
|
//
|
||||||
// > The total value in zatoshi of transparent outputs from a coinbase transaction,
|
// > 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.
|
// 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)
|
let left = (transparent_value_balance - sapling_value_balance - orchard_value_balance)
|
||||||
.map_err(|_| SubsidyError::SumOverflow)?;
|
.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)?;
|
.map_err(|_| SubsidyError::SumOverflow)?;
|
||||||
|
|
||||||
if left > right {
|
if left > right {
|
||||||
|
|
|
@ -12,8 +12,6 @@ use zebra_chain::{
|
||||||
transparent::{self, Script},
|
transparent::{self, Script},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::block::subsidy::general::block_subsidy;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -24,6 +22,7 @@ mod tests;
|
||||||
pub fn funding_stream_values(
|
pub fn funding_stream_values(
|
||||||
height: Height,
|
height: Height,
|
||||||
network: &Network,
|
network: &Network,
|
||||||
|
expected_block_subsidy: Amount<NonNegative>,
|
||||||
) -> Result<HashMap<FundingStreamReceiver, Amount<NonNegative>>, Error> {
|
) -> Result<HashMap<FundingStreamReceiver, Amount<NonNegative>>, Error> {
|
||||||
let canopy_height = Canopy.activation_height(network).unwrap();
|
let canopy_height = Canopy.activation_height(network).unwrap();
|
||||||
let mut results = HashMap::new();
|
let mut results = HashMap::new();
|
||||||
|
@ -31,14 +30,13 @@ pub fn funding_stream_values(
|
||||||
if height >= canopy_height {
|
if height >= canopy_height {
|
||||||
let funding_streams = network.funding_streams(height);
|
let funding_streams = network.funding_streams(height);
|
||||||
if funding_streams.height_range().contains(&height) {
|
if funding_streams.height_range().contains(&height) {
|
||||||
let block_subsidy = block_subsidy(height, network)?;
|
|
||||||
for (&receiver, recipient) in funding_streams.recipients() {
|
for (&receiver, recipient) in funding_streams.recipients() {
|
||||||
// - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`:
|
// - Spec equation: `fs.value = floor(block_subsidy(height)*(fs.numerator/fs.denominator))`:
|
||||||
// https://zips.z.cash/protocol/protocol.pdf#subsidies
|
// https://zips.z.cash/protocol/protocol.pdf#subsidies
|
||||||
// - In Rust, "integer division rounds towards zero":
|
// - In Rust, "integer division rounds towards zero":
|
||||||
// https://doc.rust-lang.org/stable/reference/expressions/operator-expr.html#arithmetic-and-logical-binary-operators
|
// 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.
|
// 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)?;
|
/ FUNDING_STREAM_RECEIVER_DENOMINATOR)?;
|
||||||
|
|
||||||
results.insert(receiver, amount_value);
|
results.insert(receiver, amount_value);
|
||||||
|
@ -93,13 +91,6 @@ pub fn funding_stream_address(
|
||||||
funding_streams.recipient(receiver)?.addresses().get(index)
|
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
|
/// 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]
|
/// as the given lock_script as described in [protocol specification §7.10][7.10]
|
||||||
///
|
///
|
||||||
|
|
|
@ -10,6 +10,8 @@ use zebra_chain::parameters::{
|
||||||
NetworkKind,
|
NetworkKind,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::block::subsidy::general::block_subsidy;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Check mainnet funding stream values are correct for the entire period.
|
/// 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;
|
let network = &Network::Mainnet;
|
||||||
|
|
||||||
// funding streams not active
|
// funding streams not active
|
||||||
let canopy_height_minus1 = Canopy.activation_height(network).unwrap() - 1;
|
let canopy_height_minus1 = (Canopy.activation_height(network).unwrap() - 1).unwrap();
|
||||||
assert!(funding_stream_values(canopy_height_minus1.unwrap(), network)?.is_empty());
|
|
||||||
|
assert!(funding_stream_values(
|
||||||
|
canopy_height_minus1,
|
||||||
|
network,
|
||||||
|
block_subsidy(canopy_height_minus1, network)?
|
||||||
|
)?
|
||||||
|
.is_empty());
|
||||||
|
|
||||||
// funding stream is active
|
// funding stream is active
|
||||||
let canopy_height = Canopy.activation_height(network);
|
let canopy_height = Canopy.activation_height(network).unwrap();
|
||||||
let canopy_height_plus1 = Canopy.activation_height(network).unwrap() + 1;
|
let canopy_height_plus1 = (Canopy.activation_height(network).unwrap() + 1).unwrap();
|
||||||
let canopy_height_plus2 = Canopy.activation_height(network).unwrap() + 2;
|
let canopy_height_plus2 = (Canopy.activation_height(network).unwrap() + 2).unwrap();
|
||||||
|
|
||||||
let mut hash_map = HashMap::new();
|
let mut hash_map = HashMap::new();
|
||||||
hash_map.insert(FundingStreamReceiver::Ecc, Amount::try_from(21_875_000)?);
|
hash_map.insert(FundingStreamReceiver::Ecc, Amount::try_from(21_875_000)?);
|
||||||
|
@ -39,28 +47,46 @@ fn test_funding_stream_values() -> Result<(), Report> {
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
funding_stream_values(canopy_height.unwrap(), network).unwrap(),
|
funding_stream_values(
|
||||||
|
canopy_height,
|
||||||
|
network,
|
||||||
|
block_subsidy(canopy_height, network)?
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
hash_map
|
hash_map
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
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
|
hash_map
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
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
|
hash_map
|
||||||
);
|
);
|
||||||
|
|
||||||
// funding stream period is ending
|
// funding stream period is ending
|
||||||
let range = network.pre_nu6_funding_streams().height_range();
|
let range = network.pre_nu6_funding_streams().height_range();
|
||||||
let end = range.end;
|
let end = range.end;
|
||||||
let last = end - 1;
|
let last = (end - 1).unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
funding_stream_values(last.unwrap(), network).unwrap(),
|
funding_stream_values(last, network, block_subsidy(last, network)?).unwrap(),
|
||||||
hash_map
|
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
|
// TODO: Replace this with Mainnet once there's an NU6 activation height defined for Mainnet
|
||||||
let network = testnet::Parameters::build()
|
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),
|
||||||
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(())
|
Ok(())
|
||||||
|
|
|
@ -26,10 +26,7 @@ pub fn halving_divisor(height: Height, network: &Network) -> Option<u64> {
|
||||||
.expect("blossom activation height should be available");
|
.expect("blossom activation height should be available");
|
||||||
|
|
||||||
if height < network.slow_start_shift() {
|
if height < network.slow_start_shift() {
|
||||||
panic!(
|
None
|
||||||
"unsupported block height {height:?}: checkpoints should handle blocks below {:?}",
|
|
||||||
network.slow_start_shift()
|
|
||||||
)
|
|
||||||
} else if height < blossom_height {
|
} else if height < blossom_height {
|
||||||
let pre_blossom_height = height - network.slow_start_shift();
|
let pre_blossom_height = height - network.slow_start_shift();
|
||||||
let halving_shift = pre_blossom_height / PRE_BLOSSOM_HALVING_INTERVAL;
|
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]
|
/// `MinerSubsidy(height)` as described in [protocol specification §7.8][7.8]
|
||||||
///
|
///
|
||||||
/// [7.8]: https://zips.z.cash/protocol/protocol.pdf#subsidies
|
/// [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>, _> =
|
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`.
|
/// Returns all output amounts in `Transaction`.
|
||||||
|
@ -129,7 +132,10 @@ fn lockbox_input_value(network: &Network, height: Height) -> Amount<NonNegative>
|
||||||
return Amount::zero();
|
return Amount::zero();
|
||||||
};
|
};
|
||||||
|
|
||||||
let &deferred_amount_per_block = funding_stream_values(nu6_activation_height, network)
|
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")
|
.expect("we always expect a funding stream hashmap response even if empty")
|
||||||
.get(&FundingStreamReceiver::Deferred)
|
.get(&FundingStreamReceiver::Deferred)
|
||||||
.expect("we expect a lockbox funding stream after NU5");
|
.expect("we expect a lockbox funding stream after NU5");
|
||||||
|
|
|
@ -20,7 +20,7 @@ use zebra_chain::{
|
||||||
use zebra_script::CachedFfiTransaction;
|
use zebra_script::CachedFfiTransaction;
|
||||||
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
|
use zebra_test::transcript::{ExpectedTranscriptError, Transcript};
|
||||||
|
|
||||||
use crate::transaction;
|
use crate::{block_subsidy, transaction};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -292,6 +292,7 @@ fn subsidy_is_valid_for_network(network: Network) -> Result<(), Report> {
|
||||||
let block_iter = network.block_iter();
|
let block_iter = network.block_iter();
|
||||||
|
|
||||||
for (&height, block) in block_iter {
|
for (&height, block) in block_iter {
|
||||||
|
let height = block::Height(height);
|
||||||
let block = block
|
let block = block
|
||||||
.zcash_deserialize_into::<Block>()
|
.zcash_deserialize_into::<Block>()
|
||||||
.expect("block is structurally valid");
|
.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");
|
.expect("Canopy activation height is known");
|
||||||
|
|
||||||
// TODO: first halving, second halving, third halving, and very large halvings
|
// TODO: first halving, second halving, third halving, and very large halvings
|
||||||
if block::Height(height) >= canopy_activation_height {
|
if height >= canopy_activation_height {
|
||||||
check::subsidy_is_valid(&block, &network)
|
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");
|
.expect("subsidies should pass for this block");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,6 +326,14 @@ fn coinbase_validation_failure() -> Result<(), Report> {
|
||||||
.expect("block should deserialize");
|
.expect("block should deserialize");
|
||||||
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
|
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
|
// Remove coinbase transaction
|
||||||
block.transactions.remove(0);
|
block.transactions.remove(0);
|
||||||
|
|
||||||
|
@ -330,8 +342,7 @@ fn coinbase_validation_failure() -> Result<(), Report> {
|
||||||
let expected = BlockError::NoTransactions;
|
let expected = BlockError::NoTransactions;
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
// Validate the block using subsidy_is_valid
|
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy).unwrap_err();
|
||||||
let result = check::subsidy_is_valid(&block, &network).unwrap_err();
|
|
||||||
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
|
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
@ -341,6 +352,14 @@ fn coinbase_validation_failure() -> Result<(), Report> {
|
||||||
.expect("block should deserialize");
|
.expect("block should deserialize");
|
||||||
let mut block = Arc::try_unwrap(block).expect("block should unwrap");
|
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
|
// Remove coinbase transaction
|
||||||
block.transactions.remove(0);
|
block.transactions.remove(0);
|
||||||
|
|
||||||
|
@ -349,8 +368,7 @@ fn coinbase_validation_failure() -> Result<(), Report> {
|
||||||
let expected = BlockError::Transaction(TransactionError::CoinbasePosition);
|
let expected = BlockError::Transaction(TransactionError::CoinbasePosition);
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
// Validate the block using subsidy_is_valid
|
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy).unwrap_err();
|
||||||
let result = check::subsidy_is_valid(&block, &network).unwrap_err();
|
|
||||||
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
|
let expected = BlockError::Transaction(TransactionError::Subsidy(SubsidyError::NoCoinbase));
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
|
@ -374,8 +392,15 @@ fn coinbase_validation_failure() -> Result<(), Report> {
|
||||||
let expected = BlockError::Transaction(TransactionError::CoinbaseAfterFirst);
|
let expected = BlockError::Transaction(TransactionError::CoinbaseAfterFirst);
|
||||||
assert_eq!(expected, result);
|
assert_eq!(expected, result);
|
||||||
|
|
||||||
// Validate the block using subsidy_is_valid, which does not detect this error
|
let expected_block_subsidy = subsidy::general::block_subsidy(
|
||||||
check::subsidy_is_valid(&block, &network)
|
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");
|
.expect("subsidy does not check for extra coinbase transactions");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -399,11 +424,15 @@ fn funding_stream_validation_for_network(network: Network) -> Result<(), Report>
|
||||||
.expect("Canopy activation height is known");
|
.expect("Canopy activation height is known");
|
||||||
|
|
||||||
for (&height, block) in block_iter {
|
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 block = Block::zcash_deserialize(&block[..]).expect("block should deserialize");
|
||||||
|
let expected_block_subsidy =
|
||||||
|
subsidy::general::block_subsidy(height, &network).expect("valid block subsidy");
|
||||||
|
|
||||||
// Validate
|
// Validate
|
||||||
let result = check::subsidy_is_valid(&block, &network);
|
let result = check::subsidy_is_valid(&block, &network, expected_block_subsidy);
|
||||||
assert!(result.is_ok());
|
assert!(result.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,7 +476,15 @@ fn funding_stream_validation_failure() -> Result<(), Report> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Validate it
|
// 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(
|
let expected = Err(BlockError::Transaction(TransactionError::Subsidy(
|
||||||
SubsidyError::FundingStreamNotFound,
|
SubsidyError::FundingStreamNotFound,
|
||||||
)));
|
)));
|
||||||
|
@ -470,14 +507,30 @@ fn miner_fees_validation_for_network(network: Network) -> Result<(), Report> {
|
||||||
let block_iter = network.block_iter();
|
let block_iter = network.block_iter();
|
||||||
|
|
||||||
for (&height, block) in 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 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
|
// fake the miner fee to a big amount
|
||||||
let miner_fees = Amount::try_from(MAX_MONEY / 2).unwrap();
|
let miner_fees = Amount::try_from(MAX_MONEY / 2).unwrap();
|
||||||
|
|
||||||
// Validate
|
// 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());
|
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> {
|
fn miner_fees_validation_failure() -> Result<(), Report> {
|
||||||
let _init_guard = zebra_test::init();
|
let _init_guard = zebra_test::init();
|
||||||
let network = Network::Mainnet;
|
let network = Network::Mainnet;
|
||||||
|
|
||||||
let block =
|
let block =
|
||||||
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_347499_BYTES[..])
|
Arc::<Block>::zcash_deserialize(&zebra_test::vectors::BLOCK_MAINNET_347499_BYTES[..])
|
||||||
.expect("block should deserialize");
|
.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
|
// fake the miner fee to a small amount
|
||||||
let miner_fees = Amount::zero();
|
let miner_fees = Amount::zero();
|
||||||
|
|
||||||
// Validate
|
// 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(
|
let expected = Err(BlockError::Transaction(TransactionError::Subsidy(
|
||||||
SubsidyError::InvalidMinerFees,
|
SubsidyError::InvalidMinerFees,
|
||||||
|
|
|
@ -28,21 +28,22 @@ use tower::{Service, ServiceExt};
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
|
amount,
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
parameters::{Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
parameters::{subsidy::FundingStreamReceiver, Network, GENESIS_PREVIOUS_BLOCK_HASH},
|
||||||
work::equihash,
|
work::equihash,
|
||||||
};
|
};
|
||||||
use zebra_state::{self as zs, CheckpointVerifiedBlock};
|
use zebra_state::{self as zs, CheckpointVerifiedBlock};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
block::VerifyBlockError,
|
block::VerifyBlockError,
|
||||||
|
block_subsidy,
|
||||||
checkpoint::types::{
|
checkpoint::types::{
|
||||||
Progress,
|
Progress::{self, *},
|
||||||
Progress::*,
|
|
||||||
TargetHeight::{self, *},
|
TargetHeight::{self, *},
|
||||||
},
|
},
|
||||||
error::BlockError,
|
error::BlockError,
|
||||||
BoxError, ParameterCheckpoint as _,
|
funding_stream_values, BoxError, ParameterCheckpoint as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) mod list;
|
pub(crate) mod list;
|
||||||
|
@ -607,8 +608,16 @@ where
|
||||||
crate::block::check::equihash_solution_is_valid(&block.header)?;
|
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
|
// 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(
|
crate::block::check::merkle_root_validity(
|
||||||
&self.network,
|
&self.network,
|
||||||
|
@ -981,6 +990,8 @@ pub enum VerifyCheckpointError {
|
||||||
CheckpointList(BoxError),
|
CheckpointList(BoxError),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
VerifyBlock(VerifyBlockError),
|
VerifyBlock(VerifyBlockError),
|
||||||
|
#[error("invalid block subsidy")]
|
||||||
|
SubsidyError(#[from] amount::Error),
|
||||||
#[error("too many queued blocks at this height")]
|
#[error("too many queued blocks at this height")]
|
||||||
QueuedLimit,
|
QueuedLimit,
|
||||||
#[error("the block hash does not match the chained checkpoint hash, expected {expected:?} found {found:?}")]
|
#[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::{
|
pub use block::{
|
||||||
subsidy::{
|
subsidy::{
|
||||||
funding_streams::{
|
funding_streams::{funding_stream_address, funding_stream_values, new_coinbase_script},
|
||||||
funding_stream_address, funding_stream_recipient_info, funding_stream_values,
|
general::{block_subsidy, miner_subsidy},
|
||||||
new_coinbase_script,
|
|
||||||
},
|
|
||||||
general::miner_subsidy,
|
|
||||||
},
|
},
|
||||||
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
|
Request, VerifyBlockError, MAX_BLOCK_SIGOPS,
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,7 +22,9 @@ use zebra_chain::{
|
||||||
},
|
},
|
||||||
work::difficulty::{ParameterDifficulty as _, U256},
|
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_network::AddressBookPeers;
|
||||||
use zebra_node_services::mempool;
|
use zebra_node_services::mempool;
|
||||||
use zebra_state::{ReadRequest, ReadResponse};
|
use zebra_state::{ReadRequest, ReadResponse};
|
||||||
|
@ -1176,7 +1178,15 @@ where
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let miner = miner_subsidy(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),
|
code: ErrorCode::ServerError(0),
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
|
@ -1184,8 +1194,8 @@ where
|
||||||
// Always zero for post-halving blocks
|
// Always zero for post-halving blocks
|
||||||
let founders = Amount::zero();
|
let founders = Amount::zero();
|
||||||
|
|
||||||
let funding_streams =
|
let funding_streams = funding_stream_values(height, &network, expected_block_subsidy)
|
||||||
funding_stream_values(height, &network).map_err(|error| Error {
|
.map_err(|error| Error {
|
||||||
code: ErrorCode::ServerError(0),
|
code: ErrorCode::ServerError(0),
|
||||||
message: error.to_string(),
|
message: error.to_string(),
|
||||||
data: None,
|
data: None,
|
||||||
|
@ -1208,7 +1218,7 @@ where
|
||||||
let (_receivers, funding_streams): (Vec<_>, _) = funding_streams.into_iter().unzip();
|
let (_receivers, funding_streams): (Vec<_>, _) = funding_streams.into_iter().unzip();
|
||||||
|
|
||||||
Ok(BlockSubsidy {
|
Ok(BlockSubsidy {
|
||||||
miner: miner.into(),
|
miner: miner_subsidy.into(),
|
||||||
founders: founders.into(),
|
founders: founders.into(),
|
||||||
funding_streams,
|
funding_streams,
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,7 +19,9 @@ use zebra_chain::{
|
||||||
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
transaction::{Transaction, UnminedTx, VerifiedUnminedTx},
|
||||||
transparent,
|
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_node_services::mempool;
|
||||||
use zebra_state::GetBlockTemplateChainInfo;
|
use zebra_state::GetBlockTemplateChainInfo;
|
||||||
|
|
||||||
|
@ -375,7 +377,8 @@ pub fn standard_coinbase_outputs(
|
||||||
miner_fee: Amount<NonNegative>,
|
miner_fee: Amount<NonNegative>,
|
||||||
like_zcashd: bool,
|
like_zcashd: bool,
|
||||||
) -> Vec<(Amount<NonNegative>, transparent::Script)> {
|
) -> 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");
|
.expect("funding stream value calculations are valid for reasonable chain heights");
|
||||||
|
|
||||||
// Optional TODO: move this into a zebra_consensus function?
|
// Optional TODO: move this into a zebra_consensus function?
|
||||||
|
@ -392,7 +395,7 @@ pub fn standard_coinbase_outputs(
|
||||||
})
|
})
|
||||||
.collect();
|
.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")
|
.expect("reward calculations are valid for reasonable chain heights")
|
||||||
+ miner_fee;
|
+ miner_fee;
|
||||||
let miner_reward =
|
let miner_reward =
|
||||||
|
|
|
@ -5,7 +5,6 @@ use zebra_chain::{
|
||||||
parameters::subsidy::FundingStreamReceiver,
|
parameters::subsidy::FundingStreamReceiver,
|
||||||
transparent,
|
transparent,
|
||||||
};
|
};
|
||||||
use zebra_consensus::funding_stream_recipient_info;
|
|
||||||
|
|
||||||
use crate::methods::get_block_template_rpcs::types::zec::Zec;
|
use crate::methods::get_block_template_rpcs::types::zec::Zec;
|
||||||
|
|
||||||
|
@ -69,10 +68,10 @@ impl FundingStream {
|
||||||
value: Amount<NonNegative>,
|
value: Amount<NonNegative>,
|
||||||
address: &transparent::Address,
|
address: &transparent::Address,
|
||||||
) -> FundingStream {
|
) -> FundingStream {
|
||||||
let (recipient, specification) = funding_stream_recipient_info(receiver);
|
let (name, specification) = receiver.info();
|
||||||
|
|
||||||
FundingStream {
|
FundingStream {
|
||||||
recipient: recipient.to_string(),
|
recipient: name.to_string(),
|
||||||
specification: specification.to_string(),
|
specification: specification.to_string(),
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
value_zat: value,
|
value_zat: value,
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::{Amount, NegativeAllowed},
|
amount::Amount,
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
transaction::Transaction,
|
transaction::Transaction,
|
||||||
transparent,
|
transparent,
|
||||||
|
@ -11,7 +11,7 @@ use zebra_chain::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
request::ContextuallyVerifiedBlock, service::chain_tip::ChainTipBlock, CheckpointVerifiedBlock,
|
request::ContextuallyVerifiedBlock, service::chain_tip::ChainTipBlock,
|
||||||
SemanticallyVerifiedBlock,
|
SemanticallyVerifiedBlock,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ impl Prepare for Arc<Block> {
|
||||||
height,
|
height,
|
||||||
new_outputs,
|
new_outputs,
|
||||||
transaction_hashes,
|
transaction_hashes,
|
||||||
|
deferred_balance: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,18 +61,6 @@ impl SemanticallyVerifiedBlock {
|
||||||
ContextuallyVerifiedBlock::test_with_zero_spent_utxos(self)
|
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,
|
/// Returns a [`ContextuallyVerifiedBlock`] created from this block,
|
||||||
/// with no chain value pool change.
|
/// with no chain value pool change.
|
||||||
///
|
///
|
||||||
|
@ -112,19 +101,17 @@ impl ContextuallyVerifiedBlock {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a [`ContextuallyVerifiedBlock`] from a [`Block`] or [`SemanticallyVerifiedBlock`],
|
/// 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.
|
/// Only for use in tests.
|
||||||
pub fn test_with_chain_pool_change(
|
pub fn test_with_zero_chain_pool_change(block: impl Into<SemanticallyVerifiedBlock>) -> Self {
|
||||||
block: impl Into<SemanticallyVerifiedBlock>,
|
|
||||||
fake_chain_value_pool_change: ValueBalance<NegativeAllowed>,
|
|
||||||
) -> Self {
|
|
||||||
let SemanticallyVerifiedBlock {
|
let SemanticallyVerifiedBlock {
|
||||||
block,
|
block,
|
||||||
hash,
|
hash,
|
||||||
height,
|
height,
|
||||||
new_outputs,
|
new_outputs,
|
||||||
transaction_hashes,
|
transaction_hashes,
|
||||||
|
deferred_balance: _,
|
||||||
} = block.into();
|
} = block.into();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
@ -137,40 +124,7 @@ impl ContextuallyVerifiedBlock {
|
||||||
// TODO: fix the tests, and stop adding unrelated inputs and outputs.
|
// TODO: fix the tests, and stop adding unrelated inputs and outputs.
|
||||||
spent_outputs: new_outputs,
|
spent_outputs: new_outputs,
|
||||||
transaction_hashes,
|
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()`]
|
/// 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.
|
/// 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
|
/// The database format minor version, incremented each time the on-disk database format has a
|
||||||
/// significant data format change.
|
/// significant data format change.
|
||||||
|
@ -55,7 +55,7 @@ const DATABASE_FORMAT_VERSION: u64 = 25;
|
||||||
/// - adding new column families,
|
/// - adding new column families,
|
||||||
/// - changing the format of a column family in a compatible way, or
|
/// - changing the format of a column family in a compatible way, or
|
||||||
/// - breaking changes with compatibility code in all supported Zebra versions.
|
/// - 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
|
/// The database format patch version, incremented each time the on-disk database format has a
|
||||||
/// significant format compatibility fix.
|
/// significant format compatibility fix.
|
||||||
|
|
|
@ -7,7 +7,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use zebra_chain::{
|
use zebra_chain::{
|
||||||
amount::NegativeAllowed,
|
amount::{Amount, NegativeAllowed, NonNegative},
|
||||||
block::{self, Block},
|
block::{self, Block},
|
||||||
history_tree::HistoryTree,
|
history_tree::HistoryTree,
|
||||||
orchard,
|
orchard,
|
||||||
|
@ -161,6 +161,8 @@ pub struct SemanticallyVerifiedBlock {
|
||||||
/// A precomputed list of the hashes of the transactions in this block,
|
/// A precomputed list of the hashes of the transactions in this block,
|
||||||
/// in the same order as `block.transactions`.
|
/// in the same order as `block.transactions`.
|
||||||
pub transaction_hashes: Arc<[transaction::Hash]>,
|
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
|
/// 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]>,
|
pub(super) transaction_hashes: Arc<[transaction::Hash]>,
|
||||||
/// The tresstate associated with the block.
|
/// The tresstate associated with the block.
|
||||||
pub(super) treestate: Treestate,
|
pub(super) treestate: Treestate,
|
||||||
|
/// This block's contribution to the deferred pool.
|
||||||
|
pub(super) deferred_balance: Option<Amount<NonNegative>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FinalizedBlock {
|
impl FinalizedBlock {
|
||||||
|
@ -314,6 +318,7 @@ impl FinalizedBlock {
|
||||||
new_outputs: block.new_outputs,
|
new_outputs: block.new_outputs,
|
||||||
transaction_hashes: block.transaction_hashes,
|
transaction_hashes: block.transaction_hashes,
|
||||||
treestate,
|
treestate,
|
||||||
|
deferred_balance: block.deferred_balance,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,6 +391,7 @@ impl ContextuallyVerifiedBlock {
|
||||||
height,
|
height,
|
||||||
new_outputs,
|
new_outputs,
|
||||||
transaction_hashes,
|
transaction_hashes,
|
||||||
|
deferred_balance,
|
||||||
} = semantically_verified;
|
} = semantically_verified;
|
||||||
|
|
||||||
// This is redundant for the non-finalized state,
|
// This is redundant for the non-finalized state,
|
||||||
|
@ -401,32 +407,27 @@ impl ContextuallyVerifiedBlock {
|
||||||
new_outputs,
|
new_outputs,
|
||||||
spent_outputs: spent_outputs.clone(),
|
spent_outputs: spent_outputs.clone(),
|
||||||
transaction_hashes,
|
transaction_hashes,
|
||||||
chain_value_pool_change: block
|
chain_value_pool_change: block.chain_value_pool_change(
|
||||||
.chain_value_pool_change(&utxos_from_ordered_utxos(spent_outputs))?,
|
&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 {
|
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`].
|
/// using a precalculated [`block::Hash`].
|
||||||
///
|
///
|
||||||
/// Note: a [`CheckpointVerifiedBlock`] isn't actually finalized
|
/// Note: a [`CheckpointVerifiedBlock`] isn't actually finalized
|
||||||
|
@ -436,33 +437,14 @@ impl CheckpointVerifiedBlock {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Arc<Block>> for CheckpointVerifiedBlock {
|
impl SemanticallyVerifiedBlock {
|
||||||
fn from(block: Arc<Block>) -> Self {
|
/// Creates [`SemanticallyVerifiedBlock`] from [`Block`] and [`block::Hash`].
|
||||||
let hash = block.hash();
|
pub fn with_hash(block: Arc<Block>, hash: block::Hash) -> Self {
|
||||||
|
let height = block
|
||||||
CheckpointVerifiedBlock::with_hash(block, hash)
|
.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);
|
||||||
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;
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
block,
|
block,
|
||||||
|
@ -470,6 +452,71 @@ impl From<ContextuallyVerifiedBlock> for SemanticallyVerifiedBlock {
|
||||||
height,
|
height,
|
||||||
new_outputs,
|
new_outputs,
|
||||||
transaction_hashes,
|
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.
|
/// 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.
|
/// balance of the set of addresses.
|
||||||
AddressBalance(HashSet<transparent::Address>),
|
AddressBalance(HashSet<transparent::Address>),
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,7 @@ impl From<SemanticallyVerifiedBlock> for ChainTipBlock {
|
||||||
height,
|
height,
|
||||||
new_outputs: _,
|
new_outputs: _,
|
||||||
transaction_hashes,
|
transaction_hashes,
|
||||||
|
deferred_balance: _,
|
||||||
} = prepared;
|
} = prepared;
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|
|
@ -21,7 +21,7 @@ use zebra_chain::{
|
||||||
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
|
use crate::service::finalized_state::disk_format::{FromDisk, IntoDisk};
|
||||||
|
|
||||||
impl IntoDisk for ValueBalance<NonNegative> {
|
impl IntoDisk for ValueBalance<NonNegative> {
|
||||||
type Bytes = [u8; 32];
|
type Bytes = [u8; 40];
|
||||||
|
|
||||||
fn as_bytes(&self) -> Self::Bytes {
|
fn as_bytes(&self) -> Self::Bytes {
|
||||||
self.to_bytes()
|
self.to_bytes()
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
---
|
---
|
||||||
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
||||||
assertion_line: 125
|
|
||||||
expression: cf_data
|
expression: cf_data
|
||||||
|
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
KV(
|
KV(
|
||||||
k: "",
|
k: "",
|
||||||
v: "24f4000000000000000000000000000000000000000000000000000000000000",
|
v: "24f40000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
---
|
---
|
||||||
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
||||||
assertion_line: 125
|
|
||||||
expression: cf_data
|
expression: cf_data
|
||||||
|
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
KV(
|
KV(
|
||||||
k: "",
|
k: "",
|
||||||
v: "6cdc020000000000000000000000000000000000000000000000000000000000",
|
v: "6cdc0200000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
---
|
---
|
||||||
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
||||||
assertion_line: 125
|
|
||||||
expression: cf_data
|
expression: cf_data
|
||||||
|
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
KV(
|
KV(
|
||||||
k: "",
|
k: "",
|
||||||
v: "24f4000000000000000000000000000000000000000000000000000000000000",
|
v: "24f40000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
---
|
---
|
||||||
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
source: zebra-state/src/service/finalized_state/disk_format/tests/snapshot.rs
|
||||||
assertion_line: 125
|
|
||||||
expression: cf_data
|
expression: cf_data
|
||||||
|
|
||||||
---
|
---
|
||||||
[
|
[
|
||||||
KV(
|
KV(
|
||||||
k: "",
|
k: "",
|
||||||
v: "6cdc020000000000000000000000000000000000000000000000000000000000",
|
v: "6cdc0200000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -541,6 +541,14 @@ impl DbFormatChange {
|
||||||
timer.finish(module_path!(), line!(), "tree keys and caches upgrade");
|
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 Upgrades Usually Go Here
|
||||||
//
|
//
|
||||||
// New code goes above this comment!
|
// New code goes above this comment!
|
||||||
|
|
|
@ -22,6 +22,7 @@ use zebra_chain::{
|
||||||
},
|
},
|
||||||
parameters::Network::{self, *},
|
parameters::Network::{self, *},
|
||||||
serialization::{ZcashDeserializeInto, ZcashSerialize},
|
serialization::{ZcashDeserializeInto, ZcashSerialize},
|
||||||
|
transparent::new_ordered_outputs_with_height,
|
||||||
};
|
};
|
||||||
use zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS};
|
use zebra_test::vectors::{MAINNET_BLOCKS, TESTNET_BLOCKS};
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ use crate::{
|
||||||
constants::{state_database_format_version_in_code, STATE_DATABASE_KIND},
|
constants::{state_database_format_version_in_code, STATE_DATABASE_KIND},
|
||||||
request::{FinalizedBlock, Treestate},
|
request::{FinalizedBlock, Treestate},
|
||||||
service::finalized_state::{disk_db::DiskWriteBatch, ZebraDb, STATE_COLUMN_FAMILIES_IN_CODE},
|
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.
|
/// 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
|
// Now, use the database
|
||||||
let original_block = Arc::new(original_block);
|
let original_block = Arc::new(original_block);
|
||||||
let checkpoint_verified = if original_block.coinbase_height().is_some() {
|
let checkpoint_verified = if original_block.coinbase_height().is_some() {
|
||||||
original_block.clone().into()
|
CheckpointVerifiedBlock::from(original_block.clone())
|
||||||
} else {
|
} else {
|
||||||
// Fake a zero height
|
// Fake a zero height
|
||||||
CheckpointVerifiedBlock::with_hash_and_height(
|
let hash = original_block.hash();
|
||||||
original_block.clone(),
|
let transaction_hashes: Arc<[_]> = original_block
|
||||||
original_block.hash(),
|
.transactions
|
||||||
Height(0),
|
.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();
|
let dummy_treestate = Treestate::default();
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
//! each time the database format (column, serialization, etc) changes.
|
//! each time the database format (column, serialization, etc) changes.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Borrow,
|
|
||||||
collections::{BTreeMap, HashMap},
|
collections::{BTreeMap, HashMap},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
@ -203,16 +202,24 @@ impl DiskWriteBatch {
|
||||||
|
|
||||||
// Value pool methods
|
// Value pool methods
|
||||||
|
|
||||||
/// Prepare a database batch containing the chain value pool update from `finalized.block`,
|
/// Prepares a database batch containing the chain value pool update from `finalized.block`, and
|
||||||
/// and return it (without actually writing anything).
|
/// returns it without actually writing anything.
|
||||||
///
|
///
|
||||||
/// If this method returns an error, it will be propagated,
|
/// The batch is modified by this method and written by the caller. The caller should not write
|
||||||
/// and the batch should not be written to the database.
|
/// 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
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// - Propagates any errors from updating value pools
|
/// - 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(
|
pub fn prepare_chain_value_pools_batch(
|
||||||
&mut self,
|
&mut self,
|
||||||
db: &ZebraDb,
|
db: &ZebraDb,
|
||||||
|
@ -220,14 +227,18 @@ impl DiskWriteBatch {
|
||||||
utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
|
utxos_spent_by_block: HashMap<transparent::OutPoint, transparent::Utxo>,
|
||||||
value_pool: ValueBalance<NonNegative>,
|
value_pool: ValueBalance<NonNegative>,
|
||||||
) -> Result<(), BoxError> {
|
) -> Result<(), BoxError> {
|
||||||
let chain_value_pools_cf = db.chain_value_pools_cf().with_batch_for_writing(self);
|
let _ = db
|
||||||
|
.chain_value_pools_cf()
|
||||||
let FinalizedBlock { block, .. } = finalized;
|
.with_batch_for_writing(self)
|
||||||
|
.zs_insert(
|
||||||
let new_pool = value_pool.add_block(block.borrow(), &utxos_spent_by_block)?;
|
&(),
|
||||||
|
&value_pool.add_chain_value_pool_change(
|
||||||
// The batch is modified by this method and written by the caller.
|
finalized.block.chain_value_pool_change(
|
||||||
let _ = chain_value_pools_cf.zs_insert(&(), &new_pool);
|
&utxos_spent_by_block,
|
||||||
|
finalized.deferred_balance,
|
||||||
|
)?,
|
||||||
|
)?,
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,17 +53,18 @@ pub struct Chain {
|
||||||
//
|
//
|
||||||
/// The last height this chain forked at. Diagnostics only.
|
/// 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
|
/// This field is only used for metrics. It is not consensus-critical, and it is not checked for
|
||||||
/// for equality.
|
/// equality.
|
||||||
///
|
///
|
||||||
/// We keep the same last fork height in both sides of a clone, because every new block clones
|
/// We keep the same last fork height in both sides of a clone, because every new block clones a
|
||||||
/// a chain, even if it's just growing that chain.
|
/// 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>,
|
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`].
|
/// The internal state of [`Chain`].
|
||||||
|
@ -199,12 +200,11 @@ pub struct ChainInner {
|
||||||
|
|
||||||
// Chain Pools
|
// Chain Pools
|
||||||
//
|
//
|
||||||
/// The chain value pool balances of the tip of this [`Chain`],
|
/// The chain value pool balances of the tip of this [`Chain`], including the block value pool
|
||||||
/// including the block value pool changes from all finalized blocks,
|
/// changes from all finalized blocks, and the non-finalized blocks in this chain.
|
||||||
/// and the non-finalized blocks in this chain.
|
|
||||||
///
|
///
|
||||||
/// When a new chain is created from the finalized tip,
|
/// When a new chain is created from the finalized tip, it is initialized with the finalized tip
|
||||||
/// it is initialized with the finalized tip chain value pool balances.
|
/// chain value pool balances.
|
||||||
pub(crate) chain_value_pools: ValueBalance<NonNegative>,
|
pub(crate) chain_value_pools: ValueBalance<NonNegative>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -420,7 +420,7 @@ proptest! {
|
||||||
// which is not included in the UTXO set
|
// which is not included in the UTXO set
|
||||||
if block.height > block::Height(0) {
|
if block.height > block::Height(0) {
|
||||||
let utxos = &block.new_outputs.iter().map(|(k, ordered_utxo)| (*k, ordered_utxo.utxo.clone())).collect();
|
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;
|
expected_finalized_value_pool += *block_value_pool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -447,7 +447,7 @@ proptest! {
|
||||||
let mut expected_non_finalized_value_pool = Ok(expected_finalized_value_pool?);
|
let mut expected_non_finalized_value_pool = Ok(expected_finalized_value_pool?);
|
||||||
for block in non_finalized_blocks {
|
for block in non_finalized_blocks {
|
||||||
let utxos = block.new_outputs.clone();
|
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;
|
expected_non_finalized_value_pool += *block_value_pool;
|
||||||
|
|
||||||
let result_receiver = state_service.queue_and_commit_to_non_finalized_state(block.clone());
|
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)| {
|
.prop_map(|(network, mut blocks, finalized_blocks_count)| {
|
||||||
let non_finalized_blocks = blocks.split_off(finalized_blocks_count);
|
let non_finalized_blocks = blocks.split_off(finalized_blocks_count);
|
||||||
let finalized_blocks: Vec<_> = blocks
|
let finalized_blocks: Vec<_> =
|
||||||
.into_iter()
|
blocks.into_iter().map(CheckpointVerifiedBlock).collect();
|
||||||
.map(|prepared_block| CheckpointVerifiedBlock::from(prepared_block.block))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
(
|
(
|
||||||
network,
|
network,
|
||||||
|
|
Loading…
Reference in New Issue