From 9d0d251a378fdc641f53a26a5cef4c68a12439f4 Mon Sep 17 00:00:00 2001 From: debris Date: Sat, 26 Nov 2016 15:05:54 +0100 Subject: [PATCH 1/3] few functions are more idiomatic, initial support for bip30 --- db/src/storage.rs | 72 +++++++++++++----------------- db/src/transaction_meta.rs | 50 ++++++++++++++++----- verification/src/chain_verifier.rs | 10 +++++ verification/src/lib.rs | 2 + 4 files changed, 84 insertions(+), 50 deletions(-) diff --git a/db/src/storage.rs b/db/src/storage.rs index fabbe181..1adf709d 100644 --- a/db/src/storage.rs +++ b/db/src/storage.rs @@ -175,7 +175,7 @@ impl Storage { if let Some(accepted_tx) = accepted_txs.iter().next() { context.meta.insert( accepted_tx.hash(), - TransactionMeta::new(number, accepted_tx.outputs.len()).coinbase() + TransactionMeta::new_coinbase(number, accepted_tx.outputs.len()) ); } @@ -186,29 +186,27 @@ impl Storage { ); for input in &accepted_tx.inputs { - if !match context.meta.get_mut(&input.previous_output.hash) { - Some(ref mut meta) => { + use std::collections::hash_map::Entry; + + match context.meta.entry(input.previous_output.hash.clone()) { + Entry::Occupied(mut entry) => { + let meta = entry.get_mut(); + if meta.is_spent(input.previous_output.index as usize) { + return Err(Error::double_spend(&input.previous_output.hash)); + } + meta.denote_used(input.previous_output.index as usize); + }, + Entry::Vacant(entry) => { + let mut meta = self.transaction_meta(&input.previous_output.hash) + .ok_or(Error::unknown_spending(&input.previous_output.hash))?; + if meta.is_spent(input.previous_output.index as usize) { return Err(Error::double_spend(&input.previous_output.hash)); } meta.denote_used(input.previous_output.index as usize); - true + entry.insert(meta); }, - None => false, - } { - let mut meta = self.transaction_meta(&input.previous_output.hash) - .ok_or(Error::unknown_spending(&input.previous_output.hash))?; - - if meta.is_spent(input.previous_output.index as usize) { - return Err(Error::double_spend(&input.previous_output.hash)); - } - - meta.denote_used(input.previous_output.index as usize); - - context.meta.insert( - input.previous_output.hash.clone(), - meta); } } } @@ -238,30 +236,24 @@ impl Storage { // remove meta context.db_transaction.delete(Some(COL_TRANSACTIONS_META), &**tx_hash); - // denote outputs used - if tx_hash_num == 0 { continue; } // coinbase transaction does not have inputs + // coinbase transaction does not have inputs + if tx_hash_num == 0 { + continue; + } + + // denote outputs as unused for input in &tx.inputs { - if !match context.meta.get_mut(&input.previous_output.hash) { - Some(ref mut meta) => { - meta.denote_unused(input.previous_output.index as usize); - true + use std::collections::hash_map::Entry; + match context.meta.entry(input.previous_output.hash.clone()) { + Entry::Occupied(mut entry) => { + entry.get_mut().denote_unused(input.previous_output.index as usize); + }, + Entry::Vacant(entry) => { + let mut meta = self.transaction_meta(&input.previous_output.hash) + .expect("No transaction metadata! Possible db corruption"); + meta.denote_unused(input.previous_output.index as usize); + entry.insert(meta); }, - None => false, - } { - let mut meta = - self.transaction_meta(&input.previous_output.hash) - .unwrap_or_else(|| panic!( - // decanonization should always have meta - // because block could not have made canonical without writing meta - "No transaction metadata for {}! Corrupted DB? Reindex?", - &input.previous_output.hash - )); - - meta.denote_unused(input.previous_output.index as usize); - - context.meta.insert( - input.previous_output.hash.clone(), - meta); } } } diff --git a/db/src/transaction_meta.rs b/db/src/transaction_meta.rs index 29936fc7..8e9cfa15 100644 --- a/db/src/transaction_meta.rs +++ b/db/src/transaction_meta.rs @@ -7,7 +7,8 @@ use byteorder::{LittleEndian, ByteOrder}; #[derive(Debug, Clone)] pub struct TransactionMeta { block_height: u32, - // first bit is coinbase flag, others - one per output listed + /// first bit indicate if transaction is a coinbase transaction + /// next bits indicate if transaction has spend outputs bits: BitVec, } @@ -17,7 +18,7 @@ pub enum Error { } impl TransactionMeta { - /// new transaction description for indexing + /// New transaction description for indexing pub fn new(block_height: u32, outputs: usize) -> Self { TransactionMeta { block_height: block_height, @@ -25,22 +26,25 @@ impl TransactionMeta { } } - pub fn coinbase(mut self) -> Self { - self.bits.set(0, true); - self + /// New coinbase transaction + pub fn new_coinbase(block_height: u32, outputs: usize) -> Self { + let mut result = Self::new(block_height, outputs); + result.bits.set(0, true); + result } + /// Returns true if it is a coinbase transaction pub fn is_coinbase(&self) -> bool { self.bits.get(0) - .expect("One bit should always exists, since it is created as usize + 1; minimum value of usize is 0; 0 + 1 = 1; qed") + .expect("One bit should always exists, since it is created as usize + 1; minimum value of usize is 0; 0 + 1 = 1; qed") } - /// denote particular output as used + /// Denote particular output as used pub fn denote_used(&mut self, index: usize) { self.bits.set(index + 1 , true); } - /// denote particular output as not used + /// Denote particular output as not used pub fn denote_unused(&mut self, index: usize) { self.bits.set(index + 1, false); } @@ -61,8 +65,34 @@ impl TransactionMeta { }) } - pub fn height(&self) -> u32 { self.block_height } + pub fn height(&self) -> u32 { + self.block_height + } - pub fn is_spent(&self, idx: usize) -> bool { self.bits.get(idx + 1).expect("Index should be verified by the caller") } + pub fn is_spent(&self, idx: usize) -> bool { + self.bits.get(idx + 1).expect("Index should be verified by the caller") + } + pub fn is_fully_spent(&self) -> bool { + // skip coinbase bit, the rest needs to true + self.bits.iter().skip(1).all(|x| x) + } +} + +#[cfg(test)] +mod tests { + use super::TransactionMeta; + + #[test] + fn test_is_fully_spent() { + let t = TransactionMeta::new(0, 0); + assert!(t.is_fully_spent()); + + let mut t = TransactionMeta::new(0, 1); + assert!(!t.is_fully_spent()); + t.denote_used(0); + assert!(t.is_fully_spent()); + t.denote_unused(0); + assert!(!t.is_fully_spent()); + } } diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index eb6c770f..c9d26502 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -68,6 +68,16 @@ impl ChainVerifier { let coinbase_spends = block.transactions()[0].total_spends(); + // bip30 + // TODO: add exceptions + for (tx_index, tx) in block.transactions.iter().enumerate() { + if let Some(meta) = self.store.transaction_meta(&tx.hash()) { + if !meta.is_fully_spent() { + return Err(Error::Transaction(tx_index, TransactionError::BIP30)); + } + } + } + let mut total_unspent = 0u64; for (tx_index, tx) in block.transactions().iter().enumerate().skip(1) { diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 9096ffca..24587be5 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -84,6 +84,8 @@ pub enum TransactionError { SigopsP2SH(usize), /// Coinbase transaction is found at position that is not 0 MisplacedCoinbase(usize), + /// Not fully spent transaction with the same hash already exists. + BIP30, } #[derive(PartialEq, Debug)] From 42ce57e6a86962fa47268eaf55f1103ad8f93a37 Mon Sep 17 00:00:00 2001 From: debris Date: Sun, 27 Nov 2016 15:05:49 +0100 Subject: [PATCH 2/3] fixed bip30 tests, tx finality reg tests passing --- Cargo.lock | 1 + chain/src/block.rs | 4 +++ chain/src/lib.rs | 3 +- chain/src/transaction.rs | 55 +++++++++++++++--------------- network/Cargo.toml | 1 + network/src/consensus.rs | 6 ++++ network/src/lib.rs | 4 +++ script/src/script.rs | 4 --- script/src/verify.rs | 8 ++--- verification/src/chain_verifier.rs | 19 ++++++++--- verification/src/lib.rs | 6 ++-- verification/src/utils.rs | 2 +- 12 files changed, 68 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8642092..fc9fd279 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -398,6 +398,7 @@ name = "network" version = "0.1.0" dependencies = [ "chain 0.1.0", + "primitives 0.1.0", "serialization 0.1.0", ] diff --git a/chain/src/block.rs b/chain/src/block.rs index 38169bd3..a1c437dc 100644 --- a/chain/src/block.rs +++ b/chain/src/block.rs @@ -70,6 +70,10 @@ impl Block { pub fn hash(&self) -> H256 { self.block_header.hash() } + + pub fn is_final(&self, height: u32) -> bool { + self.transactions.iter().all(|t| t.is_final(height, self.block_header.time)) + } } #[cfg(test)] diff --git a/chain/src/lib.rs b/chain/src/lib.rs index d00636a7..268324d1 100644 --- a/chain/src/lib.rs +++ b/chain/src/lib.rs @@ -23,7 +23,8 @@ pub use self::merkle_root::merkle_node_hash; pub use self::transaction::{ Transaction, TransactionInput, TransactionOutput, OutPoint, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_FINAL, - SEQUENCE_LOCKTIME_TYPE_FLAG, SEQUENCE_LOCKTIME_MASK + SEQUENCE_LOCKTIME_TYPE_FLAG, SEQUENCE_LOCKTIME_MASK, + LOCKTIME_THRESHOLD }; pub type ShortTransactionID = hash::H48; diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index 91eb3e88..5ab7a1bc 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -30,6 +30,9 @@ pub const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = (1 << 22); // applied to extract that lock-time from the sequence field. pub const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000ffff; +/// Threshold for `nLockTime`: below this value it is interpreted as block number, +/// otherwise as UNIX timestamp. +pub const LOCKTIME_THRESHOLD: u32 = 500000000; // Tue Nov 5 00:53:20 1985 UTC #[derive(Debug, PartialEq, Clone, Default)] pub struct OutPoint { @@ -66,10 +69,6 @@ impl OutPoint { &self.hash } - pub fn index(&self) -> u32 { - self.index - } - pub fn is_null(&self) -> bool { self.hash.is_zero() && self.index == u32::max_value() } @@ -82,6 +81,12 @@ pub struct TransactionInput { pub sequence: u32, } +impl TransactionInput { + pub fn is_final(&self) -> bool { + self.sequence == SEQUENCE_FINAL + } +} + impl Serializable for TransactionInput { fn serialize(&self, stream: &mut Stream) { stream @@ -116,20 +121,6 @@ impl HeapSizeOf for TransactionInput { } } -impl TransactionInput { - pub fn previous_output(&self) -> &OutPoint { - &self.previous_output - } - - pub fn script_sig(&self) -> &[u8] { - &self.script_sig - } - - pub fn sequence(&self) -> u32 { - self.sequence - } -} - #[derive(Debug, PartialEq, Clone)] pub struct TransactionOutput { pub value: u64, @@ -176,16 +167,6 @@ impl HeapSizeOf for TransactionOutput { } } -impl TransactionOutput { - pub fn value(&self) -> u64 { - self.value - } - - pub fn script_pubkey(&self) -> &[u8] { - &self.script_pubkey - } -} - #[derive(Debug, PartialEq, Default, Clone)] pub struct Transaction { pub version: i32, @@ -254,6 +235,24 @@ impl Transaction { self.inputs.len() == 1 && self.inputs[0].previous_output.is_null() } + pub fn is_final(&self, block_height: u32, block_time: u32) -> bool { + if self.lock_time == 0 { + return true; + } + + let max_lock_time = if self.lock_time < LOCKTIME_THRESHOLD { + block_height + } else { + block_time + }; + + if self.lock_time < max_lock_time { + return true; + } + + self.inputs.iter().all(TransactionInput::is_final) + } + pub fn total_spends(&self) -> u64 { self.outputs .iter() diff --git a/network/Cargo.toml b/network/Cargo.toml index 930d034e..0d8e5383 100644 --- a/network/Cargo.toml +++ b/network/Cargo.toml @@ -6,3 +6,4 @@ authors = ["debris "] [dependencies] serialization = { path = "../serialization" } chain = { path = "../chain" } +primitives = { path = "../primitives" } diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 6c37bb68..fca4219a 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -1,3 +1,4 @@ +use hash::H256; use super::Magic; #[derive(Debug, Clone)] @@ -28,6 +29,11 @@ impl ConsensusParams { }, } } + + pub fn is_bip30_expcetion(&self, hash: &H256, height: u32) -> bool { + (height == 91842 && hash == &H256::from_reversed_str("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) || + (height == 91880 && hash == &H256::from_reversed_str("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")) + } } #[cfg(test)] diff --git a/network/src/lib.rs b/network/src/lib.rs index 1b266d51..4108a9db 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -1,8 +1,12 @@ extern crate chain; +extern crate primitives; extern crate serialization as ser; mod consensus; mod magic; +pub use primitives::hash; + pub use consensus::ConsensusParams; pub use magic::Magic; + diff --git a/script/src/script.rs b/script/src/script.rs index 6f7e0815..54acbca2 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -16,10 +16,6 @@ pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20; /// Maximum script length in bytes pub const MAX_SCRIPT_SIZE: usize = 10000; -/// Threshold for `nLockTime`: below this value it is interpreted as block number, -/// otherwise as UNIX timestamp. -pub const LOCKTIME_THRESHOLD: u32 = 500000000; // Tue Nov 5 00:53:20 1985 UTC - #[derive(PartialEq, Debug)] pub enum ScriptType { NonStandard, diff --git a/script/src/verify.rs b/script/src/verify.rs index d2b8c17c..6ea02240 100644 --- a/script/src/verify.rs +++ b/script/src/verify.rs @@ -1,9 +1,9 @@ use keys::{Public, Signature}; use chain::{ - SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG, + self, SEQUENCE_FINAL, SEQUENCE_LOCKTIME_DISABLE_FLAG, SEQUENCE_LOCKTIME_MASK, SEQUENCE_LOCKTIME_TYPE_FLAG }; -use {script, SignatureVersion, Script, TransactionInputSigner, Num}; +use {SignatureVersion, Script, TransactionInputSigner, Num}; pub trait SignatureChecker { fn check_signature( @@ -64,8 +64,8 @@ impl SignatureChecker for TransactionSignatureChecker { // the nLockTime in the transaction. let lock_time_u32: u32 = lock_time.into(); if !( - (self.signer.lock_time < script::LOCKTIME_THRESHOLD && lock_time_u32 < script::LOCKTIME_THRESHOLD) || - (self.signer.lock_time >= script::LOCKTIME_THRESHOLD && lock_time_u32 >= script::LOCKTIME_THRESHOLD) + (self.signer.lock_time < chain::LOCKTIME_THRESHOLD && lock_time_u32 < chain::LOCKTIME_THRESHOLD) || + (self.signer.lock_time >= chain::LOCKTIME_THRESHOLD && lock_time_u32 >= chain::LOCKTIME_THRESHOLD) ) { return false; } diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index c9d26502..66cb7dc0 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -57,6 +57,13 @@ impl ChainVerifier { } fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> { + if !block.is_final(at_height) { + return Err(Error::NonFinalBlock); + } + + let block_hash = block.hash(); + let consensus_params = self.network.consensus_params(); + // check that difficulty matches the adjusted level if let Some(work) = self.work_required(block, at_height) { if !self.skip_pow && work != block.header().nbits { @@ -69,11 +76,10 @@ impl ChainVerifier { let coinbase_spends = block.transactions()[0].total_spends(); // bip30 - // TODO: add exceptions for (tx_index, tx) in block.transactions.iter().enumerate() { if let Some(meta) = self.store.transaction_meta(&tx.hash()) { - if !meta.is_fully_spent() { - return Err(Error::Transaction(tx_index, TransactionError::BIP30)); + if !meta.is_fully_spent() && !consensus_params.is_bip30_expcetion(&block_hash, at_height) { + return Err(Error::Transaction(tx_index, TransactionError::UnspentTransactionWithTheSameHash)); } } } @@ -178,7 +184,7 @@ impl ChainVerifier { signer: signer, input_index: input_index, }; - let input: Script = input.script_sig().to_vec().into(); + let input: Script = input.script_sig.to_vec().into(); let output: Script = paired_output.script_pubkey.to_vec().into(); if is_strict_p2sh && output.is_pay_to_script_hash() { @@ -250,7 +256,7 @@ impl ChainVerifier { // check that coinbase has a valid signature let coinbase = &block.transactions()[0]; // is_coinbase() = true above guarantees that there is at least one input - let coinbase_script_len = coinbase.inputs[0].script_sig().len(); + let coinbase_script_len = coinbase.inputs[0].script_sig.len(); if coinbase_script_len < 2 || coinbase_script_len > 100 { return Err(Error::CoinbaseSignatureLength(coinbase_script_len)); } @@ -465,6 +471,7 @@ mod tests { let genesis = test_data::block_builder() .transaction() .coinbase() + .output().value(1).build() .build() .transaction() .output().value(50).build() @@ -498,6 +505,7 @@ mod tests { let genesis = test_data::block_builder() .transaction() .coinbase() + .output().value(1).build() .build() .transaction() .output().value(50).build() @@ -535,6 +543,7 @@ mod tests { let genesis = test_data::block_builder() .transaction() .coinbase() + .output().value(1).build() .build() .transaction() .output().value(50).build() diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 24587be5..90b1f619 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -59,6 +59,8 @@ pub enum Error { CoinbaseSignatureLength(usize), /// Block size is invalid Size(usize), + /// Block transactions are not final. + NonFinalBlock, } #[derive(Debug, PartialEq)] @@ -84,8 +86,8 @@ pub enum TransactionError { SigopsP2SH(usize), /// Coinbase transaction is found at position that is not 0 MisplacedCoinbase(usize), - /// Not fully spent transaction with the same hash already exists. - BIP30, + /// Not fully spent transaction with the same hash already exists, bip30. + UnspentTransactionWithTheSameHash, } #[derive(PartialEq, Debug)] diff --git a/verification/src/utils.rs b/verification/src/utils.rs index 6dfbeef9..9719d079 100644 --- a/verification/src/utils.rs +++ b/verification/src/utils.rs @@ -121,7 +121,7 @@ pub fn transaction_sigops(transaction: &chain::Transaction) -> Result Date: Sun, 27 Nov 2016 19:19:31 +0300 Subject: [PATCH 3/3] fix typo --- network/src/consensus.rs | 2 +- verification/src/chain_verifier.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index fca4219a..3890a73f 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -30,7 +30,7 @@ impl ConsensusParams { } } - pub fn is_bip30_expcetion(&self, hash: &H256, height: u32) -> bool { + pub fn is_bip30_exception(&self, hash: &H256, height: u32) -> bool { (height == 91842 && hash == &H256::from_reversed_str("0x00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec")) || (height == 91880 && hash == &H256::from_reversed_str("0x00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721")) } diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 66cb7dc0..b317951b 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -78,7 +78,7 @@ impl ChainVerifier { // bip30 for (tx_index, tx) in block.transactions.iter().enumerate() { if let Some(meta) = self.store.transaction_meta(&tx.hash()) { - if !meta.is_fully_spent() && !consensus_params.is_bip30_expcetion(&block_hash, at_height) { + if !meta.is_fully_spent() && !consensus_params.is_bip30_exception(&block_hash, at_height) { return Err(Error::Transaction(tx_index, TransactionError::UnspentTransactionWithTheSameHash)); } }