zebra/zebra-chain/src/block/arbitrary.rs

662 lines
24 KiB
Rust
Raw Normal View History

//! Randomised property testing for [`Block`]s.
use proptest::{
arbitrary::{any, Arbitrary},
prelude::*,
};
use std::{collections::HashMap, sync::Arc};
use crate::{
amount::NonNegative,
block,
fmt::SummaryDebug,
parameters::{
Network,
NetworkUpgrade::{self, *},
GENESIS_PREVIOUS_BLOCK_HASH,
},
serialization,
transaction::arbitrary::MAX_ARBITRARY_ITEMS,
transparent::{
new_transaction_ordered_outputs, CoinbaseSpendRestriction,
MIN_TRANSPARENT_COINBASE_MATURITY,
},
work::{difficulty::CompactDifficulty, equihash},
};
use super::*;
/// The chain length for most zebra-chain proptests.
///
/// Most generated chains will contain transparent spends at or before this height.
///
/// This height was chosen a tradeoff between chains with no spends,
/// and chains which spend outputs created by previous spends.
///
/// The raw probability of having no spends during a test run is:
/// ```text
/// shielded_input = shielded_pool_count / pool_count
/// expected_transactions = expected_inputs = MAX_ARBITRARY_ITEMS/2
/// shielded_input^(expected_transactions * expected_inputs * (PREVOUTS_CHAIN_HEIGHT - 1))
/// ```
///
/// This probability is approximately 3%. However, proptest generation and
/// minimisation strategies can create additional chains with no transparent spends.
///
/// To increase the proportion of test runs with proptest spends, increase `PREVOUTS_CHAIN_HEIGHT`.
pub const PREVOUTS_CHAIN_HEIGHT: usize = 4;
/// The chain length for most zebra-state proptests.
///
/// Most generated chains will contain transparent spends at or before this height.
///
/// This height was chosen as a tradeoff between chains with no transparent spends,
/// and chains which spend outputs created by previous spends.
///
/// See [`block::arbitrary::PREVOUTS_CHAIN_HEIGHT`] for details.
pub const MAX_PARTIAL_CHAIN_BLOCKS: usize =
MIN_TRANSPARENT_COINBASE_MATURITY as usize + PREVOUTS_CHAIN_HEIGHT;
2021-04-22 20:19:33 -07:00
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
/// The configuration data for proptest when generating arbitrary chains
pub struct LedgerState {
/// The height of the generated block, or the start height of the generated chain.
///
/// To get the network upgrade, use the `network_upgrade` method.
///
/// If `network_upgrade_override` is not set, the network upgrade is derived
/// from the `height` and `network`.
pub height: Height,
/// The network to generate fake blocks for.
2021-04-22 20:19:33 -07:00
pub network: Network,
/// Overrides the network upgrade calculated from `height` and `network`.
///
/// To get the network upgrade, use the `network_upgrade` method.
network_upgrade_override: Option<NetworkUpgrade>,
/// Overrides the previous block hashes in blocks generated by this ledger.
previous_block_hash_override: Option<block::Hash>,
/// Regardless of tip height and network, every transaction is this version.
transaction_version_override: Option<u32>,
/// Every V5 and later transaction has a valid `network_upgrade` field.
///
/// If `false`, zero or more transactions may have invalid network upgrades.
transaction_has_valid_network_upgrade: bool,
/// Generate coinbase transactions.
///
/// In a block or transaction vector, make the first transaction a coinbase
/// transaction.
///
/// For an individual transaction, make the transaction a coinbase
/// transaction.
pub(crate) has_coinbase: bool,
}
/// Overrides for arbitrary [`LedgerState`]s.
#[derive(Debug, Clone, Copy)]
pub struct LedgerStateOverride {
/// Every chain starts at this block. Single blocks have this height.
pub height_override: Option<Height>,
/// Every chain starts with a block with this previous block hash.
/// Single blocks have this previous block hash.
pub previous_block_hash_override: Option<block::Hash>,
/// Regardless of tip height and network, every block has features from this
/// network upgrade.
pub network_upgrade_override: Option<NetworkUpgrade>,
/// Regardless of tip height and network, every transaction is this version.
pub transaction_version_override: Option<u32>,
/// Every V5 and later transaction has a valid `network_upgrade` field.
///
/// If `false`, zero or more transactions may have invalid network upgrades.
pub transaction_has_valid_network_upgrade: bool,
/// Every block has exactly one coinbase transaction.
/// Transactions are always coinbase transactions.
pub always_has_coinbase: bool,
2021-04-22 20:19:33 -07:00
}
impl LedgerState {
/// Returns the default strategy for creating arbitrary `LedgerState`s.
pub fn default_strategy() -> BoxedStrategy<Self> {
Self::arbitrary_with(LedgerStateOverride::default())
}
/// Returns a strategy for creating arbitrary `LedgerState`s, without any
/// overrides.
pub fn no_override_strategy() -> BoxedStrategy<Self> {
Self::arbitrary_with(LedgerStateOverride {
height_override: None,
previous_block_hash_override: None,
network_upgrade_override: None,
transaction_version_override: None,
transaction_has_valid_network_upgrade: false,
always_has_coinbase: false,
})
}
/// Returns a strategy for creating `LedgerState`s with features from
/// `network_upgrade_override`.
///
/// These featues ignore the actual tip height and network.
pub fn network_upgrade_strategy(
network_upgrade_override: NetworkUpgrade,
transaction_version_override: impl Into<Option<u32>>,
transaction_has_valid_network_upgrade: bool,
) -> BoxedStrategy<Self> {
Self::arbitrary_with(LedgerStateOverride {
height_override: None,
previous_block_hash_override: None,
network_upgrade_override: Some(network_upgrade_override),
transaction_version_override: transaction_version_override.into(),
transaction_has_valid_network_upgrade,
always_has_coinbase: false,
})
}
/// Returns a strategy for creating `LedgerState`s that always have coinbase
/// transactions.
///
/// Also applies `network_upgrade_override`, if present.
pub fn coinbase_strategy(
network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
transaction_version_override: impl Into<Option<u32>>,
transaction_has_valid_network_upgrade: bool,
) -> BoxedStrategy<Self> {
Self::arbitrary_with(LedgerStateOverride {
height_override: None,
previous_block_hash_override: None,
network_upgrade_override: network_upgrade_override.into(),
transaction_version_override: transaction_version_override.into(),
transaction_has_valid_network_upgrade,
always_has_coinbase: true,
})
}
/// Returns a strategy for creating `LedgerState`s that start with a genesis
/// block.
///
/// These strategies also have coinbase transactions, and an optional network
/// upgrade override.
///
/// Use the `Genesis` network upgrade to get a random genesis block, with
/// Zcash genesis features.
pub fn genesis_strategy(
network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
transaction_version_override: impl Into<Option<u32>>,
transaction_has_valid_network_upgrade: bool,
) -> BoxedStrategy<Self> {
Self::arbitrary_with(LedgerStateOverride {
height_override: Some(Height(0)),
previous_block_hash_override: Some(GENESIS_PREVIOUS_BLOCK_HASH),
network_upgrade_override: network_upgrade_override.into(),
transaction_version_override: transaction_version_override.into(),
transaction_has_valid_network_upgrade,
always_has_coinbase: true,
})
}
/// Returns a strategy for creating `LedgerState`s that start at `height`.
///
/// These strategies also have coinbase transactions, and an optional network
/// upgrade override.
pub fn height_strategy(
height: Height,
network_upgrade_override: impl Into<Option<NetworkUpgrade>>,
transaction_version_override: impl Into<Option<u32>>,
transaction_has_valid_network_upgrade: bool,
) -> BoxedStrategy<Self> {
Self::arbitrary_with(LedgerStateOverride {
height_override: Some(height),
previous_block_hash_override: None,
network_upgrade_override: network_upgrade_override.into(),
transaction_version_override: transaction_version_override.into(),
transaction_has_valid_network_upgrade,
always_has_coinbase: true,
})
}
/// Returns the network upgrade for this ledger state.
///
/// If `network_upgrade_override` is set, it replaces the upgrade calculated
/// using `height` and `network`.
pub fn network_upgrade(&self) -> NetworkUpgrade {
if let Some(network_upgrade_override) = self.network_upgrade_override {
network_upgrade_override
} else {
NetworkUpgrade::current(self.network, self.height)
2021-04-22 20:19:33 -07:00
}
}
/// Returns the transaction version override.
pub fn transaction_version_override(&self) -> Option<u32> {
self.transaction_version_override
}
/// Returns `true` if all transactions have valid network upgrade fields.
///
/// If `false`, some transactions have invalid network upgrades.
pub fn transaction_has_valid_network_upgrade(&self) -> bool {
self.transaction_has_valid_network_upgrade
}
}
impl Default for LedgerState {
fn default() -> Self {
// TODO: stop having a default network
let default_network = Network::default();
let default_override = LedgerStateOverride::default();
let most_recent_nu = NetworkUpgrade::current(default_network, Height::MAX);
let most_recent_activation_height =
most_recent_nu.activation_height(default_network).unwrap();
LedgerState {
height: most_recent_activation_height,
network: default_network,
network_upgrade_override: default_override.network_upgrade_override,
previous_block_hash_override: default_override.previous_block_hash_override,
transaction_version_override: default_override.transaction_version_override,
transaction_has_valid_network_upgrade: default_override
.transaction_has_valid_network_upgrade,
has_coinbase: default_override.always_has_coinbase,
}
}
2021-04-22 20:19:33 -07:00
}
impl Default for LedgerStateOverride {
2021-04-22 20:19:33 -07:00
fn default() -> Self {
let default_network = Network::default();
// TODO: dynamically select any future network upgrade (#1974)
let nu5_activation_height = Nu5.activation_height(default_network);
let nu5_override = if nu5_activation_height.is_some() {
None
} else {
Some(Nu5)
};
2021-04-22 20:19:33 -07:00
LedgerStateOverride {
height_override: None,
previous_block_hash_override: None,
network_upgrade_override: nu5_override,
transaction_version_override: None,
transaction_has_valid_network_upgrade: false,
always_has_coinbase: true,
2021-04-22 20:19:33 -07:00
}
}
}
impl Arbitrary for LedgerState {
type Parameters = LedgerStateOverride;
/// Generate an arbitrary [`LedgerState`].
///
/// The default strategy arbitrarily skips some coinbase transactions, and
/// has an arbitrary start height. To override, use a specific [`LegderState`]
/// strategy method.
fn arbitrary_with(ledger_override: Self::Parameters) -> Self::Strategy {
(
any::<Height>(),
any::<Network>(),
any::<bool>(),
any::<bool>(),
)
.prop_map(
move |(height, network, transaction_has_valid_network_upgrade, has_coinbase)| {
LedgerState {
height: ledger_override.height_override.unwrap_or(height),
network,
network_upgrade_override: ledger_override.network_upgrade_override,
previous_block_hash_override: ledger_override.previous_block_hash_override,
transaction_version_override: ledger_override.transaction_version_override,
transaction_has_valid_network_upgrade: ledger_override
.transaction_has_valid_network_upgrade
|| transaction_has_valid_network_upgrade,
has_coinbase: ledger_override.always_has_coinbase || has_coinbase,
}
},
)
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
2020-10-02 15:51:51 -07:00
impl Arbitrary for Block {
type Parameters = LedgerState;
fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
let transactions_strategy =
(1..MAX_ARBITRARY_ITEMS).prop_flat_map(move |transaction_count| {
Transaction::vec_strategy(ledger_state, transaction_count)
});
// TODO: if needed, fixup:
// - history and authorizing data commitments
// - the transaction merkle root
2020-10-02 15:51:51 -07:00
(Header::arbitrary_with(ledger_state), transactions_strategy)
.prop_map(move |(header, transactions)| Self {
header,
transactions,
2020-10-02 15:51:51 -07:00
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
/// Skip checking transparent coinbase spends in [`Block::partial_chain_strategy`].
#[allow(clippy::result_unit_err)]
pub fn allow_all_transparent_coinbase_spends(
_: transparent::OutPoint,
_: transparent::CoinbaseSpendRestriction,
_: transparent::Utxo,
) -> Result<(), ()> {
Ok(())
}
2020-10-02 15:51:51 -07:00
impl Block {
/// Returns a strategy for creating vectors of blocks with increasing height.
///
/// Each vector is `count` blocks long.
///
/// `check_transparent_coinbase_spend` is used to check if
/// transparent coinbase UTXOs are valid, before using them in blocks.
/// Use [`allow_all_transparent_coinbase_spends`] to disable this check.
pub fn partial_chain_strategy<F, T, E>(
mut current: LedgerState,
2020-10-02 15:51:51 -07:00
count: usize,
check_transparent_coinbase_spend: F,
) -> BoxedStrategy<SummaryDebug<Vec<Arc<Self>>>>
where
F: Fn(
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
transparent::Utxo,
) -> Result<T, E>
+ Copy
+ 'static,
{
2020-10-02 15:51:51 -07:00
let mut vec = Vec::with_capacity(count);
// generate block strategies with the correct heights
2020-10-02 15:51:51 -07:00
for _ in 0..count {
vec.push((Just(current.height), Block::arbitrary_with(current)));
current.height.0 += 1;
2020-10-02 15:51:51 -07:00
}
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
// after the vec strategy generates blocks, fixup invalid parts of the blocks
vec.prop_map(move |mut vec| {
let mut previous_block_hash = None;
let mut utxos = HashMap::new();
let mut chain_value_pools = ValueBalance::zero();
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
for (height, block) in vec.iter_mut() {
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
// fixup the previous block hash
if let Some(previous_block_hash) = previous_block_hash {
block.header.previous_block_hash = previous_block_hash;
}
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
let mut new_transactions = Vec::new();
for (tx_index_in_block, transaction) in block.transactions.drain(..).enumerate() {
if let Some(transaction) = fix_generated_transaction(
(*transaction).clone(),
tx_index_in_block,
*height,
&mut chain_value_pools,
&mut utxos,
check_transparent_coinbase_spend,
) {
Reject UTXO double spends (#2511) * Reject transparent output double-spends Check that transparent spends use unspent outputs from: * earlier transaction in the same block, * earlier blocks in the parent non-finalized chain, or * the finalized state. * Fixup UTXOs in proptests * Add a comment * Clarify a consensus rule implementation * Fix an incorrect comment * Fix an incorrect error message * Clarify a comment * Document `unspent_utxos` * Simplify the UTXO check Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Further simplify and fix the UTXO check - split each error case into a separate check - combine `contains` and `insert` - add a missing check against the non-finalized unspent UTXOs - rename arguments and edit error strings for clarity * Share test methods between check test modules * Make some chain fields available to tests * Make error field names consistent with transparent::Input * WIP: Add tests for UTXO double-spends - accept output and spend in the same block - accept output and spend in a later block - reject output and double-spend all in the same block - reject output then double-spend in a later block - reject output, spend, then double-spend all in different blocks * Use Extend rather than multiple pushes Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com> * Use Extend for more pushes * Limit the number of proptest cases, to speed up tests * Test rejection of UTXOs that were never in the chain * Test rejection of spends of later transactions in the same block Co-authored-by: Janito Vaqueiro Ferreira Filho <janito.vff@gmail.com>
2021-07-22 16:40:15 -07:00
new_transactions.push(Arc::new(transaction));
}
}
// delete invalid transactions
block.transactions = new_transactions;
Check remaining transaction value & make value balance signs match the spec (#2566) * Make Amount arithmetic more generic To modify generated amounts, we need some extra operations on `Amount`. We also need to extend existing operations to both `NonNegative` and `NegativeAllowed` amounts. * Add a constrain method for ValueBalance * Derive Eq for ValueBalance * impl Neg for ValueBalance * Make some Amount arithmetic expectations explicit * Explain why we use i128 for multiplication And expand the overflow error details. * Expand Amount::sum error details * Make amount::Error field order consistent * Rename an amount::Error variant to Constraint, so it's clearer * Add specific pool variants to ValueBalanceError * Update coinbase remaining value consensus rule comment This consensus rule was updated recently to include coinbase transactions, but Zebra doesn't check block subsidy or miner fees yet. * Add test methods for modifying transparent values and shielded value balances * Temporarily set values and value balances to zero in proptests In both generated chains and proptests that construct their own transactions. Using zero values reduces value calculation and value check test coverage. A future change will use non-zero values, and fix them so the check passes. * Add extra fields to remaining transaction value errors * Swap the transparent value balance sign to match shielded value balances This makes the signs of all the chain value pools consistent. * Use a NonNegative constraint for transparent values This fix: * makes the type signature match the consensus rules * avoids having to write code to handle negative values * Allocate total generated transaction input value to outputs If there isn't enough input value for an output, set it to zero. Temporarily reduce all generated values to avoid overflow. (We'll remove this workaround when we calculate chain value balances.) * Consistently use ValueBalanceError for ValueBalances * Make the value balance signs match the spec And rename and document methods so their signs are clearer. * Convert amount::Errors to specific pool ValueBalanceErrors * Move some error changes to the next PR * Add extra info to remaining transaction value errors (#2585) * Distinguish between overflow and negative remaining transaction value errors And make some error types cloneable. * Add methods for updating chain value pools (#2586) * Move amount::test to amount::tests:vectors * Make ValueBalance traits more consistent with Amount - implement Add and Sub variants with Result and Assign - derive Hash * Clarify some comments and expects * Create ValueBalance update methods for blocks and transactions Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
2021-08-09 10:22:26 -07:00
// TODO: if needed, fixup after modifying the block:
// - history and authorizing data commitments
// - the transaction merkle root
// now that we've made all the changes, calculate our block hash,
// so the next block can use it
previous_block_hash = Some(block.hash());
}
SummaryDebug(
vec.into_iter()
.map(|(_height, block)| Arc::new(block))
.collect(),
)
})
.boxed()
2020-10-02 15:51:51 -07:00
}
}
/// Fix `transaction` so it obeys more consensus rules.
///
/// Spends [`OutPoint`]s from `utxos`, and adds newly created outputs.
///
/// If the transaction can't be fixed, returns `None`.
pub fn fix_generated_transaction<F, T, E>(
mut transaction: Transaction,
tx_index_in_block: usize,
height: Height,
chain_value_pools: &mut ValueBalance<NonNegative>,
utxos: &mut HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
check_transparent_coinbase_spend: F,
) -> Option<Transaction>
where
F: Fn(
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
transparent::Utxo,
) -> Result<T, E>
+ Copy
+ 'static,
{
let mut spend_restriction = transaction.coinbase_spend_restriction(height);
let mut new_inputs = Vec::new();
Check remaining transaction value & make value balance signs match the spec (#2566) * Make Amount arithmetic more generic To modify generated amounts, we need some extra operations on `Amount`. We also need to extend existing operations to both `NonNegative` and `NegativeAllowed` amounts. * Add a constrain method for ValueBalance * Derive Eq for ValueBalance * impl Neg for ValueBalance * Make some Amount arithmetic expectations explicit * Explain why we use i128 for multiplication And expand the overflow error details. * Expand Amount::sum error details * Make amount::Error field order consistent * Rename an amount::Error variant to Constraint, so it's clearer * Add specific pool variants to ValueBalanceError * Update coinbase remaining value consensus rule comment This consensus rule was updated recently to include coinbase transactions, but Zebra doesn't check block subsidy or miner fees yet. * Add test methods for modifying transparent values and shielded value balances * Temporarily set values and value balances to zero in proptests In both generated chains and proptests that construct their own transactions. Using zero values reduces value calculation and value check test coverage. A future change will use non-zero values, and fix them so the check passes. * Add extra fields to remaining transaction value errors * Swap the transparent value balance sign to match shielded value balances This makes the signs of all the chain value pools consistent. * Use a NonNegative constraint for transparent values This fix: * makes the type signature match the consensus rules * avoids having to write code to handle negative values * Allocate total generated transaction input value to outputs If there isn't enough input value for an output, set it to zero. Temporarily reduce all generated values to avoid overflow. (We'll remove this workaround when we calculate chain value balances.) * Consistently use ValueBalanceError for ValueBalances * Make the value balance signs match the spec And rename and document methods so their signs are clearer. * Convert amount::Errors to specific pool ValueBalanceErrors * Move some error changes to the next PR * Add extra info to remaining transaction value errors (#2585) * Distinguish between overflow and negative remaining transaction value errors And make some error types cloneable. * Add methods for updating chain value pools (#2586) * Move amount::test to amount::tests:vectors * Make ValueBalance traits more consistent with Amount - implement Add and Sub variants with Result and Assign - derive Hash * Clarify some comments and expects * Create ValueBalance update methods for blocks and transactions Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
2021-08-09 10:22:26 -07:00
let mut spent_outputs = HashMap::new();
// fixup the transparent spends
for mut input in transaction.inputs().to_vec().into_iter() {
if input.outpoint().is_some() {
// the transparent chain value pool is the sum of unspent UTXOs,
// so we don't need to check it separately, because we only spend unspent UTXOs
if let Some(selected_outpoint) = find_valid_utxo_for_spend(
&mut transaction,
&mut spend_restriction,
height,
utxos,
check_transparent_coinbase_spend,
) {
input.set_outpoint(selected_outpoint);
new_inputs.push(input);
Check remaining transaction value & make value balance signs match the spec (#2566) * Make Amount arithmetic more generic To modify generated amounts, we need some extra operations on `Amount`. We also need to extend existing operations to both `NonNegative` and `NegativeAllowed` amounts. * Add a constrain method for ValueBalance * Derive Eq for ValueBalance * impl Neg for ValueBalance * Make some Amount arithmetic expectations explicit * Explain why we use i128 for multiplication And expand the overflow error details. * Expand Amount::sum error details * Make amount::Error field order consistent * Rename an amount::Error variant to Constraint, so it's clearer * Add specific pool variants to ValueBalanceError * Update coinbase remaining value consensus rule comment This consensus rule was updated recently to include coinbase transactions, but Zebra doesn't check block subsidy or miner fees yet. * Add test methods for modifying transparent values and shielded value balances * Temporarily set values and value balances to zero in proptests In both generated chains and proptests that construct their own transactions. Using zero values reduces value calculation and value check test coverage. A future change will use non-zero values, and fix them so the check passes. * Add extra fields to remaining transaction value errors * Swap the transparent value balance sign to match shielded value balances This makes the signs of all the chain value pools consistent. * Use a NonNegative constraint for transparent values This fix: * makes the type signature match the consensus rules * avoids having to write code to handle negative values * Allocate total generated transaction input value to outputs If there isn't enough input value for an output, set it to zero. Temporarily reduce all generated values to avoid overflow. (We'll remove this workaround when we calculate chain value balances.) * Consistently use ValueBalanceError for ValueBalances * Make the value balance signs match the spec And rename and document methods so their signs are clearer. * Convert amount::Errors to specific pool ValueBalanceErrors * Move some error changes to the next PR * Add extra info to remaining transaction value errors (#2585) * Distinguish between overflow and negative remaining transaction value errors And make some error types cloneable. * Add methods for updating chain value pools (#2586) * Move amount::test to amount::tests:vectors * Make ValueBalance traits more consistent with Amount - implement Add and Sub variants with Result and Assign - derive Hash * Clarify some comments and expects * Create ValueBalance update methods for blocks and transactions Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
2021-08-09 10:22:26 -07:00
let spent_utxo = utxos
.remove(&selected_outpoint)
.expect("selected outpoint must have a UTXO");
spent_outputs.insert(selected_outpoint, spent_utxo.utxo.output);
}
// otherwise, drop the invalid input, because it has no valid UTXOs to spend
} else {
// preserve coinbase inputs
new_inputs.push(input.clone());
}
}
// delete invalid inputs
*transaction.inputs_mut() = new_inputs;
let (_remaining_transaction_value, new_chain_value_pools) = transaction
.fix_chain_value_pools(*chain_value_pools, &spent_outputs)
.expect("value fixes produce valid chain value pools and remaining transaction values");
Check remaining transaction value & make value balance signs match the spec (#2566) * Make Amount arithmetic more generic To modify generated amounts, we need some extra operations on `Amount`. We also need to extend existing operations to both `NonNegative` and `NegativeAllowed` amounts. * Add a constrain method for ValueBalance * Derive Eq for ValueBalance * impl Neg for ValueBalance * Make some Amount arithmetic expectations explicit * Explain why we use i128 for multiplication And expand the overflow error details. * Expand Amount::sum error details * Make amount::Error field order consistent * Rename an amount::Error variant to Constraint, so it's clearer * Add specific pool variants to ValueBalanceError * Update coinbase remaining value consensus rule comment This consensus rule was updated recently to include coinbase transactions, but Zebra doesn't check block subsidy or miner fees yet. * Add test methods for modifying transparent values and shielded value balances * Temporarily set values and value balances to zero in proptests In both generated chains and proptests that construct their own transactions. Using zero values reduces value calculation and value check test coverage. A future change will use non-zero values, and fix them so the check passes. * Add extra fields to remaining transaction value errors * Swap the transparent value balance sign to match shielded value balances This makes the signs of all the chain value pools consistent. * Use a NonNegative constraint for transparent values This fix: * makes the type signature match the consensus rules * avoids having to write code to handle negative values * Allocate total generated transaction input value to outputs If there isn't enough input value for an output, set it to zero. Temporarily reduce all generated values to avoid overflow. (We'll remove this workaround when we calculate chain value balances.) * Consistently use ValueBalanceError for ValueBalances * Make the value balance signs match the spec And rename and document methods so their signs are clearer. * Convert amount::Errors to specific pool ValueBalanceErrors * Move some error changes to the next PR * Add extra info to remaining transaction value errors (#2585) * Distinguish between overflow and negative remaining transaction value errors And make some error types cloneable. * Add methods for updating chain value pools (#2586) * Move amount::test to amount::tests:vectors * Make ValueBalance traits more consistent with Amount - implement Add and Sub variants with Result and Assign - derive Hash * Clarify some comments and expects * Create ValueBalance update methods for blocks and transactions Co-authored-by: Alfredo Garcia <oxarbitrage@gmail.com>
2021-08-09 10:22:26 -07:00
// TODO: if needed, check output count here as well
if transaction.has_transparent_or_shielded_inputs() {
// consensus rule: skip genesis created UTXOs
// Zebra implementation: also skip shielded chain value pool changes
if height > Height(0) {
*chain_value_pools = new_chain_value_pools;
utxos.extend(new_transaction_ordered_outputs(
&transaction,
transaction.hash(),
tx_index_in_block,
height,
));
}
Some(transaction)
} else {
None
}
}
/// Find a valid [`OutPoint`] in `utxos` to spend in `transaction`.
///
/// Modifies `transaction` and updates `spend_restriction` if needed.
///
/// If there is no valid output, or many search attempts have failed, returns `None`.
pub fn find_valid_utxo_for_spend<F, T, E>(
transaction: &mut Transaction,
spend_restriction: &mut CoinbaseSpendRestriction,
spend_height: Height,
utxos: &HashMap<transparent::OutPoint, transparent::OrderedUtxo>,
check_transparent_coinbase_spend: F,
) -> Option<transparent::OutPoint>
where
F: Fn(
transparent::OutPoint,
transparent::CoinbaseSpendRestriction,
transparent::Utxo,
) -> Result<T, E>
+ Copy
+ 'static,
{
let has_shielded_outputs = transaction.has_shielded_outputs();
let delete_transparent_outputs = CoinbaseSpendRestriction::OnlyShieldedOutputs { spend_height };
let mut attempts: usize = 0;
// choose an arbitrary spendable UTXO, in hash set order
while let Some((candidate_outpoint, candidate_utxo)) = utxos.iter().next() {
let candidate_utxo = candidate_utxo.clone().utxo;
attempts += 1;
// Avoid O(n^2) algorithmic complexity by giving up early,
// rather than exhausively checking the entire UTXO set
if attempts > 100 {
return None;
}
// try the utxo as-is, then try it with deleted transparent outputs
if check_transparent_coinbase_spend(
*candidate_outpoint,
*spend_restriction,
candidate_utxo.clone(),
)
.is_ok()
{
return Some(*candidate_outpoint);
} else if has_shielded_outputs
&& check_transparent_coinbase_spend(
*candidate_outpoint,
delete_transparent_outputs,
candidate_utxo.clone(),
)
.is_ok()
{
*transaction.outputs_mut() = Vec::new();
*spend_restriction = delete_transparent_outputs;
return Some(*candidate_outpoint);
}
}
None
}
impl Arbitrary for Commitment {
type Parameters = ();
fn arbitrary_with(_args: ()) -> Self::Strategy {
(any::<[u8; 32]>(), any::<Network>(), any::<Height>())
.prop_map(|(commitment_bytes, network, block_height)| {
if block_height == Heartwood.activation_height(network).unwrap() {
Commitment::ChainHistoryActivationReserved
} else {
Commitment::from_bytes(commitment_bytes, network, block_height)
.expect("unexpected failure in from_bytes parsing")
}
})
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}
impl Arbitrary for Header {
type Parameters = LedgerState;
fn arbitrary_with(ledger_state: Self::Parameters) -> Self::Strategy {
(
// version is interpreted as i32 in the spec, so we are limited to i32::MAX here
(4u32..(i32::MAX as u32)),
any::<Hash>(),
any::<merkle::Root>(),
any::<[u8; 32]>(),
serialization::arbitrary::datetime_u32(),
any::<CompactDifficulty>(),
any::<[u8; 32]>(),
any::<equihash::Solution>(),
)
.prop_map(
move |(
version,
mut previous_block_hash,
merkle_root,
commitment_bytes,
time,
difficulty_threshold,
nonce,
solution,
)| {
if let Some(previous_block_hash_override) =
ledger_state.previous_block_hash_override
{
previous_block_hash = previous_block_hash_override;
} else if ledger_state.height == Height(0) {
previous_block_hash = GENESIS_PREVIOUS_BLOCK_HASH;
}
Header {
version,
previous_block_hash,
merkle_root,
commitment_bytes,
time,
difficulty_threshold,
nonce,
solution,
}
},
)
.boxed()
}
type Strategy = BoxedStrategy<Self>;
}