use super::super::transaction_verifier::{
CapacityVerifier, DaoScriptSizeVerifier, DuplicateDepsVerifier, EmptyVerifier,
MaturityVerifier, OutputsDataVerifier, Since, SinceVerifier, SizeVerifier, VersionVerifier,
};
use crate::error::TransactionErrorSource;
use crate::transaction_verifier::ScriptHashTypeVerifier;
use crate::{TransactionError, TxVerifyEnv};
use ckb_chain_spec::{
OUTPUT_INDEX_DAO, build_genesis_type_id_script,
consensus::{Consensus, ConsensusBuilder},
};
use ckb_error::{Error, assert_error_eq};
use ckb_test_chain_utils::{MOCK_MEDIAN_TIME_COUNT, MockMedianTime};
use ckb_traits::CellDataProvider;
use ckb_types::{
bytes::Bytes,
constants::TX_VERSION,
core::{
BlockNumber, Capacity, EpochNumber, EpochNumberWithFraction, HeaderView, ScriptHashType,
TransactionBuilder, TransactionInfo, TransactionView, capacity_bytes,
cell::{CellMetaBuilder, ResolvedTransaction},
hardfork::HardForks,
},
h256,
packed::{Byte32, CellDep, CellInput, CellOutput, OutPoint, Script},
prelude::*,
};
use std::sync::Arc;
#[test]
pub fn test_empty() {
let transaction = TransactionBuilder::default().build();
let verifier = EmptyVerifier::new(&transaction);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::Empty {
inner: TransactionErrorSource::Inputs,
}
);
}
#[test]
pub fn test_version() {
let transaction = TransactionBuilder::default()
.version(TX_VERSION + 1)
.build();
let verifier = VersionVerifier::new(&transaction, TX_VERSION);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::MismatchedVersion {
expected: 0,
actual: 1
},
);
}
#[test]
pub fn test_exceeded_maximum_block_bytes() {
let data: Bytes = vec![1; 500].into();
let transaction = TransactionBuilder::default()
.output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
)
.output_data(data)
.build();
let verifier = SizeVerifier::new(&transaction, 100);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::ExceededMaximumBlockBytes {
actual: 661,
limit: 100
},
);
}
#[test]
pub fn test_unknown_hash_type_output_lock() {
let transaction = TransactionBuilder::default()
.output(
CellOutput::new_builder()
.lock(Script::default().as_builder().hash_type(3).build())
.build(),
)
.build();
let verifier = ScriptHashTypeVerifier::new(&transaction);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::InvalidScriptHashType {
hash_type: 3.into(),
},
);
}
#[test]
pub fn test_not_enabled_hash_type_output_lock() {
let transaction = TransactionBuilder::default()
.output(
CellOutput::new_builder()
.lock(
Script::default()
.as_builder()
.hash_type(ScriptHashType::Data3)
.build(),
)
.build(),
)
.build();
let verifier = ScriptHashTypeVerifier::new(&transaction);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::ScriptHashTypeNotPermitted {
hash_type: ScriptHashType::Data3.into(),
},
);
}
#[test]
pub fn test_capacity_out_of_bound() {
let data = Bytes::from(vec![1; 51]);
let transaction = TransactionBuilder::default()
.output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
)
.output_data(data)
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.build(),
],
resolved_dep_groups: vec![],
});
let dao_type_hash = build_genesis_type_id_script(OUTPUT_INDEX_DAO).calc_script_hash();
let verifier = CapacityVerifier::new(rtx, dao_type_hash);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::InsufficientCellCapacity {
inner: TransactionErrorSource::Outputs,
index: 0,
capacity: capacity_bytes!(50),
occupied_capacity: capacity_bytes!(92),
}
);
}
#[test]
pub fn test_skip_dao_capacity_check() {
let dao_type_script = build_genesis_type_id_script(OUTPUT_INDEX_DAO);
let transaction = TransactionBuilder::default()
.output(
CellOutput::new_builder()
.capacity(capacity_bytes!(500))
.type_(Some(dao_type_script.clone()))
.build(),
)
.output_data(Bytes::new())
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![],
resolved_dep_groups: vec![],
});
let verifier = CapacityVerifier::new(rtx, dao_type_script.calc_script_hash());
assert!(verifier.verify().is_ok());
}
#[test]
pub fn test_inputs_cellbase_maturity() {
let transaction = TransactionBuilder::default().build();
let output = CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build();
let base_epoch = EpochNumberWithFraction::new(10, 0, 10);
let cellbase_maturity = EpochNumberWithFraction::new(5, 0, 1);
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_dep_groups: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(output, Bytes::new())
.transaction_info(mock_transaction_info(30, base_epoch, 0))
.build(),
],
});
let mut current_epoch = EpochNumberWithFraction::new(0, 0, 10);
let threshold = cellbase_maturity.to_rational() + base_epoch.to_rational();
while current_epoch.number() < cellbase_maturity.number() + base_epoch.number() + 5 {
let verifier = MaturityVerifier::new(Arc::clone(&rtx), current_epoch, cellbase_maturity);
let current = current_epoch.to_rational();
if current < threshold {
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::CellbaseImmaturity {
inner: TransactionErrorSource::Inputs,
index: 0
},
"base_epoch = {base_epoch}, current_epoch = {current_epoch}, cellbase_maturity = {cellbase_maturity}"
);
} else {
assert!(
verifier.verify().is_ok(),
"base_epoch = {base_epoch}, current_epoch = {current_epoch}, cellbase_maturity = {cellbase_maturity}"
);
}
{
let number = current_epoch.number();
let length = current_epoch.length();
let index = current_epoch.index();
current_epoch = if index == length {
EpochNumberWithFraction::new(number + 1, 0, length)
} else {
EpochNumberWithFraction::new(number, index + 1, length)
};
}
}
}
#[test]
fn test_ignore_genesis_cellbase_maturity() {
let transaction = TransactionBuilder::default().build();
let output = CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build();
let base_epoch = EpochNumberWithFraction::new(0, 0, 10);
let cellbase_maturity = EpochNumberWithFraction::new(5, 0, 1);
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_dep_groups: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(output, Bytes::new())
.transaction_info(mock_transaction_info(0, base_epoch, 0))
.build(),
],
});
let mut current_epoch = EpochNumberWithFraction::new(0, 0, 10);
while current_epoch.number() < cellbase_maturity.number() + base_epoch.number() + 5 {
let verifier = MaturityVerifier::new(Arc::clone(&rtx), current_epoch, cellbase_maturity);
assert!(
verifier.verify().is_ok(),
"base_epoch = {base_epoch}, current_epoch = {current_epoch}, cellbase_maturity = {cellbase_maturity}"
);
{
let number = current_epoch.number();
let length = current_epoch.length();
let index = current_epoch.index();
current_epoch = if index == length {
EpochNumberWithFraction::new(number + 1, 0, length)
} else {
EpochNumberWithFraction::new(number, index + 1, length)
};
}
}
}
#[test]
pub fn test_deps_cellbase_maturity() {
let transaction = TransactionBuilder::default().build();
let output = CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build();
let base_epoch = EpochNumberWithFraction::new(0, 0, 10);
let cellbase_maturity = EpochNumberWithFraction::new(5, 0, 1);
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: vec![
CellMetaBuilder::from_cell_output(output.clone(), Bytes::new())
.transaction_info(mock_transaction_info(30, base_epoch, 0))
.build(),
CellMetaBuilder::from_cell_output(output, Bytes::new())
.transaction_info(mock_transaction_info(40, base_epoch, 1))
.build(),
],
resolved_inputs: Vec::new(),
resolved_dep_groups: vec![],
});
let mut current_epoch = EpochNumberWithFraction::new(0, 0, 10);
let threshold = cellbase_maturity.to_rational() + base_epoch.to_rational();
while current_epoch.number() < cellbase_maturity.number() + base_epoch.number() + 5 {
let verifier = MaturityVerifier::new(Arc::clone(&rtx), current_epoch, cellbase_maturity);
let current = current_epoch.to_rational();
if current < threshold {
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::CellbaseImmaturity {
inner: TransactionErrorSource::CellDeps,
index: 0
},
"base_epoch = {base_epoch}, current_epoch = {current_epoch}, cellbase_maturity = {cellbase_maturity}"
);
} else {
assert!(
verifier.verify().is_ok(),
"base_epoch = {base_epoch}, current_epoch = {current_epoch}, cellbase_maturity = {cellbase_maturity}"
);
}
{
let number = current_epoch.number();
let length = current_epoch.length();
let index = current_epoch.index();
current_epoch = if index == length {
EpochNumberWithFraction::new(number + 1, 0, length)
} else {
EpochNumberWithFraction::new(number, index + 1, length)
};
}
}
}
#[test]
pub fn test_capacity_invalid() {
let transaction = TransactionBuilder::default()
.outputs(vec![
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
CellOutput::new_builder()
.capacity(capacity_bytes!(100))
.build(),
])
.outputs_data(vec![Bytes::new().into(); 2])
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(49))
.build(),
Bytes::new(),
)
.build(),
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(100))
.build(),
Bytes::new(),
)
.build(),
],
resolved_dep_groups: vec![],
});
let dao_type_hash = build_genesis_type_id_script(OUTPUT_INDEX_DAO).calc_script_hash();
let verifier = CapacityVerifier::new(rtx, dao_type_hash);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::OutputsSumOverflow {
inputs_sum: capacity_bytes!(149),
outputs_sum: capacity_bytes!(150),
},
);
}
#[test]
pub fn test_duplicate_cell_deps() {
let out_point = OutPoint::new(h256!("0x1").into(), 0);
let cell_dep = CellDep::new_builder().out_point(out_point).build();
let transaction = TransactionBuilder::default()
.cell_deps(vec![cell_dep.clone(), cell_dep.clone()])
.build();
let verifier = DuplicateDepsVerifier::new(&transaction);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::DuplicateCellDeps {
out_point: cell_dep.out_point()
},
);
}
#[test]
pub fn test_duplicate_header_deps() {
let transaction = TransactionBuilder::default()
.header_deps(vec![h256!("0x1").into(), h256!("0x1").into()])
.build();
let verifier = DuplicateDepsVerifier::new(&transaction);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::DuplicateHeaderDeps {
hash: h256!("0x1").into()
},
);
}
fn verify_since(
rtx: Arc<ResolvedTransaction>,
median_time_context: MockMedianTime,
block_number: BlockNumber,
epoch_number: EpochNumber,
) -> Result<(), Error> {
let parent_hash = Arc::new(median_time_context.get_last_block_hash());
let consensus = Arc::new(
ConsensusBuilder::default()
.median_time_block_count(11)
.build(),
);
let tx_env = {
let epoch = EpochNumberWithFraction::new(epoch_number, 0, 10);
let header = HeaderView::new_advanced_builder()
.number(block_number)
.epoch(epoch)
.parent_hash(parent_hash.as_ref().to_owned())
.build();
Arc::new(TxVerifyEnv::new_commit(&header))
};
SinceVerifier::new(rtx, consensus, median_time_context, tx_env).verify()
}
#[test]
fn test_since() {
let valids = vec![
0x0000_0000_0000_0001,
0x2000_0000_0000_0001,
0x4000_0000_0000_0001,
0x8000_0000_0000_0001,
0xa000_0000_0000_0001,
0xc000_0000_0000_0001,
];
for v in valids.into_iter() {
let since = Since(v);
assert!(since.flags_is_valid());
}
let invalids = vec![
0x0100_0000_0000_0001,
0x1000_0000_0000_0001,
0xd000_0000_0000_0001,
];
for v in invalids.into_iter() {
let since = Since(v);
assert!(!since.flags_is_valid());
}
}
fn create_tx_with_lock(since: u64) -> TransactionView {
TransactionBuilder::default()
.inputs(vec![CellInput::new(
OutPoint::new(h256!("0x1").into(), 0),
since,
)])
.build()
}
fn create_resolve_tx_with_transaction_info(
tx: &TransactionView,
transaction_info: TransactionInfo,
) -> Arc<ResolvedTransaction> {
Arc::new(ResolvedTransaction {
transaction: tx.clone(),
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.transaction_info(transaction_info)
.build(),
],
resolved_dep_groups: vec![],
})
}
#[test]
fn test_invalid_since_verify() {
let tx = create_tx_with_lock(0x0100_0000_0000_0001);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(rtx, median_time_context, 5, 1).unwrap_err(),
TransactionError::InvalidSince { index: 0 },
);
}
#[test]
fn test_timestamp_since_millis_overflow_is_invalid() {
for flag in &[
0x4000_0000_0000_0000u64, 0xc000_0000_0000_0000u64, ] {
let tx = create_tx_with_lock(*flag | (u64::MAX / 1000 + 1));
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(rtx, median_time_context, 5, 1).unwrap_err(),
TransactionError::InvalidSince { index: 0 },
);
}
}
#[test]
fn test_valid_zero_length_since() {
let tx = create_tx_with_lock(0xa000_0000_0000_0000);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert!(verify_since(rtx, median_time_context, 5, 1).is_ok(),);
}
#[test]
fn test_fraction_epoch_since_verify() {
let tx = create_tx_with_lock(0x2000_0a00_0500_0010);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
let consensus = Arc::new(
ConsensusBuilder::default()
.median_time_block_count(MOCK_MEDIAN_TIME_COUNT)
.build(),
);
let block_number = 11;
let parent_hash = Arc::new(median_time_context.get_block_hash(block_number - 1));
let tx_env = {
let epoch = EpochNumberWithFraction::new(16, 1, 10);
let header = HeaderView::new_advanced_builder()
.number(block_number)
.epoch(epoch)
.parent_hash(parent_hash.as_ref().to_owned())
.build();
Arc::new(TxVerifyEnv::new_commit(&header))
};
let result = SinceVerifier::new(
Arc::clone(&rtx),
Arc::clone(&consensus),
median_time_context.clone(),
tx_env,
)
.verify();
assert_error_eq!(result.unwrap_err(), TransactionError::Immature { index: 0 });
let tx_env = {
let epoch = EpochNumberWithFraction::new(16, 5, 10);
let header = HeaderView::new_advanced_builder()
.number(block_number)
.epoch(epoch)
.parent_hash(parent_hash.as_ref().to_owned())
.build();
Arc::new(TxVerifyEnv::new_commit(&header))
};
let result =
SinceVerifier::new(rtx, Arc::clone(&consensus), median_time_context, tx_env).verify();
assert!(result.is_ok());
}
#[test]
fn test_fraction_epoch_since_verify_v2021() {
let median_time_context = MockMedianTime::new(vec![0; 11]);
let transaction_info =
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1);
let tx1 = create_tx_with_lock(0x2000_0a00_0f00_000f);
let rtx1 = create_resolve_tx_with_transaction_info(&tx1, transaction_info.clone());
let tx2 = create_tx_with_lock(0x2000_0a00_0500_0010);
let rtx2 = create_resolve_tx_with_transaction_info(&tx2, transaction_info);
let tx_env = {
let block_number = 11;
let epoch = EpochNumberWithFraction::new(16, 5, 10);
let parent_hash = Arc::new(median_time_context.get_block_hash(block_number - 1));
let header = HeaderView::new_advanced_builder()
.number(block_number)
.epoch(epoch)
.parent_hash(parent_hash.as_ref().to_owned())
.build();
Arc::new(TxVerifyEnv::new_commit(&header))
};
{
let hardfork_switch = HardForks::new_mirana();
let consensus = Arc::new(
ConsensusBuilder::default()
.median_time_block_count(MOCK_MEDIAN_TIME_COUNT)
.hardfork_switch(hardfork_switch)
.build(),
);
let result = SinceVerifier::new(
rtx1,
Arc::clone(&consensus),
median_time_context.clone(),
Arc::clone(&tx_env),
)
.verify();
assert_error_eq!(
result.unwrap_err(),
TransactionError::InvalidSince { index: 0 }
);
let result = SinceVerifier::new(rtx2, consensus, median_time_context, tx_env).verify();
assert!(result.is_ok(), "result = {result:?}");
}
}
#[test]
pub fn test_absolute_block_number_lock() {
let tx = create_tx_with_lock(0x0000_0000_0000_000a);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(Arc::clone(&rtx), median_time_context.clone(), 5, 1).unwrap_err(),
TransactionError::Immature { index: 0 },
);
assert!(verify_since(rtx, median_time_context, 10, 1).is_ok());
}
#[test]
pub fn test_absolute_epoch_number_lock() {
let tx = create_tx_with_lock(0x2000_0100_0000_000a);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(Arc::clone(&rtx), median_time_context.clone(), 5, 1).unwrap_err(),
TransactionError::Immature { index: 0 },
);
assert!(verify_since(rtx, median_time_context, 100, 10).is_ok());
}
#[test]
pub fn test_relative_timestamp_lock() {
let tx = create_tx_with_lock(0xc000_0000_0000_0002);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(Arc::clone(&rtx), median_time_context, 4, 1).unwrap_err(),
TransactionError::Immature { index: 0 },
);
let median_time_context =
MockMedianTime::new(vec![0, 100_000, 1_124_000, 2_000_000, 3_000_000]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert!(verify_since(rtx, median_time_context, 4, 1).is_ok());
}
#[test]
pub fn test_relative_epoch() {
let tx = create_tx_with_lock(0xa000_1000_0000_0002);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(Arc::clone(&rtx), median_time_context.clone(), 4, 1).unwrap_err(),
TransactionError::Immature { index: 0 },
);
assert!(verify_since(rtx, median_time_context, 4, 2).is_ok());
}
#[test]
pub fn test_since_both() {
let tx = TransactionBuilder::default()
.inputs(vec![
CellInput::new(OutPoint::new(h256!("0x1").into(), 0), 0x0000_0000_0000_000a),
CellInput::new(OutPoint::new(h256!("0x1").into(), 0), 0xc000_0000_0000_0002),
])
.build();
let median_time_context =
MockMedianTime::new(vec![0, 100_000, 1_124_000, 2_000_000, 3_000_000]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(Arc::clone(&rtx), median_time_context, 4, 1).unwrap_err(),
TransactionError::Immature { index: 0 },
);
let median_time_context = MockMedianTime::new(vec![
0, 1, 2, 3, 4, 100_000, 1_124_000, 2_000_000, 3_000_000, 4_000_000, 5_000_000, 6_000_000,
]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert!(verify_since(rtx, median_time_context, 10, 1).is_ok());
}
#[test]
fn test_since_overflow() {
for flag in &[
0b0000_0000u64, 0b1000_0000u64, 0b0100_0000u64, 0b1100_0000u64, ] {
let tx = create_tx_with_lock((flag << 56) + 0xffff_ffff_ffffu64);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(Arc::clone(&rtx), median_time_context, 5, 1).unwrap_err(),
TransactionError::Immature { index: 0 },
);
}
for flag in &[
0b0010_0000u64, 0b1010_0000u64, ] {
let tx = create_tx_with_lock((flag << 56) + 0xffff_ffff_ffffu64);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(rtx, median_time_context, 5, 1).unwrap_err(),
TransactionError::InvalidSince { index: 0 },
);
}
}
#[test]
fn test_since_timestamp_metric_overflow() {
for since in [
0x40ff_ffff_ffff_ffffu64, 0xc0ff_ffff_ffff_ffffu64, ] {
let tx = create_tx_with_lock(since);
let median_time_context = MockMedianTime::new(vec![0; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(rtx, median_time_context, 5, 1).unwrap_err(),
TransactionError::InvalidSince { index: 0 },
);
}
}
#[test]
fn test_relative_since_timestamp_add_overflow() {
let tx = create_tx_with_lock(0xc000_0000_0000_0000 | (u64::MAX / 1000));
let median_time_context = MockMedianTime::new(vec![1000; 11]);
let rtx = create_resolve_tx_with_transaction_info(
&tx,
median_time_context.get_transaction_info(1, EpochNumberWithFraction::new(0, 0, 10), 1),
);
assert_error_eq!(
verify_since(rtx, median_time_context, 5, 1).unwrap_err(),
TransactionError::InvalidSince { index: 0 },
);
}
#[test]
pub fn test_outputs_data_length_mismatch() {
let transaction = TransactionBuilder::default()
.output(CellOutput::default())
.build();
let verifier = OutputsDataVerifier::new(&transaction);
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::OutputsDataLengthMismatch {
outputs_len: 1,
outputs_data_len: 0
},
);
let transaction = TransactionBuilder::default()
.output(CellOutput::default())
.output_data(Bytes::default())
.build();
let verifier = OutputsDataVerifier::new(&transaction);
assert!(verifier.verify().is_ok());
}
fn mock_block_hash(block_number: BlockNumber) -> Byte32 {
let vec: Vec<u8> = (0..32).map(|_| block_number as u8).collect();
Byte32::from_slice(vec.as_slice()).unwrap()
}
fn mock_transaction_info(
block_number: BlockNumber,
block_epoch: EpochNumberWithFraction,
index: usize,
) -> TransactionInfo {
let block_hash = mock_block_hash(block_number);
TransactionInfo {
block_number,
block_epoch,
block_hash,
index,
}
}
struct EmptyDataProvider;
impl CellDataProvider for EmptyDataProvider {
fn get_cell_data(&self, _out_point: &OutPoint) -> Option<Bytes> {
None
}
fn get_cell_data_hash(&self, _out_point: &OutPoint) -> Option<Byte32> {
None
}
}
fn build_consensus_with_dao_limiting_block(block_number: u64) -> (Arc<Consensus>, Script) {
let dao_script = build_genesis_type_id_script(OUTPUT_INDEX_DAO);
let mut consensus = ConsensusBuilder::default()
.starting_block_limiting_dao_withdrawing_lock(block_number)
.build();
consensus.dao_type_hash = dao_script.calc_script_hash();
let dao_type_script = Script::new_builder()
.code_hash(dao_script.calc_script_hash())
.hash_type(ScriptHashType::Type)
.build();
(Arc::new(consensus), dao_type_script)
}
#[test]
fn test_dao_disables_different_lock_script_size() {
let (consensus, dao_type_script) = build_consensus_with_dao_limiting_block(20000);
let transaction = TransactionBuilder::default()
.outputs(vec![
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
CellOutput::new_builder()
.capacity(capacity_bytes!(200))
.lock(Script::new_builder().args(Bytes::from(vec![1; 20])).build())
.type_(Some(dao_type_script.clone()))
.build(),
])
.outputs_data(vec![Bytes::new().into(); 2])
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.transaction_info(mock_transaction_info(
20010,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(201))
.lock(Script::new_builder().args(Bytes::new()).build())
.type_(Some(dao_type_script))
.build(),
Bytes::from(vec![0; 8]),
)
.transaction_info(mock_transaction_info(
20011,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
],
resolved_dep_groups: vec![],
});
let verifier = DaoScriptSizeVerifier::new(rtx, consensus, EmptyDataProvider {});
assert_error_eq!(
verifier.verify().unwrap_err(),
TransactionError::DaoLockSizeMismatch { index: 1 },
);
}
#[test]
fn test_dao_disables_different_lock_script_size_before_limiting_block() {
let (consensus, dao_type_script) = build_consensus_with_dao_limiting_block(21000);
let transaction = TransactionBuilder::default()
.outputs(vec![
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
CellOutput::new_builder()
.capacity(capacity_bytes!(200))
.lock(Script::new_builder().args(Bytes::from(vec![1; 20])).build())
.type_(Some(dao_type_script.clone()))
.build(),
])
.outputs_data(vec![Bytes::new().into(); 2])
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.transaction_info(mock_transaction_info(
20010,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(201))
.lock(Script::new_builder().args(Bytes::new()).build())
.type_(Some(dao_type_script))
.build(),
Bytes::from(vec![0; 8]),
)
.transaction_info(mock_transaction_info(
20011,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
],
resolved_dep_groups: vec![],
});
let verifier = DaoScriptSizeVerifier::new(rtx, consensus, EmptyDataProvider {});
assert!(verifier.verify().is_ok());
}
#[test]
fn test_non_dao_allows_lock_script_size() {
let (consensus, _dao_type_script) = build_consensus_with_dao_limiting_block(20000);
let transaction = TransactionBuilder::default()
.outputs(vec![
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
CellOutput::new_builder()
.capacity(capacity_bytes!(200))
.lock(Script::new_builder().args(Bytes::from(vec![1; 20])).build())
.build(),
])
.outputs_data(vec![Bytes::new().into(); 2])
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.transaction_info(mock_transaction_info(
20010,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(201))
.lock(Script::new_builder().args(Bytes::new()).build())
.build(),
Bytes::from(vec![0; 8]),
)
.transaction_info(mock_transaction_info(
20011,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
],
resolved_dep_groups: vec![],
});
let verifier = DaoScriptSizeVerifier::new(rtx, consensus, EmptyDataProvider {});
assert!(verifier.verify().is_ok());
}
#[test]
fn test_dao_allows_different_lock_script_size_in_withdraw_phase_2() {
let (consensus, dao_type_script) = build_consensus_with_dao_limiting_block(20000);
let transaction = TransactionBuilder::default()
.outputs(vec![
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
CellOutput::new_builder()
.capacity(capacity_bytes!(200))
.lock(Script::new_builder().args(Bytes::from(vec![1; 20])).build())
.type_(Some(dao_type_script.clone()))
.build(),
])
.outputs_data(vec![Bytes::new().into(); 2])
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.transaction_info(mock_transaction_info(
20010,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(201))
.lock(Script::new_builder().args(Bytes::new()).build())
.type_(Some(dao_type_script))
.build(),
Bytes::from(vec![1; 8]),
)
.transaction_info(mock_transaction_info(
20011,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
],
resolved_dep_groups: vec![],
});
let verifier = DaoScriptSizeVerifier::new(rtx, consensus, EmptyDataProvider {});
assert!(verifier.verify().is_ok());
}
#[test]
fn test_dao_allows_different_lock_script_size_using_normal_cells_in_withdraw_phase_2() {
let (consensus, dao_type_script) = build_consensus_with_dao_limiting_block(20000);
let transaction = TransactionBuilder::default()
.outputs(vec![
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
CellOutput::new_builder()
.capacity(capacity_bytes!(200))
.lock(Script::new_builder().args(Bytes::from(vec![1; 20])).build())
.type_(Some(dao_type_script))
.build(),
])
.outputs_data(vec![])
.build();
let rtx = Arc::new(ResolvedTransaction {
transaction,
resolved_cell_deps: Vec::new(),
resolved_inputs: vec![
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(50))
.build(),
Bytes::new(),
)
.transaction_info(mock_transaction_info(
20010,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
CellMetaBuilder::from_cell_output(
CellOutput::new_builder()
.capacity(capacity_bytes!(201))
.lock(Script::new_builder().args(Bytes::new()).build())
.build(),
Bytes::from(vec![1; 8]),
)
.transaction_info(mock_transaction_info(
20011,
EpochNumberWithFraction::new(10, 0, 10),
0,
))
.build(),
],
resolved_dep_groups: vec![],
});
let verifier = DaoScriptSizeVerifier::new(rtx, consensus, EmptyDataProvider {});
assert!(verifier.verify().is_ok());
}