Merge pull request #524 from paritytech/bch_nov2018_ordering
BCH Nov2018 HF: canonical transaction ordering
This commit is contained in:
commit
b329cba1bb
|
@ -3,9 +3,9 @@ use primitives::hash::H256;
|
||||||
use primitives::compact::Compact;
|
use primitives::compact::Compact;
|
||||||
use chain::{OutPoint, TransactionOutput, IndexedTransaction};
|
use chain::{OutPoint, TransactionOutput, IndexedTransaction};
|
||||||
use storage::{SharedStore, TransactionOutputProvider};
|
use storage::{SharedStore, TransactionOutputProvider};
|
||||||
use network::ConsensusParams;
|
use network::{ConsensusParams, TransactionOrdering};
|
||||||
use memory_pool::{MemoryPool, OrderingStrategy, Entry};
|
use memory_pool::{MemoryPool, OrderingStrategy, Entry};
|
||||||
use verification::{work_required, block_reward_satoshi, transaction_sigops};
|
use verification::{work_required, block_reward_satoshi, transaction_sigops, median_timestamp_inclusive};
|
||||||
|
|
||||||
const BLOCK_VERSION: u32 = 0x20000000;
|
const BLOCK_VERSION: u32 = 0x20000000;
|
||||||
const BLOCK_HEADER_SIZE: u32 = 4 + 32 + 32 + 4 + 4 + 4;
|
const BLOCK_HEADER_SIZE: u32 = 4 + 32 + 32 + 4 + 4 + 4;
|
||||||
|
@ -116,7 +116,9 @@ impl SizePolicy {
|
||||||
|
|
||||||
/// Block assembler
|
/// Block assembler
|
||||||
pub struct BlockAssembler {
|
pub struct BlockAssembler {
|
||||||
|
/// Maximal block size.
|
||||||
pub max_block_size: u32,
|
pub max_block_size: u32,
|
||||||
|
/// Maximal # of sigops in the block.
|
||||||
pub max_block_sigops: u32,
|
pub max_block_sigops: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +248,13 @@ impl BlockAssembler {
|
||||||
let mut transactions = Vec::new();
|
let mut transactions = Vec::new();
|
||||||
|
|
||||||
let mempool_iter = mempool.iter(OrderingStrategy::ByTransactionScore);
|
let mempool_iter = mempool.iter(OrderingStrategy::ByTransactionScore);
|
||||||
let tx_iter = FittingTransactionsIterator::new(store.as_transaction_output_provider(), mempool_iter, self.max_block_size, self.max_block_sigops, height, time);
|
let tx_iter = FittingTransactionsIterator::new(
|
||||||
|
store.as_transaction_output_provider(),
|
||||||
|
mempool_iter,
|
||||||
|
self.max_block_size,
|
||||||
|
self.max_block_sigops,
|
||||||
|
height,
|
||||||
|
time);
|
||||||
for entry in tx_iter {
|
for entry in tx_iter {
|
||||||
// miner_fee is i64, but we can safely cast it to u64
|
// miner_fee is i64, but we can safely cast it to u64
|
||||||
// memory pool should restrict miner fee to be positive
|
// memory pool should restrict miner fee to be positive
|
||||||
|
@ -255,6 +263,15 @@ impl BlockAssembler {
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sort block transactions
|
||||||
|
let median_time_past = median_timestamp_inclusive(previous_header_hash.clone(), store.as_block_header_provider());
|
||||||
|
match consensus.fork.transaction_ordering(median_time_past) {
|
||||||
|
TransactionOrdering::Canonical => transactions.sort_unstable_by(|tx1, tx2|
|
||||||
|
tx1.hash.cmp(&tx2.hash)),
|
||||||
|
// memory pool iter returns transactions in topological order
|
||||||
|
TransactionOrdering::Topological => (),
|
||||||
|
}
|
||||||
|
|
||||||
BlockTemplate {
|
BlockTemplate {
|
||||||
version: version,
|
version: version,
|
||||||
previous_header_hash: previous_header_hash,
|
previous_header_hash: previous_header_hash,
|
||||||
|
@ -271,7 +288,16 @@ impl BlockAssembler {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{SizePolicy, NextStep};
|
extern crate test_data;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
use db::BlockChainDatabase;
|
||||||
|
use primitives::hash::H256;
|
||||||
|
use storage::SharedStore;
|
||||||
|
use network::{ConsensusParams, ConsensusFork, Network, BitcoinCashConsensusParams};
|
||||||
|
use memory_pool::MemoryPool;
|
||||||
|
use self::test_data::{ChainBuilder, TransactionBuilder};
|
||||||
|
use super::{BlockAssembler, SizePolicy, NextStep, BlockTemplate};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_size_policy() {
|
fn test_size_policy() {
|
||||||
|
@ -317,4 +343,41 @@ mod tests {
|
||||||
fn test_fitting_transactions_iterator_locked_transaction() {
|
fn test_fitting_transactions_iterator_locked_transaction() {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_assembler_transaction_order() {
|
||||||
|
fn construct_block(consensus: ConsensusParams) -> (BlockTemplate, H256, H256) {
|
||||||
|
let chain = &mut ChainBuilder::new();
|
||||||
|
TransactionBuilder::with_default_input(0).set_output(30).store(chain) // transaction0
|
||||||
|
.into_input(0).set_output(50).store(chain); // transaction0 -> transaction1
|
||||||
|
let hash0 = chain.at(0).hash();
|
||||||
|
let hash1 = chain.at(1).hash();
|
||||||
|
|
||||||
|
let mut pool = MemoryPool::new();
|
||||||
|
let storage: SharedStore = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()]));
|
||||||
|
pool.insert_verified(chain.at(0).into());
|
||||||
|
pool.insert_verified(chain.at(1).into());
|
||||||
|
|
||||||
|
(BlockAssembler {
|
||||||
|
max_block_size: 0xffffffff,
|
||||||
|
max_block_sigops: 0xffffffff,
|
||||||
|
}.create_new_block(&storage, &pool, 0, &consensus), hash0, hash1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// when topological consensus is used
|
||||||
|
let topological_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCore);
|
||||||
|
let (block, hash0, hash1) = construct_block(topological_consensus);
|
||||||
|
assert!(hash1 < hash0);
|
||||||
|
assert_eq!(block.transactions[0].hash, hash0);
|
||||||
|
assert_eq!(block.transactions[1].hash, hash1);
|
||||||
|
|
||||||
|
// when canonocal consensus is used
|
||||||
|
let mut canonical_fork = BitcoinCashConsensusParams::new(Network::Mainnet);
|
||||||
|
canonical_fork.magnetic_anomaly_time = 0;
|
||||||
|
let canonical_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(canonical_fork));
|
||||||
|
let (block, hash0, hash1) = construct_block(canonical_consensus);
|
||||||
|
assert!(hash1 < hash0);
|
||||||
|
assert_eq!(block.transactions[0].hash, hash1);
|
||||||
|
assert_eq!(block.transactions[1].hash, hash0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,9 @@ pub struct BitcoinCashConsensusParams {
|
||||||
/// Time of monolith (aka May 2018) hardfork.
|
/// Time of monolith (aka May 2018) hardfork.
|
||||||
/// https://github.com/bitcoincashorg/spec/blob/4fbb0face661e293bcfafe1a2a4744dcca62e50d/may-2018-hardfork.md
|
/// https://github.com/bitcoincashorg/spec/blob/4fbb0face661e293bcfafe1a2a4744dcca62e50d/may-2018-hardfork.md
|
||||||
pub monolith_time: u32,
|
pub monolith_time: u32,
|
||||||
|
/// Time of magnetic anomaly (aka Nov 2018) hardfork.
|
||||||
|
/// https://github.com/bitcoincashorg/bitcoincash.org/blob/f92f5412f2ed60273c229f68dd8703b6d5d09617/spec/2018-nov-upgrade.md
|
||||||
|
pub magnetic_anomaly_time: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -57,6 +60,17 @@ pub enum ConsensusFork {
|
||||||
BitcoinCash(BitcoinCashConsensusParams),
|
BitcoinCash(BitcoinCashConsensusParams),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
/// Describes the ordering of transactions within single block.
|
||||||
|
pub enum TransactionOrdering {
|
||||||
|
/// Topological tranasaction ordering: if tx TX2 depends on tx TX1,
|
||||||
|
/// it should come AFTER TX1 (not necessary **right** after it).
|
||||||
|
Topological,
|
||||||
|
/// Canonical transaction ordering: transactions are ordered by their
|
||||||
|
/// hash (in ascending order).
|
||||||
|
Canonical,
|
||||||
|
}
|
||||||
|
|
||||||
impl ConsensusParams {
|
impl ConsensusParams {
|
||||||
pub fn new(network: Network, fork: ConsensusFork) -> Self {
|
pub fn new(network: Network, fork: ConsensusFork) -> Self {
|
||||||
match network {
|
match network {
|
||||||
|
@ -234,6 +248,14 @@ impl ConsensusFork {
|
||||||
unreachable!("BitcoinCash has no SegWit; weight is only checked with SegWit activated; qed"),
|
unreachable!("BitcoinCash has no SegWit; weight is only checked with SegWit activated; qed"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transaction_ordering(&self, median_time_past: u32) -> TransactionOrdering {
|
||||||
|
match *self {
|
||||||
|
ConsensusFork::BitcoinCash(ref fork) if median_time_past >= fork.magnetic_anomaly_time
|
||||||
|
=> TransactionOrdering::Canonical,
|
||||||
|
_ => TransactionOrdering::Topological,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BitcoinCashConsensusParams {
|
impl BitcoinCashConsensusParams {
|
||||||
|
@ -243,16 +265,19 @@ impl BitcoinCashConsensusParams {
|
||||||
height: 478559,
|
height: 478559,
|
||||||
difficulty_adjustion_height: 504031,
|
difficulty_adjustion_height: 504031,
|
||||||
monolith_time: 1526400000,
|
monolith_time: 1526400000,
|
||||||
|
magnetic_anomaly_time: 1542300000,
|
||||||
},
|
},
|
||||||
Network::Testnet => BitcoinCashConsensusParams {
|
Network::Testnet => BitcoinCashConsensusParams {
|
||||||
height: 1155876,
|
height: 1155876,
|
||||||
difficulty_adjustion_height: 1188697,
|
difficulty_adjustion_height: 1188697,
|
||||||
monolith_time: 1526400000,
|
monolith_time: 1526400000,
|
||||||
|
magnetic_anomaly_time: 1542300000,
|
||||||
},
|
},
|
||||||
Network::Regtest | Network::Unitest => BitcoinCashConsensusParams {
|
Network::Regtest | Network::Unitest => BitcoinCashConsensusParams {
|
||||||
height: 0,
|
height: 0,
|
||||||
difficulty_adjustion_height: 0,
|
difficulty_adjustion_height: 0,
|
||||||
monolith_time: 1526400000,
|
monolith_time: 1526400000,
|
||||||
|
magnetic_anomaly_time: 1542300000,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@ mod network;
|
||||||
|
|
||||||
pub use primitives::{hash, compact};
|
pub use primitives::{hash, compact};
|
||||||
|
|
||||||
pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams};
|
pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams, TransactionOrdering};
|
||||||
pub use deployments::Deployment;
|
pub use deployments::Deployment;
|
||||||
pub use network::{Magic, Network};
|
pub use network::{Magic, Network};
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use network::{ConsensusParams, ConsensusFork};
|
use network::{ConsensusParams, ConsensusFork, TransactionOrdering};
|
||||||
use crypto::dhash256;
|
use crypto::dhash256;
|
||||||
use storage::{TransactionOutputProvider, BlockHeaderProvider};
|
use storage::{TransactionOutputProvider, BlockHeaderProvider};
|
||||||
use script;
|
use script;
|
||||||
use ser::Stream;
|
use ser::Stream;
|
||||||
use sigops::{transaction_sigops, transaction_sigops_cost} ;
|
use sigops::{transaction_sigops, transaction_sigops_cost} ;
|
||||||
use work::block_reward_satoshi;
|
use work::block_reward_satoshi;
|
||||||
use duplex_store::DuplexTransactionOutputProvider;
|
use duplex_store::{transaction_index_for_output_check, DuplexTransactionOutputProvider};
|
||||||
use deployments::BlockDeployments;
|
use deployments::BlockDeployments;
|
||||||
use canon::CanonBlock;
|
use canon::CanonBlock;
|
||||||
use error::{Error, TransactionError};
|
use error::{Error, TransactionError};
|
||||||
|
@ -19,6 +19,7 @@ pub struct BlockAcceptor<'a> {
|
||||||
pub coinbase_claim: BlockCoinbaseClaim<'a>,
|
pub coinbase_claim: BlockCoinbaseClaim<'a>,
|
||||||
pub coinbase_script: BlockCoinbaseScript<'a>,
|
pub coinbase_script: BlockCoinbaseScript<'a>,
|
||||||
pub witness: BlockWitness<'a>,
|
pub witness: BlockWitness<'a>,
|
||||||
|
pub ordering: BlockTransactionOrdering<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BlockAcceptor<'a> {
|
impl<'a> BlockAcceptor<'a> {
|
||||||
|
@ -35,9 +36,10 @@ impl<'a> BlockAcceptor<'a> {
|
||||||
finality: BlockFinality::new(block, height, deployments, headers),
|
finality: BlockFinality::new(block, height, deployments, headers),
|
||||||
serialized_size: BlockSerializedSize::new(block, consensus, deployments, height, median_time_past),
|
serialized_size: BlockSerializedSize::new(block, consensus, deployments, height, median_time_past),
|
||||||
coinbase_script: BlockCoinbaseScript::new(block, consensus, height),
|
coinbase_script: BlockCoinbaseScript::new(block, consensus, height),
|
||||||
coinbase_claim: BlockCoinbaseClaim::new(block, store, height),
|
coinbase_claim: BlockCoinbaseClaim::new(block, consensus, store, height, median_time_past),
|
||||||
sigops: BlockSigops::new(block, store, consensus, height),
|
sigops: BlockSigops::new(block, store, consensus, height),
|
||||||
witness: BlockWitness::new(block, deployments),
|
witness: BlockWitness::new(block, deployments),
|
||||||
|
ordering: BlockTransactionOrdering::new(block, consensus, median_time_past),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +50,7 @@ impl<'a> BlockAcceptor<'a> {
|
||||||
self.coinbase_claim.check()?;
|
self.coinbase_claim.check()?;
|
||||||
self.coinbase_script.check()?;
|
self.coinbase_script.check()?;
|
||||||
self.witness.check()?;
|
self.witness.check()?;
|
||||||
|
self.ordering.check()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -187,14 +190,22 @@ pub struct BlockCoinbaseClaim<'a> {
|
||||||
block: CanonBlock<'a>,
|
block: CanonBlock<'a>,
|
||||||
store: &'a TransactionOutputProvider,
|
store: &'a TransactionOutputProvider,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
transaction_ordering: TransactionOrdering,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> BlockCoinbaseClaim<'a> {
|
impl<'a> BlockCoinbaseClaim<'a> {
|
||||||
fn new(block: CanonBlock<'a>, store: &'a TransactionOutputProvider, height: u32) -> Self {
|
fn new(
|
||||||
|
block: CanonBlock<'a>,
|
||||||
|
consensus_params: &ConsensusParams,
|
||||||
|
store: &'a TransactionOutputProvider,
|
||||||
|
height: u32,
|
||||||
|
median_time_past: u32
|
||||||
|
) -> Self {
|
||||||
BlockCoinbaseClaim {
|
BlockCoinbaseClaim {
|
||||||
block: block,
|
block: block,
|
||||||
store: store,
|
store: store,
|
||||||
height: height,
|
height: height,
|
||||||
|
transaction_ordering: consensus_params.fork.transaction_ordering(median_time_past),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,8 +218,9 @@ impl<'a> BlockCoinbaseClaim<'a> {
|
||||||
// (1) Total sum of all referenced outputs
|
// (1) Total sum of all referenced outputs
|
||||||
let mut incoming: u64 = 0;
|
let mut incoming: u64 = 0;
|
||||||
for input in tx.raw.inputs.iter() {
|
for input in tx.raw.inputs.iter() {
|
||||||
let (sum, overflow) = incoming.overflowing_add(
|
let prevout_tx_idx = transaction_index_for_output_check(self.transaction_ordering, tx_idx);
|
||||||
store.transaction_output(&input.previous_output, tx_idx).map(|o| o.value).unwrap_or(0));
|
let prevout = store.transaction_output(&input.previous_output, prevout_tx_idx);
|
||||||
|
let (sum, overflow) = incoming.overflowing_add(prevout.map(|o| o.value).unwrap_or(0));
|
||||||
if overflow {
|
if overflow {
|
||||||
return Err(Error::ReferencedInputsSumOverflow);
|
return Err(Error::ReferencedInputsSumOverflow);
|
||||||
}
|
}
|
||||||
|
@ -339,12 +351,43 @@ impl<'a> BlockWitness<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BlockTransactionOrdering<'a> {
|
||||||
|
block: CanonBlock<'a>,
|
||||||
|
transaction_ordering: TransactionOrdering,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BlockTransactionOrdering<'a> {
|
||||||
|
fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, median_time_past: u32) -> Self {
|
||||||
|
BlockTransactionOrdering {
|
||||||
|
block,
|
||||||
|
transaction_ordering: consensus.fork.transaction_ordering(median_time_past),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&self) -> Result<(), Error> {
|
||||||
|
match self.transaction_ordering {
|
||||||
|
// topological transaction ordering is checked in TransactionMissingInputs
|
||||||
|
TransactionOrdering::Topological => Ok(()),
|
||||||
|
// canonical transaction ordering means that transactions are ordered by
|
||||||
|
// their id (i.e. hash) in ascending order
|
||||||
|
TransactionOrdering::Canonical =>
|
||||||
|
if self.block.transactions.windows(2).skip(1).all(|w| w[0].hash < w[1].hash) {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(Error::NonCanonicalTransactionOrdering)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
extern crate test_data;
|
extern crate test_data;
|
||||||
|
|
||||||
|
use chain::{IndexedBlock, Transaction};
|
||||||
|
use network::{Network, ConsensusFork, ConsensusParams, BitcoinCashConsensusParams};
|
||||||
use {Error, CanonBlock};
|
use {Error, CanonBlock};
|
||||||
use super::BlockCoinbaseScript;
|
use super::{BlockCoinbaseScript, BlockTransactionOrdering};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_block_coinbase_script() {
|
fn test_block_coinbase_script() {
|
||||||
|
@ -374,4 +417,42 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(coinbase_script_validator2.check(), Err(Error::CoinbaseScript));
|
assert_eq!(coinbase_script_validator2.check(), Err(Error::CoinbaseScript));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_transaction_ordering_works() {
|
||||||
|
let tx1: Transaction = test_data::TransactionBuilder::with_output(1).into();
|
||||||
|
let tx2: Transaction = test_data::TransactionBuilder::with_output(2).into();
|
||||||
|
let tx3: Transaction = test_data::TransactionBuilder::with_output(3).into();
|
||||||
|
let bad_block: IndexedBlock = test_data::block_builder()
|
||||||
|
.with_transaction(tx1.clone())
|
||||||
|
.with_transaction(tx2.clone())
|
||||||
|
.with_transaction(tx3.clone())
|
||||||
|
.header().build()
|
||||||
|
.build()
|
||||||
|
.into();
|
||||||
|
let good_block: IndexedBlock = test_data::block_builder()
|
||||||
|
.with_transaction(tx1)
|
||||||
|
.with_transaction(tx3)
|
||||||
|
.with_transaction(tx2)
|
||||||
|
.header().build()
|
||||||
|
.build()
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let bad_block = CanonBlock::new(&bad_block);
|
||||||
|
let good_block = CanonBlock::new(&good_block);
|
||||||
|
|
||||||
|
// when topological ordering is used => we don't care about tx ordering
|
||||||
|
let consensus = ConsensusParams::new(Network::Unitest, ConsensusFork::BitcoinCore);
|
||||||
|
let checker = BlockTransactionOrdering::new(bad_block, &consensus, 0);
|
||||||
|
assert_eq!(checker.check(), Ok(()));
|
||||||
|
|
||||||
|
// when topological ordering is used => we care about tx ordering
|
||||||
|
let mut bch = BitcoinCashConsensusParams::new(Network::Unitest);
|
||||||
|
bch.magnetic_anomaly_time = 0;
|
||||||
|
let consensus = ConsensusParams::new(Network::Unitest, ConsensusFork::BitcoinCash(bch));
|
||||||
|
let checker = BlockTransactionOrdering::new(bad_block, &consensus, 0);
|
||||||
|
assert_eq!(checker.check(), Err(Error::NonCanonicalTransactionOrdering));
|
||||||
|
let checker = BlockTransactionOrdering::new(good_block, &consensus, 0);
|
||||||
|
assert_eq!(checker.check(), Ok(()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use primitives::bytes::Bytes;
|
||||||
use storage::{TransactionMetaProvider, TransactionOutputProvider};
|
use storage::{TransactionMetaProvider, TransactionOutputProvider};
|
||||||
use network::{ConsensusParams, ConsensusFork};
|
use network::{ConsensusParams, ConsensusFork};
|
||||||
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion};
|
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion};
|
||||||
use duplex_store::DuplexTransactionOutputProvider;
|
use duplex_store::{DuplexTransactionOutputProvider, transaction_index_for_output_check};
|
||||||
use deployments::BlockDeployments;
|
use deployments::BlockDeployments;
|
||||||
use script::Builder;
|
use script::Builder;
|
||||||
use sigops::transaction_sigops;
|
use sigops::transaction_sigops;
|
||||||
|
@ -41,10 +41,12 @@ impl<'a> TransactionAcceptor<'a> {
|
||||||
deployments: &'a BlockDeployments<'a>,
|
deployments: &'a BlockDeployments<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str());
|
trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str());
|
||||||
|
let tx_ordering = consensus.fork.transaction_ordering(median_time_past);
|
||||||
|
let missing_input_tx_index = transaction_index_for_output_check(tx_ordering,transaction_index);
|
||||||
TransactionAcceptor {
|
TransactionAcceptor {
|
||||||
premature_witness: TransactionPrematureWitness::new(transaction, deployments),
|
premature_witness: TransactionPrematureWitness::new(transaction, deployments),
|
||||||
bip30: TransactionBip30::new_for_sync(transaction, meta_store, consensus, block_hash, height),
|
bip30: TransactionBip30::new_for_sync(transaction, meta_store, consensus, block_hash, height),
|
||||||
missing_inputs: TransactionMissingInputs::new(transaction, output_store, transaction_index),
|
missing_inputs: TransactionMissingInputs::new(transaction, output_store, missing_input_tx_index),
|
||||||
maturity: TransactionMaturity::new(transaction, meta_store, height),
|
maturity: TransactionMaturity::new(transaction, meta_store, height),
|
||||||
overspent: TransactionOverspent::new(transaction, output_store),
|
overspent: TransactionOverspent::new(transaction, output_store),
|
||||||
double_spent: TransactionDoubleSpend::new(transaction, output_store),
|
double_spent: TransactionDoubleSpend::new(transaction, output_store),
|
||||||
|
|
|
@ -154,11 +154,12 @@ mod tests {
|
||||||
extern crate test_data;
|
extern crate test_data;
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use chain::IndexedBlock;
|
use chain::{IndexedBlock, Transaction, Block};
|
||||||
use storage::{Error as DBError};
|
use storage::Error as DBError;
|
||||||
use db::BlockChainDatabase;
|
use db::BlockChainDatabase;
|
||||||
use network::{Network, ConsensusParams, ConsensusFork};
|
use network::{Network, ConsensusParams, ConsensusFork, BitcoinCashConsensusParams};
|
||||||
use script;
|
use script;
|
||||||
|
use constants::DOUBLE_SPACING_SECONDS;
|
||||||
use super::BackwardsCompatibleChainVerifier as ChainVerifier;
|
use super::BackwardsCompatibleChainVerifier as ChainVerifier;
|
||||||
use {Verify, Error, TransactionError, VerificationLevel};
|
use {Verify, Error, TransactionError, VerificationLevel};
|
||||||
|
|
||||||
|
@ -178,7 +179,6 @@ mod tests {
|
||||||
assert!(verifier.verify(VerificationLevel::Full, &b1.into()).is_ok());
|
assert!(verifier.verify(VerificationLevel::Full, &b1.into()).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn first_tx() {
|
fn first_tx() {
|
||||||
let storage = BlockChainDatabase::init_test_chain(
|
let storage = BlockChainDatabase::init_test_chain(
|
||||||
|
@ -335,6 +335,66 @@ mod tests {
|
||||||
assert_eq!(expected, verifier.verify(VerificationLevel::Full, &block.into()));
|
assert_eq!(expected, verifier.verify(VerificationLevel::Full, &block.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn transaction_references_same_block_and_goes_before_previous() {
|
||||||
|
let mut blocks = vec![test_data::block_builder()
|
||||||
|
.transaction()
|
||||||
|
.coinbase()
|
||||||
|
.output().value(50).build()
|
||||||
|
.build()
|
||||||
|
.merkled_header().build()
|
||||||
|
.build()];
|
||||||
|
let input_tx = blocks[0].transactions()[0].clone();
|
||||||
|
let mut parent_hash = blocks[0].hash();
|
||||||
|
// waiting 100 blocks for genesis coinbase to become valid
|
||||||
|
for _ in 0..100 {
|
||||||
|
let block: Block = test_data::block_builder()
|
||||||
|
.transaction().coinbase().build()
|
||||||
|
.merkled_header().parent(parent_hash).build()
|
||||||
|
.build()
|
||||||
|
.into();
|
||||||
|
parent_hash = block.hash();
|
||||||
|
blocks.push(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
let storage = Arc::new(BlockChainDatabase::init_test_chain(blocks.into_iter().map(Into::into).collect()));
|
||||||
|
|
||||||
|
let tx1: Transaction = test_data::TransactionBuilder::with_version(4)
|
||||||
|
.add_input(&input_tx, 0)
|
||||||
|
.add_output(40)
|
||||||
|
.into();
|
||||||
|
let tx2: Transaction = test_data::TransactionBuilder::with_version(1)
|
||||||
|
.add_input(&tx1, 0)
|
||||||
|
.add_output(30)
|
||||||
|
.into();
|
||||||
|
let block = test_data::block_builder()
|
||||||
|
.transaction()
|
||||||
|
.coinbase()
|
||||||
|
.output().value(2).build()
|
||||||
|
.build()
|
||||||
|
.with_transaction(tx2)
|
||||||
|
.with_transaction(tx1)
|
||||||
|
.merkled_header()
|
||||||
|
.time(DOUBLE_SPACING_SECONDS + 101) // to pass BCH work check
|
||||||
|
.parent(parent_hash)
|
||||||
|
.build()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
// when topological order is required
|
||||||
|
let topological_consensus = ConsensusParams::new(Network::Unitest, ConsensusFork::BitcoinCore);
|
||||||
|
let verifier = ChainVerifier::new(storage.clone(), topological_consensus);
|
||||||
|
let expected = Err(Error::Transaction(1, TransactionError::Overspend));
|
||||||
|
assert_eq!(expected, verifier.verify(VerificationLevel::Header, &block.clone().into()));
|
||||||
|
|
||||||
|
// when canonical order is required
|
||||||
|
let mut canonical_params = BitcoinCashConsensusParams::new(Network::Unitest);
|
||||||
|
canonical_params.magnetic_anomaly_time = 0;
|
||||||
|
let canonical_consensus = ConsensusParams::new(Network::Unitest, ConsensusFork::BitcoinCash(canonical_params));
|
||||||
|
let verifier = ChainVerifier::new(storage, canonical_consensus);
|
||||||
|
let expected = Ok(());
|
||||||
|
assert_eq!(expected, verifier.verify(VerificationLevel::Header, &block.into()));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore]
|
#[ignore]
|
||||||
fn coinbase_happy() {
|
fn coinbase_happy() {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//! require sophisticated (in more than one source) previous transaction lookups
|
//! require sophisticated (in more than one source) previous transaction lookups
|
||||||
|
|
||||||
use chain::{OutPoint, TransactionOutput};
|
use chain::{OutPoint, TransactionOutput};
|
||||||
|
use network::TransactionOrdering;
|
||||||
use storage::TransactionOutputProvider;
|
use storage::TransactionOutputProvider;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -41,3 +42,19 @@ impl TransactionOutputProvider for NoopStore {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts actual transaction index into transaction index to use in
|
||||||
|
/// TransactionOutputProvider::transaction_output call.
|
||||||
|
/// When topological ordering is used, we expect ascendant transaction (TX1)
|
||||||
|
/// to come BEFORE descendant transaction (TX2) in the block, like this:
|
||||||
|
/// [ ... TX1 ... TX2 ... ]
|
||||||
|
/// When canonical ordering is used, transactions order within block is not
|
||||||
|
/// relevant for this check and ascendant transaction (TX1) can come AFTER
|
||||||
|
/// descendant, like this:
|
||||||
|
/// [ ... TX2 ... TX1 ... ]
|
||||||
|
pub fn transaction_index_for_output_check(ordering: TransactionOrdering, tx_idx: usize) -> usize {
|
||||||
|
match ordering {
|
||||||
|
TransactionOrdering::Topological => tx_idx,
|
||||||
|
TransactionOrdering::Canonical => ::std::usize::MAX,
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,6 +57,8 @@ pub enum Error {
|
||||||
WitnessMerkleCommitmentMismatch,
|
WitnessMerkleCommitmentMismatch,
|
||||||
/// SegWit: unexpected witness
|
/// SegWit: unexpected witness
|
||||||
UnexpectedWitness,
|
UnexpectedWitness,
|
||||||
|
/// Non-canonical tranasctions ordering within block
|
||||||
|
NonCanonicalTransactionOrdering,
|
||||||
/// Database error
|
/// Database error
|
||||||
Database(DBError),
|
Database(DBError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ fn work_required_bitcoin_cash_adjusted(parent_header: IndexedBlockHeader, time:
|
||||||
// If the new block's timestamp is more than 2 * 10 minutes then allow
|
// If the new block's timestamp is more than 2 * 10 minutes then allow
|
||||||
// mining of a min-difficulty block.
|
// mining of a min-difficulty block.
|
||||||
let max_bits = consensus.network.max_bits();
|
let max_bits = consensus.network.max_bits();
|
||||||
if consensus.network == Network::Testnet {
|
if consensus.network == Network::Testnet || consensus.network == Network::Unitest {
|
||||||
let max_time_gap = parent_header.raw.time + DOUBLE_SPACING_SECONDS;
|
let max_time_gap = parent_header.raw.time + DOUBLE_SPACING_SECONDS;
|
||||||
if time > max_time_gap {
|
if time > max_time_gap {
|
||||||
return max_bits.into();
|
return max_bits.into();
|
||||||
|
@ -218,6 +218,7 @@ mod tests {
|
||||||
height: 1000,
|
height: 1000,
|
||||||
difficulty_adjustion_height: 0xffffffff,
|
difficulty_adjustion_height: 0xffffffff,
|
||||||
monolith_time: 0xffffffff,
|
monolith_time: 0xffffffff,
|
||||||
|
magnetic_anomaly_time: 0xffffffff,
|
||||||
}));
|
}));
|
||||||
let mut header_provider = MemoryBlockHeaderProvider::default();
|
let mut header_provider = MemoryBlockHeaderProvider::default();
|
||||||
header_provider.insert(BlockHeader {
|
header_provider.insert(BlockHeader {
|
||||||
|
@ -271,6 +272,7 @@ mod tests {
|
||||||
height: 1000,
|
height: 1000,
|
||||||
difficulty_adjustion_height: 0xffffffff,
|
difficulty_adjustion_height: 0xffffffff,
|
||||||
monolith_time: 0xffffffff,
|
monolith_time: 0xffffffff,
|
||||||
|
magnetic_anomaly_time: 0xffffffff,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue