#![allow(clippy::indexing_slicing)]
use std::sync::Arc;
use tari_common::configuration::Network;
use tari_common_types::tari_address::TariAddress;
use tari_node_components::blocks::BlockValidationError;
use tari_script::{push_pubkey_script, script};
use tari_test_utils::unpack_enum;
use tari_transaction_components::{
CoinbaseBuilder,
aggregated_body::AggregateBody,
consensus::ConsensusConstantsBuilder,
crypto_factories::CryptoFactories,
key_manager::{TariKeyId, TransactionKeyManagerInterface},
tari_amount::{T, uT},
tari_proof_of_work::Difficulty,
test_helpers::schema_to_transaction,
transaction_components::{
EncryptedData,
MemoField,
RangeProofType,
TransactionError,
encrypted_data::STATIC_ENCRYPTED_DATA_SIZE_TOTAL,
},
txn_schema,
validation::AggregatedBodyValidationError,
};
use tokio::time::Instant;
use super::BlockBodyFullValidator;
use crate::{
block_spec,
consensus::BaseNodeConsensusManager,
test_helpers::{BlockSpec, blockchain::TestBlockchain},
validation::{BlockBodyValidator, ValidationError},
};
fn setup_with_rules(
rules: BaseNodeConsensusManager,
check_rangeproof: bool,
) -> (TestBlockchain, BlockBodyFullValidator) {
let blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyFullValidator::new(rules, check_rangeproof);
(blockchain, validator)
}
fn setup(check_rangeproof: bool) -> (TestBlockchain, BlockBodyFullValidator) {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_coinbase_lockheight(0)
.with_max_block_transaction_weight(127_795)
.build(),
)
.build()
.unwrap();
setup_with_rules(rules, check_rangeproof)
}
#[tokio::test]
async fn it_passes_if_large_output_block_is_valid() {
let (mut blockchain, validator) = setup(false);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let mut outs = Vec::new();
for _ in 0..498 {
outs.push(9000 * uT);
}
let schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: outs);
let (txs, _outputs) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (chain_block, _coinbase_b) = blockchain.create_next_tip(block_spec!("B",parent: "A", transactions: txs));
let (mut block, mmr_roots) = blockchain
.db()
.calculate_mmr_roots(chain_block.block().clone())
.unwrap();
block.header.input_mr = mmr_roots.input_mr;
block.header.block_output_mr = mmr_roots.block_output_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
let txn = blockchain.db().db_read_access().unwrap();
let start = Instant::now();
assert!(validator.validate_body(&*txn, &block).is_ok());
let finished = start.elapsed();
println!("finished validating in: {}", finished.as_millis());
}
#[tokio::test]
async fn it_validates_when_a_coinbase_is_spent() {
let (mut blockchain, validator) = setup(false);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![9000 * uT]);
let (txs, _outputs) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (chain_block, _coinbase_b) = blockchain.create_next_tip(block_spec!("B",parent: "A", transactions: txs));
let (mut block, mmr_roots) = blockchain
.db()
.calculate_mmr_roots(chain_block.block().clone())
.unwrap();
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.block_output_mr = mmr_roots.block_output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
let txn = blockchain.db().db_read_access().unwrap();
assert!(validator.validate_body(&*txn, &block).is_ok());
}
#[tokio::test]
async fn it_passes_if_large_block_is_valid() {
let (mut blockchain, validator) = setup(false);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T, 5 * T]);
let (txs, outputs) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (_block, _coinbase_b) = blockchain
.append(block_spec!("B", parent: "A", transactions: txs))
.unwrap();
let mut schemas = Vec::new();
for output in outputs {
let new_schema = txn_schema!(from: vec![output.clone()], to: vec![1 * T, 1 * T, 1 * T, 1 * T]);
schemas.push(new_schema);
}
let (txs, _) = schema_to_transaction(&schemas, &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (chain_block, _coinbase_c) = blockchain.create_next_tip(block_spec!("C",parent: "B", transactions: txs));
let (mut block, mmr_roots) = blockchain
.db()
.calculate_mmr_roots(chain_block.block().clone())
.unwrap();
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.block_output_mr = mmr_roots.block_output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
let txn = blockchain.db().db_read_access().unwrap();
let start = Instant::now();
validator.validate_body(&*txn, &block).unwrap();
let finished = start.elapsed();
println!("finished validating in: {}", finished.as_millis());
}
#[tokio::test]
async fn it_passes_if_block_is_valid() {
let (mut blockchain, validator) = setup(true);
let (chain_block, _) = blockchain.create_next_tip(BlockSpec::default());
let (mut block, mmr_roots) = blockchain
.db()
.calculate_mmr_roots(chain_block.block().clone())
.unwrap();
block.header.input_mr = mmr_roots.input_mr;
block.header.output_mr = mmr_roots.output_mr;
block.header.block_output_mr = mmr_roots.block_output_mr;
block.header.output_smt_size = mmr_roots.output_smt_size;
block.header.kernel_mr = mmr_roots.kernel_mr;
block.header.kernel_mmr_size = mmr_roots.kernel_mmr_size;
block.header.validator_node_mr = mmr_roots.validator_node_mr;
block.header.validator_node_size = mmr_roots.validator_node_size;
let txn = blockchain.db().db_read_access().unwrap();
assert!(validator.validate_body(&*txn, &block).is_ok());
}
#[tokio::test]
async fn it_checks_the_coinbase_reward() {
let (mut blockchain, validator) = setup(true);
let (block, _) = blockchain.create_chained_block(block_spec!("A", parent: "GB", reward: 10 * T, ));
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
println!("err {err:?}");
assert!(matches!(
err,
ValidationError::BlockError(BlockValidationError::TransactionError(
TransactionError::InvalidCoinbase
))
));
}
#[tokio::test]
async fn it_allows_multiple_coinbases() {
let (mut blockchain, validator) = setup(true);
let (mut block, coinbase) = blockchain.create_unmined_block(block_spec!("A1", parent: "GB"));
let commitment_mask_key = blockchain.km.get_random_key(None, None).unwrap();
let wallet_payment_address = TariAddress::default();
let (_, coinbase_output) = CoinbaseBuilder::new(blockchain.km.clone())
.with_block_height(1)
.with_fees(0.into())
.with_commitment_mask_id(commitment_mask_key.key_id.clone())
.with_encryption_key_id(TariKeyId::default())
.with_sender_offset_key_id(TariKeyId::default())
.with_script_key_id(TariKeyId::default())
.with_script(push_pubkey_script(wallet_payment_address.public_spend_key()))
.with_range_proof_type(RangeProofType::RevealedValue)
.build_with_reward(
blockchain.rules().consensus_constants(1),
coinbase.value(),
MemoField::new_empty(),
)
.unwrap();
block.body.add_output(coinbase_output.to_transaction_output().unwrap());
block.body.sort();
let (block, _) = blockchain.create_unmined_block(block_spec!("A2", parent: "GB", skip_coinbase: true,));
let block = blockchain.mine_block("GB", block, Difficulty::min());
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(
err,
ValidationError::BlockError(BlockValidationError::TransactionError(TransactionError::NoCoinbase))
));
}
#[tokio::test]
async fn it_checks_duplicate_kernel() {
let (mut blockchain, validator) = setup(true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let (txs, _) = schema_to_transaction(
&[txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T])],
&blockchain.km,
);
blockchain
.add_next_tip(block_spec!("1", transactions: txs.iter().map(|t| (**t).clone()).collect()))
.unwrap();
let (block, _) = blockchain.create_next_tip(
BlockSpec::new()
.with_transactions(txs.iter().map(|t| (**t).clone()).collect())
.finish(),
);
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(err, ValidationError::DuplicateKernelError(_)));
}
#[tokio::test]
async fn it_checks_double_spends() {
let (mut blockchain, validator) = setup(true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let (txs, _) = schema_to_transaction(
&[txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T])],
&blockchain.km,
);
blockchain
.add_next_tip(block_spec!("1", transactions: txs.iter().map(|t| (**t).clone()).collect()))
.unwrap();
let (txs2, _) = schema_to_transaction(
&[txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T])],
&blockchain.km,
);
let (block, _) = blockchain.create_next_tip(
BlockSpec::new()
.with_transactions(txs2.iter().map(|t| (**t).clone()).collect())
.finish(),
);
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(err, ValidationError::ContainsSTxO));
}
#[tokio::test]
async fn it_checks_input_maturity() {
let (mut blockchain, validator) = setup(true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let mut schema = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T]);
let mut features = schema.from[0].features().clone();
features.maturity = 100;
schema.from[0].set_features(features);
let (txs, _) = schema_to_transaction(&[schema], &blockchain.km);
let (block, _) = blockchain.create_next_tip(
BlockSpec::new()
.with_transactions(txs.iter().map(|t| (**t).clone()).collect())
.finish(),
);
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(
err,
ValidationError::TransactionError(TransactionError::InputMaturity)
));
unpack_enum!(ValidationError::TransactionError(TransactionError::InputMaturity) = err);
}
#[tokio::test]
async fn it_checks_txo_sort_order() {
let (mut blockchain, validator) = setup(true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]);
let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (mut block, _) = blockchain.create_unmined_block(block_spec!("B->A", transactions: txs));
let outputs = block.body.outputs().iter().rev().cloned().collect::<Vec<_>>();
let inputs = block.body.inputs().clone();
let kernels = block.body.kernels().clone();
block.body = AggregateBody::new_sorted_unchecked(inputs, outputs, kernels);
let block = blockchain.mine_block("A", block, Difficulty::min());
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(
err,
ValidationError::AggregatedBodyValidationError(AggregatedBodyValidationError::UnsortedOrDuplicateOutput)
));
}
#[tokio::test]
async fn it_limits_the_script_byte_size() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_coinbase_lockheight(0)
.with_max_script_byte_size(2)
.build(),
)
.build()
.unwrap();
let (mut blockchain, validator) = setup_with_rules(rules, true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let mut schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]);
schema1.script = script!(Nop Nop Nop).unwrap();
let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (block, _) = blockchain.create_next_tip(block_spec!("B", transactions: txs));
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(
err,
ValidationError::AggregatedBodyValidationError(AggregatedBodyValidationError::TariScriptExceedsMaxSize { .. })
));
}
#[tokio::test]
async fn it_limits_the_encrypted_data_byte_size() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let (mut blockchain, validator) = setup_with_rules(rules, true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let mut schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]);
schema1.script = script!(Nop Nop Nop).unwrap();
let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km);
let mut txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let mut outputs = txs[0].body.outputs().clone();
outputs[0].encrypted_data = EncryptedData::from_bytes(&vec![0; STATIC_ENCRYPTED_DATA_SIZE_TOTAL + 250]).unwrap();
txs[0].body = AggregateBody::new(txs[0].body.inputs().clone(), outputs, txs[0].body.kernels().clone());
let (block, _) = blockchain.create_next_tip(block_spec!("B", transactions: txs));
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(
err,
ValidationError::AggregatedBodyValidationError(
AggregatedBodyValidationError::EncryptedDataExceedsMaxSize { .. }
)
));
}
#[tokio::test]
async fn it_rejects_invalid_input_metadata() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let (mut blockchain, validator) = setup_with_rules(rules.clone(), true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let mut schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]);
schema1.from[0].set_sender_offset_public_key(Default::default());
let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (block, _) = blockchain.create_next_tip(block_spec!("B", transactions: txs));
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, block.block()).unwrap_err();
assert!(matches!(err, ValidationError::UnknownInputs(_)));
}
#[tokio::test]
async fn it_rejects_zero_conf_double_spends() {
let (mut blockchain, validator) = setup(true);
let (_, coinbase) = blockchain.append(block_spec!("1", parent: "GB")).unwrap();
let schema = txn_schema!(from: vec![coinbase.clone()], to: vec![201 * T]);
let (initial_tx, outputs) = schema_to_transaction(&[schema], &blockchain.km);
let schema = txn_schema!(from: vec![outputs[0].clone()], to: vec![200 * T]);
let (first_spend, _) = schema_to_transaction(&[schema], &blockchain.km);
let schema = txn_schema!(from: vec![outputs[0].clone()], to: vec![150 * T]);
let (double_spend, _) = schema_to_transaction(&[schema], &blockchain.km);
let transactions = initial_tx
.into_iter()
.chain(first_spend)
.chain(double_spend)
.map(|b| Arc::try_unwrap(b).unwrap())
.collect::<Vec<_>>();
let (unmined, _) = blockchain.create_unmined_block(block_spec!("2", parent: "1", transactions: transactions));
let txn = blockchain.db().db_read_access().unwrap();
let err = validator.validate_body(&*txn, &unmined).unwrap_err();
assert!(matches!(
err,
ValidationError::AggregatedBodyValidationError(AggregatedBodyValidationError::UnsortedOrDuplicateInput)
));
}
mod body_only {
use super::*;
#[tokio::test]
async fn it_rejects_invalid_input_metadata() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let mut blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyFullValidator::new(rules, true);
let (_, coinbase_a) = blockchain.add_next_tip(block_spec!("A")).unwrap();
let mut schema1 = txn_schema!(from: vec![coinbase_a.clone()], to: vec![50 * T, 12 * T]);
schema1.from[0].set_sender_offset_public_key(Default::default());
let (txs, _) = schema_to_transaction(&[schema1], &blockchain.km);
let txs = txs.into_iter().map(|t| Arc::try_unwrap(t).unwrap()).collect::<Vec<_>>();
let (block, _) = blockchain.create_next_tip(BlockSpec::new().with_transactions(txs).finish());
let metadata = blockchain.db().get_chain_metadata().unwrap();
let db = blockchain.db().db_read_access().unwrap();
let err = validator.validate(&*db, block.block(), Some(&metadata)).unwrap_err();
assert!(matches!(err, ValidationError::UnknownInputs(_)));
}
}
mod orphan_validator {
use tari_transaction_components::{transaction_components::OutputType, txn_schema};
use super::*;
use crate::validation::block_body::BlockBodyInternalConsistencyValidator;
#[tokio::test]
async fn it_rejects_zero_conf_double_spends() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let mut blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyInternalConsistencyValidator::new(rules, false, CryptoFactories::default());
let (_, coinbase) = blockchain.append(block_spec!("1", parent: "GB")).unwrap();
let schema = txn_schema!(from: vec![coinbase.clone()], to: vec![201 * T]);
let (initial_tx, outputs) = schema_to_transaction(&[schema], &blockchain.km);
let schema = txn_schema!(from: vec![outputs[0].clone()], to: vec![200 * T]);
let (first_spend, _) = schema_to_transaction(&[schema], &blockchain.km);
let schema = txn_schema!(from: vec![outputs[0].clone()], to: vec![150 * T]);
let (double_spend, _) = schema_to_transaction(&[schema], &blockchain.km);
let transactions = initial_tx
.into_iter()
.chain(first_spend)
.chain(double_spend)
.map(|b| Arc::try_unwrap(b).unwrap())
.collect::<Vec<_>>();
let (unmined, _) = blockchain.create_unmined_block(block_spec!("2", parent: "1", transactions: transactions));
let err = validator.validate(&unmined).unwrap_err();
assert!(matches!(
err,
ValidationError::AggregatedBodyValidationError(AggregatedBodyValidationError::UnsortedOrDuplicateInput)
));
}
#[tokio::test]
async fn it_rejects_unpermitted_output_types() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_permitted_output_types(vec![OutputType::Coinbase])
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let mut blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyInternalConsistencyValidator::new(rules, false, CryptoFactories::default());
let (_, coinbase) = blockchain.append(block_spec!("1", parent: "GB")).unwrap();
let schema = txn_schema!(from: vec![coinbase.clone()], to: vec![201 * T]);
let (tx, _) = schema_to_transaction(&[schema], &blockchain.km);
let transactions = tx.into_iter().map(|b| Arc::try_unwrap(b).unwrap()).collect::<Vec<_>>();
let (unmined, _) = blockchain.create_unmined_block(block_spec!("2", parent: "1", transactions: transactions));
let err = validator.validate(&unmined).unwrap_err();
unpack_enum!(ValidationError::AggregatedBodyValidationError(err) = err);
unpack_enum!(AggregatedBodyValidationError::OutputTypeNotPermitted { output_type } = err);
assert_eq!(output_type, OutputType::Standard);
}
#[tokio::test]
async fn it_rejects_unpermitted_range_proof_types() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_permitted_range_proof_types(vec![
(OutputType::Standard, vec![RangeProofType::RevealedValue]),
(OutputType::Coinbase, vec![RangeProofType::RevealedValue]),
(OutputType::Burn, vec![RangeProofType::RevealedValue]),
(OutputType::ValidatorNodeRegistration, vec![
RangeProofType::RevealedValue,
]),
(OutputType::CodeTemplateRegistration, vec![
RangeProofType::RevealedValue,
]),
])
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let mut blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyInternalConsistencyValidator::new(rules, false, CryptoFactories::default());
let (_, coinbase) = blockchain.append(block_spec!("1", parent: "GB")).unwrap();
let schema = txn_schema!(from: vec![coinbase.clone()], to: vec![201 * T]);
let (tx, _) = schema_to_transaction(&[schema], &blockchain.km);
let transactions = tx.into_iter().map(|b| Arc::try_unwrap(b).unwrap()).collect::<Vec<_>>();
let (unmined, _) = blockchain.create_unmined_block(block_spec!("2", parent: "1", transactions: transactions));
let err = validator.validate(&unmined).unwrap_err();
unpack_enum!(ValidationError::AggregatedBodyValidationError(err) = err);
unpack_enum!(AggregatedBodyValidationError::RangeProofTypeNotPermitted { range_proof_type } = err);
assert_eq!(range_proof_type, RangeProofType::BulletProofPlus);
}
#[tokio::test]
async fn it_accepts_permitted_range_proof_types() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_permitted_range_proof_types(vec![
(OutputType::Standard, vec![RangeProofType::BulletProofPlus]),
(OutputType::Coinbase, vec![RangeProofType::BulletProofPlus]),
(OutputType::Burn, vec![RangeProofType::BulletProofPlus]),
(OutputType::ValidatorNodeRegistration, vec![
RangeProofType::BulletProofPlus,
]),
(OutputType::CodeTemplateRegistration, vec![
RangeProofType::BulletProofPlus,
]),
])
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let mut blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyInternalConsistencyValidator::new(rules, false, CryptoFactories::default());
let (_, coinbase) = blockchain.append(block_spec!("1", parent: "GB")).unwrap();
let schema = txn_schema!(from: vec![coinbase.clone()], to: vec![201 * T]);
let (tx, _) = schema_to_transaction(&[schema], &blockchain.km);
let transactions = tx.into_iter().map(|b| Arc::try_unwrap(b).unwrap()).collect::<Vec<_>>();
let (unmined, _) = blockchain.create_unmined_block(block_spec!("2", parent: "1", transactions: transactions));
assert!(validator.validate(&unmined).is_ok());
}
#[tokio::test]
async fn it_rejects_when_output_types_are_not_matched() {
let rules = BaseNodeConsensusManager::builder(Network::LocalNet)
.add_consensus_constants(
ConsensusConstantsBuilder::new(Network::LocalNet)
.with_permitted_range_proof_types(vec![(OutputType::CodeTemplateRegistration, vec![
RangeProofType::BulletProofPlus,
])])
.with_coinbase_lockheight(0)
.build(),
)
.build()
.unwrap();
let mut blockchain = TestBlockchain::create(rules.clone());
let validator = BlockBodyInternalConsistencyValidator::new(rules, false, CryptoFactories::default());
let (_, coinbase) = blockchain.append(block_spec!("1", parent: "GB")).unwrap();
let schema = txn_schema!(from: vec![coinbase.clone()], to: vec![201 * T]);
let (tx, _) = schema_to_transaction(&[schema], &blockchain.km);
let transactions = tx.into_iter().map(|b| Arc::try_unwrap(b).unwrap()).collect::<Vec<_>>();
let (unmined, _) = blockchain.create_unmined_block(block_spec!("2", parent: "1", transactions: transactions));
let err = validator.validate(&unmined).unwrap_err();
unpack_enum!(ValidationError::AggregatedBodyValidationError(err) = err);
unpack_enum!(AggregatedBodyValidationError::OutputTypeNotMatchedToRangeProofType { output_type } = err);
assert!(output_type == OutputType::Standard || output_type == OutputType::Coinbase);
}
}