some more verification rules
This commit is contained in:
parent
c5e91d033c
commit
17a7c16447
|
@ -3,7 +3,7 @@ use hex::ToHex;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Sapling {
|
pub struct Sapling {
|
||||||
pub amount: u64,
|
pub amount: i64,
|
||||||
pub spends: Vec<SaplingSpendDescription>,
|
pub spends: Vec<SaplingSpendDescription>,
|
||||||
pub outputs: Vec<SaplingOutputDescription>,
|
pub outputs: Vec<SaplingOutputDescription>,
|
||||||
pub binding_sig: [u8; 64],
|
pub binding_sig: [u8; 64],
|
||||||
|
|
|
@ -158,7 +158,7 @@ impl ConsensusParams {
|
||||||
20_000
|
20_000
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_transaction_value(&self) -> u64 {
|
pub fn max_transaction_value(&self) -> i64 {
|
||||||
21_000_000 * 100_000_000 // No amount larger than this (in satoshi) is valid
|
21_000_000 * 100_000_000 // No amount larger than this (in satoshi) is valid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,4 +173,8 @@ impl ConsensusParams {
|
||||||
100_000
|
100_000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transaction_expiry_height_threshold(&self) -> u32 {
|
||||||
|
500_000_000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use primitives::hash::H256;
|
use primitives::hash::H256;
|
||||||
use ser::Serializable;
|
use ser::Serializable;
|
||||||
use primitives::bytes::Bytes;
|
use primitives::bytes::Bytes;
|
||||||
use chain::{Transaction, IndexedTransaction, TransactionInput, TransactionOutput, OutPoint};
|
use chain::{Transaction, IndexedTransaction, TransactionInput, TransactionOutput, OutPoint,
|
||||||
|
JoinSplit, Sapling};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct ChainBuilder {
|
pub struct ChainBuilder {
|
||||||
|
@ -76,6 +77,16 @@ impl TransactionBuilder {
|
||||||
builder.add_input(&Transaction::default(), output_index)
|
builder.add_input(&Transaction::default(), output_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_sapling(sapling: Sapling) -> TransactionBuilder {
|
||||||
|
let builder = TransactionBuilder::default();
|
||||||
|
builder.set_sapling(sapling)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_join_split(join_split: JoinSplit) -> TransactionBuilder {
|
||||||
|
let builder = TransactionBuilder::default();
|
||||||
|
builder.set_join_split(join_split)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset(self) -> TransactionBuilder {
|
pub fn reset(self) -> TransactionBuilder {
|
||||||
TransactionBuilder::default()
|
TransactionBuilder::default()
|
||||||
}
|
}
|
||||||
|
@ -143,6 +154,21 @@ impl TransactionBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_sapling(mut self, sapling: Sapling) -> TransactionBuilder {
|
||||||
|
self.transaction.sapling = Some(sapling);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_join_split(mut self, join_split: JoinSplit) -> TransactionBuilder {
|
||||||
|
self.transaction.join_split = Some(join_split);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_expiry_height(mut self, expiry_height: u32) -> TransactionBuilder {
|
||||||
|
self.transaction.expiry_height = expiry_height;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn lock(mut self) -> Self {
|
pub fn lock(mut self) -> Self {
|
||||||
self.transaction.inputs[0].sequence = 0;
|
self.transaction.inputs[0].sequence = 0;
|
||||||
self.transaction.lock_time = 500000;
|
self.transaction.lock_time = 500000;
|
||||||
|
|
|
@ -114,5 +114,11 @@ pub enum TransactionError {
|
||||||
InvalidVersionGroup,
|
InvalidVersionGroup,
|
||||||
/// Transaction has too large output value.
|
/// Transaction has too large output value.
|
||||||
ValueOverflow,
|
ValueOverflow,
|
||||||
|
/// Transaction expiry height is too high.
|
||||||
|
ExpiryHeightTooHigh,
|
||||||
|
/// Sapling with empty spends && outputs has non-empty balance.
|
||||||
|
EmptySaplingHasBalance,
|
||||||
|
/// Both value_pub_old && value_pub_new in join split description are non-zero.
|
||||||
|
JoinSplitBothPubsNonZero,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,12 +10,15 @@ use constants::{MIN_COINBASE_SIZE, MAX_COINBASE_SIZE};
|
||||||
|
|
||||||
pub struct TransactionVerifier<'a> {
|
pub struct TransactionVerifier<'a> {
|
||||||
pub version: TransactionVersion<'a>,
|
pub version: TransactionVersion<'a>,
|
||||||
|
pub expiry: TransactionExpiry<'a>,
|
||||||
pub empty: TransactionEmpty<'a>,
|
pub empty: TransactionEmpty<'a>,
|
||||||
pub null_non_coinbase: TransactionNullNonCoinbase<'a>,
|
pub null_non_coinbase: TransactionNullNonCoinbase<'a>,
|
||||||
pub oversized_coinbase: TransactionOversizedCoinbase<'a>,
|
pub oversized_coinbase: TransactionOversizedCoinbase<'a>,
|
||||||
pub join_split_in_coinbase: TransactionJoinSplitInCoinbase<'a>,
|
pub join_split_in_coinbase: TransactionJoinSplitInCoinbase<'a>,
|
||||||
pub size: TransactionAbsoluteSize<'a>,
|
pub size: TransactionAbsoluteSize<'a>,
|
||||||
pub value_overflow: TransactionValueOverflow<'a>,
|
pub sapling: TransactionSapling<'a>,
|
||||||
|
pub join_split: TransactionJoinSplit<'a>,
|
||||||
|
pub value_overflow: TransactionOutputValueOverflow<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TransactionVerifier<'a> {
|
impl<'a> TransactionVerifier<'a> {
|
||||||
|
@ -23,22 +26,28 @@ impl<'a> TransactionVerifier<'a> {
|
||||||
trace!(target: "verification", "Tx pre-verification {}", transaction.hash.to_reversed_str());
|
trace!(target: "verification", "Tx pre-verification {}", transaction.hash.to_reversed_str());
|
||||||
TransactionVerifier {
|
TransactionVerifier {
|
||||||
version: TransactionVersion::new(transaction),
|
version: TransactionVersion::new(transaction),
|
||||||
|
expiry: TransactionExpiry::new(transaction, consensus),
|
||||||
empty: TransactionEmpty::new(transaction),
|
empty: TransactionEmpty::new(transaction),
|
||||||
null_non_coinbase: TransactionNullNonCoinbase::new(transaction),
|
null_non_coinbase: TransactionNullNonCoinbase::new(transaction),
|
||||||
oversized_coinbase: TransactionOversizedCoinbase::new(transaction, MIN_COINBASE_SIZE..MAX_COINBASE_SIZE),
|
oversized_coinbase: TransactionOversizedCoinbase::new(transaction, MIN_COINBASE_SIZE..MAX_COINBASE_SIZE),
|
||||||
join_split_in_coinbase: TransactionJoinSplitInCoinbase::new(transaction),
|
join_split_in_coinbase: TransactionJoinSplitInCoinbase::new(transaction),
|
||||||
size: TransactionAbsoluteSize::new(transaction, consensus),
|
size: TransactionAbsoluteSize::new(transaction, consensus),
|
||||||
value_overflow: TransactionValueOverflow::new(transaction, consensus),
|
sapling: TransactionSapling::new(transaction),
|
||||||
|
join_split: TransactionJoinSplit::new(transaction),
|
||||||
|
value_overflow: TransactionOutputValueOverflow::new(transaction, consensus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(&self) -> Result<(), TransactionError> {
|
pub fn check(&self) -> Result<(), TransactionError> {
|
||||||
self.version.check()?;
|
self.version.check()?;
|
||||||
|
self.expiry.check()?;
|
||||||
self.empty.check()?;
|
self.empty.check()?;
|
||||||
self.null_non_coinbase.check()?;
|
self.null_non_coinbase.check()?;
|
||||||
self.oversized_coinbase.check()?;
|
self.oversized_coinbase.check()?;
|
||||||
self.join_split_in_coinbase.check()?;
|
self.join_split_in_coinbase.check()?;
|
||||||
self.size.check()?;
|
self.size.check()?;
|
||||||
|
self.sapling.check()?;
|
||||||
|
self.join_split.check()?;
|
||||||
self.value_overflow.check()?;
|
self.value_overflow.check()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -50,7 +59,7 @@ pub struct MemoryPoolTransactionVerifier<'a> {
|
||||||
pub is_coinbase: TransactionMemoryPoolCoinbase<'a>,
|
pub is_coinbase: TransactionMemoryPoolCoinbase<'a>,
|
||||||
pub size: TransactionAbsoluteSize<'a>,
|
pub size: TransactionAbsoluteSize<'a>,
|
||||||
pub sigops: TransactionSigops<'a>,
|
pub sigops: TransactionSigops<'a>,
|
||||||
pub value_overflow: TransactionValueOverflow<'a>,
|
pub value_overflow: TransactionOutputValueOverflow<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> MemoryPoolTransactionVerifier<'a> {
|
impl<'a> MemoryPoolTransactionVerifier<'a> {
|
||||||
|
@ -62,7 +71,7 @@ impl<'a> MemoryPoolTransactionVerifier<'a> {
|
||||||
is_coinbase: TransactionMemoryPoolCoinbase::new(transaction),
|
is_coinbase: TransactionMemoryPoolCoinbase::new(transaction),
|
||||||
size: TransactionAbsoluteSize::new(transaction, consensus),
|
size: TransactionAbsoluteSize::new(transaction, consensus),
|
||||||
sigops: TransactionSigops::new(transaction, consensus.max_block_sigops()),
|
sigops: TransactionSigops::new(transaction, consensus.max_block_sigops()),
|
||||||
value_overflow: TransactionValueOverflow::new(transaction, consensus),
|
value_overflow: TransactionOutputValueOverflow::new(transaction, consensus),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,18 +101,20 @@ impl<'a> TransactionEmpty<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&self) -> Result<(), TransactionError> {
|
fn check(&self) -> Result<(), TransactionError> {
|
||||||
// If version == 1 or nJoinSplit == 0, then tx_in_count MUST NOT be 0.
|
// Transactions containing empty `vin` must have either non-empty `vjoinsplit` or non-empty `vShieldedSpend`.
|
||||||
if self.transaction.raw.version == 1 || self.transaction.raw.join_split.is_none() {
|
if self.transaction.raw.inputs.is_empty() {
|
||||||
if self.transaction.raw.inputs.is_empty() {
|
let is_empty_join_split = self.transaction.raw.join_split.is_none();
|
||||||
|
let is_empty_shielded_spends = self.transaction.raw.sapling.as_ref().map(|s| s.spends.is_empty()).unwrap_or(true);
|
||||||
|
if is_empty_join_split && is_empty_shielded_spends {
|
||||||
return Err(TransactionError::Empty);
|
return Err(TransactionError::Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transactions containing empty `vin` must have either non-empty `vjoinsplit`.
|
// Transactions containing empty `vout` must have either non-empty `vjoinsplit` or non-empty `vShieldedOutput`.
|
||||||
// Transactions containing empty `vout` must have either non-empty `vjoinsplit`.
|
if self.transaction.raw.outputs.is_empty() {
|
||||||
// TODO [Sapling]: ... or non-empty `vShieldedOutput`
|
let is_empty_join_split = self.transaction.raw.join_split.is_none();
|
||||||
if self.transaction.raw.is_empty() {
|
let is_empty_shielded_outputs = self.transaction.raw.sapling.as_ref().map(|s| s.outputs.is_empty()).unwrap_or(true);
|
||||||
if self.transaction.raw.join_split.is_none() {
|
if is_empty_join_split && is_empty_shielded_outputs {
|
||||||
return Err(TransactionError::Empty);
|
return Err(TransactionError::Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,33 +297,141 @@ impl<'a> TransactionJoinSplitInCoinbase<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check for overflow of output values.
|
/// Check that transaction sapling is well-formed.
|
||||||
pub struct TransactionValueOverflow<'a> {
|
pub struct TransactionSapling<'a> {
|
||||||
transaction: &'a IndexedTransaction,
|
transaction: &'a IndexedTransaction,
|
||||||
max_value: u64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TransactionValueOverflow<'a> {
|
impl<'a> TransactionSapling<'a> {
|
||||||
|
fn new(transaction: &'a IndexedTransaction) -> Self {
|
||||||
|
TransactionSapling {
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self) -> Result<(), TransactionError> {
|
||||||
|
if let Some(ref sapling) = self.transaction.raw.sapling {
|
||||||
|
// sapling balance should be zero if spends and outputs are empty
|
||||||
|
if sapling.amount != 0 && sapling.spends.is_empty() && sapling.outputs.is_empty() {
|
||||||
|
return Err(TransactionError::EmptySaplingHasBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Check that transaction join split is well-formed.
|
||||||
|
pub struct TransactionJoinSplit<'a> {
|
||||||
|
transaction: &'a IndexedTransaction,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TransactionJoinSplit<'a> {
|
||||||
|
fn new(transaction: &'a IndexedTransaction) -> Self {
|
||||||
|
TransactionJoinSplit {
|
||||||
|
transaction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self) -> Result<(), TransactionError> {
|
||||||
|
if let Some(ref join_split) = self.transaction.raw.join_split {
|
||||||
|
for desc in &join_split.descriptions {
|
||||||
|
if desc.value_pub_old != 0 && desc.value_pub_new != 0 {
|
||||||
|
return Err(TransactionError::JoinSplitBothPubsNonZero)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check for overflow of output values.
|
||||||
|
pub struct TransactionOutputValueOverflow<'a> {
|
||||||
|
transaction: &'a IndexedTransaction,
|
||||||
|
max_value: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TransactionOutputValueOverflow<'a> {
|
||||||
fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams) -> Self {
|
fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams) -> Self {
|
||||||
TransactionValueOverflow {
|
TransactionOutputValueOverflow {
|
||||||
transaction,
|
transaction,
|
||||||
max_value: consensus.max_transaction_value(),
|
max_value: consensus.max_transaction_value(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&self) -> Result<(), TransactionError> {
|
fn check(&self) -> Result<(), TransactionError> {
|
||||||
let mut total_output = 0u64;
|
let mut total_output = 0i64;
|
||||||
|
|
||||||
|
// each output should be less than max_value
|
||||||
|
// the sum of all outputs should be less than max value
|
||||||
for output in &self.transaction.raw.outputs {
|
for output in &self.transaction.raw.outputs {
|
||||||
if output.value > self.max_value {
|
if output.value > self.max_value as u64 {
|
||||||
return Err(TransactionError::ValueOverflow)
|
return Err(TransactionError::ValueOverflow)
|
||||||
}
|
}
|
||||||
|
|
||||||
total_output = match total_output.checked_add(output.value) {
|
total_output = match total_output.checked_add(output.value as i64) {
|
||||||
Some(total_output) if total_output <= self.max_value => total_output,
|
Some(total_output) if total_output <= self.max_value => total_output,
|
||||||
_ => return Err(TransactionError::ValueOverflow),
|
_ => return Err(TransactionError::ValueOverflow),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ref sapling) = self.transaction.raw.sapling {
|
||||||
|
// check that sapling amount is within limits
|
||||||
|
if sapling.amount < -self.max_value || sapling.amount > self.max_value {
|
||||||
|
return Err(TransactionError::ValueOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// negative sapling amount takes value from transparent pool
|
||||||
|
if sapling.amount < 0 {
|
||||||
|
total_output = match total_output.checked_add(-sapling.amount) {
|
||||||
|
Some(total_output) if total_output <= self.max_value => total_output,
|
||||||
|
_ => return Err(TransactionError::ValueOverflow),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref join_split) = self.transaction.raw.join_split {
|
||||||
|
for desc in &join_split.descriptions {
|
||||||
|
if desc.value_pub_old > self.max_value as u64 {
|
||||||
|
return Err(TransactionError::ValueOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
if desc.value_pub_new > self.max_value as u64 {
|
||||||
|
return Err(TransactionError::ValueOverflow);
|
||||||
|
}
|
||||||
|
|
||||||
|
total_output = match total_output.checked_add(desc.value_pub_old as i64) {
|
||||||
|
Some(total_output) if total_output <= self.max_value => total_output,
|
||||||
|
_ => return Err(TransactionError::ValueOverflow),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that transaction expiry height is too high.
|
||||||
|
pub struct TransactionExpiry<'a> {
|
||||||
|
transaction: &'a IndexedTransaction,
|
||||||
|
height_threshold: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TransactionExpiry<'a> {
|
||||||
|
fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams) -> Self {
|
||||||
|
TransactionExpiry {
|
||||||
|
transaction,
|
||||||
|
height_threshold: consensus.transaction_expiry_height_threshold(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self) -> Result<(), TransactionError> {
|
||||||
|
if self.transaction.raw.overwintered && self.transaction.raw.expiry_height >= self.height_threshold {
|
||||||
|
return Err(TransactionError::ExpiryHeightTooHigh);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -322,17 +441,15 @@ mod tests {
|
||||||
extern crate test_data;
|
extern crate test_data;
|
||||||
|
|
||||||
use chain::{BTC_TX_VERSION, OVERWINTER_TX_VERSION, OVERWINTER_TX_VERSION_GROUP_ID,
|
use chain::{BTC_TX_VERSION, OVERWINTER_TX_VERSION, OVERWINTER_TX_VERSION_GROUP_ID,
|
||||||
SAPLING_TX_VERSION_GROUP_ID};
|
SAPLING_TX_VERSION_GROUP_ID, Sapling, JoinSplit, JoinSplitDescription};
|
||||||
use network::{Network, ConsensusParams};
|
use network::{Network, ConsensusParams};
|
||||||
use error::TransactionError;
|
use error::TransactionError;
|
||||||
use super::{TransactionEmpty, TransactionVersion, TransactionJoinSplitInCoinbase, TransactionValueOverflow};
|
use super::{TransactionEmpty, TransactionVersion, TransactionJoinSplitInCoinbase,
|
||||||
|
TransactionOutputValueOverflow, TransactionExpiry, TransactionSapling, TransactionJoinSplit};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_empty_works() {
|
fn transaction_empty_works() {
|
||||||
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(1)
|
// empty inputs
|
||||||
.add_output(0)
|
|
||||||
.add_default_join_split()
|
|
||||||
.into()).check(), Err(TransactionError::Empty));
|
|
||||||
|
|
||||||
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
||||||
.add_output(0)
|
.add_output(0)
|
||||||
|
@ -344,7 +461,25 @@ mod tests {
|
||||||
.into()).check(), Ok(()));
|
.into()).check(), Ok(()));
|
||||||
|
|
||||||
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
||||||
|
.add_output(0)
|
||||||
|
.set_sapling(Sapling { spends: vec![Default::default()], ..Default::default() })
|
||||||
|
.into()).check(), Ok(()));
|
||||||
|
|
||||||
|
// empty outputs
|
||||||
|
|
||||||
|
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
||||||
|
.add_default_input(0)
|
||||||
.into()).check(), Err(TransactionError::Empty));
|
.into()).check(), Err(TransactionError::Empty));
|
||||||
|
|
||||||
|
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
||||||
|
.add_default_input(0)
|
||||||
|
.add_default_join_split()
|
||||||
|
.into()).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionEmpty::new(&test_data::TransactionBuilder::with_version(2)
|
||||||
|
.add_default_input(0)
|
||||||
|
.set_sapling(Sapling { outputs: vec![Default::default()], ..Default::default() })
|
||||||
|
.into()).check(), Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -386,17 +521,149 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn transaction_value_overflow_works() {
|
fn transaction_output_value_overflow_works() {
|
||||||
let consensus = ConsensusParams::new(Network::Mainnet);
|
let consensus = ConsensusParams::new(Network::Mainnet);
|
||||||
|
|
||||||
assert_eq!(TransactionValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() + 1)
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() as u64 + 1)
|
||||||
.into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
.into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
|
||||||
assert_eq!(TransactionValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() / 2)
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() as u64 / 2)
|
||||||
.add_output(consensus.max_transaction_value() / 2 + 1)
|
.add_output(consensus.max_transaction_value() as u64 / 2 + 1)
|
||||||
.into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
.into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
|
||||||
assert_eq!(TransactionValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value())
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() as u64)
|
||||||
.into(), &consensus).check(), Ok(()));
|
.into(), &consensus).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_sapling(Sapling {
|
||||||
|
amount: consensus.max_transaction_value(),
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_sapling(Sapling {
|
||||||
|
amount: consensus.max_transaction_value() + 1,
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() as u64 / 2 + 1)
|
||||||
|
.set_sapling(Sapling {
|
||||||
|
amount: -consensus.max_transaction_value() / 2,
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: consensus.max_transaction_value() as u64,
|
||||||
|
value_pub_new: 0,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: consensus.max_transaction_value() as u64 + 1,
|
||||||
|
value_pub_new: 0,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: 0,
|
||||||
|
value_pub_new: consensus.max_transaction_value() as u64,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: 0,
|
||||||
|
value_pub_new: consensus.max_transaction_value() as u64 + 1,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
|
||||||
|
assert_eq!(TransactionOutputValueOverflow::new(&test_data::TransactionBuilder::with_output(consensus.max_transaction_value() as u64 / 2 + 1)
|
||||||
|
.set_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: consensus.max_transaction_value() as u64 / 2,
|
||||||
|
value_pub_new: 0,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into(), &consensus).check(), Err(TransactionError::ValueOverflow));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_expiry_works() {
|
||||||
|
let consensus = ConsensusParams::new(Network::Mainnet);
|
||||||
|
|
||||||
|
assert_eq!(TransactionExpiry::new(&test_data::TransactionBuilder::overwintered()
|
||||||
|
.set_expiry_height(consensus.transaction_expiry_height_threshold() - 1).into(), &consensus).check(),
|
||||||
|
Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionExpiry::new(&test_data::TransactionBuilder::overwintered()
|
||||||
|
.set_expiry_height(consensus.transaction_expiry_height_threshold()).into(), &consensus).check(),
|
||||||
|
Err(TransactionError::ExpiryHeightTooHigh));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_sapling_works() {
|
||||||
|
assert_eq!(TransactionSapling::new(&test_data::TransactionBuilder::with_sapling(Sapling {
|
||||||
|
amount: 100,
|
||||||
|
spends: vec![Default::default()],
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionSapling::new(&test_data::TransactionBuilder::with_sapling(Sapling {
|
||||||
|
amount: 100,
|
||||||
|
outputs: vec![Default::default()],
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionSapling::new(&test_data::TransactionBuilder::with_sapling(Sapling {
|
||||||
|
amount: 100,
|
||||||
|
outputs: vec![Default::default()],
|
||||||
|
spends: vec![Default::default()],
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionSapling::new(&test_data::TransactionBuilder::with_sapling(Sapling {
|
||||||
|
amount: 100,
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Err(TransactionError::EmptySaplingHasBalance));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_join_split_works() {
|
||||||
|
assert_eq!(TransactionJoinSplit::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: 100,
|
||||||
|
value_pub_new: 0,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionJoinSplit::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: 0,
|
||||||
|
value_pub_new: 100,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Ok(()));
|
||||||
|
|
||||||
|
assert_eq!(TransactionJoinSplit::new(&test_data::TransactionBuilder::with_join_split(JoinSplit {
|
||||||
|
descriptions: vec![JoinSplitDescription {
|
||||||
|
value_pub_old: 100,
|
||||||
|
value_pub_new: 100,
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
..Default::default()
|
||||||
|
}).into()).check(), Err(TransactionError::JoinSplitBothPubsNonZero));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue