From d4a191aec192eec267c38762738f8e28e6961365 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 16 Aug 2017 11:26:03 +0300 Subject: [PATCH 01/35] segwit: initial flush --- chain/src/block.rs | 21 ++++ chain/src/transaction.rs | 149 +++++++++++++++++++++++- chain/src/witness.rs | 0 message/src/common/inventory.rs | 14 ++- message/src/message/message.rs | 8 +- message/src/serialization/mod.rs | 2 +- message/src/serialization/stream.rs | 10 +- network/src/consensus.rs | 118 ++++++++++++++----- network/src/deployments.rs | 32 +++++ network/src/lib.rs | 2 +- p2p/src/net/peer_context.rs | 11 +- p2p/src/p2p.rs | 4 +- p2p/src/protocol/sync.rs | 11 ++ pbtc/config.rs | 2 +- primitives/src/bytes.rs | 7 ++ rpc/src/v1/impls/raw.rs | 1 + script/src/error.rs | 18 +++ script/src/interpreter.rs | 53 ++++++--- script/src/lib.rs | 2 +- script/src/script.rs | 25 ++++ script/src/sign.rs | 93 +++++++++------ serialization/src/lib.rs | 5 +- serialization/src/stream.rs | 22 +++- sync/src/inbound_connection.rs | 2 + sync/src/synchronization_client_core.rs | 4 +- sync/src/synchronization_executor.rs | 28 +++++ sync/src/synchronization_server.rs | 20 +++- sync/src/synchronization_verifier.rs | 2 +- test-data/src/block.rs | 1 + test-data/src/chain_builder.rs | 2 + verification/src/accept_block.rs | 25 ++-- verification/src/accept_chain.rs | 5 +- verification/src/accept_transaction.rs | 5 +- verification/src/chain_verifier.rs | 6 +- verification/src/deployments.rs | 44 ++++++- verification/src/verify_transaction.rs | 13 ++- 36 files changed, 634 insertions(+), 133 deletions(-) create mode 100644 chain/src/witness.rs diff --git a/chain/src/block.rs b/chain/src/block.rs index 86dbcfaf..1ae890db 100644 --- a/chain/src/block.rs +++ b/chain/src/block.rs @@ -11,6 +11,12 @@ pub struct Block { pub transactions: Vec, } +#[derive(Debug, PartialEq, Clone)] +pub struct WitnessBlock<'a> { + pub block_header: BlockHeader, + pub transactions: Vec<&'a Transaction>, +} + impl From<&'static str> for Block { fn from(s: &'static str) -> Self { deserialize(&s.from_hex().unwrap() as &[u8]).unwrap() @@ -32,6 +38,17 @@ impl Block { merkle_root(&hashes) } + /// Returns block's witness merkle root. + pub fn witness_merkle_root(&self) -> H256 { + let hashes = self.transactions.iter() + .enumerate() + .map(|(i, tx)| match i { + 0 => H256::from(0), + _ => tx.witness_hash() + }).collect::>(); + merkle_root(&hashes) + } + pub fn transactions(&self) -> &[Transaction] { &self.transactions } @@ -43,6 +60,10 @@ impl Block { pub fn hash(&self) -> H256 { self.block_header.hash() } + + pub fn cost(&self) -> u64 { + unimplemented!() + } } #[cfg(test)] diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index a860959e..9effc7a8 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -1,13 +1,18 @@ //! Bitcoin trainsaction. //! https://en.bitcoin.it/wiki/Protocol_documentation#tx +use std::io; use heapsize::HeapSizeOf; use hex::FromHex; use bytes::Bytes; -use ser::{deserialize, serialize}; +use ser::{deserialize, serialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; use crypto::dhash256; use hash::H256; use constants::{SEQUENCE_FINAL, LOCKTIME_THRESHOLD}; +use ser::{Error, Serializable, Deserializable, Stream, Reader}; + +const WITNESS_MARKER: u8 = 0; +const WITNESS_FLAG: u8 = 1; #[derive(Debug, PartialEq, Eq, Clone, Default, Serializable, Deserializable)] pub struct OutPoint { @@ -28,11 +33,12 @@ impl OutPoint { } } -#[derive(Debug, PartialEq, Default, Clone, Serializable, Deserializable)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct TransactionInput { pub previous_output: OutPoint, pub script_sig: Bytes, pub sequence: u32, + pub script_witness: Vec, } impl TransactionInput { @@ -41,17 +47,23 @@ impl TransactionInput { previous_output: OutPoint::null(), script_sig: script_sig, sequence: SEQUENCE_FINAL, + script_witness: vec![], } } pub fn is_final(&self) -> bool { self.sequence == SEQUENCE_FINAL } + + pub fn has_witness(&self) -> bool { + !self.script_witness.is_empty() + } } impl HeapSizeOf for TransactionInput { fn heap_size_of_children(&self) -> usize { - self.script_sig.heap_size_of_children() + self.script_sig.heap_size_of_children() + + self.script_witness.heap_size_of_children() } } @@ -76,7 +88,7 @@ impl HeapSizeOf for TransactionOutput { } } -#[derive(Debug, PartialEq, Default, Clone, Serializable, Deserializable)] +#[derive(Debug, PartialEq, Default, Clone)] pub struct Transaction { pub version: i32, pub inputs: Vec, @@ -101,6 +113,10 @@ impl Transaction { dhash256(&serialize(self)) } + pub fn witness_hash(&self) -> H256 { + dhash256(&serialize_with_flags(self, SERIALIZE_TRANSACTION_WITNESS)) + } + pub fn inputs(&self) -> &[TransactionInput] { &self.inputs } @@ -149,6 +165,10 @@ impl Transaction { self.inputs.iter().all(TransactionInput::is_final) } + pub fn has_witness(&self) -> bool { + self.inputs.iter().any(TransactionInput::has_witness) + } + pub fn total_spends(&self) -> u64 { let mut result = 0u64; for output in self.outputs.iter() { @@ -159,13 +179,93 @@ impl Transaction { } result } + + pub fn cost(&self) -> u64 { + unimplemented!() + } +} + +impl Serializable for TransactionInput { + fn serialize(&self, stream: &mut Stream) { + stream + .append(&self.previous_output) + .append(&self.script_sig) + .append(&self.sequence); + } +} + +impl Deserializable for TransactionInput { + fn deserialize(reader: &mut Reader) -> Result where Self: Sized, T: io::Read { + Ok(TransactionInput { + previous_output: reader.read()?, + script_sig: reader.read()?, + sequence: reader.read()?, + script_witness: vec![], + }) + } +} + +impl Serializable for Transaction { + fn serialize(&self, stream: &mut Stream) { + let include_transaction_witness = stream.include_transaction_witness() && self.has_witness(); + match include_transaction_witness { + false => stream + .append(&self.version) + .append_list(&self.inputs) + .append_list(&self.outputs) + .append(&self.lock_time), + true => { + stream + .append(&self.version) + .append(&WITNESS_MARKER) + .append(&WITNESS_FLAG) + .append_list(&self.inputs) + .append_list(&self.outputs); + for input in &self.inputs { + stream.append_list(&input.script_witness); + } + stream.append(&self.lock_time) + } + }; + } +} + +impl Deserializable for Transaction { + fn deserialize(reader: &mut Reader) -> Result where Self: Sized, T: io::Read { + let version = reader.read()?; + let mut inputs: Vec = reader.read_list()?; + let read_witness = if inputs.is_empty() { + let witness_flag: u8 = reader.read()?; + if witness_flag != WITNESS_FLAG { + return Err(Error::MalformedData); + } + + inputs = reader.read_list()?; + true + } else { + false + }; + let outputs = reader.read_list()?; + if read_witness { + for input in inputs.iter_mut() { + input.script_witness = reader.read_list()?; + } + } + + Ok(Transaction { + version: version, + inputs: inputs, + outputs: outputs, + lock_time: reader.read()?, + }) + } } #[cfg(test)] mod tests { use hash::H256; use ser::Serializable; - use super::Transaction; + use super::{Transaction, TransactionInput, OutPoint, TransactionOutput}; // real transaction from block 80000 // https://blockchain.info/rawtx/5a4ebf66822b0b2d56bd9dc64ece0bc38ee7844a23ff1d7320a88c5fdb2ad3e2 @@ -183,6 +283,7 @@ mod tests { let tx_output = &t.outputs[0]; assert_eq!(tx_output.value, 5000000000); assert_eq!(tx_output.script_pubkey, "76a914404371705fa9bd789a2fcd52d2c580b65d35549d88ac".into()); + assert!(!t.has_witness()); } #[test] @@ -198,4 +299,42 @@ mod tests { let tx: Transaction = raw_tx.into(); assert_eq!(tx.serialized_size(), raw_tx.len() / 2); } + + #[test] + fn test_transaction_reader_with_witness() { + // test case from https://github.com/bitcoin/bips/blob/master/bip-0143.mediawiki + let actual: Transaction = "01000000000102fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f00000000494830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01eeffffffef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a0100000000ffffffff02202cb206000000001976a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac9093510d000000001976a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac000247304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee0121025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee635711000000".into(); + let expected = Transaction { + version: 1, + inputs: vec![TransactionInput { + previous_output: OutPoint { + hash: "fff7f7881a8099afa6940d42d1e7f6362bec38171ea3edf433541db4e4ad969f".into(), + index: 0, + }, + script_sig: "4830450221008b9d1dc26ba6a9cb62127b02742fa9d754cd3bebf337f7a55d114c8e5cdd30be022040529b194ba3f9281a99f2b1c0a19c0489bc22ede944ccf4ecbab4cc618ef3ed01".into(), + sequence: 0xffffffee, + script_witness: vec![], + }, TransactionInput { + previous_output: OutPoint { + hash: "ef51e1b804cc89d182d279655c3aa89e815b1b309fe287d9b2b55d57b90ec68a".into(), + index: 1, + }, + script_sig: "".into(), + sequence: 0xffffffff, + script_witness: vec![ + "304402203609e17b84f6a7d30c80bfa610b5b4542f32a8a0d5447a12fb1366d7f01cc44a0220573a954c4518331561406f90300e8f3358f51928d43c212a8caed02de67eebee01".into(), + "025476c2e83188368da1ff3e292e7acafcdb3566bb0ad253f62fc70f07aeee6357".into(), + ], + }], + outputs: vec![TransactionOutput { + value: 0x0000000006b22c20, + script_pubkey: "76a9148280b37df378db99f66f85c95a783a76ac7a6d5988ac".into(), + }, TransactionOutput { + value: 0x000000000d519390, + script_pubkey: "76a9143bde42dbee7e4dbe6a21b2d50ce2f0167faa815988ac".into(), + }], + lock_time: 0x00000011, + }; + assert_eq!(actual, expected); + } } diff --git a/chain/src/witness.rs b/chain/src/witness.rs new file mode 100644 index 00000000..e69de29b diff --git a/message/src/common/inventory.rs b/message/src/common/inventory.rs index 263a4f20..996ec41d 100644 --- a/message/src/common/inventory.rs +++ b/message/src/common/inventory.rs @@ -3,13 +3,16 @@ use hash::H256; use ser::{Serializable, Stream, Deserializable, Reader, Error as ReaderError}; #[derive(Debug, PartialEq, Clone, Copy)] -#[repr(u8)] +#[repr(u32)] pub enum InventoryType { Error = 0, MessageTx = 1, MessageBlock = 2, MessageFilteredBlock = 3, MessageCompactBlock = 4, + MessageWitnessTx = 0x40000001, + MessageWitnessBlock = 0x40000002, + MessageWitnessFilteredBlock = 0x40000003, } impl InventoryType { @@ -20,6 +23,9 @@ impl InventoryType { 2 => Some(InventoryType::MessageBlock), 3 => Some(InventoryType::MessageFilteredBlock), 4 => Some(InventoryType::MessageCompactBlock), + 0x40000001 => Some(InventoryType::MessageWitnessTx), + 0x40000002 => Some(InventoryType::MessageWitnessBlock), + 0x40000003 => Some(InventoryType::MessageWitnessFilteredBlock), _ => None } } @@ -122,11 +128,17 @@ mod tests { assert_eq!(2u32, InventoryType::MessageBlock.into()); assert_eq!(3u32, InventoryType::MessageFilteredBlock.into()); assert_eq!(4u32, InventoryType::MessageCompactBlock.into()); + assert_eq!(0x40000001u32, InventoryType::MessageWitnessTx.into()); + assert_eq!(0x40000002u32, InventoryType::MessageWitnessBlock.into()); + assert_eq!(0x40000003u32, InventoryType::MessageWitnessFilteredBlock.into()); assert_eq!(InventoryType::from_u32(0).unwrap(), InventoryType::Error); assert_eq!(InventoryType::from_u32(1).unwrap(), InventoryType::MessageTx); assert_eq!(InventoryType::from_u32(2).unwrap(), InventoryType::MessageBlock); assert_eq!(InventoryType::from_u32(3).unwrap(), InventoryType::MessageFilteredBlock); assert_eq!(InventoryType::from_u32(4).unwrap(), InventoryType::MessageCompactBlock); + assert_eq!(InventoryType::from_u32(0x40000001).unwrap(), InventoryType::MessageWitnessTx); + assert_eq!(InventoryType::from_u32(0x40000002).unwrap(), InventoryType::MessageWitnessBlock); + assert_eq!(InventoryType::from_u32(0x40000003).unwrap(), InventoryType::MessageWitnessFilteredBlock); } } diff --git a/message/src/message/message.rs b/message/src/message/message.rs index fa502ebc..d55015c5 100644 --- a/message/src/message/message.rs +++ b/message/src/message/message.rs @@ -2,7 +2,7 @@ use ser::Stream; use bytes::{TaggedBytes, Bytes}; use network::Magic; use common::Command; -use serialization::serialize_payload; +use serialization::serialize_payload_with_flags; use {Payload, MessageResult, MessageHeader}; pub fn to_raw_message(magic: Magic, command: Command, payload: &Bytes) -> Bytes { @@ -19,7 +19,11 @@ pub struct Message { impl Message where T: Payload { pub fn new(magic: Magic, version: u32, payload: &T) -> MessageResult { - let serialized = try!(serialize_payload(payload, version)); + Self::with_flags(magic, version, payload, 0) + } + + pub fn with_flags(magic: Magic, version: u32, payload: &T, serialization_flags: u32) -> MessageResult { + let serialized = try!(serialize_payload_with_flags(payload, version, serialization_flags)); let message = Message { bytes: TaggedBytes::new(to_raw_message(magic, T::command().into(), &serialized)), diff --git a/message/src/serialization/mod.rs b/message/src/serialization/mod.rs index b489a951..0648af83 100644 --- a/message/src/serialization/mod.rs +++ b/message/src/serialization/mod.rs @@ -1,5 +1,5 @@ mod stream; mod reader; -pub use self::stream::serialize_payload; +pub use self::stream::{serialize_payload, serialize_payload_with_flags}; pub use self::reader::deserialize_payload; diff --git a/message/src/serialization/stream.rs b/message/src/serialization/stream.rs index cd4effd8..5c27675c 100644 --- a/message/src/serialization/stream.rs +++ b/message/src/serialization/stream.rs @@ -3,7 +3,11 @@ use ser::Stream; use {Payload, Error, MessageResult}; pub fn serialize_payload(t: &T, version: u32) -> MessageResult where T: Payload { - let mut stream = PayloadStream::new(version); + serialize_payload_with_flags(t, version, 0) +} + +pub fn serialize_payload_with_flags(t: &T, version: u32, serialization_flags: u32) -> MessageResult where T: Payload { + let mut stream = PayloadStream::new(version, serialization_flags); try!(stream.append(t)); Ok(stream.out()) } @@ -14,9 +18,9 @@ pub struct PayloadStream { } impl PayloadStream { - pub fn new(version: u32) -> Self { + pub fn new(version: u32, serialization_flags: u32) -> Self { PayloadStream { - stream: Stream::default(), + stream: Stream::with_flags(serialization_flags), version: version, } } diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 13655cd9..832f95ea 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -1,12 +1,20 @@ use std::cmp::max; use hash::H256; -use {Magic, Deployment}; +use {Magic, Deployment, Deployments}; /// First block of SegWit2x fork. pub const SEGWIT2X_FORK_BLOCK: u32 = 0xFFFFFFFF; // not known (yet?) /// First block of BitcoinCash fork. pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitcoin-cash/block/478559 +mod segwit { + /// The maximum allowed weight for a block, see BIP 141 (network rule) + pub const MAX_BLOCK_WEIGHT: usize = 4_000_000; + + /// Witness scale factor. + pub const WITNESS_SCALE_FACTOR: usize = 4; +} + #[derive(Debug, Clone)] /// Parameters that influence chain consensus. pub struct ConsensusParams { @@ -76,7 +84,16 @@ impl ConsensusParams { timeout: 1493596800, activation: Some(770112), }), - segwit_deployment: None, + segwit_deployment: match fork { + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { + name: "segwit", + bit: 1, + start_time: 1479168000, + timeout: 1510704000, + activation: None, + }), + ConsensusFork::BitcoinCash(_) => None, + }, }, Magic::Testnet => ConsensusParams { network: magic, @@ -94,7 +111,16 @@ impl ConsensusParams { timeout: 1493596800, activation: Some(419328), }), - segwit_deployment: None, + segwit_deployment: match fork { + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { + name: "segwit", + bit: 1, + start_time: 1462060800, + timeout: 1493596800, + activation: None, + }), + ConsensusFork::BitcoinCash(_) => None, + }, }, Magic::Regtest | Magic::Unitest => ConsensusParams { network: magic, @@ -112,7 +138,16 @@ impl ConsensusParams { timeout: 0, activation: Some(0), }), - segwit_deployment: None, + segwit_deployment: match fork { + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { + name: "segwit", + bit: 1, + start_time: 0, + timeout: ::std::u32::MAX, + activation: None, + }), + ConsensusFork::BitcoinCash(_) => None, + }, }, } } @@ -134,26 +169,39 @@ impl ConsensusFork { 160_000 } - pub fn min_block_size(&self, height: u32) -> usize { - match *self { - ConsensusFork::SegWit2x(fork_height) if height == fork_height => 0, - // size of first fork block must be larger than 1MB - ConsensusFork::BitcoinCash(fork_height) if height == fork_height => 1_000_001, - ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 0, - } - } - pub fn max_block_size(&self, height: u32) -> usize { match *self { - ConsensusFork::SegWit2x(fork_height) if height >= fork_height => 2_000_000, ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => 8_000_000, ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 1_000_000, } } - pub fn max_transaction_size(&self, _height: u32) -> usize { - // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 - 1_000_000 + pub fn check_block_size(&self, size: usize, height: u32, deployments: &Deployments) -> bool { + match *self { + // bitcoin cash fork block must be > 1_000_0000 and <= 8_000_000 + ConsensusFork::BitcoinCash(fork_height) if height == fork_height => + size > 1_000_000 && size <= 8_000_000, + // bitcoin cash support blocks up to 8_000_000 + ConsensusFork::BitcoinCash(fork_height) if height > fork_height => + size <= 8_000_000, + // when segwit is deployed, this expression is used. which, in turn, also allows block size <= 1_000_000 + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => + size.saturating_mul(segwit::WITNESS_SCALE_FACTOR) <= segwit::MAX_BLOCK_WEIGHT, + // without segwit and before fork, max size is 1_000_000 + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => + size <= 1_000_000, + } + } + + pub fn check_transaction_size(&self, size: usize, deployments: &Deployments) -> bool { + match *self { + // when segwit is deployed, this expression is used. which, in turn, is the same max tx size 1_000_000 + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => + size.saturating_mul(segwit::WITNESS_SCALE_FACTOR) <= segwit::MAX_BLOCK_WEIGHT, + // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 + // ConsensusFork::NoFork | ConsensusFork::SegWit2x: max size of tx is 1_000_000 + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => size <= 1_000_000, + } } pub fn max_block_sigops(&self, height: u32, block_size: usize) -> usize { @@ -170,6 +218,7 @@ impl ConsensusFork { mod tests { use super::super::Magic; use super::{ConsensusParams, ConsensusFork}; + use deployments::tests::DummyDeployments; #[test] fn test_consensus_params_bip34_height() { @@ -207,19 +256,30 @@ mod tests { } #[test] - fn test_consensus_fork_min_block_size() { - assert_eq!(ConsensusFork::NoFork.min_block_size(0), 0); - assert_eq!(ConsensusFork::SegWit2x(100).min_block_size(0), 0); - assert_eq!(ConsensusFork::SegWit2x(100).min_block_size(100), 0); - assert_eq!(ConsensusFork::BitcoinCash(100).min_block_size(0), 0); - assert_eq!(ConsensusFork::BitcoinCash(100).min_block_size(100), 1_000_001); - } + fn test_consensus_fork_check_transaction_size() { + assert_eq!(ConsensusFork::NoFork.check_transaction_size(800_000, &DummyDeployments::default()), true); + assert_eq!(ConsensusFork::NoFork.check_transaction_size(1_000_000, &DummyDeployments::default()), true); + assert_eq!(ConsensusFork::NoFork.check_transaction_size(4_000_000, &DummyDeployments::default()), false); - #[test] - fn test_consensus_fork_max_transaction_size() { - assert_eq!(ConsensusFork::NoFork.max_transaction_size(0), 1_000_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_transaction_size(0), 1_000_000); - assert_eq!(ConsensusFork::BitcoinCash(100).max_transaction_size(0), 1_000_000); + assert_eq!(ConsensusFork::NoFork.check_transaction_size(800_000, &DummyDeployments::deployed()), true); + assert_eq!(ConsensusFork::NoFork.check_transaction_size(1_000_000, &DummyDeployments::deployed()), true); + assert_eq!(ConsensusFork::NoFork.check_transaction_size(4_000_000, &DummyDeployments::deployed()), false); + + assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(800_000, &DummyDeployments::default()), true); + assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(1_000_000, &DummyDeployments::default()), true); + assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(4_000_000, &DummyDeployments::default()), false); + + assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(800_000, &DummyDeployments::deployed()), true); + assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(1_000_000, &DummyDeployments::deployed()), true); + assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(4_000_000, &DummyDeployments::deployed()), false); + + assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(800_000, &DummyDeployments::default()), true); + assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(1_000_000, &DummyDeployments::default()), true); + assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(4_000_000, &DummyDeployments::default()), false); + + assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(800_000, &DummyDeployments::deployed()), true); + assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(1_000_000, &DummyDeployments::deployed()), true); + assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(4_000_000, &DummyDeployments::deployed()), false); } #[test] diff --git a/network/src/deployments.rs b/network/src/deployments.rs index 4428b22f..c5304673 100644 --- a/network/src/deployments.rs +++ b/network/src/deployments.rs @@ -15,9 +15,41 @@ pub struct Deployment { pub activation: Option, } +/// Deployments state. +pub trait Deployments { + /// Is deployment currently active? + fn is_active(&self, name: &str) -> bool; +} + impl Deployment { pub fn matches(&self, version: u32) -> bool { (version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS && (version & (1 << self.bit)) != 0 } } +#[cfg(test)] +pub mod tests { + use super::Deployments; + + #[derive(Default, Debug)] + pub struct DummyDeployments { + pub segwit_active: bool, + } + + impl DummyDeployments { + pub fn deployed() -> Self { + DummyDeployments { + segwit_active: true, + } + } + } + + impl Deployments for DummyDeployments { + fn is_active(&self, name: &str) -> bool { + match name { + "segwit" => self.segwit_active, + _ => false, + } + } + } +} diff --git a/network/src/lib.rs b/network/src/lib.rs index 7b97f698..b3a85ab3 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -9,6 +9,6 @@ mod magic; pub use primitives::{hash, compact}; pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK}; -pub use deployments::Deployment; +pub use deployments::{Deployment, Deployments}; pub use magic::Magic; diff --git a/p2p/src/net/peer_context.rs b/p2p/src/net/peer_context.rs index b832f4aa..5ccc79ba 100644 --- a/p2p/src/net/peer_context.rs +++ b/p2p/src/net/peer_context.rs @@ -61,7 +61,12 @@ impl PeerContext { /// Request is always automatically send. pub fn send_request(&self, payload: &T) where T: Payload { - let send = Context::send_to_peer(self.context.clone(), self.info.id, payload); + self.send_request_with_flags(payload, 0) + } + + /// Request is always automatically send. + pub fn send_request_with_flags(&self, payload: &T, serialization_flags: u32) where T: Payload { + let send = Context::send_to_peer(self.context.clone(), self.info.id, payload, serialization_flags); self.context.spawn(send); } @@ -94,14 +99,14 @@ impl PeerContext { let mut queue = self.response_queue.lock(); if is_final { if sync.permission_for_response(id) { - let send = Context::send_to_peer(self.context.clone(), self.info.id, payload); + let send = Context::send_to_peer(self.context.clone(), self.info.id, payload, 0); self.context.spawn(send); self.send_awaiting(&mut sync, &mut queue, id); } else { queue.push_finished_response(id, self.to_message(payload).into()); } } else if sync.is_permitted(id) { - let send = Context::send_to_peer(self.context.clone(), self.info.id, payload); + let send = Context::send_to_peer(self.context.clone(), self.info.id, payload, 0); self.context.spawn(send); } else { queue.push_unfinished_response(id, self.to_message(payload).into()); diff --git a/p2p/src/p2p.rs b/p2p/src/p2p.rs index e96e4a58..14e5871b 100644 --- a/p2p/src/p2p.rs +++ b/p2p/src/p2p.rs @@ -320,11 +320,11 @@ impl Context { } /// Send message to a channel with given peer id. - pub fn send_to_peer(context: Arc, peer: PeerId, payload: &T) -> IoFuture<()> where T: Payload { + pub fn send_to_peer(context: Arc, peer: PeerId, payload: &T, serialization_flags: u32) -> IoFuture<()> where T: Payload { match context.connections.channel(peer) { Some(channel) => { let info = channel.peer_info(); - let message = Message::new(info.magic, info.version, payload).expect("failed to create outgoing message"); + let message = Message::with_flags(info.magic, info.version, payload, serialization_flags).expect("failed to create outgoing message"); channel.session().stats().lock().report_send(T::command().into(), message.len()); Context::send(context, channel, message) }, diff --git a/p2p/src/protocol/sync.rs b/p2p/src/protocol/sync.rs index 34386dd7..915224ec 100644 --- a/p2p/src/protocol/sync.rs +++ b/p2p/src/protocol/sync.rs @@ -3,6 +3,7 @@ use bytes::Bytes; use message::{Command, Error, Payload, types, deserialize_payload}; use protocol::Protocol; use net::PeerContext; +use ser::SERIALIZE_TRANSACTION_WITNESS; pub type InboundSyncConnectionRef = Box; pub type OutboundSyncConnectionRef = Arc; @@ -43,6 +44,8 @@ pub trait OutboundSyncConnection : Send + Sync { fn send_getheaders(&self, message: &types::GetHeaders); fn send_transaction(&self, message: &types::Tx); fn send_block(&self, message: &types::Block); + fn send_witness_transaction(&self, message: &types::Tx); + fn send_witness_block(&self, message: &types::Block); fn send_headers(&self, message: &types::Headers); fn respond_headers(&self, message: &types::Headers, id: u32); fn send_mempool(&self, message: &types::MemPool); @@ -98,6 +101,14 @@ impl OutboundSyncConnection for OutboundSync { self.context.send_request(message); } + fn send_witness_transaction(&self, message: &types::Tx) { + self.context.send_request_with_flags(message, SERIALIZE_TRANSACTION_WITNESS); + } + + fn send_witness_block(&self, message: &types::Block) { + self.context.send_request_with_flags(message, SERIALIZE_TRANSACTION_WITNESS); + } + fn send_headers(&self, message: &types::Headers) { self.context.send_request(message); } diff --git a/pbtc/config.rs b/pbtc/config.rs index 1b2463d7..e960cc5d 100644 --- a/pbtc/config.rs +++ b/pbtc/config.rs @@ -115,7 +115,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { let services = Services::default().with_network(true); let services = match consensus.fork { ConsensusFork::BitcoinCash(_) => services.with_bitcoin_cash(true), - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => services, + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => services.with_witness(true), }; let verification_level = match matches.value_of("verification-level") { diff --git a/primitives/src/bytes.rs b/primitives/src/bytes.rs index 9b5e6621..b1f4d6a6 100644 --- a/primitives/src/bytes.rs +++ b/primitives/src/bytes.rs @@ -2,6 +2,7 @@ use std::{ops, str, fmt, io, marker}; use hex::{ToHex, FromHex, FromHexError}; +use heapsize::HeapSizeOf; /// Wrapper around `Vec` #[derive(Default, PartialEq, Clone, Eq, Hash)] @@ -25,6 +26,12 @@ impl Bytes { } } +impl HeapSizeOf for Bytes { + fn heap_size_of_children(&self) -> usize { + self.0.heap_size_of_children() + } +} + impl<'a> From<&'a [u8]> for Bytes { fn from(v: &[u8]) -> Self { Bytes(v.into()) diff --git a/rpc/src/v1/impls/raw.rs b/rpc/src/v1/impls/raw.rs index 0d32a76b..b4860970 100644 --- a/rpc/src/v1/impls/raw.rs +++ b/rpc/src/v1/impls/raw.rs @@ -48,6 +48,7 @@ impl RawClientCore { }, script_sig: GlobalBytes::new(), // default script sequence: input.sequence.unwrap_or(default_sequence), + script_witness: vec![], }).collect(); // prepare outputs diff --git a/script/src/error.rs b/script/src/error.rs index dbaab08e..b23b81c8 100644 --- a/script/src/error.rs +++ b/script/src/error.rs @@ -51,6 +51,15 @@ pub enum Error { // Softfork safeness DiscourageUpgradableNops, + + // SegWit-related errors + WitnessProgramWrongLength, + WitnessProgramWitnessEmpty, + WitnessProgramMismatch, + WitnessMalleated, + WitnessMalleatedP2SH, + WitnessUnexpected, + WitnessPubKeyType, } impl fmt::Display for Error { @@ -101,6 +110,15 @@ impl fmt::Display for Error { // Softfork safeness Error::DiscourageUpgradableNops => "Discourage Upgradable Nops".fmt(f), + + // SegWit-related errors + Error::WitnessProgramWrongLength => "Witness program has incorrect length".fmt(f), + Error::WitnessProgramWitnessEmpty => "Witness program was passed an empty witness".fmt(f), + Error::WitnessProgramMismatch => "Witness program hash mismatch".fmt(f), + Error::WitnessMalleated => "Witness requires empty scriptSig".fmt(f), + Error::WitnessMalleatedP2SH => "Witness requires only-redeemscript scriptSig".fmt(f), + Error::WitnessUnexpected => "Witness provided for non-witness script".fmt(f), + Error::WitnessPubKeyType => "Using non-compressed keys in segwit".fmt(f), } } } diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 95876f30..787d76c2 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -5,7 +5,7 @@ use chain::constants::SEQUENCE_LOCKTIME_DISABLE_FLAG; use crypto::{sha1, sha256, dhash160, dhash256, ripemd160}; use sign::{SignatureVersion, Sighash}; use { - script, Builder, Script, Num, VerificationFlags, Opcode, Error, SignatureChecker, Stack + script, Builder, Script, ScriptWitness, Num, VerificationFlags, Opcode, Error, SignatureChecker, Stack }; /// Helper function. @@ -244,6 +244,7 @@ fn cast_to_bool(data: &[u8]) -> bool { pub fn verify_script( script_sig: &Script, script_pubkey: &Script, + witness: &ScriptWitness, flags: &VerificationFlags, checker: &SignatureChecker, version: SignatureVersion, @@ -266,6 +267,18 @@ pub fn verify_script( return Err(Error::EvalFalse); } + // Verify witness program + let mut verify_cleanstack = flags.verify_cleanstack; + if flags.verify_witness { + if let Some((witness_version, witness_program)) = script_pubkey.parse_witness_program() { + if !script_sig.is_empty() { + return Err(Error::WitnessMalleated); + } + verify_witness_program(witness, witness_version, witness_program, flags, checker)?; + verify_cleanstack = false; + } + } + // Additional validation for spend-to-script-hash transactions: if flags.verify_p2sh && script_pubkey.is_pay_to_script_hash() { if !script_sig.is_push_only() { @@ -290,7 +303,7 @@ pub fn verify_script( // The CLEANSTACK check is only performed after potential P2SH evaluation, // as the non-P2SH evaluation of a P2SH script will obviously not result in // a clean stack (the P2SH inputs remain). The same holds for witness evaluation. - if flags.verify_cleanstack { + if verify_cleanstack { // Disallow CLEANSTACK without P2SH, as otherwise a switch CLEANSTACK->P2SH+CLEANSTACK // would be possible, which is not a softfork (and P2SH should be one). assert!(flags.verify_p2sh); @@ -303,6 +316,16 @@ pub fn verify_script( Ok(()) } +fn verify_witness_program( + _witness: &ScriptWitness, + _witness_version: u8, + _witness_program: &[u8], + _flags: &VerificationFlags, + _checker: &SignatureChecker +) -> Result<(), Error> { + unimplemented!() +} + /// Evaluautes the script #[cfg_attr(feature="cargo-clippy", allow(match_same_arms))] pub fn eval_script( @@ -920,7 +943,7 @@ mod tests { use chain::Transaction; use sign::SignatureVersion; use { - Opcode, Script, VerificationFlags, Builder, Error, Num, TransactionInputSigner, + Opcode, Script, ScriptWitness, VerificationFlags, Builder, Error, Num, TransactionInputSigner, NoopSignatureChecker, TransactionSignatureChecker, Stack }; use super::{eval_script, verify_script, is_public_key}; @@ -1870,7 +1893,7 @@ mod tests { let output: Script = "76a914df3bd30160e6c6145baaf2c88a8844c13a00d1d588ac".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/rawtx/02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d @@ -1887,7 +1910,7 @@ mod tests { let output: Script = "a9141a8b0026343166625c7475f01e48b5ede8c0252e87".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/en/tx/12b5633bad1f9c167d523ad1aa1947b2732a865bf5414eab2f9e5ae5d5c191ba?show_adv=true @@ -1904,7 +1927,7 @@ mod tests { let output: Script = "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/rawtx/fb0a1d8d34fa5537e461ac384bac761125e1bfa7fec286fa72511240fa66864d @@ -1921,7 +1944,7 @@ mod tests { let output: Script = "76a9147a2a3b481ca80c4ba7939c54d9278e50189d94f988ac".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/rawtx/eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb @@ -1939,12 +1962,12 @@ mod tests { let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); let flags = VerificationFlags::default() .verify_p2sh(true) .verify_locktime(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Err(Error::NumberOverflow)); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Err(Error::NumberOverflow)); } // https://blockchain.info/rawtx/54fabd73f1d20c980a0686bf0035078e07f69c58437e4d586fb29aa0bee9814f @@ -1960,7 +1983,7 @@ mod tests { let input: Script = "483045022100d92e4b61452d91a473a43cde4b469a472467c0ba0cbd5ebba0834e4f4762810402204802b76b7783db57ac1f61d2992799810e173e91055938750815b6d8a675902e014f".into(); let output: Script = "76009f69905160a56b210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71ad6c".into(); let flags = VerificationFlags::default(); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); } #[test] @@ -2013,7 +2036,7 @@ mod tests { let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); } @@ -2064,7 +2087,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount, &script_pubkey, SignatureVersion::ForkId, sighashtype); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &flags, &checker, SignatureVersion::ForkId), Ok(())); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags, &checker, SignatureVersion::ForkId), Ok(())); } // signature with wrong amount @@ -2072,7 +2095,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount + 1, &script_pubkey, SignatureVersion::ForkId, sighashtype); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &flags, &checker, SignatureVersion::ForkId), Err(Error::EvalFalse)); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags, &checker, SignatureVersion::ForkId), Err(Error::EvalFalse)); } // fork-id signature passed when not expected @@ -2080,7 +2103,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount + 1, &script_pubkey, SignatureVersion::ForkId, sighashtype); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &flags, &checker, SignatureVersion::Base), Err(Error::EvalFalse)); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Err(Error::EvalFalse)); } // non-fork-id signature passed when expected @@ -2088,7 +2111,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount + 1, &script_pubkey, SignatureVersion::Base, 1); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &flags.verify_strictenc(true), &checker, SignatureVersion::ForkId), Err(Error::SignatureMustUseForkId)); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags.verify_strictenc(true), &checker, SignatureVersion::ForkId), Err(Error::SignatureMustUseForkId)); } } } diff --git a/script/src/lib.rs b/script/src/lib.rs index bcf75eef..454ebada 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -24,7 +24,7 @@ pub use self::flags::VerificationFlags; pub use self::interpreter::{eval_script, verify_script}; pub use self::opcode::Opcode; pub use self::num::Num; -pub use self::script::{Script, ScriptType, ScriptAddress}; +pub use self::script::{Script, ScriptType, ScriptAddress, ScriptWitness}; pub use self::sign::{TransactionInputSigner, UnsignedTransactionInput, SignatureVersion}; pub use self::stack::Stack; pub use self::verify::{SignatureChecker, NoopSignatureChecker, TransactionSignatureChecker}; diff --git a/script/src/script.rs b/script/src/script.rs index 2e18073e..00265c7f 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -99,6 +99,11 @@ impl Script { self.data.clone() } + /// Is empty script + pub fn is_empty(&self) -> bool { + self.data.len() == 0 + } + /// Extra-fast test for pay-to-public-key-hash (P2PKH) scripts. pub fn is_pay_to_public_key_hash(&self) -> bool { self.data.len() == 25 && @@ -139,6 +144,20 @@ impl Script { self.data[1] == Opcode::OP_PUSHBYTES_20 as u8 } + /// Parse witness program. Returns Some(witness program version, code) or None if not a witness program. + pub fn parse_witness_program(&self) -> Option<(u8, &[u8])> { + if self.data.len() > 4 || self.data.len() > 42 || self.data.len() != self.data[1] as usize + 2 { + return None; + } + let witness_version = match Opcode::from_u8(self.data[0]) { + Some(Opcode::OP_0) => 0, + Some(x) if x >= Opcode::OP_1 && x <= Opcode::OP_16 => (x as u8) - (Opcode::OP_1 as u8) - 1, + _ => return None, + }; + let witness_program = &self.data[2..]; + Some((witness_version, witness_program)) + } + /// Extra-fast test for pay-to-witness-script-hash scripts. pub fn is_pay_to_witness_script_hash(&self) -> bool { self.data.len() == 34 && @@ -331,6 +350,10 @@ impl Script { ScriptType::Multisig } else if self.is_null_data_script() { ScriptType::NullData + } else if self.is_pay_to_witness_key_hash() { + ScriptType::WitnessKey + } else if self.is_pay_to_witness_script_hash() { + ScriptType::WitnessScript } else { ScriptType::NonStandard } @@ -547,6 +570,8 @@ impl fmt::Display for Script { } } +pub struct ScriptWitness; + #[cfg(test)] mod tests { use {Builder, Opcode}; diff --git a/script/src/sign.rs b/script/src/sign.rs index 50680e3d..da953afa 100644 --- a/script/src/sign.rs +++ b/script/src/sign.rs @@ -143,7 +143,7 @@ impl TransactionInputSigner { match sigversion { SignatureVersion::ForkId if sighash.fork_id => self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash), SignatureVersion::Base | SignatureVersion::ForkId => self.signature_hash_original(input_index, script_pubkey, sighashtype, sighash), - _ => 1u8.into(), + SignatureVersion::WitnessV0 => self.signature_hash_witness0(input_index, input_amount, script_pubkey, sighashtype, sighash), } } @@ -172,6 +172,7 @@ impl TransactionInputSigner { previous_output: unsigned_input.previous_output.clone(), sequence: unsigned_input.sequence, script_sig: script_sig.to_bytes(), + script_witness: vec![], } } @@ -184,6 +185,7 @@ impl TransactionInputSigner { previous_output: input.previous_output.clone(), script_sig: script_pubkey.to_bytes(), sequence: input.sequence, + script_witness: vec![], }] } else { self.inputs.iter() @@ -199,6 +201,7 @@ impl TransactionInputSigner { SighashBase::Single | SighashBase::None if n != input_index => 0, _ => input.sequence, }, + script_witness: vec![], }) .collect() }; @@ -231,44 +234,14 @@ impl TransactionInputSigner { dhash256(&out) } + fn signature_hash_witness0(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 { + self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash) + } + fn signature_hash_fork_id(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 { - let hash_prevouts = match sighash.anyone_can_pay { - false => { - let mut stream = Stream::default(); - for input in &self.inputs { - stream.append(&input.previous_output); - } - dhash256(&stream.out()) - }, - true => 0u8.into(), - }; - - let hash_sequence = match sighash.base { - SighashBase::All if !sighash.anyone_can_pay => { - let mut stream = Stream::default(); - for input in &self.inputs { - stream.append(&input.sequence); - } - dhash256(&stream.out()) - }, - _ => 0u8.into(), - }; - - let hash_outputs = match sighash.base { - SighashBase::All => { - let mut stream = Stream::default(); - for output in &self.outputs { - stream.append(output); - } - dhash256(&stream.out()) - }, - SighashBase::Single if input_index < self.outputs.len() => { - let mut stream = Stream::default(); - stream.append(&self.outputs[input_index]); - dhash256(&stream.out()) - }, - _ => 0u8.into(), - }; + let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs); + let hash_sequence = compute_hash_sequence(sighash, &self.inputs); + let hash_outputs = compute_hash_outputs(sighash, input_index, &self.outputs); let mut stream = Stream::default(); stream.append(&self.version); @@ -286,6 +259,50 @@ impl TransactionInputSigner { } } +fn compute_hash_prevouts(sighash: Sighash, inputs: &[UnsignedTransactionInput]) -> H256 { + match sighash.anyone_can_pay { + false => { + let mut stream = Stream::default(); + for input in inputs { + stream.append(&input.previous_output); + } + dhash256(&stream.out()) + }, + true => 0u8.into(), + } +} + +fn compute_hash_sequence(sighash: Sighash, inputs: &[UnsignedTransactionInput]) -> H256 { + match sighash.base { + SighashBase::All if !sighash.anyone_can_pay => { + let mut stream = Stream::default(); + for input in inputs { + stream.append(&input.sequence); + } + dhash256(&stream.out()) + }, + _ => 0u8.into(), + } +} + +fn compute_hash_outputs(sighash: Sighash, input_index: usize, outputs: &[TransactionOutput]) -> H256 { + match sighash.base { + SighashBase::All => { + let mut stream = Stream::default(); + for output in outputs { + stream.append(output); + } + dhash256(&stream.out()) + }, + SighashBase::Single if input_index < outputs.len() => { + let mut stream = Stream::default(); + stream.append(&outputs[input_index]); + dhash256(&stream.out()) + }, + _ => 0u8.into(), + } +} + #[cfg(test)] mod tests { use bytes::Bytes; diff --git a/serialization/src/lib.rs b/serialization/src/lib.rs index a55527e9..83bbbaeb 100644 --- a/serialization/src/lib.rs +++ b/serialization/src/lib.rs @@ -12,5 +12,8 @@ pub use primitives::{hash, bytes, compact}; pub use compact_integer::CompactInteger; pub use list::List; pub use reader::{Reader, Deserializable, deserialize, deserialize_iterator, ReadIterator, Error}; -pub use stream::{Stream, Serializable, serialize, serialize_list, serialized_list_size}; +pub use stream::{ + Stream, Serializable, serialize, serialize_with_flags, serialize_list, serialized_list_size, + SERIALIZE_TRANSACTION_WITNESS, +}; diff --git a/serialization/src/stream.rs b/serialization/src/stream.rs index 1e4eb366..076a9ecc 100644 --- a/serialization/src/stream.rs +++ b/serialization/src/stream.rs @@ -4,12 +4,21 @@ use std::borrow::Borrow; use compact_integer::CompactInteger; use bytes::Bytes; +/// Do not serialize transaction witness data. +pub const SERIALIZE_TRANSACTION_WITNESS: u32 = 0x40000000; + pub fn serialize(t: &T) -> Bytes where T: Serializable{ let mut stream = Stream::default(); stream.append(t); stream.out() } +pub fn serialize_with_flags(t: &T, flags: u32) -> Bytes where T: Serializable{ + let mut stream = Stream::with_flags(flags); + stream.append(t); + stream.out() +} + pub fn serialize_list(t: &[K]) -> Bytes where T: Serializable, K: Borrow { let mut stream = Stream::default(); stream.append_list(t); @@ -36,12 +45,23 @@ pub trait Serializable { #[derive(Default)] pub struct Stream { buffer: Vec, + flags: u32, } impl Stream { /// New stream pub fn new() -> Self { - Stream { buffer: Vec::new() } + Stream { buffer: Vec::new(), flags: 0 } + } + + /// Create stream with given flags, + pub fn with_flags(flags: u32) -> Self { + Stream { buffer: Vec::new(), flags: flags } + } + + /// Are transactions written to this stream with witness data? + pub fn include_transaction_witness(&self) -> bool { + (self.flags & SERIALIZE_TRANSACTION_WITNESS) != 0 } /// Serializes the struct and appends it to the end of stream. diff --git a/sync/src/inbound_connection.rs b/sync/src/inbound_connection.rs index e3f17d90..101d621b 100644 --- a/sync/src/inbound_connection.rs +++ b/sync/src/inbound_connection.rs @@ -196,6 +196,8 @@ pub mod tests { fn send_getheaders(&self, _message: &types::GetHeaders) { *self.messages.lock().entry("getheaders".to_owned()).or_insert(0) += 1; } fn send_transaction(&self, _message: &types::Tx) { *self.messages.lock().entry("transaction".to_owned()).or_insert(0) += 1; } fn send_block(&self, _message: &types::Block) { *self.messages.lock().entry("block".to_owned()).or_insert(0) += 1; } + fn send_witness_transaction(&self, _message: &types::Tx) { *self.messages.lock().entry("witness_transaction".to_owned()).or_insert(0) += 1; } + fn send_witness_block(&self, _message: &types::Block) { *self.messages.lock().entry("witness_block".to_owned()).or_insert(0) += 1; } fn send_headers(&self, _message: &types::Headers) { *self.messages.lock().entry("headers".to_owned()).or_insert(0) += 1; } fn respond_headers(&self, _message: &types::Headers, _id: RequestId) { *self.messages.lock().entry("headers".to_owned()).or_insert(0) += 1; } fn send_mempool(&self, _message: &types::MemPool) { *self.messages.lock().entry("mempool".to_owned()).or_insert(0) += 1; } diff --git a/sync/src/synchronization_client_core.rs b/sync/src/synchronization_client_core.rs index 22c923dd..d9f7f922 100644 --- a/sync/src/synchronization_client_core.rs +++ b/sync/src/synchronization_client_core.rs @@ -243,7 +243,9 @@ impl ClientCore for SynchronizationClientCore where T: TaskExecutor { _ => false, }, // we never ask for merkle blocks && we never ask for compact blocks - InventoryType::MessageCompactBlock | InventoryType::MessageFilteredBlock => false, + InventoryType::MessageCompactBlock | InventoryType::MessageFilteredBlock + | InventoryType::MessageWitnessBlock | InventoryType::MessageWitnessFilteredBlock + | InventoryType::MessageWitnessTx => false, // unknown inventory type InventoryType::Error => { self.peers.misbehaving(peer_index, &format!("Provided unknown inventory type {:?}", item.hash.to_reversed_str())); diff --git a/sync/src/synchronization_executor.rs b/sync/src/synchronization_executor.rs index 25de588b..cc3969e5 100644 --- a/sync/src/synchronization_executor.rs +++ b/sync/src/synchronization_executor.rs @@ -28,8 +28,12 @@ pub enum Task { MerkleBlock(PeerIndex, types::MerkleBlock), /// Send cmpcmblock CompactBlock(PeerIndex, types::CompactBlock), + /// Send block with witness data + WitnessBlock(PeerIndex, IndexedBlock), /// Send transaction Transaction(PeerIndex, IndexedTransaction), + /// Send transaction with witness data + WitnessTransaction(PeerIndex, IndexedTransaction), /// Send block transactions BlockTxn(PeerIndex, types::BlockTxn), /// Send notfound @@ -117,6 +121,17 @@ impl LocalSynchronizationTaskExecutor { } } + fn execute_witness_block(&self, peer_index: PeerIndex, block: IndexedBlock) { + if let Some(connection) = self.peers.connection(peer_index) { + trace!(target: "sync", "Sending witness block {} to peer#{}", block.hash().to_reversed_str(), peer_index); + self.peers.hash_known_as(peer_index, block.hash().clone(), KnownHashType::Block); + let block = types::Block { + block: block.to_raw_block(), + }; + connection.send_witness_block(&block); + } + } + fn execute_transaction(&self, peer_index: PeerIndex, transaction: IndexedTransaction) { if let Some(connection) = self.peers.connection(peer_index) { trace!(target: "sync", "Sending transaction {} to peer#{}", transaction.hash.to_reversed_str(), peer_index); @@ -128,6 +143,17 @@ impl LocalSynchronizationTaskExecutor { } } + fn execute_witness_transaction(&self, peer_index: PeerIndex, transaction: IndexedTransaction) { + if let Some(connection) = self.peers.connection(peer_index) { + trace!(target: "sync", "Sending witness transaction {} to peer#{}", transaction.hash.to_reversed_str(), peer_index); + self.peers.hash_known_as(peer_index, transaction.hash, KnownHashType::Transaction); + let transaction = types::Tx { + transaction: transaction.raw, + }; + connection.send_witness_transaction(&transaction); + } + } + fn execute_block_txn(&self, peer_index: PeerIndex, blocktxn: types::BlockTxn) { if let Some(connection) = self.peers.connection(peer_index) { trace!(target: "sync", "Sending blocktxn with {} transactions to peer#{}", blocktxn.request.transactions.len(), peer_index); @@ -202,7 +228,9 @@ impl TaskExecutor for LocalSynchronizationTaskExecutor { Task::Block(peer_index, block) => self.execute_block(peer_index, block), Task::MerkleBlock(peer_index, block) => self.execute_merkleblock(peer_index, block), Task::CompactBlock(peer_index, block) => self.execute_compact_block(peer_index, block), + Task::WitnessBlock(peer_index, block) => self.execute_witness_block(peer_index, block), Task::Transaction(peer_index, transaction) => self.execute_transaction(peer_index, transaction), + Task::WitnessTransaction(peer_index, transaction) => self.execute_witness_transaction(peer_index, transaction), Task::BlockTxn(peer_index, blocktxn) => self.execute_block_txn(peer_index, blocktxn), Task::NotFound(peer_index, notfound) => self.execute_notfound(peer_index, notfound), Task::Inventory(peer_index, inventory) => self.execute_inventory(peer_index, inventory), diff --git a/sync/src/synchronization_server.rs b/sync/src/synchronization_server.rs index dc283a2a..37888d34 100644 --- a/sync/src/synchronization_server.rs +++ b/sync/src/synchronization_server.rs @@ -273,6 +273,16 @@ impl ServerTaskExecutor where TExecutor: TaskExecutor { notfound.inventory.push(next_item); } }, + common::InventoryType::MessageWitnessTx => { + // only transaction from memory pool can be requested + if let Some(transaction) = self.memory_pool.read().read_by_hash(&next_item.hash) { + trace!(target: "sync", "'getblocks' response to peer#{} is ready with witness-tx {}", peer_index, next_item.hash.to_reversed_str()); + let transaction = IndexedTransaction::new(next_item.hash, transaction.clone()); + self.executor.execute(Task::WitnessTransaction(peer_index, transaction)); + } else { + notfound.inventory.push(next_item); + } + }, common::InventoryType::MessageBlock => { if let Some(block) = self.storage.block(next_item.hash.clone().into()) { trace!(target: "sync", "'getblocks' response to peer#{} is ready with block {}", peer_index, next_item.hash.to_reversed_str()); @@ -312,9 +322,15 @@ impl ServerTaskExecutor where TExecutor: TaskExecutor { notfound.inventory.push(next_item); } }, - _ => { - + common::InventoryType::MessageWitnessBlock => { + if let Some(block) = self.storage.block(next_item.hash.clone().into()) { + trace!(target: "sync", "'getblocks' response to peer#{} is ready with witness-block {}", peer_index, next_item.hash.to_reversed_str()); + self.executor.execute(Task::WitnessBlock(peer_index, block.into())); + } else { + notfound.inventory.push(next_item); + } }, + common::InventoryType::Error | common::InventoryType::MessageWitnessFilteredBlock => (), } Some(ServerTask::ReversedGetData(peer_index, message, notfound)) diff --git a/sync/src/synchronization_verifier.rs b/sync/src/synchronization_verifier.rs index 9050cfb0..67550801 100644 --- a/sync/src/synchronization_verifier.rs +++ b/sync/src/synchronization_verifier.rs @@ -170,7 +170,7 @@ impl AsyncVerifier { }, Ok(tx_output_provider) => { let time: u32 = get_time().sec as u32; - match verifier.verifier.verify_mempool_transaction(&tx_output_provider, height, time, &transaction.raw) { + match verifier.verifier.verify_mempool_transaction(storage.as_block_header_provider(), &tx_output_provider, height, time, &transaction.raw) { Ok(_) => sink.on_transaction_verification_success(transaction.into()), Err(e) => sink.on_transaction_verification_error(&format!("{:?}", e), &transaction.hash), } diff --git a/test-data/src/block.rs b/test-data/src/block.rs index f9f3cb72..73f6257b 100644 --- a/test-data/src/block.rs +++ b/test-data/src/block.rs @@ -440,6 +440,7 @@ impl TransactionInputBuilder where F: Invoke { previous_output: self.output.expect("Building input without previous output"), script_sig: self.signature, sequence: self.sequence, + script_witness: vec![], } ) } diff --git a/test-data/src/chain_builder.rs b/test-data/src/chain_builder.rs index 49909036..baf95a80 100644 --- a/test-data/src/chain_builder.rs +++ b/test-data/src/chain_builder.rs @@ -107,6 +107,7 @@ impl TransactionBuilder { }, script_sig: Bytes::new_with_len(0), sequence: 0xffffffff, + script_witness: vec![], }); self } @@ -123,6 +124,7 @@ impl TransactionBuilder { }, script_sig: Bytes::new_with_len(0), sequence: 0xffffffff, + script_witness: vec![], }]; self } diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index e0258ba4..3184346d 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -1,10 +1,10 @@ -use network::ConsensusParams; +use network::{ConsensusParams, Deployments as NetworkDeployments}; use db::{TransactionOutputProvider, BlockHeaderProvider}; use script; use sigops::transaction_sigops; use work::block_reward_satoshi; use duplex_store::DuplexTransactionOutputProvider; -use deployments::Deployments; +use deployments::{Deployments, ActiveDeployments}; use canon::CanonBlock; use error::{Error, TransactionError}; use timestamp::median_timestamp; @@ -24,12 +24,12 @@ impl<'a> BlockAcceptor<'a> { consensus: &'a ConsensusParams, block: CanonBlock<'a>, height: u32, - deployments: &'a Deployments, + deployments: ActiveDeployments<'a>, headers: &'a BlockHeaderProvider, ) -> Self { BlockAcceptor { - finality: BlockFinality::new(block, height, deployments, headers, consensus), - serialized_size: BlockSerializedSize::new(block, consensus, height), + finality: BlockFinality::new(block, height, deployments.deployments, headers, consensus), + serialized_size: BlockSerializedSize::new(block, consensus, deployments, height), coinbase_script: BlockCoinbaseScript::new(block, consensus, height), coinbase_claim: BlockCoinbaseClaim::new(block, store, height), sigops: BlockSigops::new(block, store, consensus, height), @@ -83,26 +83,29 @@ impl<'a> BlockFinality<'a> { pub struct BlockSerializedSize<'a> { block: CanonBlock<'a>, consensus: &'a ConsensusParams, + deployments: ActiveDeployments<'a>, height: u32, } impl<'a> BlockSerializedSize<'a> { - fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, height: u32) -> Self { + fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, deployments: ActiveDeployments<'a>, height: u32) -> Self { BlockSerializedSize { block: block, consensus: consensus, + deployments: deployments, height: height, } } fn check(&self) -> Result<(), Error> { let size = self.block.size(); - if size < self.consensus.fork.min_block_size(self.height) || - size > self.consensus.fork.max_block_size(self.height) { - Err(Error::Size(size)) - } else { - Ok(()) + if !self.consensus.fork.check_block_size(size, self.height, &self.deployments) { + return Err(Error::Size(size)) } + if self.deployments.is_active("segwit") { + // TODO: block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT + } + Ok(()) } } diff --git a/verification/src/accept_chain.rs b/verification/src/accept_chain.rs index a9622510..f972f3d2 100644 --- a/verification/src/accept_chain.rs +++ b/verification/src/accept_chain.rs @@ -6,7 +6,7 @@ use canon::CanonBlock; use accept_block::BlockAcceptor; use accept_header::HeaderAcceptor; use accept_transaction::TransactionAcceptor; -use deployments::Deployments; +use deployments::{Deployments, ActiveDeployments}; use duplex_store::DuplexTransactionOutputProvider; use VerificationLevel; @@ -21,9 +21,10 @@ impl<'a> ChainAcceptor<'a> { trace!(target: "verification", "Block verification {}", block.hash().to_reversed_str()); let output_store = DuplexTransactionOutputProvider::new(store.as_transaction_output_provider(), block.raw()); let headers = store.as_block_header_provider(); + let active_deployments = ActiveDeployments::new(deployments, height, headers, consensus); ChainAcceptor { - block: BlockAcceptor::new(store.as_transaction_output_provider(), consensus, block, height, deployments, headers), + block: BlockAcceptor::new(store.as_transaction_output_provider(), consensus, block, height, active_deployments, headers), header: HeaderAcceptor::new(headers, consensus, block.header(), height, deployments), transactions: block.transactions() .into_iter() diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 45af1222..9b7fdcc4 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -2,7 +2,7 @@ use primitives::hash::H256; use primitives::bytes::Bytes; use db::{TransactionMetaProvider, TransactionOutputProvider, BlockHeaderProvider}; use network::{ConsensusParams, ConsensusFork}; -use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; +use script::{Script, ScriptWitness, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; use duplex_store::DuplexTransactionOutputProvider; use deployments::Deployments; use script::Builder; @@ -356,6 +356,7 @@ impl<'a> TransactionEval<'a> { let input: Script = input.script_sig.clone().into(); let output: Script = output.script_pubkey.into(); + let script_witness = ScriptWitness; let flags = VerificationFlags::default() .verify_p2sh(self.verify_p2sh) @@ -364,7 +365,7 @@ impl<'a> TransactionEval<'a> { .verify_checksequence(self.verify_checksequence) .verify_dersig(self.verify_dersig); - try!(verify_script(&input, &output, &flags, &checker, self.signature_version) + try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version) .map_err(|_| TransactionError::Signature(index))); } diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 030c03b0..6c3d634f 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -12,7 +12,7 @@ use verify_header::HeaderVerifier; use verify_transaction::MemoryPoolTransactionVerifier; use accept_chain::ChainAcceptor; use accept_transaction::MemoryPoolTransactionAcceptor; -use deployments::Deployments; +use deployments::{Deployments, ActiveDeployments}; use {Verify, VerificationLevel}; pub struct BackwardsCompatibleChainVerifier { @@ -89,6 +89,7 @@ impl BackwardsCompatibleChainVerifier { pub fn verify_mempool_transaction( &self, + block_header_provider: &BlockHeaderProvider, prevout_provider: &T, height: u32, time: u32, @@ -96,7 +97,8 @@ impl BackwardsCompatibleChainVerifier { ) -> Result<(), TransactionError> where T: TransactionOutputProvider { let indexed_tx = transaction.clone().into(); // let's do preverification first - let tx_verifier = MemoryPoolTransactionVerifier::new(&indexed_tx, &self.consensus, height); + let deployments = ActiveDeployments::new(&self.deployments, height, block_header_provider, &self.consensus); + let tx_verifier = MemoryPoolTransactionVerifier::new(&indexed_tx, &self.consensus, deployments); try!(tx_verifier.check()); let canon_tx = CanonTransaction::new(&indexed_tx); diff --git a/verification/src/deployments.rs b/verification/src/deployments.rs index 15f48c4d..c74d3786 100644 --- a/verification/src/deployments.rs +++ b/verification/src/deployments.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; use parking_lot::Mutex; -use network::{ConsensusParams, Deployment}; +use network::{ConsensusParams, Deployment, Deployments as NetworkDeployments}; use hash::H256; use db::{BlockHeaderProvider, BlockRef, BlockAncestors, BlockIterator}; use timestamp::median_timestamp; @@ -51,11 +51,19 @@ struct DeploymentState { /// Last known deployment states type DeploymentStateCache = HashMap<&'static str, DeploymentState>; -#[derive(Default)] +#[derive(Default, Debug)] pub struct Deployments { cache: Mutex, } +#[derive(Clone, Copy)] +pub struct ActiveDeployments<'a> { + pub deployments: &'a Deployments, + number: u32, + headers: &'a BlockHeaderProvider, + consensus: &'a ConsensusParams, +} + impl Deployments { pub fn new() -> Self { Deployments::default() @@ -71,6 +79,17 @@ impl Deployments { None => false } } + + /// Returns true if SegWit deployment is active + pub fn segwit(&self, number: u32, headers: &BlockHeaderProvider, consensus: &ConsensusParams) -> bool { + match consensus.segwit_deployment { + Some(segwit) => { + let mut cache = self.cache.lock(); + threshold_state(&mut cache, segwit, number, headers, consensus).is_active() + }, + None => false + } + } } /// Calculates threshold state of given deployment @@ -121,6 +140,27 @@ fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, num } +impl<'a> ActiveDeployments<'a> { + pub fn new(deployments: &'a Deployments, number: u32, headers: &'a BlockHeaderProvider, consensus: &'a ConsensusParams) -> Self { + ActiveDeployments { + deployments: deployments, + number: number, + headers: headers, + consensus: consensus, + } + } +} + +impl<'a> NetworkDeployments for ActiveDeployments<'a> { + fn is_active(&self, name: &str) -> bool { + match name { + "csv" => self.deployments.segwit(self.number, self.headers, self.consensus), + "segwit" => self.deployments.segwit(self.number, self.headers, self.consensus), + _ => false, + } + } +} + fn first_of_the_period(block: u32, miner_confirmation_window: u32) -> u32 { if block < miner_confirmation_window - 1 { 0 diff --git a/verification/src/verify_transaction.rs b/verification/src/verify_transaction.rs index 5760935f..2b143d50 100644 --- a/verification/src/verify_transaction.rs +++ b/verification/src/verify_transaction.rs @@ -6,6 +6,7 @@ use duplex_store::NoopStore; use sigops::transaction_sigops; use error::TransactionError; use constants::{MIN_COINBASE_SIZE, MAX_COINBASE_SIZE}; +use deployments::ActiveDeployments; pub struct TransactionVerifier<'a> { pub empty: TransactionEmpty<'a>, @@ -40,13 +41,13 @@ pub struct MemoryPoolTransactionVerifier<'a> { } impl<'a> MemoryPoolTransactionVerifier<'a> { - pub fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams, height: u32) -> Self { + pub fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams, deployments: ActiveDeployments<'a>) -> Self { trace!(target: "verification", "Mempool-Tx pre-verification {}", transaction.hash.to_reversed_str()); MemoryPoolTransactionVerifier { empty: TransactionEmpty::new(transaction), null_non_coinbase: TransactionNullNonCoinbase::new(transaction), is_coinbase: TransactionMemoryPoolCoinbase::new(transaction), - size: TransactionSize::new(transaction, consensus, height), + size: TransactionSize::new(transaction, deployments, consensus), sigops: TransactionSigops::new(transaction, ConsensusFork::absolute_maximum_block_sigops()), } } @@ -147,21 +148,21 @@ impl<'a> TransactionMemoryPoolCoinbase<'a> { pub struct TransactionSize<'a> { transaction: &'a IndexedTransaction, + deployments: ActiveDeployments<'a>, consensus: &'a ConsensusParams, - height: u32, } impl<'a> TransactionSize<'a> { - fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams, height: u32) -> Self { + fn new(transaction: &'a IndexedTransaction, deployments: ActiveDeployments<'a>, consensus: &'a ConsensusParams) -> Self { TransactionSize { transaction: transaction, + deployments: deployments, consensus: consensus, - height: height, } } fn check(&self) -> Result<(), TransactionError> { - if self.transaction.raw.serialized_size() > self.consensus.fork.max_transaction_size(self.height) { + if !self.consensus.fork.check_transaction_size(self.transaction.raw.serialized_size(), &self.deployments) { Err(TransactionError::MaxSize) } else { Ok(()) From b90c8add96659800fbbe049067929e24fc0b7130 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 16 Aug 2017 13:47:51 +0300 Subject: [PATCH 02/35] segwit: premature witness check --- verification/src/accept_chain.rs | 2 +- verification/src/accept_transaction.rs | 33 ++++++++++++++++++++++---- verification/src/error.rs | 2 ++ verification/src/verify_transaction.rs | 27 ++++++++++++++++++++- 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/verification/src/accept_chain.rs b/verification/src/accept_chain.rs index f972f3d2..d7f4bbb9 100644 --- a/verification/src/accept_chain.rs +++ b/verification/src/accept_chain.rs @@ -39,7 +39,7 @@ impl<'a> ChainAcceptor<'a> { height, block.header.raw.time, tx_index, - deployments, + active_deployments, headers, )) .collect(), diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 9b7fdcc4..fe6d74da 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -1,10 +1,10 @@ use primitives::hash::H256; use primitives::bytes::Bytes; use db::{TransactionMetaProvider, TransactionOutputProvider, BlockHeaderProvider}; -use network::{ConsensusParams, ConsensusFork}; +use network::{ConsensusParams, ConsensusFork, Deployments as NetworkDeployments}; use script::{Script, ScriptWitness, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; use duplex_store::DuplexTransactionOutputProvider; -use deployments::Deployments; +use deployments::{Deployments, ActiveDeployments}; use script::Builder; use sigops::transaction_sigops; use canon::CanonTransaction; @@ -13,6 +13,7 @@ use error::TransactionError; use VerificationLevel; pub struct TransactionAcceptor<'a> { + pub premature_witness: TransactionPrematureWitness<'a>, pub bip30: TransactionBip30<'a>, pub missing_inputs: TransactionMissingInputs<'a>, pub maturity: TransactionMaturity<'a>, @@ -36,22 +37,24 @@ impl<'a> TransactionAcceptor<'a> { height: u32, time: u32, transaction_index: usize, - deployments: &'a Deployments, + deployments: ActiveDeployments<'a>, headers: &'a BlockHeaderProvider, ) -> Self { trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str()); TransactionAcceptor { + premature_witness: TransactionPrematureWitness::new(transaction, deployments.is_active("segwit")), bip30: TransactionBip30::new_for_sync(transaction, meta_store, consensus, block_hash, height), missing_inputs: TransactionMissingInputs::new(transaction, output_store, transaction_index), maturity: TransactionMaturity::new(transaction, meta_store, height), overspent: TransactionOverspent::new(transaction, output_store), double_spent: TransactionDoubleSpend::new(transaction, output_store), return_replay_protection: TransactionReturnReplayProtection::new(transaction, consensus, height), - eval: TransactionEval::new(transaction, output_store, consensus, verification_level, height, time, deployments, headers), + eval: TransactionEval::new(transaction, output_store, consensus, verification_level, height, time, deployments.deployments, headers), } } pub fn check(&self) -> Result<(), TransactionError> { + try!(self.premature_witness.check()); try!(self.bip30.check()); try!(self.missing_inputs.check()); try!(self.maturity.check()); @@ -435,6 +438,28 @@ impl<'a> TransactionReturnReplayProtection<'a> { } } +pub struct TransactionPrematureWitness<'a> { + transaction: CanonTransaction<'a>, + is_segwit_active: bool, +} + +impl<'a> TransactionPrematureWitness<'a> { + pub fn new(transaction: CanonTransaction<'a>, is_segwit_active: bool) -> Self { + TransactionPrematureWitness { + transaction: transaction, + is_segwit_active: is_segwit_active, + } + } + + pub fn check(&self) -> Result<(), TransactionError> { + if !self.is_segwit_active && (*self.transaction).raw.has_witness() { + Err(TransactionError::PrematureWitness) + } else { + Ok(()) + } + } +} + #[cfg(test)] mod tests { use chain::{IndexedTransaction, Transaction, TransactionOutput}; diff --git a/verification/src/error.rs b/verification/src/error.rs index 1647e722..7aba27ca 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -95,5 +95,7 @@ pub enum TransactionError { UsingSpentOutput(H256, u32), /// Transaction, protected using BitcoinCash OP_RETURN replay protection (REQ-6-1). ReturnReplayProtection, + /// Transaction with witness is received before SegWit is activated. + PrematureWitness, } diff --git a/verification/src/verify_transaction.rs b/verification/src/verify_transaction.rs index 2b143d50..77a9a240 100644 --- a/verification/src/verify_transaction.rs +++ b/verification/src/verify_transaction.rs @@ -1,7 +1,7 @@ use std::ops; use ser::Serializable; use chain::IndexedTransaction; -use network::{ConsensusParams, ConsensusFork}; +use network::{ConsensusParams, ConsensusFork, Deployments}; use duplex_store::NoopStore; use sigops::transaction_sigops; use error::TransactionError; @@ -37,6 +37,7 @@ pub struct MemoryPoolTransactionVerifier<'a> { pub null_non_coinbase: TransactionNullNonCoinbase<'a>, pub is_coinbase: TransactionMemoryPoolCoinbase<'a>, pub size: TransactionSize<'a>, + pub premature_witness: TransactionPrematureWitness<'a>, pub sigops: TransactionSigops<'a>, } @@ -48,6 +49,7 @@ impl<'a> MemoryPoolTransactionVerifier<'a> { null_non_coinbase: TransactionNullNonCoinbase::new(transaction), is_coinbase: TransactionMemoryPoolCoinbase::new(transaction), size: TransactionSize::new(transaction, deployments, consensus), + premature_witness: TransactionPrematureWitness::new(transaction, deployments), sigops: TransactionSigops::new(transaction, ConsensusFork::absolute_maximum_block_sigops()), } } @@ -57,6 +59,7 @@ impl<'a> MemoryPoolTransactionVerifier<'a> { try!(self.null_non_coinbase.check()); try!(self.is_coinbase.check()); try!(self.size.check()); + try!(self.premature_witness.check()); try!(self.sigops.check()); Ok(()) } @@ -192,3 +195,25 @@ impl<'a> TransactionSigops<'a> { } } } + +pub struct TransactionPrematureWitness<'a> { + transaction: &'a IndexedTransaction, + deployments: ActiveDeployments<'a>, +} + +impl<'a> TransactionPrematureWitness<'a> { + pub fn new(transaction: &'a IndexedTransaction, deployments: ActiveDeployments<'a>) -> Self { + TransactionPrematureWitness { + transaction: transaction, + deployments: deployments, + } + } + + pub fn check(&self) -> Result<(), TransactionError> { + if self.transaction.raw.has_witness() && !self.deployments.is_active("segwit") { + Err(TransactionError::PrematureWitness) + } else { + Ok(()) + } + } +} From abbffb49bb5a7cfeb145f6ae987265ca147c89cb Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 17 Aug 2017 10:51:46 +0300 Subject: [PATCH 03/35] segwit: commitment check + weight check --- Cargo.lock | 1 + chain/src/block.rs | 2 +- chain/src/indexed_block.rs | 19 ++++++- network/src/consensus.rs | 7 ++- network/src/lib.rs | 2 +- script/src/flags.rs | 5 ++ script/src/lib.rs | 2 +- script/src/script.rs | 12 +++++ verification/Cargo.toml | 1 + verification/src/accept_block.rs | 68 +++++++++++++++++++++++++- verification/src/accept_transaction.rs | 10 ++-- verification/src/error.rs | 8 +++ verification/src/lib.rs | 1 + 13 files changed, 128 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 28e1b48d..d0f3668b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,7 @@ name = "verification" version = "0.1.0" dependencies = [ + "bitcrypto 0.1.0", "chain 0.1.0", "db 0.1.0", "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/chain/src/block.rs b/chain/src/block.rs index 1ae890db..7188eb2d 100644 --- a/chain/src/block.rs +++ b/chain/src/block.rs @@ -44,7 +44,7 @@ impl Block { .enumerate() .map(|(i, tx)| match i { 0 => H256::from(0), - _ => tx.witness_hash() + _ => tx.witness_hash(), }).collect::>(); merkle_root(&hashes) } diff --git a/chain/src/indexed_block.rs b/chain/src/indexed_block.rs index 5bd80de4..945d5770 100644 --- a/chain/src/indexed_block.rs +++ b/chain/src/indexed_block.rs @@ -1,7 +1,7 @@ use std::cmp; use hash::H256; use hex::FromHex; -use ser::{Serializable, serialized_list_size, deserialize}; +use ser::{Serializable, serialized_list_size, deserialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; use block::Block; use transaction::Transaction; use merkle_root::merkle_root; @@ -54,10 +54,27 @@ impl IndexedBlock { header_size + txs_size } + pub fn size_with_witness(&self) -> usize { + // TODO: optimize me + serialize_with_flags(&Block { + block_header: self.header.raw.clone(), + transactions: self.transactions.iter().map(|tx| tx.raw.clone()).collect(), + }, SERIALIZE_TRANSACTION_WITNESS).len() + } + pub fn merkle_root(&self) -> H256 { merkle_root(&self.transactions.iter().map(|tx| &tx.hash).collect::>()) } + pub fn witness_merkle_root(&self) -> H256 { + merkle_root(&self.transactions.iter() + .enumerate() + .map(|(i, tx)| match i { + 0 => H256::from(0), + _ => tx.raw.witness_hash(), + }).collect::>()) + } + pub fn is_final(&self, height: u32) -> bool { self.transactions.iter().all(|tx| tx.raw.is_final_in_block(height, self.header.raw.time)) } diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 832f95ea..ce7ebfdd 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -7,7 +7,8 @@ pub const SEGWIT2X_FORK_BLOCK: u32 = 0xFFFFFFFF; // not known (yet?) /// First block of BitcoinCash fork. pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitcoin-cash/block/478559 -mod segwit { +/// Segwit-related constants. +pub mod segwit { /// The maximum allowed weight for a block, see BIP 141 (network rule) pub const MAX_BLOCK_WEIGHT: usize = 4_000_000; @@ -172,6 +173,7 @@ impl ConsensusFork { pub fn max_block_size(&self, height: u32) -> usize { match *self { ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => 8_000_000, + ConsensusFork::SegWit2x(fork_height) if height >= fork_height => 2_000_000, ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 1_000_000, } } @@ -184,6 +186,9 @@ impl ConsensusFork { // bitcoin cash support blocks up to 8_000_000 ConsensusFork::BitcoinCash(fork_height) if height > fork_height => size <= 8_000_000, + // max size of SegWit2x block is 2MB + ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + size <= 2_000_000, // when segwit is deployed, this expression is used. which, in turn, also allows block size <= 1_000_000 ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => size.saturating_mul(segwit::WITNESS_SCALE_FACTOR) <= segwit::MAX_BLOCK_WEIGHT, diff --git a/network/src/lib.rs b/network/src/lib.rs index b3a85ab3..45892419 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -8,7 +8,7 @@ mod magic; pub use primitives::{hash, compact}; -pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK}; +pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK, segwit}; pub use deployments::{Deployment, Deployments}; pub use magic::Magic; diff --git a/script/src/flags.rs b/script/src/flags.rs index 8c761b48..12961a37 100644 --- a/script/src/flags.rs +++ b/script/src/flags.rs @@ -92,5 +92,10 @@ impl VerificationFlags { self.verify_dersig = value; self } + + pub fn verify_witness(mut self, value: bool) -> Self { + self.verify_witness = value; + self + } } diff --git a/script/src/lib.rs b/script/src/lib.rs index 454ebada..4d7ba675 100644 --- a/script/src/lib.rs +++ b/script/src/lib.rs @@ -24,7 +24,7 @@ pub use self::flags::VerificationFlags; pub use self::interpreter::{eval_script, verify_script}; pub use self::opcode::Opcode; pub use self::num::Num; -pub use self::script::{Script, ScriptType, ScriptAddress, ScriptWitness}; +pub use self::script::{Script, ScriptType, ScriptAddress, ScriptWitness, is_witness_commitment_script}; pub use self::sign::{TransactionInputSigner, UnsignedTransactionInput, SignatureVersion}; pub use self::stack::Stack; pub use self::verify::{SignatureChecker, NoopSignatureChecker, TransactionSignatureChecker}; diff --git a/script/src/script.rs b/script/src/script.rs index 00265c7f..0200c23d 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -572,6 +572,18 @@ impl fmt::Display for Script { pub struct ScriptWitness; +/// Passed bytes array is a commitment script? +/// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Commitment_structure +pub fn is_witness_commitment_script(script: &[u8]) -> bool { + script.len() >= 36 && + script[0] == Opcode::OP_RETURN as u8 && + script[1] == 0x24 && + script[2] == 0xAA && + script[3] == 0x21 && + script[4] == 0xA9 && + script[5] == 0xED +} + #[cfg(test)] mod tests { use {Builder, Opcode}; diff --git a/verification/Cargo.toml b/verification/Cargo.toml index 7cd07b2c..ad80798b 100644 --- a/verification/Cargo.toml +++ b/verification/Cargo.toml @@ -15,6 +15,7 @@ serialization = { path = "../serialization" } script = { path = "../script" } network = { path = "../network" } db = { path = "../db" } +bitcrypto = { path = "../crypto" } [dev-dependencies] test-data = { path = "../test-data" } diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index 3184346d..fce40386 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -1,6 +1,8 @@ -use network::{ConsensusParams, Deployments as NetworkDeployments}; +use network::{ConsensusParams, Deployments as NetworkDeployments, segwit}; +use crypto::dhash256; use db::{TransactionOutputProvider, BlockHeaderProvider}; use script; +use ser::Stream; use sigops::transaction_sigops; use work::block_reward_satoshi; use duplex_store::DuplexTransactionOutputProvider; @@ -16,6 +18,7 @@ pub struct BlockAcceptor<'a> { pub sigops: BlockSigops<'a>, pub coinbase_claim: BlockCoinbaseClaim<'a>, pub coinbase_script: BlockCoinbaseScript<'a>, + pub witness: BlockWitness<'a>, } impl<'a> BlockAcceptor<'a> { @@ -33,6 +36,7 @@ impl<'a> BlockAcceptor<'a> { coinbase_script: BlockCoinbaseScript::new(block, consensus, height), coinbase_claim: BlockCoinbaseClaim::new(block, store, height), sigops: BlockSigops::new(block, store, consensus, height), + witness: BlockWitness::new(block, deployments), } } @@ -42,6 +46,7 @@ impl<'a> BlockAcceptor<'a> { self.serialized_size.check()?; self.coinbase_claim.check()?; self.coinbase_script.check()?; + self.witness.check()?; Ok(()) } } @@ -102,8 +107,15 @@ impl<'a> BlockSerializedSize<'a> { if !self.consensus.fork.check_block_size(size, self.height, &self.deployments) { return Err(Error::Size(size)) } - if self.deployments.is_active("segwit") { + + let is_segwit_active = self.deployments.is_active("segwit"); + if is_segwit_active { // TODO: block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT + let size_with_witness = self.block.size_with_witness(); + let weight = size * (segwit::WITNESS_SCALE_FACTOR - 1) + size_with_witness; + if weight > segwit::MAX_BLOCK_WEIGHT { + return Err(Error::Weight(weight)); + } } Ok(()) } @@ -244,6 +256,58 @@ impl<'a> BlockCoinbaseScript<'a> { } } +pub struct BlockWitness<'a> { + block: CanonBlock<'a>, + segwit_active: bool, +} + +impl<'a> BlockWitness<'a> { + fn new(block: CanonBlock<'a>, deployments: ActiveDeployments<'a>) -> Self { + BlockWitness { + block: block, + segwit_active: deployments.is_active("segwit"), + } + } + + fn check(&self) -> Result<(), Error> { + if !self.segwit_active { + return Ok(()); + } + + // check witness from coinbase transaction + let mut has_witness = false; + if let Some(coinbase) = self.block.transactions.first() { + let commitment = coinbase.raw.outputs.iter().rev() + .find(|output| script::is_witness_commitment_script(&output.script_pubkey)); + if let Some(commitment) = commitment { + let witness_merkle_root = self.block.raw().witness_merkle_root(); + if coinbase.raw.inputs.get(0).map(|i| i.script_witness.len()).unwrap_or_default() != 1 || + coinbase.raw.inputs[0].script_witness[0].len() != 32 { + return Err(Error::WitnessInvalidNonceSize); + } + + let mut stream = Stream::new(); + stream.append(&witness_merkle_root); + stream.append_slice(&coinbase.raw.inputs[0].script_witness[0]); + let hash_witness = dhash256(&stream.out()); + + if hash_witness != commitment.script_pubkey[6..].into() { + return Err(Error::WitnessMerkleCommitmentMismatch); + } + + has_witness = true; + } + } + + // witness commitment is required when block contains transactions with witness + if !has_witness && self.block.transactions.iter().any(|tx| tx.raw.has_witness()) { + return Err(Error::UnexpectedWitness); + } + + Ok(()) + } +} + #[cfg(test)] mod tests { extern crate test_data; diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index fe6d74da..7d6e4897 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -291,6 +291,7 @@ pub struct TransactionEval<'a> { verify_locktime: bool, verify_checksequence: bool, verify_dersig: bool, + verify_witness: bool, signature_version: SignatureVersion, } @@ -318,6 +319,7 @@ impl<'a> TransactionEval<'a> { }; let verify_checksequence = deployments.csv(height, headers, params); + let verify_witness = deployments.segwit(height, headers, params); TransactionEval { transaction: transaction, @@ -328,6 +330,7 @@ impl<'a> TransactionEval<'a> { verify_locktime: verify_locktime, verify_checksequence: verify_checksequence, verify_dersig: verify_dersig, + verify_witness: verify_witness, signature_version: signature_version, } } @@ -366,7 +369,8 @@ impl<'a> TransactionEval<'a> { .verify_strictenc(self.verify_strictenc) .verify_locktime(self.verify_locktime) .verify_checksequence(self.verify_checksequence) - .verify_dersig(self.verify_dersig); + .verify_dersig(self.verify_dersig) + .verify_witness(self.verify_witness); try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version) .map_err(|_| TransactionError::Signature(index))); @@ -444,14 +448,14 @@ pub struct TransactionPrematureWitness<'a> { } impl<'a> TransactionPrematureWitness<'a> { - pub fn new(transaction: CanonTransaction<'a>, is_segwit_active: bool) -> Self { + fn new(transaction: CanonTransaction<'a>, is_segwit_active: bool) -> Self { TransactionPrematureWitness { transaction: transaction, is_segwit_active: is_segwit_active, } } - pub fn check(&self) -> Result<(), TransactionError> { + fn check(&self) -> Result<(), TransactionError> { if !self.is_segwit_active && (*self.transaction).raw.has_witness() { Err(TransactionError::PrematureWitness) } else { diff --git a/verification/src/error.rs b/verification/src/error.rs index 7aba27ca..16d23dc6 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -36,6 +36,8 @@ pub enum Error { CoinbaseSignatureLength(usize), /// Block size is invalid Size(usize), + /// Block weight is invalid + Weight(usize), /// Block transactions are not final. NonFinalBlock, /// Old version block. @@ -46,6 +48,12 @@ pub enum Error { TransactionFeesOverflow, /// Sum of all referenced outputs in block transactions resulted in the overflow ReferencedInputsSumOverflow, + /// SegWit: bad witess nonce size + WitnessInvalidNonceSize, + /// SegWit: witness merkle mismatch + WitnessMerkleCommitmentMismatch, + /// SegWit: unexpected witness + UnexpectedWitness, /// Database error Database(DBError), } diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 0a1a1cc6..3fe014d0 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -65,6 +65,7 @@ extern crate network; extern crate primitives; extern crate serialization as ser; extern crate script; +extern crate bitcrypto as crypto; pub mod constants; mod canon; From e84e0a57df463be8a82160eb896c277c1b6239fe Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 17 Aug 2017 11:11:03 +0300 Subject: [PATCH 04/35] segwit: sigops check --- network/src/consensus.rs | 15 ++++++++++++++- verification/src/accept_block.rs | 7 +++++-- verification/src/error.rs | 2 +- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index ce7ebfdd..ce3a30be 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -11,7 +11,8 @@ pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitco pub mod segwit { /// The maximum allowed weight for a block, see BIP 141 (network rule) pub const MAX_BLOCK_WEIGHT: usize = 4_000_000; - + /// The maximum allowed number of signature check operations in a block (network rule) + pub const MAX_BLOCK_SIGOPS_COST: usize = 80_000; /// Witness scale factor. pub const WITNESS_SCALE_FACTOR: usize = 4; } @@ -217,6 +218,18 @@ impl ConsensusFork { ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => 20_000, } } + + pub fn check_block_sigops(&self, sigops: usize, height: u32, block_size: usize, deployments: &Deployments) -> bool { + match *self { + // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) + ConsensusFork::BitcoinCash(fork_height) if height >= fork_height && block_size > 1_000_000 => + sigops <= 20_000 * (max(block_size, 1_000_000) / 1_000_000), + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => + sigops * segwit::WITNESS_SCALE_FACTOR <= segwit::MAX_BLOCK_SIGOPS_COST, + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => + sigops <= 20_000, + } + } } #[cfg(test)] diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index fce40386..16b248e8 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -110,11 +110,14 @@ impl<'a> BlockSerializedSize<'a> { let is_segwit_active = self.deployments.is_active("segwit"); if is_segwit_active { - // TODO: block.vtx.size() * WITNESS_SCALE_FACTOR > MAX_BLOCK_WEIGHT + if self.block.transactions.len() * segwit::WITNESS_SCALE_FACTOR > segwit::MAX_BLOCK_WEIGHT { + return Err(Error::Weight); + } + let size_with_witness = self.block.size_with_witness(); let weight = size * (segwit::WITNESS_SCALE_FACTOR - 1) + size_with_witness; if weight > segwit::MAX_BLOCK_WEIGHT { - return Err(Error::Weight(weight)); + return Err(Error::Weight); } } Ok(()) diff --git a/verification/src/error.rs b/verification/src/error.rs index 16d23dc6..f611dcbb 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -37,7 +37,7 @@ pub enum Error { /// Block size is invalid Size(usize), /// Block weight is invalid - Weight(usize), + Weight, /// Block transactions are not final. NonFinalBlock, /// Old version block. From b0c1605ee49bb659b28c8d263bfa4a1d539930aa Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 17 Aug 2017 13:09:19 +0300 Subject: [PATCH 05/35] swgwit: verify_witness_program --- script/src/error.rs | 2 + script/src/flags.rs | 5 ++ script/src/interpreter.rs | 102 ++++++++++++++++++++----- script/src/script.rs | 7 +- verification/src/accept_transaction.rs | 8 +- 5 files changed, 101 insertions(+), 23 deletions(-) diff --git a/script/src/error.rs b/script/src/error.rs index b23b81c8..8151d8e9 100644 --- a/script/src/error.rs +++ b/script/src/error.rs @@ -51,6 +51,7 @@ pub enum Error { // Softfork safeness DiscourageUpgradableNops, + DiscourageUpgradableWitnessProgram, // SegWit-related errors WitnessProgramWrongLength, @@ -110,6 +111,7 @@ impl fmt::Display for Error { // Softfork safeness Error::DiscourageUpgradableNops => "Discourage Upgradable Nops".fmt(f), + Error::DiscourageUpgradableWitnessProgram => "Discourage Upgradable Witness Program".fmt(f), // SegWit-related errors Error::WitnessProgramWrongLength => "Witness program has incorrect length".fmt(f), diff --git a/script/src/flags.rs b/script/src/flags.rs index 12961a37..0344eb81 100644 --- a/script/src/flags.rs +++ b/script/src/flags.rs @@ -97,5 +97,10 @@ impl VerificationFlags { self.verify_witness = value; self } + + pub fn verify_nulldummy(mut self, value: bool) -> Self { + self.verify_nulldummy = value; + self + } } diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 787d76c2..50e185aa 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -4,6 +4,7 @@ use keys::{Signature, Public}; use chain::constants::SEQUENCE_LOCKTIME_DISABLE_FLAG; use crypto::{sha1, sha256, dhash160, dhash256, ripemd160}; use sign::{SignatureVersion, Sighash}; +use script::MAX_SCRIPT_ELEMENT_SIZE; use { script, Builder, Script, ScriptWitness, Num, VerificationFlags, Opcode, Error, SignatureChecker, Stack }; @@ -274,8 +275,11 @@ pub fn verify_script( if !script_sig.is_empty() { return Err(Error::WitnessMalleated); } - verify_witness_program(witness, witness_version, witness_program, flags, checker)?; + verify_cleanstack = false; + if !verify_witness_program(witness, witness_version, witness_program, flags, checker, version)? { + return Err(Error::EvalFalse); + } } } @@ -317,13 +321,71 @@ pub fn verify_script( } fn verify_witness_program( - _witness: &ScriptWitness, - _witness_version: u8, - _witness_program: &[u8], - _flags: &VerificationFlags, - _checker: &SignatureChecker -) -> Result<(), Error> { - unimplemented!() + witness: &ScriptWitness, + witness_version: u8, + witness_program: &[u8], + flags: &VerificationFlags, + checker: &SignatureChecker, + version: SignatureVersion, +) -> Result { + if witness_version != 0 { + if flags.verify_discourage_upgradable_witness_program { + return Err(Error::DiscourageUpgradableWitnessProgram); + } + + return Ok(true); + } + + let witness_stack = &witness.stack; + let witness_stack_len = witness.stack.len(); + let (mut stack, script_pubkey) = match witness_program.len() { + 32 => { + if witness_stack_len == 0 { + return Err(Error::WitnessProgramWitnessEmpty); + } + + let stack = &witness_stack[0..witness_stack_len - 1]; + let script_pubkey = Script::new(stack[witness_stack_len - 1].clone()); + + let script_pubkey_hash = dhash256(&script_pubkey); + if script_pubkey_hash != witness_program[0..64].into() { + return Err(Error::WitnessProgramMismatch); + } + + (stack.iter().cloned().collect::>().into(), script_pubkey) + }, + 20 => { + if witness_stack_len != 2 { + return Err(Error::WitnessProgramMismatch); + } + + let script_pubkey = Builder::default() + .push_opcode(Opcode::OP_DUP) + .push_opcode(Opcode::OP_HASH160) + .push_data(witness_program) + .push_opcode(Opcode::OP_EQUALVERIFY) + .push_opcode(Opcode::OP_CHECKSIG) + .into_script(); + + (witness_stack.clone(), script_pubkey) + }, + _ => return Err(Error::WitnessProgramWrongLength), + }; + + if stack.iter().any(|s| s.len() > MAX_SCRIPT_ELEMENT_SIZE) { + return Err(Error::PushSize); + } + + if !eval_script(&mut stack, &script_pubkey, flags, checker, version)? { + return Ok(false); + } + + let success = !stack.is_empty() && { + let last = stack.last()?; + cast_to_bool(last) + }; + + Ok(success) } /// Evaluautes the script @@ -1893,7 +1955,7 @@ mod tests { let output: Script = "76a914df3bd30160e6c6145baaf2c88a8844c13a00d1d588ac".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/rawtx/02b082113e35d5386285094c2829e7e2963fa0b5369fb7f4b79c4c90877dcd3d @@ -1910,7 +1972,7 @@ mod tests { let output: Script = "a9141a8b0026343166625c7475f01e48b5ede8c0252e87".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/en/tx/12b5633bad1f9c167d523ad1aa1947b2732a865bf5414eab2f9e5ae5d5c191ba?show_adv=true @@ -1927,7 +1989,7 @@ mod tests { let output: Script = "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/rawtx/fb0a1d8d34fa5537e461ac384bac761125e1bfa7fec286fa72511240fa66864d @@ -1944,7 +2006,7 @@ mod tests { let output: Script = "76a9147a2a3b481ca80c4ba7939c54d9278e50189d94f988ac".into(); let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); } // https://blockchain.info/rawtx/eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb @@ -1962,12 +2024,12 @@ mod tests { let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); let flags = VerificationFlags::default() .verify_p2sh(true) .verify_locktime(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Err(Error::NumberOverflow)); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Err(Error::NumberOverflow)); } // https://blockchain.info/rawtx/54fabd73f1d20c980a0686bf0035078e07f69c58437e4d586fb29aa0bee9814f @@ -1983,7 +2045,7 @@ mod tests { let input: Script = "483045022100d92e4b61452d91a473a43cde4b469a472467c0ba0cbd5ebba0834e4f4762810402204802b76b7783db57ac1f61d2992799810e173e91055938750815b6d8a675902e014f".into(); let output: Script = "76009f69905160a56b210378d430274f8c5ec1321338151e9f27f4c676a008bdf8638d07c0b6be9ab35c71ad6c".into(); let flags = VerificationFlags::default(); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); } #[test] @@ -2036,7 +2098,7 @@ mod tests { let flags = VerificationFlags::default() .verify_p2sh(true); - assert_eq!(verify_script(&input, &output, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Ok(())); + assert_eq!(verify_script(&input, &output, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Ok(())); } @@ -2087,7 +2149,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount, &script_pubkey, SignatureVersion::ForkId, sighashtype); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags, &checker, SignatureVersion::ForkId), Ok(())); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness::default(), &flags, &checker, SignatureVersion::ForkId), Ok(())); } // signature with wrong amount @@ -2095,7 +2157,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount + 1, &script_pubkey, SignatureVersion::ForkId, sighashtype); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags, &checker, SignatureVersion::ForkId), Err(Error::EvalFalse)); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness::default(), &flags, &checker, SignatureVersion::ForkId), Err(Error::EvalFalse)); } // fork-id signature passed when not expected @@ -2103,7 +2165,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount + 1, &script_pubkey, SignatureVersion::ForkId, sighashtype); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags, &checker, SignatureVersion::Base), Err(Error::EvalFalse)); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness::default(), &flags, &checker, SignatureVersion::Base), Err(Error::EvalFalse)); } // non-fork-id signature passed when expected @@ -2111,7 +2173,7 @@ mod tests { let signed_input = checker.signer.signed_input(&key_pair, 0, amount + 1, &script_pubkey, SignatureVersion::Base, 1); let script_sig = signed_input.script_sig.into(); - assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness, &flags.verify_strictenc(true), &checker, SignatureVersion::ForkId), Err(Error::SignatureMustUseForkId)); + assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness::default(), &flags.verify_strictenc(true), &checker, SignatureVersion::ForkId), Err(Error::SignatureMustUseForkId)); } } } diff --git a/script/src/script.rs b/script/src/script.rs index 0200c23d..35079b40 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -3,7 +3,7 @@ use std::{fmt, ops}; use bytes::Bytes; use keys::{self, AddressHash, Public}; -use {Opcode, Error}; +use {Opcode, Error, Stack}; /// Maximum number of bytes pushable to the stack pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; @@ -570,7 +570,10 @@ impl fmt::Display for Script { } } -pub struct ScriptWitness; +#[derive(Default)] +pub struct ScriptWitness { + pub stack: Stack, +} /// Passed bytes array is a commitment script? /// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Commitment_structure diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 7d6e4897..556ebcc6 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -292,6 +292,7 @@ pub struct TransactionEval<'a> { verify_checksequence: bool, verify_dersig: bool, verify_witness: bool, + verify_nulldummy: bool, signature_version: SignatureVersion, } @@ -320,6 +321,7 @@ impl<'a> TransactionEval<'a> { let verify_checksequence = deployments.csv(height, headers, params); let verify_witness = deployments.segwit(height, headers, params); + let verify_nulldummy = verify_witness; TransactionEval { transaction: transaction, @@ -331,6 +333,7 @@ impl<'a> TransactionEval<'a> { verify_checksequence: verify_checksequence, verify_dersig: verify_dersig, verify_witness: verify_witness, + verify_nulldummy: verify_nulldummy, signature_version: signature_version, } } @@ -360,9 +363,11 @@ impl<'a> TransactionEval<'a> { checker.input_index = index; checker.input_amount = output.value; + let script_witness = ScriptWitness { + stack: input.script_witness.clone().into(), // TODO + }; let input: Script = input.script_sig.clone().into(); let output: Script = output.script_pubkey.into(); - let script_witness = ScriptWitness; let flags = VerificationFlags::default() .verify_p2sh(self.verify_p2sh) @@ -370,6 +375,7 @@ impl<'a> TransactionEval<'a> { .verify_locktime(self.verify_locktime) .verify_checksequence(self.verify_checksequence) .verify_dersig(self.verify_dersig) + .verify_nulldummy(self.verify_nulldummy) .verify_witness(self.verify_witness); try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version) From 1b92a9d8d47b4705601ab25e9dd804c848364987 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 17 Aug 2017 13:22:20 +0300 Subject: [PATCH 06/35] segwit: p2sh witness --- script/src/interpreter.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 50e185aa..74bb42c6 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -302,6 +302,19 @@ pub fn verify_script( if !res { return Err(Error::EvalFalse); } + + if flags.verify_witness { + if let Some((witness_version, witness_program)) = pubkey2.parse_witness_program() { + if script_sig != Builder::default().push_data(&pubkey2) { + return Err(Error::WitnessMalleatedP2SH); + } + + verify_cleanstack = false; + if !verify_witness_program(witness, witness_version, witness_program, flags, checker, version)? { + return Err(Error::EvalFalse); + } + } + } } // The CLEANSTACK check is only performed after potential P2SH evaluation, From bda2277b718364e33726fc750b375f311394bf88 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 17 Aug 2017 13:51:12 +0300 Subject: [PATCH 07/35] segwit: added sync TODOs --- script/src/interpreter.rs | 10 +++++++--- sync/src/synchronization_client_core.rs | 7 ++++++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 74bb42c6..c3d18097 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -887,9 +887,13 @@ pub fn eval_script( let signature = try!(stack.pop()); let sighash = parse_hash_type(version, &signature); let mut subscript = script.subscript(begincode); - if version != SignatureVersion::ForkId || !sighash.fork_id { - let signature_script = Builder::default().push_data(&*signature).into_script(); - subscript = subscript.find_and_delete(&*signature_script); + match version { + SignatureVersion::ForkId if sighash.fork_id => (), + SignatureVersion::WitnessV0 => (), + SignatureVersion::Base | SignatureVersion::ForkId => { + let signature_script = Builder::default().push_data(&*signature).into_script(); + subscript = subscript.find_and_delete(&*signature_script); + }, } try!(check_signature_encoding(&signature, flags, version)); diff --git a/sync/src/synchronization_client_core.rs b/sync/src/synchronization_client_core.rs index d9f7f922..6a7f40ea 100644 --- a/sync/src/synchronization_client_core.rs +++ b/sync/src/synchronization_client_core.rs @@ -262,6 +262,7 @@ impl ClientCore for SynchronizationClientCore where T: TaskExecutor { } // ask for unknown items +// TODO: if segwit is active, ask with witness data let message = types::GetData::with_inventory(unknown_inventory); self.executor.execute(Task::GetData(peer_index, message)); } @@ -963,6 +964,7 @@ impl SynchronizationClientCore where T: TaskExecutor { // remember that peer is asked for these blocks self.peers_tasks.on_blocks_requested(peer, &chunk_hashes); +// TODO: if block is believed to have witness, ask with witness data // request blocks let getdata = types::GetData { inventory: chunk_hashes.into_iter().map(InventoryVector::block).collect(), @@ -1045,6 +1047,9 @@ impl SynchronizationClientCore where T: TaskExecutor { // update block processing speed self.block_speed_meter.checkpoint(); +// TODO: if segwit activates after this block, disconnect from all nodes without NODE_WITNESS support +// TODO: no more connections to !NODE_WITNESS nodes + // remove flags let needs_relay = !self.do_not_relay.remove(block.hash()); @@ -1102,7 +1107,7 @@ impl SynchronizationClientCore where T: TaskExecutor { Err(e) => { // process as irrecoverable failure panic!("Block {} insertion failed with error {:?}", block_hash.to_reversed_str(), e); - } + }, } } From 1e28ec4ed55461a7af616ad51c00259a087aa982 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Thu, 17 Aug 2017 16:19:29 +0300 Subject: [PATCH 08/35] segwit: sigops cost stub --- network/src/consensus.rs | 9 +++++ script/src/interpreter.rs | 2 +- script/src/script.rs | 2 +- verification/src/accept_block.rs | 16 +++++++-- verification/src/sigops.rs | 57 +++++++++++++++++++++++++++++++- 5 files changed, 80 insertions(+), 6 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index ce3a30be..d0c4554d 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -230,6 +230,15 @@ impl ConsensusFork { sigops <= 20_000, } } + + pub fn check_block_sigops_cost(&self, sigops_cost: usize, deployments: &Deployments) -> bool { + match *self { + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => + sigops_cost <= segwit::MAX_BLOCK_SIGOPS_COST, + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => + true, + } + } } #[cfg(test)] diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index c3d18097..277e3dfd 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -305,7 +305,7 @@ pub fn verify_script( if flags.verify_witness { if let Some((witness_version, witness_program)) = pubkey2.parse_witness_program() { - if script_sig != Builder::default().push_data(&pubkey2) { + if script_sig != &Builder::default().push_data(&pubkey2).into_script() { return Err(Error::WitnessMalleatedP2SH); } diff --git a/script/src/script.rs b/script/src/script.rs index 35079b40..2f0dcdf6 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -146,7 +146,7 @@ impl Script { /// Parse witness program. Returns Some(witness program version, code) or None if not a witness program. pub fn parse_witness_program(&self) -> Option<(u8, &[u8])> { - if self.data.len() > 4 || self.data.len() > 42 || self.data.len() != self.data[1] as usize + 2 { + if self.data.len() < 4 || self.data.len() > 42 || self.data.len() != self.data[1] as usize + 2 { return None; } let witness_version = match Opcode::from_u8(self.data[0]) { diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index 16b248e8..1e166b4f 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -3,7 +3,7 @@ use crypto::dhash256; use db::{TransactionOutputProvider, BlockHeaderProvider}; use script; use ser::Stream; -use sigops::transaction_sigops; +use sigops::{transaction_sigops, transaction_sigops_cost} ; use work::block_reward_satoshi; use duplex_store::DuplexTransactionOutputProvider; use deployments::{Deployments, ActiveDeployments}; @@ -35,7 +35,7 @@ impl<'a> BlockAcceptor<'a> { serialized_size: BlockSerializedSize::new(block, consensus, deployments, height), coinbase_script: BlockCoinbaseScript::new(block, consensus, height), coinbase_claim: BlockCoinbaseClaim::new(block, store, height), - sigops: BlockSigops::new(block, store, consensus, height), + sigops: BlockSigops::new(block, store, consensus, deployments, height), witness: BlockWitness::new(block, deployments), } } @@ -128,15 +128,17 @@ pub struct BlockSigops<'a> { block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, + deployments: ActiveDeployments<'a>, height: u32, } impl<'a> BlockSigops<'a> { - fn new(block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, height: u32) -> Self { + fn new(block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, deployments: ActiveDeployments<'a>, height: u32) -> Self { BlockSigops { block: block, store: store, consensus: consensus, + deployments: deployments, height: height, } } @@ -150,6 +152,14 @@ impl<'a> BlockSigops<'a> { let size = self.block.size(); if sigops > self.consensus.fork.max_block_sigops(self.height, size) { + return Err(Error::MaximumSigops) + } + + // TODO: when segwit is enabled, only sigop_cost must be checked!!! + let sigops_cost = self.block.transactions.iter() + .map(|tx| transaction_sigops_cost(&tx.raw, &store, bip16_active)) + .sum::(); + if !self.consensus.fork.check_block_sigops_cost(sigops_cost, &self.deployments) { Err(Error::MaximumSigops) } else { Ok(()) diff --git a/verification/src/sigops.rs b/verification/src/sigops.rs index cf9e2e16..9725597f 100644 --- a/verification/src/sigops.rs +++ b/verification/src/sigops.rs @@ -1,6 +1,8 @@ +// TODO: excess clones +use network::segwit; use chain::Transaction; use db::TransactionOutputProvider; -use script::Script; +use script::{Script, ScriptWitness}; /// Counts signature operations in given transaction /// bip16_active flag indicates if we should also count signature operations @@ -16,6 +18,7 @@ pub fn transaction_sigops( output_script.sigops_count(false) }).sum(); + // TODO: bitcoin/bitcoin also includes input_sigops here if transaction.is_coinbase() { return output_sigops; } @@ -38,3 +41,55 @@ pub fn transaction_sigops( input_sigops + output_sigops + bip16_sigops } + +pub fn transaction_sigops_cost( + transaction: &Transaction, + store: &TransactionOutputProvider, + bip16_active: bool, +) -> usize { + let sigops_cost = transaction_sigops(transaction, store, bip16_active) * segwit::WITNESS_SCALE_FACTOR; + let witness_sigops_cost: usize = transaction.inputs.iter() + .map(|input| store.transaction_output(&input.previous_output, usize::max_value()) + .map(|output| witness_sigops(&Script::new(input.script_sig.clone()), &Script::new(output.script_pubkey.clone()), &ScriptWitness { stack: input.script_witness.clone().into() })) + .unwrap_or(0)) + .sum(); + sigops_cost + witness_sigops_cost +} + +fn witness_sigops( + script_sig: &Script, + script_pubkey: &Script, + script_witness: &ScriptWitness, +) -> usize { + if let Some((witness_version, witness_program)) = script_pubkey.parse_witness_program() { + return witness_program_sigops(witness_version, witness_program, script_witness); + } + + if script_pubkey.is_pay_to_script_hash() && script_sig.is_push_only() { + if let Some(Ok(instruction)) = script_sig.iter().last() { + if let Some(data) = instruction.data { + let subscript = Script::new(data.into()); + if let Some((witness_version, witness_program)) = subscript.parse_witness_program() { + return witness_program_sigops(witness_version, witness_program, script_witness); + } + } + } + } + + 0 +} + +fn witness_program_sigops( + witness_version: u8, + witness_program: &[u8], + script_witness: &ScriptWitness, +) -> usize { + match witness_version { + 0 if witness_program.len() == 20 => 1, + 0 if witness_program.len() == 32 => match script_witness.stack.last().ok() { + Some(subscript) => Script::new(subscript.clone()).sigops_count(true), + _ => 0 + }, + _ => 0, + } +} From 062a68204cff08e09a86549fdf6b3d393fa91be1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Fri, 18 Aug 2017 15:13:39 +0300 Subject: [PATCH 09/35] segwit: sync changes --- p2p/src/p2p.rs | 4 +- p2p/src/protocol/sync.rs | 6 +-- sync/src/inbound_connection_factory.rs | 5 ++- sync/src/lib.rs | 7 ++- sync/src/local_node.rs | 2 +- sync/src/synchronization_chain.rs | 43 +++++++++++++------ sync/src/synchronization_client_core.rs | 57 +++++++++++++++++++------ sync/src/synchronization_executor.rs | 26 +++++------ sync/src/synchronization_peers.rs | 37 +++++++++++++--- sync/src/synchronization_server.rs | 12 +++--- verification/src/lib.rs | 1 + 11 files changed, 140 insertions(+), 60 deletions(-) diff --git a/p2p/src/p2p.rs b/p2p/src/p2p.rs index 14e5871b..d9dd7c0b 100644 --- a/p2p/src/p2p.rs +++ b/p2p/src/p2p.rs @@ -395,8 +395,8 @@ impl Context { } } - pub fn create_sync_session(&self, start_height: i32, outbound_connection: OutboundSyncConnectionRef) -> InboundSyncConnectionRef { - self.local_sync_node.create_sync_session(start_height, outbound_connection) + pub fn create_sync_session(&self, start_height: i32, services: Services, outbound_connection: OutboundSyncConnectionRef) -> InboundSyncConnectionRef { + self.local_sync_node.create_sync_session(start_height, services, outbound_connection) } pub fn connections(&self) -> &Connections { diff --git a/p2p/src/protocol/sync.rs b/p2p/src/protocol/sync.rs index 915224ec..58284bf4 100644 --- a/p2p/src/protocol/sync.rs +++ b/p2p/src/protocol/sync.rs @@ -1,6 +1,6 @@ use std::sync::Arc; use bytes::Bytes; -use message::{Command, Error, Payload, types, deserialize_payload}; +use message::{Command, Error, Payload, Services, types, deserialize_payload}; use protocol::Protocol; use net::PeerContext; use ser::SERIALIZE_TRANSACTION_WITNESS; @@ -10,7 +10,7 @@ pub type OutboundSyncConnectionRef = Arc; pub type LocalSyncNodeRef = Box; pub trait LocalSyncNode : Send + Sync { - fn create_sync_session(&self, height: i32, outbound: OutboundSyncConnectionRef) -> InboundSyncConnectionRef; + fn create_sync_session(&self, height: i32, services: Services, outbound: OutboundSyncConnectionRef) -> InboundSyncConnectionRef; } pub trait InboundSyncConnection : Send + Sync { @@ -183,7 +183,7 @@ pub struct SyncProtocol { impl SyncProtocol { pub fn new(context: Arc) -> Self { let outbound_connection = Arc::new(OutboundSync::new(context.clone())); - let inbound_connection = context.global().create_sync_session(0, outbound_connection); + let inbound_connection = context.global().create_sync_session(0, context.info().version_message.services(), outbound_connection); SyncProtocol { inbound_connection: inbound_connection, context: context, diff --git a/sync/src/inbound_connection_factory.rs b/sync/src/inbound_connection_factory.rs index d07d13a0..8b748502 100644 --- a/sync/src/inbound_connection_factory.rs +++ b/sync/src/inbound_connection_factory.rs @@ -1,5 +1,6 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use p2p::{LocalSyncNode, LocalSyncNodeRef, OutboundSyncConnectionRef, InboundSyncConnectionRef}; +use message::Services; use inbound_connection::InboundConnection; use types::{PeersRef, LocalNodeRef}; @@ -30,11 +31,11 @@ impl InboundConnectionFactory { } impl LocalSyncNode for InboundConnectionFactory { - fn create_sync_session(&self, _best_block_height: i32, outbound_connection: OutboundSyncConnectionRef) -> InboundSyncConnectionRef { + fn create_sync_session(&self, _best_block_height: i32, services: Services, outbound_connection: OutboundSyncConnectionRef) -> InboundSyncConnectionRef { let peer_index = self.counter.fetch_add(1, Ordering::SeqCst) + 1; trace!(target: "sync", "Creating new sync session with peer#{}", peer_index); // remember outbound connection - self.peers.insert(peer_index, outbound_connection); + self.peers.insert(peer_index, services, outbound_connection); // create new inbound connection InboundConnection::new(peer_index, self.peers.clone(), self.node.clone()).boxed() } diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 89e63bd6..2607f49e 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -41,6 +41,7 @@ pub use types::PeersRef; use std::sync::Arc; use parking_lot::RwLock; +use message::Services; use network::{Magic, ConsensusParams}; use primitives::hash::H256; use verification::BackwardsCompatibleChainVerifier as ChainVerifier; @@ -107,7 +108,11 @@ pub fn create_local_sync_node(consensus: ConsensusParams, db: db::SharedStore, p let memory_pool = Arc::new(RwLock::new(MemoryPool::new())); let sync_state = SynchronizationStateRef::new(SynchronizationState::with_storage(db.clone())); - let sync_chain = SyncChain::new(db.clone(), memory_pool.clone()); + let sync_chain = SyncChain::new(db.clone(), consensus.clone(), memory_pool.clone()); + if sync_chain.is_segwit_active() { + peers.require_peer_services(Services::default().with_witness(true)); + } + let chain_verifier = Arc::new(ChainVerifier::new(db.clone(), consensus.clone())); let sync_executor = SyncExecutor::new(peers.clone()); let sync_server = Arc::new(ServerImpl::new(peers.clone(), db.clone(), memory_pool.clone(), sync_executor.clone())); diff --git a/sync/src/local_node.rs b/sync/src/local_node.rs index 7aea43be..66b9577e 100644 --- a/sync/src/local_node.rs +++ b/sync/src/local_node.rs @@ -374,7 +374,7 @@ pub mod tests { let memory_pool = Arc::new(RwLock::new(MemoryPool::new())); let storage = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); let sync_state = SynchronizationStateRef::new(SynchronizationState::with_storage(storage.clone())); - let chain = Chain::new(storage.clone(), memory_pool.clone()); + let chain = Chain::new(storage.clone(), ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), memory_pool.clone()); let sync_peers = Arc::new(PeersImpl::default()); let executor = DummyTaskExecutor::new(); let server = Arc::new(DummyServer::new()); diff --git a/sync/src/synchronization_chain.rs b/sync/src/synchronization_chain.rs index e5c30711..e10d86c0 100644 --- a/sync/src/synchronization_chain.rs +++ b/sync/src/synchronization_chain.rs @@ -4,10 +4,12 @@ use linked_hash_map::LinkedHashMap; use chain::{BlockHeader, Transaction, IndexedBlockHeader, IndexedBlock, IndexedTransaction}; use db; use miner::{MemoryPoolOrderingStrategy, MemoryPoolInformation}; +use network::ConsensusParams; use primitives::bytes::Bytes; use primitives::hash::H256; use utils::{BestHeadersChain, BestHeadersChainInformation, HashQueueChain, HashPosition}; use types::{BlockHeight, StorageRef, MemoryPoolRef}; +use verification::Deployments; /// Index of 'verifying' queue const VERIFYING_QUEUE: usize = 0; @@ -104,6 +106,8 @@ pub struct Chain { best_storage_block: db::BestBlock, /// Local blocks storage storage: StorageRef, + /// Consensus params. + consensus: ConsensusParams, /// In-memory queue of blocks hashes hash_chain: HashQueueChain, /// In-memory queue of blocks headers @@ -114,6 +118,8 @@ pub struct Chain { memory_pool: MemoryPoolRef, /// Blocks that have been marked as dead-ends dead_end_blocks: HashSet, + /// Is SegWit active? + is_segwit_active: bool, } impl BlockState { @@ -138,22 +144,25 @@ impl BlockState { impl Chain { /// Create new `Chain` with given storage - pub fn new(storage: StorageRef, memory_pool: MemoryPoolRef) -> Self { + pub fn new(storage: StorageRef, consensus: ConsensusParams, memory_pool: MemoryPoolRef) -> Self { // we only work with storages with genesis block let genesis_block_hash = storage.block_hash(0) .expect("storage with genesis block is required"); let best_storage_block = storage.best_block(); let best_storage_block_hash = best_storage_block.hash.clone(); + let is_segwit_active = Deployments::new().segwit(best_storage_block.number, storage.as_block_header_provider(), &consensus); Chain { genesis_block_hash: genesis_block_hash, best_storage_block: best_storage_block, storage: storage, + consensus: consensus, hash_chain: HashQueueChain::with_number_of_queues(NUMBER_OF_QUEUES), headers_chain: BestHeadersChain::new(best_storage_block_hash), verifying_transactions: LinkedHashMap::new(), memory_pool: memory_pool, dead_end_blocks: HashSet::new(), + is_segwit_active: is_segwit_active, } } @@ -179,6 +188,11 @@ impl Chain { self.memory_pool.clone() } + /// Is segwit active + pub fn is_segwit_active(&self) -> bool { + self.is_segwit_active + } + /// Get number of blocks in given state pub fn length_of_blocks_state(&self, state: BlockState) -> BlockHeight { match state { @@ -348,7 +362,8 @@ impl Chain { self.storage.canonize(block.hash())?; // remember new best block hash - self.best_storage_block = self.storage.best_block(); + self.best_storage_block = self.storage.as_store().best_block(); + self.is_segwit_active = Deployments::new().segwit(self.best_storage_block.number, self.storage.as_block_header_provider(), &self.consensus); // remove inserted block + handle possible reorganization in headers chain // TODO: mk, not sure if we need both of those params @@ -384,6 +399,7 @@ impl Chain { // remember new best block hash self.best_storage_block = self.storage.best_block(); + self.is_segwit_active = Deployments::new().segwit(self.best_storage_block.number, self.storage.as_block_header_provider(), &self.consensus); // remove inserted block + handle possible reorganization in headers chain // TODO: mk, not sure if we need both of those params @@ -723,6 +739,7 @@ mod tests { use chain::{Transaction, IndexedBlockHeader}; use db::BlockChainDatabase; use miner::MemoryPool; + use network::{Magic, ConsensusParams, ConsensusFork}; use primitives::hash::H256; use super::{Chain, BlockState, TransactionState, BlockInsertionResult}; use utils::HashPosition; @@ -731,7 +748,7 @@ mod tests { fn chain_empty() { let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); let db_best_block = db.best_block(); - let chain = Chain::new(db.clone(), Arc::new(RwLock::new(MemoryPool::new()))); + let chain = Chain::new(db.clone(), ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); assert_eq!(chain.information().scheduled, 0); assert_eq!(chain.information().requested, 0); assert_eq!(chain.information().verifying, 0); @@ -748,7 +765,7 @@ mod tests { #[test] fn chain_block_path() { let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db.clone(), Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db.clone(), ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); // add 6 blocks to scheduled queue let blocks = test_data::build_n_empty_blocks_from_genesis(6, 0); @@ -800,7 +817,7 @@ mod tests { #[test] fn chain_block_locator_hashes() { let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); let genesis_hash = chain.best_block().hash; assert_eq!(chain.block_locator_hashes(), vec![genesis_hash.clone()]); @@ -885,7 +902,7 @@ mod tests { #[test] fn chain_transaction_state() { let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); let genesis_block = test_data::genesis(); let block1 = test_data::block_h1(); let tx1: Transaction = test_data::TransactionBuilder::with_version(1).into(); @@ -922,7 +939,7 @@ mod tests { let tx2_hash = tx2.hash(); let db = Arc::new(BlockChainDatabase::init_test_chain(vec![b0.into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.verify_transaction(tx1.into()); chain.insert_verified_transaction(tx2.into()); @@ -946,7 +963,7 @@ mod tests { .set_default_input(0).set_output(400).store(test_chain); // t4 let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.verify_transaction(test_chain.at(0).into()); chain.verify_transaction(test_chain.at(1).into()); chain.verify_transaction(test_chain.at(2).into()); @@ -968,7 +985,7 @@ mod tests { .set_default_input(0).set_output(400).store(test_chain); // t4 let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.insert_verified_transaction(test_chain.at(0).into()); chain.insert_verified_transaction(test_chain.at(1).into()); chain.insert_verified_transaction(test_chain.at(2).into()); @@ -994,7 +1011,7 @@ mod tests { let tx2_hash = tx2.hash(); let db = Arc::new(BlockChainDatabase::init_test_chain(vec![b0.into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.verify_transaction(tx1.into()); chain.insert_verified_transaction(tx2.into()); @@ -1042,7 +1059,7 @@ mod tests { let tx5 = b5.transactions[0].clone(); let db = Arc::new(BlockChainDatabase::init_test_chain(vec![genesis.into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.insert_verified_transaction(tx3.into()); chain.insert_verified_transaction(tx4.into()); @@ -1086,7 +1103,7 @@ mod tests { // insert tx2 to memory pool let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.insert_verified_transaction(tx2.clone().into()); chain.insert_verified_transaction(tx3.clone().into()); // insert verified block with tx1 @@ -1105,7 +1122,7 @@ mod tests { .reset().set_input(&data_chain.at(0), 0).add_output(30).store(data_chain); // transaction0 -> transaction2 let db = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()])); - let mut chain = Chain::new(db, Arc::new(RwLock::new(MemoryPool::new()))); + let mut chain = Chain::new(db, ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), Arc::new(RwLock::new(MemoryPool::new()))); chain.insert_verified_transaction(data_chain.at(1).into()); assert_eq!(chain.information().transactions.transactions_count, 1); chain.insert_verified_transaction(data_chain.at(2).into()); diff --git a/sync/src/synchronization_client_core.rs b/sync/src/synchronization_client_core.rs index 6a7f40ea..719540c8 100644 --- a/sync/src/synchronization_client_core.rs +++ b/sync/src/synchronization_client_core.rs @@ -6,7 +6,7 @@ use futures::Future; use parking_lot::Mutex; use time::precise_time_s; use chain::{IndexedBlockHeader, IndexedTransaction, Transaction, IndexedBlock}; -use message::types; +use message::{types, Services}; use message::common::{InventoryType, InventoryVector}; use miner::transaction_fee_rate; use primitives::hash::H256; @@ -226,6 +226,8 @@ impl ClientCore for SynchronizationClientCore where T: TaskExecutor { } // else ask for all unknown transactions and blocks + let is_segwit_active = self.chain.is_segwit_active(); + let ask_for_witness = is_segwit_active && self.peers.is_segwit_enabled(peer_index); let unknown_inventory: Vec<_> = message.inventory.into_iter() .filter(|item| { match item.inv_type { @@ -253,6 +255,24 @@ impl ClientCore for SynchronizationClientCore where T: TaskExecutor { } } }) + // we are not synchronizing => + // 1) either segwit is active and we are connected to segwit-enabled nodes => we could ask for witness + // 2) or segwit is inactive => we shall not ask for witness + .map(|item| if !ask_for_witness { + item + } else { + match item.inv_type { + InventoryType::MessageTx => InventoryVector { + inv_type: InventoryType::MessageWitnessTx, + hash: item.hash, + }, + InventoryType::MessageBlock => InventoryVector { + inv_type: InventoryType::MessageWitnessBlock, + hash: item.hash, + }, + _ => item, + } + }) .collect(); // if everything is known => ignore this message @@ -262,7 +282,6 @@ impl ClientCore for SynchronizationClientCore where T: TaskExecutor { } // ask for unknown items -// TODO: if segwit is active, ask with witness data let message = types::GetData::with_inventory(unknown_inventory); self.executor.execute(Task::GetData(peer_index, message)); } @@ -964,10 +983,13 @@ impl SynchronizationClientCore where T: TaskExecutor { // remember that peer is asked for these blocks self.peers_tasks.on_blocks_requested(peer, &chunk_hashes); -// TODO: if block is believed to have witness, ask with witness data - // request blocks + // request blocks. If block is believed to have witness - ask for witness + let is_segwit_active = self.chain.is_segwit_active(); let getdata = types::GetData { - inventory: chunk_hashes.into_iter().map(InventoryVector::block).collect(), + inventory: chunk_hashes.into_iter().map(|h| InventoryVector { + inv_type: if is_segwit_active { InventoryType::MessageWitnessBlock } else { InventoryType::MessageBlock }, + hash: h, + }).collect(), }; tasks.push(Task::GetData(peer, getdata)); } @@ -1047,8 +1069,8 @@ impl SynchronizationClientCore where T: TaskExecutor { // update block processing speed self.block_speed_meter.checkpoint(); -// TODO: if segwit activates after this block, disconnect from all nodes without NODE_WITNESS support -// TODO: no more connections to !NODE_WITNESS nodes + // remember if SegWit was active before this block + let segwit_was_active = self.chain.is_segwit_active(); // remove flags let needs_relay = !self.do_not_relay.remove(block.hash()); @@ -1070,6 +1092,13 @@ impl SynchronizationClientCore where T: TaskExecutor { // update shared state self.shared_state.update_best_storage_block_height(self.chain.best_storage_block().number); + // if SegWit activated after this block insertion: + // 1) no more connections to !NODE_WITNESS nodes + // 2) disconnect from all nodes without NODE_WITNESS support + if !segwit_was_active && self.chain.is_segwit_active() { + self.peers.require_peer_services(Services::default().with_witness(true)); + } + // notify listener if let Some(best_block_hash) = insert_result.canonized_blocks_hashes.last() { if let Some(ref listener) = self.listener { @@ -1233,7 +1262,7 @@ pub mod tests { use chain::{Block, Transaction}; use db::BlockChainDatabase; use message::common::InventoryVector; - use message::types; + use message::{Services, types}; use miner::MemoryPool; use network::{ConsensusParams, ConsensusFork, Magic}; use primitives::hash::H256; @@ -1286,7 +1315,7 @@ pub mod tests { }; let sync_state = SynchronizationStateRef::new(SynchronizationState::with_storage(storage.clone())); let memory_pool = Arc::new(RwLock::new(MemoryPool::new())); - let chain = Chain::new(storage.clone(), memory_pool.clone()); + let chain = Chain::new(storage.clone(), ConsensusParams::new(Magic::Unitest, ConsensusFork::NoFork), memory_pool.clone()); let executor = DummyTaskExecutor::new(); let config = Config { close_connection_on_bad_block: true }; @@ -2087,7 +2116,7 @@ pub mod tests { let (_, core, sync) = create_sync(None, Some(dummy_verifier)); - core.lock().peers.insert(0, DummyOutboundSyncConnection::new()); + core.lock().peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); assert!(core.lock().peers.enumerate().contains(&0)); sync.on_block(0, b0.into()); @@ -2108,7 +2137,7 @@ pub mod tests { chain.mark_dead_end_block(&b0.hash()); } - core.lock().peers.insert(0, DummyOutboundSyncConnection::new()); + core.lock().peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); assert!(core.lock().peers.enumerate().contains(&0)); sync.on_headers(0, types::Headers::with_headers(vec![b0.block_header.clone(), b1.block_header.clone(), b2.block_header.clone()])); @@ -2130,7 +2159,7 @@ pub mod tests { } core.lock().set_verify_headers(true); - core.lock().peers.insert(0, DummyOutboundSyncConnection::new()); + core.lock().peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); assert!(core.lock().peers.enumerate().contains(&0)); sync.on_headers(0, types::Headers::with_headers(vec![b0.block_header.clone(), b1.block_header.clone(), b2.block_header.clone()])); @@ -2149,7 +2178,7 @@ pub mod tests { chain.mark_dead_end_block(&b0.hash()); } - core.lock().peers.insert(0, DummyOutboundSyncConnection::new()); + core.lock().peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); assert!(core.lock().peers.enumerate().contains(&0)); sync.on_block(0, b0.into()); @@ -2169,7 +2198,7 @@ pub mod tests { chain.mark_dead_end_block(&b0.hash()); } - core.lock().peers.insert(0, DummyOutboundSyncConnection::new()); + core.lock().peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); assert!(core.lock().peers.enumerate().contains(&0)); sync.on_block(0, b1.into()); diff --git a/sync/src/synchronization_executor.rs b/sync/src/synchronization_executor.rs index cc3969e5..a5fb5ce3 100644 --- a/sync/src/synchronization_executor.rs +++ b/sync/src/synchronization_executor.rs @@ -250,7 +250,7 @@ pub mod tests { use std::time; use parking_lot::{Mutex, Condvar}; use chain::Transaction; - use message::types; + use message::{Services, types}; use inbound_connection::tests::DummyOutboundSyncConnection; use local_node::tests::{default_filterload, make_filteradd}; use synchronization_peers::{PeersImpl, PeersContainer, PeersFilters, PeersOptions, BlockAnnouncementType}; @@ -303,9 +303,9 @@ pub mod tests { let executor = LocalSynchronizationTaskExecutor::new(peers.clone()); let c1 = DummyOutboundSyncConnection::new(); - peers.insert(1, c1.clone()); + peers.insert(1, Services::default(), c1.clone()); let c2 = DummyOutboundSyncConnection::new(); - peers.insert(2, c2.clone()); + peers.insert(2, Services::default(), c2.clone()); peers.set_block_announcement_type(2, BlockAnnouncementType::SendCompactBlock); executor.execute(Task::RelayNewBlock(test_data::genesis().into())); @@ -319,9 +319,9 @@ pub mod tests { let executor = LocalSynchronizationTaskExecutor::new(peers.clone()); let c1 = DummyOutboundSyncConnection::new(); - peers.insert(1, c1.clone()); + peers.insert(1, Services::default(), c1.clone()); let c2 = DummyOutboundSyncConnection::new(); - peers.insert(2, c2.clone()); + peers.insert(2, Services::default(), c2.clone()); peers.set_block_announcement_type(2, BlockAnnouncementType::SendHeaders); executor.execute(Task::RelayNewBlock(test_data::genesis().into())); @@ -343,26 +343,26 @@ pub mod tests { // peer#1 wants tx1 let c1 = DummyOutboundSyncConnection::new(); - peers.insert(1, c1.clone()); + peers.insert(1, Services::default(), c1.clone()); peers.set_bloom_filter(1, default_filterload()); peers.update_bloom_filter(1, make_filteradd(&*tx1_hash)); // peer#2 wants tx2 let c2 = DummyOutboundSyncConnection::new(); - peers.insert(2, c2.clone()); + peers.insert(2, Services::default(), c2.clone()); peers.set_bloom_filter(2, default_filterload()); peers.update_bloom_filter(2, make_filteradd(&*tx2_hash)); // peer#3 wants tx1 + tx2 transactions let c3 = DummyOutboundSyncConnection::new(); - peers.insert(3, c3.clone()); + peers.insert(3, Services::default(), c3.clone()); peers.set_bloom_filter(3, default_filterload()); peers.update_bloom_filter(3, make_filteradd(&*tx1_hash)); peers.update_bloom_filter(3, make_filteradd(&*tx2_hash)); // peer#4 has default behaviour (no filter) let c4 = DummyOutboundSyncConnection::new(); - peers.insert(4, c4.clone()); + peers.insert(4, Services::default(), c4.clone()); // peer#5 wants some other transactions let c5 = DummyOutboundSyncConnection::new(); - peers.insert(5, c5.clone()); + peers.insert(5, Services::default(), c5.clone()); peers.set_bloom_filter(5, default_filterload()); peers.update_bloom_filter(5, make_filteradd(&*tx3_hash)); @@ -389,13 +389,13 @@ pub mod tests { let executor = LocalSynchronizationTaskExecutor::new(peers.clone()); let c2 = DummyOutboundSyncConnection::new(); - peers.insert(2, c2.clone()); + peers.insert(2, Services::default(), c2.clone()); peers.set_fee_filter(2, types::FeeFilter::with_fee_rate(3000)); let c3 = DummyOutboundSyncConnection::new(); - peers.insert(3, c3.clone()); + peers.insert(3, Services::default(), c3.clone()); peers.set_fee_filter(3, types::FeeFilter::with_fee_rate(4000)); let c4 = DummyOutboundSyncConnection::new(); - peers.insert(4, c4.clone()); + peers.insert(4, Services::default(), c4.clone()); executor.execute(Task::RelayNewTransaction(test_data::genesis().transactions[0].clone().into(), 3500)); diff --git a/sync/src/synchronization_peers.rs b/sync/src/synchronization_peers.rs index d2f414fb..3eda82f5 100644 --- a/sync/src/synchronization_peers.rs +++ b/sync/src/synchronization_peers.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use parking_lot::RwLock; use chain::{IndexedBlock, IndexedTransaction}; -use message::types; +use message::{types, Services}; use p2p::OutboundSyncConnectionRef; use primitives::hash::H256; use types::PeerIndex; @@ -40,6 +40,8 @@ pub struct MerkleBlockArtefacts { /// Connected peers pub trait Peers : Send + Sync + PeersContainer + PeersFilters + PeersOptions { + /// Require peers services. + fn require_peer_services(&self, services: Services); /// Get peer connection fn connection(&self, peer_index: PeerIndex) -> Option; } @@ -49,7 +51,7 @@ pub trait PeersContainer { /// Enumerate all known peers (TODO: iterator + separate entity 'Peer') fn enumerate(&self) -> Vec; /// Insert new peer connection - fn insert(&self, peer_index: PeerIndex, connection: OutboundSyncConnectionRef); + fn insert(&self, peer_index: PeerIndex, services: Services, connection: OutboundSyncConnectionRef); /// Remove peer connection fn remove(&self, peer_index: PeerIndex); /// Close and remove peer connection due to misbehaving @@ -84,6 +86,8 @@ pub trait PeersFilters { /// Options for peers connections pub trait PeersOptions { + /// Is node supporting SegWit? + fn is_segwit_enabled(&self, peer_index: PeerIndex) -> bool; /// Set up new block announcement type for the connection fn set_block_announcement_type(&self, peer_index: PeerIndex, announcement_type: BlockAnnouncementType); /// Set up new transaction announcement type for the connection @@ -94,6 +98,8 @@ pub trait PeersOptions { struct Peer { /// Connection to this peer pub connection: OutboundSyncConnectionRef, + /// Peer services + pub services: Services, /// Connection filter pub filter: ConnectionFilter, /// Block announcement type @@ -111,9 +117,10 @@ pub struct PeersImpl { } impl Peer { - pub fn with_connection(connection: OutboundSyncConnectionRef) -> Self { + pub fn new(services: Services, connection: OutboundSyncConnectionRef) -> Self { Peer { connection: connection, + services: services, filter: ConnectionFilter::default(), block_announcement_type: BlockAnnouncementType::SendInventory, transaction_announcement_type: TransactionAnnouncementType::SendInventory, @@ -122,6 +129,19 @@ impl Peer { } impl Peers for PeersImpl { + fn require_peer_services(&self, services: Services) { + // possible optimization: force p2p level to establish connections to SegWit-nodes only + // without it, all other nodes will be eventually banned (this could take some time, though) + let mut peers = self.peers.write(); + for peer_index in peers.iter().filter(|&(_, p)| p.services.includes(&services)).map(|(p, _)| *p).collect::>() { + let peer = peers.remove(&peer_index).expect("iterating peers keys; qed"); + let expected_services: u64 = services.into(); + let actual_services: u64 = peer.services.into(); + warn!(target: "sync", "Disconnecting from peer#{} because of insufficient services. Expected {:x}, actual: {:x}", peer_index, expected_services, actual_services); + peer.connection.close(); + } + } + fn connection(&self, peer_index: PeerIndex) -> Option { self.peers.read().get(&peer_index).map(|peer| peer.connection.clone()) } @@ -132,9 +152,9 @@ impl PeersContainer for PeersImpl { self.peers.read().keys().cloned().collect() } - fn insert(&self, peer_index: PeerIndex, connection: OutboundSyncConnectionRef) { + fn insert(&self, peer_index: PeerIndex, services: Services, connection: OutboundSyncConnectionRef) { trace!(target: "sync", "Connected to peer#{}", peer_index); - assert!(self.peers.write().insert(peer_index, Peer::with_connection(connection)).is_none()); + assert!(self.peers.write().insert(peer_index, Peer::new(services, connection)).is_none()); } fn remove(&self, peer_index: PeerIndex) { @@ -227,6 +247,13 @@ impl PeersFilters for PeersImpl { } impl PeersOptions for PeersImpl { + fn is_segwit_enabled(&self, peer_index: PeerIndex) -> bool { + self.peers.read() + .get(&peer_index) + .map(|peer| peer.services.witness()) + .unwrap_or_default() + } + fn set_block_announcement_type(&self, peer_index: PeerIndex, announcement_type: BlockAnnouncementType) { if let Some(peer) = self.peers.write().get_mut(&peer_index) { peer.block_announcement_type = announcement_type; diff --git a/sync/src/synchronization_server.rs b/sync/src/synchronization_server.rs index 37888d34..f1fd96b9 100644 --- a/sync/src/synchronization_server.rs +++ b/sync/src/synchronization_server.rs @@ -482,7 +482,7 @@ pub mod tests { use parking_lot::{Mutex, RwLock}; use db::{BlockChainDatabase}; use message::types; - use message::common::{self, InventoryVector, InventoryType}; + use message::common::{self, Services, InventoryVector, InventoryType}; use primitives::hash::H256; use chain::Transaction; use inbound_connection::tests::DummyOutboundSyncConnection; @@ -664,7 +664,7 @@ pub mod tests { fn server_get_block_txn_responds_when_good_request() { let (_, _, executor, peers, server) = create_synchronization_server(); - peers.insert(0, DummyOutboundSyncConnection::new()); + peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); peers.hash_known_as(0, test_data::genesis().hash(), KnownHashType::CompactBlock); // when asking for block_txns @@ -689,7 +689,7 @@ pub mod tests { fn server_get_block_txn_do_not_responds_when_bad_request() { let (_, _, _, peers, server) = create_synchronization_server(); - peers.insert(0, DummyOutboundSyncConnection::new()); + peers.insert(0, Services::default(), DummyOutboundSyncConnection::new()); assert!(peers.enumerate().contains(&0)); // when asking for block_txns @@ -828,7 +828,7 @@ pub mod tests { storage.canonize(&b2.hash()).unwrap(); // This peer won't get any blocks, because it has not set filter for the connection - let peer_index2 = 1; peers.insert(peer_index2, DummyOutboundSyncConnection::new()); + let peer_index2 = 1; peers.insert(peer_index2, Services::default(), DummyOutboundSyncConnection::new()); let mut loop_task = ServerTask::GetData(peer_index2, types::GetData {inventory: vec![ InventoryVector { inv_type: InventoryType::MessageFilteredBlock, hash: b1_hash.clone() }, @@ -851,7 +851,7 @@ pub mod tests { let mut counter = 2; for (get_tx1, get_tx2) in peers_config { - let peer_index = counter; peers.insert(peer_index, DummyOutboundSyncConnection::new()); + let peer_index = counter; peers.insert(peer_index, Services::default(), DummyOutboundSyncConnection::new()); counter += 1; // setup filter peers.set_bloom_filter(peer_index, default_filterload()); @@ -922,7 +922,7 @@ pub mod tests { storage.canonize(&b1.hash()).unwrap(); // This peer will receive compact block - let peer_index2 = 1; peers.insert(peer_index2, DummyOutboundSyncConnection::new()); + let peer_index2 = 1; peers.insert(peer_index2, Services::default(), DummyOutboundSyncConnection::new()); // ask for data let mut loop_task = ServerTask::GetData(peer_index2, types::GetData {inventory: vec![ diff --git a/verification/src/lib.rs b/verification/src/lib.rs index 3fe014d0..15c9b5ab 100644 --- a/verification/src/lib.rs +++ b/verification/src/lib.rs @@ -109,6 +109,7 @@ pub use error::{Error, TransactionError}; pub use sigops::transaction_sigops; pub use timestamp::median_timestamp; pub use work::{work_required, is_valid_proof_of_work, is_valid_proof_of_work_hash, block_reward_satoshi}; +pub use deployments::Deployments; #[derive(Debug, Clone, Copy, PartialEq)] /// Blocks verification level. From 4c432858cf196ecebc395bd8dbe609530abe6ed3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 11:57:28 +0300 Subject: [PATCH 10/35] segwit: cleaned up duplicate structs --- chain/src/block.rs | 10 --- chain/src/transaction.rs | 6 +- chain/src/witness.rs | 0 network/src/consensus.rs | 106 ++++++------------------- network/src/deployments.rs | 33 -------- network/src/lib.rs | 2 +- verification/src/accept_block.rs | 71 +++++++++-------- verification/src/accept_chain.rs | 10 +-- verification/src/accept_header.rs | 10 +-- verification/src/accept_transaction.rs | 35 ++++---- verification/src/chain_verifier.rs | 21 +++-- verification/src/deployments.rs | 47 +++++------ verification/src/error.rs | 2 + verification/src/sigops.rs | 8 +- verification/src/verify_transaction.rs | 27 ++++--- 15 files changed, 147 insertions(+), 241 deletions(-) delete mode 100644 chain/src/witness.rs diff --git a/chain/src/block.rs b/chain/src/block.rs index 7188eb2d..09dbd945 100644 --- a/chain/src/block.rs +++ b/chain/src/block.rs @@ -11,12 +11,6 @@ pub struct Block { pub transactions: Vec, } -#[derive(Debug, PartialEq, Clone)] -pub struct WitnessBlock<'a> { - pub block_header: BlockHeader, - pub transactions: Vec<&'a Transaction>, -} - impl From<&'static str> for Block { fn from(s: &'static str) -> Self { deserialize(&s.from_hex().unwrap() as &[u8]).unwrap() @@ -60,10 +54,6 @@ impl Block { pub fn hash(&self) -> H256 { self.block_header.hash() } - - pub fn cost(&self) -> u64 { - unimplemented!() - } } #[cfg(test)] diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index 9effc7a8..2fcf87c7 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -11,7 +11,9 @@ use hash::H256; use constants::{SEQUENCE_FINAL, LOCKTIME_THRESHOLD}; use ser::{Error, Serializable, Deserializable, Stream, Reader}; +/// Must be zero. const WITNESS_MARKER: u8 = 0; +/// Must be nonzero. const WITNESS_FLAG: u8 = 1; #[derive(Debug, PartialEq, Eq, Clone, Default, Serializable, Deserializable)] @@ -179,10 +181,6 @@ impl Transaction { } result } - - pub fn cost(&self) -> u64 { - unimplemented!() - } } impl Serializable for TransactionInput { diff --git a/chain/src/witness.rs b/chain/src/witness.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/network/src/consensus.rs b/network/src/consensus.rs index d0c4554d..bc9a3a78 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -1,6 +1,6 @@ use std::cmp::max; use hash::H256; -use {Magic, Deployment, Deployments}; +use {Magic, Deployment}; /// First block of SegWit2x fork. pub const SEGWIT2X_FORK_BLOCK: u32 = 0xFFFFFFFF; // not known (yet?) @@ -9,9 +9,9 @@ pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitco /// Segwit-related constants. pub mod segwit { - /// The maximum allowed weight for a block, see BIP 141 (network rule) + /// The maximum allowed weight for a block, see BIP 141 (network rule). pub const MAX_BLOCK_WEIGHT: usize = 4_000_000; - /// The maximum allowed number of signature check operations in a block (network rule) + /// The maximum allowed number of signature check operations in a block (network rule). pub const MAX_BLOCK_SIGOPS_COST: usize = 80_000; /// Witness scale factor. pub const WITNESS_SCALE_FACTOR: usize = 4; @@ -166,50 +166,33 @@ impl ConsensusFork { 8_000_000 } - // Absolute (across all forks) maximum number of sigops in single block. Currently is max(sigops) for 8MB post-HF BitcoinCash block + /// Absolute (across all forks) maximum number of sigops in single block. Currently is max(sigops) for 8MB post-HF BitcoinCash block pub fn absolute_maximum_block_sigops() -> usize { 160_000 } + pub fn max_transaction_size(&self) -> usize { + // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 + // SegWit: size * 4 <= 4_000_000 ===> max size of tx is still 1_000_000 + 1_000_000 + } + + pub fn min_block_size(&self, height: u32) -> usize { + match *self { + // size of first fork block must be larger than 1MB + ConsensusFork::BitcoinCash(fork_height) if height == fork_height => 1_000_001, + ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 0, + } + } + pub fn max_block_size(&self, height: u32) -> usize { match *self { - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => 8_000_000, ConsensusFork::SegWit2x(fork_height) if height >= fork_height => 2_000_000, + ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => 8_000_000, ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => 1_000_000, } } - pub fn check_block_size(&self, size: usize, height: u32, deployments: &Deployments) -> bool { - match *self { - // bitcoin cash fork block must be > 1_000_0000 and <= 8_000_000 - ConsensusFork::BitcoinCash(fork_height) if height == fork_height => - size > 1_000_000 && size <= 8_000_000, - // bitcoin cash support blocks up to 8_000_000 - ConsensusFork::BitcoinCash(fork_height) if height > fork_height => - size <= 8_000_000, - // max size of SegWit2x block is 2MB - ConsensusFork::SegWit2x(fork_height) if height >= fork_height => - size <= 2_000_000, - // when segwit is deployed, this expression is used. which, in turn, also allows block size <= 1_000_000 - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => - size.saturating_mul(segwit::WITNESS_SCALE_FACTOR) <= segwit::MAX_BLOCK_WEIGHT, - // without segwit and before fork, max size is 1_000_000 - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => - size <= 1_000_000, - } - } - - pub fn check_transaction_size(&self, size: usize, deployments: &Deployments) -> bool { - match *self { - // when segwit is deployed, this expression is used. which, in turn, is the same max tx size 1_000_000 - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => - size.saturating_mul(segwit::WITNESS_SCALE_FACTOR) <= segwit::MAX_BLOCK_WEIGHT, - // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 - // ConsensusFork::NoFork | ConsensusFork::SegWit2x: max size of tx is 1_000_000 - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => size <= 1_000_000, - } - } - pub fn max_block_sigops(&self, height: u32, block_size: usize) -> usize { match *self { // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) @@ -219,24 +202,12 @@ impl ConsensusFork { } } - pub fn check_block_sigops(&self, sigops: usize, height: u32, block_size: usize, deployments: &Deployments) -> bool { + pub fn max_block_sigops_cost(&self, height: u32, block_size: usize) -> usize { match *self { - // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height && block_size > 1_000_000 => - sigops <= 20_000 * (max(block_size, 1_000_000) / 1_000_000), - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => - sigops * segwit::WITNESS_SCALE_FACTOR <= segwit::MAX_BLOCK_SIGOPS_COST, - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => - sigops <= 20_000, - } - } - - pub fn check_block_sigops_cost(&self, sigops_cost: usize, deployments: &Deployments) -> bool { - match *self { - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) if deployments.is_active("segwit") => - sigops_cost <= segwit::MAX_BLOCK_SIGOPS_COST, - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => - true, + ConsensusFork::BitcoinCash(_) => + self.max_block_sigops(height, block_size) * segwit::WITNESS_SCALE_FACTOR, + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => + segwit::MAX_BLOCK_SIGOPS_COST, } } } @@ -245,7 +216,7 @@ impl ConsensusFork { mod tests { use super::super::Magic; use super::{ConsensusParams, ConsensusFork}; - use deployments::tests::DummyDeployments; + //use deployments::tests::DummyDeployments; #[test] fn test_consensus_params_bip34_height() { @@ -282,33 +253,6 @@ mod tests { assert_eq!(ConsensusParams::new(Magic::Regtest, ConsensusFork::NoFork).miner_confirmation_window, 144); } - #[test] - fn test_consensus_fork_check_transaction_size() { - assert_eq!(ConsensusFork::NoFork.check_transaction_size(800_000, &DummyDeployments::default()), true); - assert_eq!(ConsensusFork::NoFork.check_transaction_size(1_000_000, &DummyDeployments::default()), true); - assert_eq!(ConsensusFork::NoFork.check_transaction_size(4_000_000, &DummyDeployments::default()), false); - - assert_eq!(ConsensusFork::NoFork.check_transaction_size(800_000, &DummyDeployments::deployed()), true); - assert_eq!(ConsensusFork::NoFork.check_transaction_size(1_000_000, &DummyDeployments::deployed()), true); - assert_eq!(ConsensusFork::NoFork.check_transaction_size(4_000_000, &DummyDeployments::deployed()), false); - - assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(800_000, &DummyDeployments::default()), true); - assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(1_000_000, &DummyDeployments::default()), true); - assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(4_000_000, &DummyDeployments::default()), false); - - assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(800_000, &DummyDeployments::deployed()), true); - assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(1_000_000, &DummyDeployments::deployed()), true); - assert_eq!(ConsensusFork::SegWit2x(100_000).check_transaction_size(4_000_000, &DummyDeployments::deployed()), false); - - assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(800_000, &DummyDeployments::default()), true); - assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(1_000_000, &DummyDeployments::default()), true); - assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(4_000_000, &DummyDeployments::default()), false); - - assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(800_000, &DummyDeployments::deployed()), true); - assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(1_000_000, &DummyDeployments::deployed()), true); - assert_eq!(ConsensusFork::BitcoinCash(100_000).check_transaction_size(4_000_000, &DummyDeployments::deployed()), false); - } - #[test] fn test_consensus_fork_max_block_sigops() { assert_eq!(ConsensusFork::NoFork.max_block_sigops(0, 1_000_000), 20_000); diff --git a/network/src/deployments.rs b/network/src/deployments.rs index c5304673..bceec723 100644 --- a/network/src/deployments.rs +++ b/network/src/deployments.rs @@ -15,41 +15,8 @@ pub struct Deployment { pub activation: Option, } -/// Deployments state. -pub trait Deployments { - /// Is deployment currently active? - fn is_active(&self, name: &str) -> bool; -} - impl Deployment { pub fn matches(&self, version: u32) -> bool { (version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS && (version & (1 << self.bit)) != 0 } } - -#[cfg(test)] -pub mod tests { - use super::Deployments; - - #[derive(Default, Debug)] - pub struct DummyDeployments { - pub segwit_active: bool, - } - - impl DummyDeployments { - pub fn deployed() -> Self { - DummyDeployments { - segwit_active: true, - } - } - } - - impl Deployments for DummyDeployments { - fn is_active(&self, name: &str) -> bool { - match name { - "segwit" => self.segwit_active, - _ => false, - } - } - } -} diff --git a/network/src/lib.rs b/network/src/lib.rs index 45892419..8c562948 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -9,6 +9,6 @@ mod magic; pub use primitives::{hash, compact}; pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK, segwit}; -pub use deployments::{Deployment, Deployments}; +pub use deployments::Deployment; pub use magic::Magic; diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index 1e166b4f..808c8d87 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -1,4 +1,4 @@ -use network::{ConsensusParams, Deployments as NetworkDeployments, segwit}; +use network::{ConsensusParams, segwit}; use crypto::dhash256; use db::{TransactionOutputProvider, BlockHeaderProvider}; use script; @@ -6,7 +6,7 @@ use ser::Stream; use sigops::{transaction_sigops, transaction_sigops_cost} ; use work::block_reward_satoshi; use duplex_store::DuplexTransactionOutputProvider; -use deployments::{Deployments, ActiveDeployments}; +use deployments::BlockDeployments; use canon::CanonBlock; use error::{Error, TransactionError}; use timestamp::median_timestamp; @@ -27,15 +27,15 @@ impl<'a> BlockAcceptor<'a> { consensus: &'a ConsensusParams, block: CanonBlock<'a>, height: u32, - deployments: ActiveDeployments<'a>, + deployments: &'a BlockDeployments<'a>, headers: &'a BlockHeaderProvider, ) -> Self { BlockAcceptor { - finality: BlockFinality::new(block, height, deployments.deployments, headers, consensus), + finality: BlockFinality::new(block, height, deployments, headers), serialized_size: BlockSerializedSize::new(block, consensus, deployments, height), coinbase_script: BlockCoinbaseScript::new(block, consensus, height), coinbase_claim: BlockCoinbaseClaim::new(block, store, height), - sigops: BlockSigops::new(block, store, consensus, deployments, height), + sigops: BlockSigops::new(block, store, consensus, height), witness: BlockWitness::new(block, deployments), } } @@ -59,8 +59,8 @@ pub struct BlockFinality<'a> { } impl<'a> BlockFinality<'a> { - fn new(block: CanonBlock<'a>, height: u32, deployments: &'a Deployments, headers: &'a BlockHeaderProvider, params: &ConsensusParams) -> Self { - let csv_active = deployments.csv(height, headers, params); + fn new(block: CanonBlock<'a>, height: u32, deployments: &'a BlockDeployments<'a>, headers: &'a BlockHeaderProvider) -> Self { + let csv_active = deployments.csv(); BlockFinality { block: block, @@ -88,32 +88,30 @@ impl<'a> BlockFinality<'a> { pub struct BlockSerializedSize<'a> { block: CanonBlock<'a>, consensus: &'a ConsensusParams, - deployments: ActiveDeployments<'a>, height: u32, + segwit_active: bool, } impl<'a> BlockSerializedSize<'a> { - fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, deployments: ActiveDeployments<'a>, height: u32) -> Self { + fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, deployments: &'a BlockDeployments<'a>, height: u32) -> Self { + let segwit_active = deployments.segwit(); + BlockSerializedSize { block: block, consensus: consensus, - deployments: deployments, height: height, + segwit_active: segwit_active, } } fn check(&self) -> Result<(), Error> { let size = self.block.size(); - if !self.consensus.fork.check_block_size(size, self.height, &self.deployments) { - return Err(Error::Size(size)) + if size < self.consensus.fork.min_block_size(self.height) || + size > self.consensus.fork.max_block_size(self.height) { + return Err(Error::Size(size)); } - let is_segwit_active = self.deployments.is_active("segwit"); - if is_segwit_active { - if self.block.transactions.len() * segwit::WITNESS_SCALE_FACTOR > segwit::MAX_BLOCK_WEIGHT { - return Err(Error::Weight); - } - + if self.segwit_active { let size_with_witness = self.block.size_with_witness(); let weight = size * (segwit::WITNESS_SCALE_FACTOR - 1) + size_with_witness; if weight > segwit::MAX_BLOCK_WEIGHT { @@ -128,39 +126,42 @@ pub struct BlockSigops<'a> { block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, - deployments: ActiveDeployments<'a>, height: u32, + bip16_active: bool, } impl<'a> BlockSigops<'a> { - fn new(block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, deployments: ActiveDeployments<'a>, height: u32) -> Self { + fn new(block: CanonBlock<'a>, store: &'a TransactionOutputProvider, consensus: &'a ConsensusParams, height: u32) -> Self { + let bip16_active = block.header.raw.time >= consensus.bip16_time; + BlockSigops { block: block, store: store, consensus: consensus, - deployments: deployments, height: height, + bip16_active: bip16_active, } } fn check(&self) -> Result<(), Error> { let store = DuplexTransactionOutputProvider::new(self.store, &*self.block); - let bip16_active = self.block.header.raw.time >= self.consensus.bip16_time; - let sigops = self.block.transactions.iter() - .map(|tx| transaction_sigops(&tx.raw, &store, bip16_active)) - .sum::(); + let (sigops, sigops_cost) = self.block.transactions.iter() + .map(|tx| { + let tx_sigops = transaction_sigops(&tx.raw, &store, self.bip16_active); + let tx_sigops_cost = transaction_sigops_cost(&tx.raw, &store, tx_sigops); + (tx_sigops, tx_sigops_cost) + }) + .fold((0, 0), |acc, (tx_sigops, tx_sigops_cost)| (acc.0 + tx_sigops, acc.1 + tx_sigops_cost)); + // check sigops let size = self.block.size(); if sigops > self.consensus.fork.max_block_sigops(self.height, size) { - return Err(Error::MaximumSigops) + return Err(Error::MaximumSigops); } - // TODO: when segwit is enabled, only sigop_cost must be checked!!! - let sigops_cost = self.block.transactions.iter() - .map(|tx| transaction_sigops_cost(&tx.raw, &store, bip16_active)) - .sum::(); - if !self.consensus.fork.check_block_sigops_cost(sigops_cost, &self.deployments) { - Err(Error::MaximumSigops) + // check sigops cost + if sigops_cost > self.consensus.fork.max_block_sigops_cost(self.height, size) { + Err(Error::MaximumSigopsCost) } else { Ok(()) } @@ -275,10 +276,12 @@ pub struct BlockWitness<'a> { } impl<'a> BlockWitness<'a> { - fn new(block: CanonBlock<'a>, deployments: ActiveDeployments<'a>) -> Self { + fn new(block: CanonBlock<'a>, deployments: &'a BlockDeployments<'a>) -> Self { + let segwit_active = deployments.segwit(); + BlockWitness { block: block, - segwit_active: deployments.is_active("segwit"), + segwit_active: segwit_active, } } diff --git a/verification/src/accept_chain.rs b/verification/src/accept_chain.rs index d7f4bbb9..ce7e30ca 100644 --- a/verification/src/accept_chain.rs +++ b/verification/src/accept_chain.rs @@ -6,7 +6,7 @@ use canon::CanonBlock; use accept_block::BlockAcceptor; use accept_header::HeaderAcceptor; use accept_transaction::TransactionAcceptor; -use deployments::{Deployments, ActiveDeployments}; +use deployments::BlockDeployments; use duplex_store::DuplexTransactionOutputProvider; use VerificationLevel; @@ -17,14 +17,13 @@ pub struct ChainAcceptor<'a> { } impl<'a> ChainAcceptor<'a> { - pub fn new(store: &'a Store, consensus: &'a ConsensusParams, verification_level: VerificationLevel, block: CanonBlock<'a>, height: u32, deployments: &'a Deployments) -> Self { + pub fn new(store: &'a Store, consensus: &'a ConsensusParams, verification_level: VerificationLevel, block: CanonBlock<'a>, height: u32, deployments: &'a BlockDeployments) -> Self { trace!(target: "verification", "Block verification {}", block.hash().to_reversed_str()); let output_store = DuplexTransactionOutputProvider::new(store.as_transaction_output_provider(), block.raw()); let headers = store.as_block_header_provider(); - let active_deployments = ActiveDeployments::new(deployments, height, headers, consensus); ChainAcceptor { - block: BlockAcceptor::new(store.as_transaction_output_provider(), consensus, block, height, active_deployments, headers), + block: BlockAcceptor::new(store.as_transaction_output_provider(), consensus, block, height, deployments, headers), header: HeaderAcceptor::new(headers, consensus, block.header(), height, deployments), transactions: block.transactions() .into_iter() @@ -39,8 +38,7 @@ impl<'a> ChainAcceptor<'a> { height, block.header.raw.time, tx_index, - active_deployments, - headers, + deployments, )) .collect(), } diff --git a/verification/src/accept_header.rs b/verification/src/accept_header.rs index d5c1750e..256b7b7c 100644 --- a/verification/src/accept_header.rs +++ b/verification/src/accept_header.rs @@ -3,7 +3,7 @@ use db::BlockHeaderProvider; use canon::CanonHeader; use error::Error; use work::work_required; -use deployments::Deployments; +use deployments::BlockDeployments; use timestamp::median_timestamp; pub struct HeaderAcceptor<'a> { @@ -18,11 +18,11 @@ impl<'a> HeaderAcceptor<'a> { consensus: &'a ConsensusParams, header: CanonHeader<'a>, height: u32, - deployments: &'a Deployments, + deployments: &'a BlockDeployments<'a>, ) -> Self { HeaderAcceptor { work: HeaderWork::new(header, store, height, consensus), - median_timestamp: HeaderMedianTimestamp::new(header, store, height, deployments, consensus), + median_timestamp: HeaderMedianTimestamp::new(header, store, deployments), version: HeaderVersion::new(header, height, consensus), } } @@ -99,8 +99,8 @@ pub struct HeaderMedianTimestamp<'a> { } impl<'a> HeaderMedianTimestamp<'a> { - fn new(header: CanonHeader<'a>, store: &'a BlockHeaderProvider, height: u32, deployments: &'a Deployments, params: &ConsensusParams) -> Self { - let active = deployments.csv(height, store, params); + fn new(header: CanonHeader<'a>, store: &'a BlockHeaderProvider, deployments: &'a BlockDeployments<'a>) -> Self { + let active = deployments.csv(); HeaderMedianTimestamp { header: header, store: store, diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 556ebcc6..dcb8c9df 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -1,10 +1,10 @@ use primitives::hash::H256; use primitives::bytes::Bytes; -use db::{TransactionMetaProvider, TransactionOutputProvider, BlockHeaderProvider}; -use network::{ConsensusParams, ConsensusFork, Deployments as NetworkDeployments}; +use db::{TransactionMetaProvider, TransactionOutputProvider}; +use network::{ConsensusParams, ConsensusFork}; use script::{Script, ScriptWitness, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; use duplex_store::DuplexTransactionOutputProvider; -use deployments::{Deployments, ActiveDeployments}; +use deployments::BlockDeployments; use script::Builder; use sigops::transaction_sigops; use canon::CanonTransaction; @@ -37,19 +37,18 @@ impl<'a> TransactionAcceptor<'a> { height: u32, time: u32, transaction_index: usize, - deployments: ActiveDeployments<'a>, - headers: &'a BlockHeaderProvider, + deployments: &'a BlockDeployments<'a>, ) -> Self { trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str()); TransactionAcceptor { - premature_witness: TransactionPrematureWitness::new(transaction, deployments.is_active("segwit")), + premature_witness: TransactionPrematureWitness::new(transaction, deployments), bip30: TransactionBip30::new_for_sync(transaction, meta_store, consensus, block_hash, height), missing_inputs: TransactionMissingInputs::new(transaction, output_store, transaction_index), maturity: TransactionMaturity::new(transaction, meta_store, height), overspent: TransactionOverspent::new(transaction, output_store), double_spent: TransactionDoubleSpend::new(transaction, output_store), return_replay_protection: TransactionReturnReplayProtection::new(transaction, consensus, height), - eval: TransactionEval::new(transaction, output_store, consensus, verification_level, height, time, deployments.deployments, headers), + eval: TransactionEval::new(transaction, output_store, consensus, verification_level, height, time, deployments), } } @@ -86,8 +85,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { transaction: CanonTransaction<'a>, height: u32, time: u32, - deployments: &'a Deployments, - headers: &'a BlockHeaderProvider, + deployments: &'a BlockDeployments<'a>, ) -> Self { trace!(target: "verification", "Mempool-Tx verification {}", transaction.hash.to_reversed_str()); let transaction_index = 0; @@ -99,7 +97,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> { sigops: TransactionSigops::new(transaction, output_store, consensus, max_block_sigops, time), double_spent: TransactionDoubleSpend::new(transaction, output_store), return_replay_protection: TransactionReturnReplayProtection::new(transaction, consensus, height), - eval: TransactionEval::new(transaction, output_store, consensus, VerificationLevel::Full, height, time, deployments, headers), + eval: TransactionEval::new(transaction, output_store, consensus, VerificationLevel::Full, height, time, deployments), } } @@ -304,8 +302,7 @@ impl<'a> TransactionEval<'a> { verification_level: VerificationLevel, height: u32, time: u32, - deployments: &'a Deployments, - headers: &'a BlockHeaderProvider, + deployments: &'a BlockDeployments, ) -> Self { let verify_p2sh = time >= params.bip16_time; let verify_strictenc = match params.fork { @@ -319,8 +316,8 @@ impl<'a> TransactionEval<'a> { _ => SignatureVersion::Base, }; - let verify_checksequence = deployments.csv(height, headers, params); - let verify_witness = deployments.segwit(height, headers, params); + let verify_checksequence = deployments.csv(); + let verify_witness = deployments.segwit(); let verify_nulldummy = verify_witness; TransactionEval { @@ -450,19 +447,21 @@ impl<'a> TransactionReturnReplayProtection<'a> { pub struct TransactionPrematureWitness<'a> { transaction: CanonTransaction<'a>, - is_segwit_active: bool, + segwit_active: bool, } impl<'a> TransactionPrematureWitness<'a> { - fn new(transaction: CanonTransaction<'a>, is_segwit_active: bool) -> Self { + fn new(transaction: CanonTransaction<'a>, deployments: &'a BlockDeployments<'a>) -> Self { + let segwit_active = deployments.segwit(); + TransactionPrematureWitness { transaction: transaction, - is_segwit_active: is_segwit_active, + segwit_active: segwit_active, } } fn check(&self) -> Result<(), TransactionError> { - if !self.is_segwit_active && (*self.transaction).raw.has_witness() { + if !self.segwit_active && (*self.transaction).raw.has_witness() { Err(TransactionError::PrematureWitness) } else { Ok(()) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 6c3d634f..02505025 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -12,7 +12,7 @@ use verify_header::HeaderVerifier; use verify_transaction::MemoryPoolTransactionVerifier; use accept_chain::ChainAcceptor; use accept_transaction::MemoryPoolTransactionAcceptor; -use deployments::{Deployments, ActiveDeployments}; +use deployments::{Deployments, BlockDeployments}; use {Verify, VerificationLevel}; pub struct BackwardsCompatibleChainVerifier { @@ -49,22 +49,28 @@ impl BackwardsCompatibleChainVerifier { unreachable!(); }, BlockOrigin::CanonChain { block_number } => { + let header_provider = self.store.as_store().as_block_header_provider(); + let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus); let canon_block = CanonBlock::new(block); - let chain_acceptor = ChainAcceptor::new(self.store.as_store(), &self.consensus, verification_level, canon_block, block_number, &self.deployments); + let chain_acceptor = ChainAcceptor::new(self.store.as_store(), &self.consensus, verification_level, canon_block, block_number, &deployments); chain_acceptor.check()?; }, BlockOrigin::SideChain(origin) => { let block_number = origin.block_number; + let header_provider = self.store.as_store().as_block_header_provider(); + let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus); let fork = self.store.fork(origin)?; let canon_block = CanonBlock::new(block); - let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block, block_number, &self.deployments); + let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block, block_number, &deployments); chain_acceptor.check()?; }, BlockOrigin::SideChainBecomingCanonChain(origin) => { let block_number = origin.block_number; + let header_provider = self.store.as_store().as_block_header_provider(); + let deployments = BlockDeployments::new(&self.deployments, block_number, header_provider, &self.consensus); let fork = self.store.fork(origin)?; let canon_block = CanonBlock::new(block); - let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block, block_number, &self.deployments); + let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block, block_number, &deployments); chain_acceptor.check()?; }, } @@ -97,8 +103,8 @@ impl BackwardsCompatibleChainVerifier { ) -> Result<(), TransactionError> where T: TransactionOutputProvider { let indexed_tx = transaction.clone().into(); // let's do preverification first - let deployments = ActiveDeployments::new(&self.deployments, height, block_header_provider, &self.consensus); - let tx_verifier = MemoryPoolTransactionVerifier::new(&indexed_tx, &self.consensus, deployments); + let deployments = BlockDeployments::new(&self.deployments, height, block_header_provider, &self.consensus); + let tx_verifier = MemoryPoolTransactionVerifier::new(&indexed_tx, &self.consensus, &deployments); try!(tx_verifier.check()); let canon_tx = CanonTransaction::new(&indexed_tx); @@ -112,8 +118,7 @@ impl BackwardsCompatibleChainVerifier { canon_tx, height, time, - &self.deployments, - self.store.as_block_header_provider() + &deployments, ); tx_acceptor.check() } diff --git a/verification/src/deployments.rs b/verification/src/deployments.rs index c74d3786..d06c0e93 100644 --- a/verification/src/deployments.rs +++ b/verification/src/deployments.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::collections::hash_map::Entry; use parking_lot::Mutex; -use network::{ConsensusParams, Deployment, Deployments as NetworkDeployments}; +use network::{ConsensusParams, Deployment}; use hash::H256; use db::{BlockHeaderProvider, BlockRef, BlockAncestors, BlockIterator}; use timestamp::median_timestamp; @@ -56,9 +56,8 @@ pub struct Deployments { cache: Mutex, } -#[derive(Clone, Copy)] -pub struct ActiveDeployments<'a> { - pub deployments: &'a Deployments, +pub struct BlockDeployments<'a> { + deployments: &'a Deployments, number: u32, headers: &'a BlockHeaderProvider, consensus: &'a ConsensusParams, @@ -92,6 +91,25 @@ impl Deployments { } } +impl<'a> BlockDeployments<'a> { + pub fn new(deployments: &'a Deployments, number: u32, headers: &'a BlockHeaderProvider, consensus: &'a ConsensusParams) -> Self { + BlockDeployments { + deployments: deployments, + number: number, + headers: headers, + consensus: consensus, + } + } + + pub fn csv(&self) -> bool { + self.deployments.csv(self.number, self.headers, self.consensus) + } + + pub fn segwit(&self) -> bool { + self.deployments.segwit(self.number, self.headers, self.consensus) + } +} + /// Calculates threshold state of given deployment fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, number: u32, headers: &BlockHeaderProvider, consensus: &ConsensusParams) -> ThresholdState { if let Some(activation) = deployment.activation { @@ -140,27 +158,6 @@ fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, num } -impl<'a> ActiveDeployments<'a> { - pub fn new(deployments: &'a Deployments, number: u32, headers: &'a BlockHeaderProvider, consensus: &'a ConsensusParams) -> Self { - ActiveDeployments { - deployments: deployments, - number: number, - headers: headers, - consensus: consensus, - } - } -} - -impl<'a> NetworkDeployments for ActiveDeployments<'a> { - fn is_active(&self, name: &str) -> bool { - match name { - "csv" => self.deployments.segwit(self.number, self.headers, self.consensus), - "segwit" => self.deployments.segwit(self.number, self.headers, self.consensus), - _ => false, - } - } -} - fn first_of_the_period(block: u32, miner_confirmation_window: u32) -> u32 { if block < miner_confirmation_window - 1 { 0 diff --git a/verification/src/error.rs b/verification/src/error.rs index f611dcbb..bc312260 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -32,6 +32,8 @@ pub enum Error { /// Maximum sigops operations exceeded - will not provide how much it was in total /// since it stops counting once `MAX_BLOCK_SIGOPS` is reached MaximumSigops, + /// Maximum sigops operations cost exceeded + MaximumSigopsCost, /// Coinbase signature is not in the range 2-100 CoinbaseSignatureLength(usize), /// Block size is invalid diff --git a/verification/src/sigops.rs b/verification/src/sigops.rs index 9725597f..bd89d3c2 100644 --- a/verification/src/sigops.rs +++ b/verification/src/sigops.rs @@ -45,12 +45,14 @@ pub fn transaction_sigops( pub fn transaction_sigops_cost( transaction: &Transaction, store: &TransactionOutputProvider, - bip16_active: bool, + sigops: usize, ) -> usize { - let sigops_cost = transaction_sigops(transaction, store, bip16_active) * segwit::WITNESS_SCALE_FACTOR; + let sigops_cost = sigops * segwit::WITNESS_SCALE_FACTOR; let witness_sigops_cost: usize = transaction.inputs.iter() .map(|input| store.transaction_output(&input.previous_output, usize::max_value()) - .map(|output| witness_sigops(&Script::new(input.script_sig.clone()), &Script::new(output.script_pubkey.clone()), &ScriptWitness { stack: input.script_witness.clone().into() })) + .map(|output| witness_sigops(&Script::new(input.script_sig.clone()), &Script::new(output.script_pubkey.clone()), &ScriptWitness { + stack: input.script_witness.clone().into(), // TODO: excess clone + })) .unwrap_or(0)) .sum(); sigops_cost + witness_sigops_cost diff --git a/verification/src/verify_transaction.rs b/verification/src/verify_transaction.rs index 77a9a240..16342699 100644 --- a/verification/src/verify_transaction.rs +++ b/verification/src/verify_transaction.rs @@ -1,12 +1,12 @@ use std::ops; use ser::Serializable; use chain::IndexedTransaction; -use network::{ConsensusParams, ConsensusFork, Deployments}; +use network::{ConsensusParams, ConsensusFork}; +use deployments::BlockDeployments; use duplex_store::NoopStore; use sigops::transaction_sigops; use error::TransactionError; use constants::{MIN_COINBASE_SIZE, MAX_COINBASE_SIZE}; -use deployments::ActiveDeployments; pub struct TransactionVerifier<'a> { pub empty: TransactionEmpty<'a>, @@ -42,14 +42,14 @@ pub struct MemoryPoolTransactionVerifier<'a> { } impl<'a> MemoryPoolTransactionVerifier<'a> { - pub fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams, deployments: ActiveDeployments<'a>) -> Self { + pub fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams, deployments: &'a BlockDeployments<'a>) -> Self { trace!(target: "verification", "Mempool-Tx pre-verification {}", transaction.hash.to_reversed_str()); MemoryPoolTransactionVerifier { empty: TransactionEmpty::new(transaction), null_non_coinbase: TransactionNullNonCoinbase::new(transaction), is_coinbase: TransactionMemoryPoolCoinbase::new(transaction), - size: TransactionSize::new(transaction, deployments, consensus), - premature_witness: TransactionPrematureWitness::new(transaction, deployments), + size: TransactionSize::new(transaction, consensus), + premature_witness: TransactionPrematureWitness::new(transaction, &deployments), sigops: TransactionSigops::new(transaction, ConsensusFork::absolute_maximum_block_sigops()), } } @@ -151,21 +151,20 @@ impl<'a> TransactionMemoryPoolCoinbase<'a> { pub struct TransactionSize<'a> { transaction: &'a IndexedTransaction, - deployments: ActiveDeployments<'a>, consensus: &'a ConsensusParams, } impl<'a> TransactionSize<'a> { - fn new(transaction: &'a IndexedTransaction, deployments: ActiveDeployments<'a>, consensus: &'a ConsensusParams) -> Self { + fn new(transaction: &'a IndexedTransaction, consensus: &'a ConsensusParams) -> Self { TransactionSize { transaction: transaction, - deployments: deployments, consensus: consensus, } } fn check(&self) -> Result<(), TransactionError> { - if !self.consensus.fork.check_transaction_size(self.transaction.raw.serialized_size(), &self.deployments) { + let size = self.transaction.raw.serialized_size(); + if size > self.consensus.fork.max_transaction_size() { Err(TransactionError::MaxSize) } else { Ok(()) @@ -198,19 +197,21 @@ impl<'a> TransactionSigops<'a> { pub struct TransactionPrematureWitness<'a> { transaction: &'a IndexedTransaction, - deployments: ActiveDeployments<'a>, + segwit_active: bool, } impl<'a> TransactionPrematureWitness<'a> { - pub fn new(transaction: &'a IndexedTransaction, deployments: ActiveDeployments<'a>) -> Self { + pub fn new(transaction: &'a IndexedTransaction, deployments: &'a BlockDeployments<'a>) -> Self { + let segwit_active = deployments.segwit(); + TransactionPrematureWitness { transaction: transaction, - deployments: deployments, + segwit_active: segwit_active, } } pub fn check(&self) -> Result<(), TransactionError> { - if self.transaction.raw.has_witness() && !self.deployments.is_active("segwit") { + if !self.segwit_active && self.transaction.raw.has_witness() { Err(TransactionError::PrematureWitness) } else { Ok(()) From 4bc63d60818017576853e76e8cfefbd1c697ed50 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 12:11:38 +0300 Subject: [PATCH 11/35] segwit: revert wrong removal --- network/src/consensus.rs | 17 ++++++++++++++++- network/src/deployments.rs | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index bc9a3a78..65b3b906 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -216,7 +216,6 @@ impl ConsensusFork { mod tests { use super::super::Magic; use super::{ConsensusParams, ConsensusFork}; - //use deployments::tests::DummyDeployments; #[test] fn test_consensus_params_bip34_height() { @@ -253,6 +252,22 @@ mod tests { assert_eq!(ConsensusParams::new(Magic::Regtest, ConsensusFork::NoFork).miner_confirmation_window, 144); } + #[test] + fn test_consensus_fork_min_block_size() { + assert_eq!(ConsensusFork::NoFork.min_block_size(0), 0); + assert_eq!(ConsensusFork::SegWit2x(100).min_block_size(0), 0); + assert_eq!(ConsensusFork::SegWit2x(100).min_block_size(100), 0); + assert_eq!(ConsensusFork::BitcoinCash(100).min_block_size(0), 0); + assert_eq!(ConsensusFork::BitcoinCash(100).min_block_size(100), 1_000_001); + } + + #[test] + fn test_consensus_fork_max_transaction_size() { + assert_eq!(ConsensusFork::NoFork.max_transaction_size(0), 1_000_000); + assert_eq!(ConsensusFork::SegWit2x(100).max_transaction_size(0), 1_000_000); + assert_eq!(ConsensusFork::BitcoinCash(100).max_transaction_size(0), 1_000_000); + } + #[test] fn test_consensus_fork_max_block_sigops() { assert_eq!(ConsensusFork::NoFork.max_block_sigops(0, 1_000_000), 20_000); diff --git a/network/src/deployments.rs b/network/src/deployments.rs index bceec723..4428b22f 100644 --- a/network/src/deployments.rs +++ b/network/src/deployments.rs @@ -20,3 +20,4 @@ impl Deployment { (version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS && (version & (1 << self.bit)) != 0 } } + From 0a813b2d514a8b5883d9658ded02126a079ed998 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 12:49:52 +0300 Subject: [PATCH 12/35] removed excess clone --- network/src/consensus.rs | 6 +++--- script/src/interpreter.rs | 8 ++++---- script/src/script.rs | 7 ++----- verification/src/accept_transaction.rs | 6 ++---- verification/src/sigops.rs | 8 +++----- 5 files changed, 14 insertions(+), 21 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 65b3b906..a59d9830 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -263,9 +263,9 @@ mod tests { #[test] fn test_consensus_fork_max_transaction_size() { - assert_eq!(ConsensusFork::NoFork.max_transaction_size(0), 1_000_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_transaction_size(0), 1_000_000); - assert_eq!(ConsensusFork::BitcoinCash(100).max_transaction_size(0), 1_000_000); + assert_eq!(ConsensusFork::NoFork.max_transaction_size(), 1_000_000); + assert_eq!(ConsensusFork::SegWit2x(100).max_transaction_size(), 1_000_000); + assert_eq!(ConsensusFork::BitcoinCash(100).max_transaction_size(), 1_000_000); } #[test] diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 277e3dfd..2a20cd3f 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -349,9 +349,9 @@ fn verify_witness_program( return Ok(true); } - let witness_stack = &witness.stack; - let witness_stack_len = witness.stack.len(); - let (mut stack, script_pubkey) = match witness_program.len() { + let witness_stack = witness; + let witness_stack_len = witness_stack.len(); + let (mut stack, script_pubkey): (Stack<_>, Script) = match witness_program.len() { 32 => { if witness_stack_len == 0 { return Err(Error::WitnessProgramWitnessEmpty); @@ -380,7 +380,7 @@ fn verify_witness_program( .push_opcode(Opcode::OP_CHECKSIG) .into_script(); - (witness_stack.clone(), script_pubkey) + (witness_stack.clone().into(), script_pubkey) }, _ => return Err(Error::WitnessProgramWrongLength), }; diff --git a/script/src/script.rs b/script/src/script.rs index 2f0dcdf6..16b20b52 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -3,7 +3,7 @@ use std::{fmt, ops}; use bytes::Bytes; use keys::{self, AddressHash, Public}; -use {Opcode, Error, Stack}; +use {Opcode, Error}; /// Maximum number of bytes pushable to the stack pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; @@ -570,10 +570,7 @@ impl fmt::Display for Script { } } -#[derive(Default)] -pub struct ScriptWitness { - pub stack: Stack, -} +pub type ScriptWitness = Vec; /// Passed bytes array is a commitment script? /// https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Commitment_structure diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index dcb8c9df..27af0233 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -2,7 +2,7 @@ use primitives::hash::H256; use primitives::bytes::Bytes; use db::{TransactionMetaProvider, TransactionOutputProvider}; use network::{ConsensusParams, ConsensusFork}; -use script::{Script, ScriptWitness, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; +use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SignatureVersion}; use duplex_store::DuplexTransactionOutputProvider; use deployments::BlockDeployments; use script::Builder; @@ -360,9 +360,7 @@ impl<'a> TransactionEval<'a> { checker.input_index = index; checker.input_amount = output.value; - let script_witness = ScriptWitness { - stack: input.script_witness.clone().into(), // TODO - }; + let script_witness = &input.script_witness; let input: Script = input.script_sig.clone().into(); let output: Script = output.script_pubkey.into(); diff --git a/verification/src/sigops.rs b/verification/src/sigops.rs index bd89d3c2..d24340a0 100644 --- a/verification/src/sigops.rs +++ b/verification/src/sigops.rs @@ -50,9 +50,7 @@ pub fn transaction_sigops_cost( let sigops_cost = sigops * segwit::WITNESS_SCALE_FACTOR; let witness_sigops_cost: usize = transaction.inputs.iter() .map(|input| store.transaction_output(&input.previous_output, usize::max_value()) - .map(|output| witness_sigops(&Script::new(input.script_sig.clone()), &Script::new(output.script_pubkey.clone()), &ScriptWitness { - stack: input.script_witness.clone().into(), // TODO: excess clone - })) + .map(|output| witness_sigops(&Script::new(input.script_sig.clone()), &Script::new(output.script_pubkey.clone()), &input.script_witness,)) .unwrap_or(0)) .sum(); sigops_cost + witness_sigops_cost @@ -88,9 +86,9 @@ fn witness_program_sigops( ) -> usize { match witness_version { 0 if witness_program.len() == 20 => 1, - 0 if witness_program.len() == 32 => match script_witness.stack.last().ok() { + 0 if witness_program.len() == 32 => match script_witness.last() { Some(subscript) => Script::new(subscript.clone()).sigops_count(true), - _ => 0 + _ => 0, }, _ => 0, } From 4cd0207c256a793bee8acd981624b96659053801 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 14:56:15 +0300 Subject: [PATCH 13/35] basic_witness_script_checks --- script/src/interpreter.rs | 53 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 2a20cd3f..a5b61725 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -357,15 +357,15 @@ fn verify_witness_program( return Err(Error::WitnessProgramWitnessEmpty); } + let script_pubkey = &witness_stack[witness_stack_len - 1]; let stack = &witness_stack[0..witness_stack_len - 1]; - let script_pubkey = Script::new(stack[witness_stack_len - 1].clone()); + let script_pubkey_hash = sha256(script_pubkey); - let script_pubkey_hash = dhash256(&script_pubkey); - if script_pubkey_hash != witness_program[0..64].into() { + if script_pubkey_hash != witness_program[0..32].into() { return Err(Error::WitnessProgramMismatch); } - (stack.iter().cloned().collect::>().into(), script_pubkey) + (stack.iter().cloned().collect::>().into(), Script::new(script_pubkey.clone())) }, 20 => { if witness_stack_len != 2 { @@ -2193,4 +2193,49 @@ mod tests { assert_eq!(verify_script(&script_sig, &script_pubkey, &ScriptWitness::default(), &flags.verify_strictenc(true), &checker, SignatureVersion::ForkId), Err(Error::SignatureMustUseForkId)); } } + + // original tests from bitcoin core: + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1256 + #[test] + fn basic_witness_script_checks() { + let script_sig = Script::new(Bytes::new()); + let script_pubkey = Script::new("00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into()); + let checker = TransactionSignatureChecker { + input_index: 0, + input_amount: 0, + signer: Transaction::default().into(), + }; + + assert_eq!(verify_script(&script_sig, + &script_pubkey, + &vec!["00".into()], + &VerificationFlags::default().verify_p2sh(true).verify_witness(true), + &checker, + SignatureVersion::WitnessV0), + Err(Error::EvalFalse)); + + assert_eq!(verify_script(&script_sig, + &script_pubkey, + &vec!["51".into()], + &VerificationFlags::default().verify_p2sh(true).verify_witness(true), + &checker, + SignatureVersion::WitnessV0), + Err(Error::WitnessProgramMismatch)); + + assert_eq!(verify_script(&script_sig, + &script_pubkey, + &vec!["00".into()], + &VerificationFlags::default(), + &checker, + SignatureVersion::WitnessV0), + Ok(())); + + assert_eq!(verify_script(&script_sig, + &script_pubkey, + &vec!["51".into()], + &VerificationFlags::default(), + &checker, + SignatureVersion::WitnessV0), + Ok(())); + } } From c468425daf0ea971a4570538669195081fe880a2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 17:16:46 +0300 Subject: [PATCH 14/35] segwit: script tests --- script/src/flags.rs | 5 + script/src/interpreter.rs | 486 +++++++++++++++++++++++-- script/src/script.rs | 2 +- script/src/sign.rs | 11 +- verification/src/accept_transaction.rs | 2 +- 5 files changed, 463 insertions(+), 43 deletions(-) diff --git a/script/src/flags.rs b/script/src/flags.rs index 0344eb81..bb106e39 100644 --- a/script/src/flags.rs +++ b/script/src/flags.rs @@ -102,5 +102,10 @@ impl VerificationFlags { self.verify_nulldummy = value; self } + + pub fn verify_discourage_upgradable_witness_program(mut self, value: bool) -> Self { + self.verify_discourage_upgradable_witness_program = value; + self + } } diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index a5b61725..569e29ab 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -256,6 +256,7 @@ pub fn verify_script( let mut stack = Stack::new(); let mut stack_copy = Stack::new(); + let mut had_witness = false; try!(eval_script(&mut stack, script_sig, flags, checker, version)); @@ -276,8 +277,9 @@ pub fn verify_script( return Err(Error::WitnessMalleated); } + had_witness = true; verify_cleanstack = false; - if !verify_witness_program(witness, witness_version, witness_program, flags, checker, version)? { + if !verify_witness_program(witness, witness_version, witness_program, flags, checker)? { return Err(Error::EvalFalse); } } @@ -309,8 +311,9 @@ pub fn verify_script( return Err(Error::WitnessMalleatedP2SH); } + had_witness = true; verify_cleanstack = false; - if !verify_witness_program(witness, witness_version, witness_program, flags, checker, version)? { + if !verify_witness_program(witness, witness_version, witness_program, flags, checker)? { return Err(Error::EvalFalse); } } @@ -330,6 +333,16 @@ pub fn verify_script( } } + if flags.verify_witness { + // We can't check for correct unexpected witness data if P2SH was off, so require + // that WITNESS implies P2SH. Otherwise, going from WITNESS->P2SH+WITNESS would be + // possible, which is not a softfork. + assert!(flags.verify_p2sh); + if !had_witness && !witness.is_empty() { + return Err(Error::WitnessUnexpected); + } + } + Ok(()) } @@ -339,7 +352,6 @@ fn verify_witness_program( witness_program: &[u8], flags: &VerificationFlags, checker: &SignatureChecker, - version: SignatureVersion, ) -> Result { if witness_version != 0 { if flags.verify_discourage_upgradable_witness_program { @@ -389,7 +401,7 @@ fn verify_witness_program( return Err(Error::PushSize); } - if !eval_script(&mut stack, &script_pubkey, flags, checker, version)? { + if !eval_script(&mut stack, &script_pubkey, flags, checker, SignatureVersion::WitnessV0)? { return Ok(false); } @@ -2194,48 +2206,442 @@ mod tests { } } - // original tests from bitcoin core: - // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1256 - #[test] - fn basic_witness_script_checks() { - let script_sig = Script::new(Bytes::new()); - let script_pubkey = Script::new("00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into()); - let checker = TransactionSignatureChecker { - input_index: 0, - input_amount: 0, - signer: Transaction::default().into(), + fn run_witness_test(script_sig: Script, script_pubkey: Script, script_witness: Vec, flags: VerificationFlags, sigver: SignatureVersion, amount: u64) -> Result<(), Error> { + use chain::{TransactionInput, OutPoint, TransactionOutput}; + + let tx1 = Transaction { + version: 1, + inputs: vec![TransactionInput { + previous_output: OutPoint { + hash: Default::default(), + index: 0xffffffff, + }, + script_sig: Builder::default().push_num(0.into()).push_num(0.into()).into_bytes(), + sequence: 0xffffffff, + script_witness: vec![], + }], + outputs: vec![TransactionOutput { + value: amount, + script_pubkey: script_pubkey.to_bytes(), + }], + lock_time: 0, + }; + let tx2 = Transaction { + version: 1, + inputs: vec![TransactionInput { + previous_output: OutPoint { + hash: tx1.hash(), + index: 0, + }, + script_sig: script_sig.to_bytes(), + sequence: 0xffffffff, + script_witness: script_witness.clone(), + }], + outputs: vec![TransactionOutput { + value: amount, + script_pubkey: Builder::default().into_bytes(), + }], + lock_time: 0, }; - assert_eq!(verify_script(&script_sig, - &script_pubkey, - &vec!["00".into()], - &VerificationFlags::default().verify_p2sh(true).verify_witness(true), - &checker, - SignatureVersion::WitnessV0), - Err(Error::EvalFalse)); + let checker = TransactionSignatureChecker { + input_index: 0, + input_amount: amount, + signer: tx2.into(), + }; - assert_eq!(verify_script(&script_sig, + verify_script(&script_sig, &script_pubkey, - &vec!["51".into()], - &VerificationFlags::default().verify_p2sh(true).verify_witness(true), + &script_witness, + &flags, &checker, - SignatureVersion::WitnessV0), - Err(Error::WitnessProgramMismatch)); + sigver) + } - assert_eq!(verify_script(&script_sig, - &script_pubkey, - &vec!["00".into()], - &VerificationFlags::default(), - &checker, - SignatureVersion::WitnessV0), - Ok(())); + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1257 + #[test] + fn witness_invalid_script() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("".into(), + "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), + vec!["00".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } - assert_eq!(verify_script(&script_sig, - &script_pubkey, - &vec!["51".into()], - &VerificationFlags::default(), - &checker, - SignatureVersion::WitnessV0), - Ok(())); + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1258 + #[test] + fn witness_script_hash_mismatch() { + assert_eq!(Err(Error::WitnessProgramMismatch), + run_witness_test("".into(), + "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), + vec!["51".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1259 + #[test] + fn witness_invalid_script_check_skipped() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), + vec!["00".into()], + VerificationFlags::default(), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1260 + #[test] + fn witness_script_hash_mismatch_check_skipped() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), + vec!["51".into()], + VerificationFlags::default(), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1860 + #[test] + fn witness_basic_p2wsh() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), + vec!["304402200d461c140cfdfcf36b94961db57ae8c18d1cb80e9d95a9e47ac22470c1bf125502201c8dc1cbfef6a3ef90acbbb992ca22fe9466ee6f9d4898eda277a7ac3ab4b25101".into(), + "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1872 + #[test] + fn witness_basic_p2wpkh() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "001491b24bf9f5288532960ac687abb035127b1d28a5".into(), + vec!["304402201e7216e5ccb3b61d46946ec6cc7e8c4e0117d13ac2fd4b152197e4805191c74202203e9903e33e84d9ee1dd13fb057afb7ccfb47006c23f6a067185efbc9dd780fc501".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1884 + #[test] + fn witness_basic_p2sh_p2wsh() { + assert_eq!(Ok(()), + run_witness_test("220020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), + "a914f386c2ba255cc56d20cfa6ea8b062f8b5994551887".into(), + vec!["3044022066e02c19a513049d49349cf5311a1b012b7c4fae023795a18ab1d91c23496c22022025e216342c8e07ce8ef51e8daee88f84306a9de66236cab230bb63067ded1ad301".into(), + "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1896 + #[test] + fn witness_basic_p2sh_p2wpkh() { + assert_eq!(Ok(()), + run_witness_test("16001491b24bf9f5288532960ac687abb035127b1d28a5".into(), + "a91417743beb429c55c942d2ec703b98c4d57c2df5c687".into(), + vec!["304402200929d11561cd958460371200f82e9cae64c727a495715a31828e27a7ad57b36d0220361732ced04a6f97351ecca21a56d0b8cd4932c1da1f8f569a2b68e5e48aed7801".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1908 + #[test] + fn witness_basic_p2wsh_with_wrong_key() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("".into(), + "0020ac8ebd9e52c17619a381fa4f71aebb696087c6ef17c960fd0587addad99c0610".into(), + vec!["304402202589f0512cb2408fb08ed9bd24f85eb3059744d9e4f2262d0b7f1338cff6e8b902206c0978f449693e0578c71bc543b11079fd0baae700ee5e9a6bee94db490af9fc01".into(), + "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1920 + #[test] + fn witness_basic_p2wpkh_with_wrong_key() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("".into(), + "00147cf9c846cd4882efec4bf07e44ebdad495c94f4b".into(), + vec!["304402206ef7fdb2986325d37c6eb1a8bb24aeb46dede112ed8fc76c7d7500b9b83c0d3d02201edc2322c794fe2d6b0bd73ed319e714aa9b86d8891961530d5c9b7156b60d4e01".into(), + "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1920 + #[test] + fn witness_basic_p2sh_p2wsh_with_wrong_key() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("220020ac8ebd9e52c17619a381fa4f71aebb696087c6ef17c960fd0587addad99c0610".into(), + "a91461039a003883787c0d6ebc66d97fdabe8e31449d87".into(), + vec!["30440220069ea3581afaf8187f63feee1fd2bd1f9c0dc71ea7d6e8a8b07ee2ebcf824bf402201a4fdef4c532eae59223be1eda6a397fc835142d4ddc6c74f4aa85b766a5c16f01".into(), + "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1944 + #[test] + fn witness_basic_p2sh_p2wpkh_with_wrong_key() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("1600147cf9c846cd4882efec4bf07e44ebdad495c94f4b".into(), + "a9144e0c2aed91315303fc6a1dc4c7bc21c88f75402e87".into(), + vec!["304402204209e49457c2358f80d0256bc24535b8754c14d08840fc4be762d6f5a0aed80b02202eaf7d8fc8d62f60c67adcd99295528d0e491ae93c195cec5a67e7a09532a88001".into(), + "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1956 + #[test] + fn witness_basic_p2wsh_with_wrong_key_check_skipped() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "0020ac8ebd9e52c17619a381fa4f71aebb696087c6ef17c960fd0587addad99c0610".into(), + vec!["304402202589f0512cb2408fb08ed9bd24f85eb3059744d9e4f2262d0b7f1338cff6e8b902206c0978f449693e0578c71bc543b11079fd0baae700ee5e9a6bee94db490af9fc01".into(), + "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], + VerificationFlags::default().verify_p2sh(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1968 + #[test] + fn witness_basic_p2wpkh_with_wrong_key_check_skipped() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "00147cf9c846cd4882efec4bf07e44ebdad495c94f4b".into(), + vec!["304402206ef7fdb2986325d37c6eb1a8bb24aeb46dede112ed8fc76c7d7500b9b83c0d3d02201edc2322c794fe2d6b0bd73ed319e714aa9b86d8891961530d5c9b7156b60d4e01".into(), + "4104828048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf2263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], + VerificationFlags::default().verify_p2sh(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1980 + #[test] + fn witness_basic_p2sh_p2wsh_with_wrong_key_check_skipped() { + assert_eq!(Ok(()), + run_witness_test("220020ac8ebd9e52c17619a381fa4f71aebb696087c6ef17c960fd0587addad99c0610".into(), + "a91461039a003883787c0d6ebc66d97fdabe8e31449d87".into(), + vec!["30440220069ea3581afaf8187f63feee1fd2bd1f9c0dc71ea7d6e8a8b07ee2ebcf824bf402201a4fdef4c532eae59223be1eda6a397fc835142d4ddc6c74f4aa85b766a5c16f01".into(), + "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], + VerificationFlags::default().verify_p2sh(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1992 + #[test] + fn witness_basic_p2sh_p2wpkh_with_wrong_key_check_skipped() { + assert_eq!(Ok(()), + run_witness_test("1600147cf9c846cd4882efec4bf07e44ebdad495c94f4b".into(), + "a9144e0c2aed91315303fc6a1dc4c7bc21c88f75402e87".into(), + vec!["304402204209e49457c2358f80d0256bc24535b8754c14d08840fc4be762d6f5a0aed80b02202eaf7d8fc8d62f60c67adcd99295528d0e491ae93c195cec5a67e7a09532a88001".into(), + "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], + VerificationFlags::default().verify_p2sh(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2004 + #[test] + fn witness_basic_p2wsh_with_wrong_value() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("".into(), + "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), + vec!["3044022066faa86e74e8b30e82691b985b373de4f9e26dc144ec399c4f066aa59308e7c202204712b86f28c32503faa051dbeabff2c238ece861abc36c5e0b40b1139ca222f001".into(), + "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2016 + #[test] + fn witness_basic_p2wpkh_with_wrong_value() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("".into(), + "001491b24bf9f5288532960ac687abb035127b1d28a5".into(), + vec!["304402203b3389b87448d7dfdb5e82fb854fcf92d7925f9938ea5444e36abef02c3d6a9602202410bc3265049abb07fd2e252c65ab7034d95c9d5acccabe9fadbdc63a52712601".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2028 + #[test] + fn witness_basic_p2sh_p2wsh_with_wrong_value() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("220020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), + "a914f386c2ba255cc56d20cfa6ea8b062f8b5994551887".into(), + vec!["3044022000a30c4cfc10e4387be528613575434826ad3c15587475e0df8ce3b1746aa210022008149265e4f8e9dafe1f3ea50d90cb425e9e40ea7ebdd383069a7cfa2b77004701".into(), + "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2040 + #[test] + fn witness_basic_p2sh_p2wpkh_with_wrong_value() { + assert_eq!(Err(Error::EvalFalse), + run_witness_test("16001491b24bf9f5288532960ac687abb035127b1d28a5".into(), + "a91417743beb429c55c942d2ec703b98c4d57c2df5c687".into(), + vec!["304402204fc3a2cd61a47913f2a5f9107d0ad4a504c7b31ee2d6b3b2f38c2b10ee031e940220055d58b7c3c281aaa381d8f486ac0f3e361939acfd568046cb6a311cdfa974cf01".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2052 + #[test] + fn witness_p2wpkh_with_future_version() { + assert_eq!(Err(Error::DiscourageUpgradableWitnessProgram), + run_witness_test("".into(), + "511491b24bf9f5288532960ac687abb035127b1d28a5".into(), + vec!["304402205ae57ae0534c05ca9981c8a6cdf353b505eaacb7375f96681a2d1a4ba6f02f84022056248e68643b7d8ce7c7d128c9f1f348bcab8be15d094ad5cadd24251a28df8001".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true).verify_discourage_upgradable_witness_program(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2064 + #[test] + fn witness_p2wpkh_with_wrong_witness_program_length() { + assert_eq!(Err(Error::WitnessProgramWrongLength), + run_witness_test("".into(), + "001fb34b78da162751647974d5cb7410aa428ad339dbf7d1e16e833f68a0cbf1c3".into(), + vec!["3044022064100ca0e2a33332136775a86cd83d0230e58b9aebb889c5ac952abff79a46ef02205f1bf900e022039ad3091bdaf27ac2aef3eae9ed9f190d821d3e508405b9513101".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2076 + #[test] + fn witness_p2wsh_with_empty_witness() { + assert_eq!(Err(Error::WitnessProgramWitnessEmpty), + run_witness_test("".into(), + "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), + vec![], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2083 + #[test] + fn witness_p2wsh_with_witness_program_mismatch() { + assert_eq!(Err(Error::WitnessProgramMismatch), + run_witness_test("".into(), + "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), + vec!["3044022039105b995a5f448639a997a5c90fda06f50b49df30c3bdb6663217bf79323db002206fecd54269dec569fcc517178880eb58bb40f381a282bb75766ff3637d5f4b4301".into(), + "400479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2095 + #[test] + fn witness_p2wpkh_with_witness_program_mismatch() { + assert_eq!(Err(Error::WitnessProgramMismatch), + run_witness_test("".into(), + "001491b24bf9f5288532960ac687abb035127b1d28a5".into(), + vec!["304402201a96950593cb0af32d080b0f193517f4559241a8ebd1e95e414533ad64a3f423022047f4f6d3095c23235bdff3aeff480d0529c027a3f093cb265b7cbf148553b85101".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into(), + "".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2108 + #[test] + fn witness_p2wpkh_with_non_empty_script_sig() { + assert_eq!(Err(Error::WitnessMalleated), + run_witness_test("5b".into(), + "001491b24bf9f5288532960ac687abb035127b1d28a5".into(), + vec!["304402201a96950593cb0af32d080b0f193517f4559241a8ebd1e95e414533ad64a3f423022047f4f6d3095c23235bdff3aeff480d0529c027a3f093cb265b7cbf148553b85101".into(), + "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2120 + #[test] + fn witness_p2sh_p2wpkh_with_superfluous_push_in_script_sig() { + assert_eq!(Err(Error::WitnessMalleatedP2SH), + run_witness_test("5b1600147cf9c846cd4882efec4bf07e44ebdad495c94f4b".into(), + "a9144e0c2aed91315303fc6a1dc4c7bc21c88f75402e87".into(), + vec!["304402204209e49457c2358f80d0256bc24535b8754c14d08840fc4be762d6f5a0aed80b02202eaf7d8fc8d62f60c67adcd99295528d0e491ae93c195cec5a67e7a09532a88001".into(), + "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2132 + #[test] + fn witness_p2pk_with_witness() { + assert_eq!(Err(Error::WitnessUnexpected), + run_witness_test("47304402200a5c6163f07b8d3b013c4d1d6dba25e780b39658d79ba37af7057a3b7f15ffa102201fd9b4eaa9943f734928b99a83592c2e7bf342ea2680f6a2bb705167966b742001".into(), + "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into(), + vec!["".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 0, + )); } } diff --git a/script/src/script.rs b/script/src/script.rs index 16b20b52..c5eedbfa 100644 --- a/script/src/script.rs +++ b/script/src/script.rs @@ -151,7 +151,7 @@ impl Script { } let witness_version = match Opcode::from_u8(self.data[0]) { Some(Opcode::OP_0) => 0, - Some(x) if x >= Opcode::OP_1 && x <= Opcode::OP_16 => (x as u8) - (Opcode::OP_1 as u8) - 1, + Some(x) if x >= Opcode::OP_1 && x <= Opcode::OP_16 => (x as u8) - (Opcode::OP_1 as u8) + 1, _ => return None, }; let witness_program = &self.data[2..]; diff --git a/script/src/sign.rs b/script/src/sign.rs index da953afa..f3accc81 100644 --- a/script/src/sign.rs +++ b/script/src/sign.rs @@ -242,7 +242,16 @@ impl TransactionInputSigner { let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs); let hash_sequence = compute_hash_sequence(sighash, &self.inputs); let hash_outputs = compute_hash_outputs(sighash, input_index, &self.outputs); - +/*println!("=== version = {:?}", self.version); +println!("=== hash_prevouts = {:?}", hash_prevouts); +println!("=== hash_sequence = {:?}", hash_sequence); +println!("=== previous_output = {:?}", self.inputs[input_index].previous_output); +println!("=== script_pubkey = {:?}", script_pubkey); +println!("=== input_amount = {:?}", input_amount); +println!("=== sequence = {:?}", self.inputs[input_index].sequence); +println!("=== hash_outputs = {:?}", hash_outputs); +println!("=== lock_time = {:?}", self.lock_time); +println!("=== sighashtype = {:?}", sighashtype);*/ let mut stream = Stream::default(); stream.append(&self.version); stream.append(&hash_prevouts); diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 27af0233..5afd3101 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -313,7 +313,7 @@ impl<'a> TransactionEval<'a> { let verify_dersig = height >= params.bip66_height; let signature_version = match params.fork { ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => SignatureVersion::ForkId, - _ => SignatureVersion::Base, + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => SignatureVersion::Base, }; let verify_checksequence = deployments.csv(); From 6c060815770a61f1a6bc6d05d4bdb55bf11d0047 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 17:42:40 +0300 Subject: [PATCH 15/35] segwit: multisig segwit tests --- script/src/interpreter.rs | 60 ++++++++++++++++++++++++++ verification/src/accept_transaction.rs | 2 +- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 569e29ab..5ef8a42d 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -2644,4 +2644,64 @@ mod tests { 0, )); } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2299 + #[test] + fn witness_p2wsh_checkmultisig() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "002008a6665ebfd43b02323423e764e185d98d1587f903b81507dbb69bfc41005efa".into(), + vec!["".into(), + "304402202d092ededd1f060609dbf8cb76950634ff42b3e62cf4adb69ab92397b07d742302204ff886f8d0817491a96d1daccdcc820f6feb122ee6230143303100db37dfa79f01".into(), + "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2312 + #[test] + fn witness_p2sh_p2wsh_checkmultisig() { + assert_eq!(Ok(()), + run_witness_test("22002008a6665ebfd43b02323423e764e185d98d1587f903b81507dbb69bfc41005efa".into(), + "a9146f5ecd4b83b77f3c438f5214eff96454934fc5d187".into(), + vec!["".into(), + "304402202dd7e91243f2235481ffb626c3b7baf2c859ae3a5a77fb750ef97b99a8125dc002204960de3d3c3ab9496e218ec57e5240e0e10a6f9546316fe240c216d45116d29301".into(), + "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2351 + #[test] + fn witness_p2wsh_checkmultisig_using_key2() { + assert_eq!(Ok(()), + run_witness_test("".into(), + "002008a6665ebfd43b02323423e764e185d98d1587f903b81507dbb69bfc41005efa".into(), + vec!["".into(), + "304402201e9e6f7deef5b2f21d8223c5189b7d5e82d237c10e97165dd08f547c4e5ce6ed02206796372eb1cc6acb52e13ee2d7f45807780bf96b132cb6697f69434be74b1af901".into(), + "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L2364 + #[test] + fn witness_p2sh_p2wsh_checkmultisig_using_key2() { + assert_eq!(Ok(()), + run_witness_test("22002008a6665ebfd43b02323423e764e185d98d1587f903b81507dbb69bfc41005efa".into(), + "a9146f5ecd4b83b77f3c438f5214eff96454934fc5d187".into(), + vec!["".into(), + "3044022045e667f3f0f3147b95597a24babe9afecea1f649fd23637dfa7ed7e9f3ac18440220295748e81005231135289fe3a88338dabba55afa1bdb4478691337009d82b68d01".into(), + "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], + VerificationFlags::default().verify_p2sh(true).verify_witness(true), + SignatureVersion::Base, + 1, + )); + } } diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index 5afd3101..d86e1ffd 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -313,7 +313,7 @@ impl<'a> TransactionEval<'a> { let verify_dersig = height >= params.bip66_height; let signature_version = match params.fork { ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => SignatureVersion::ForkId, - ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => SignatureVersion::Base, + ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) | ConsensusFork::SegWit2x(_) => SignatureVersion::Base, }; let verify_checksequence = deployments.csv(); From a97bdaf0bcdf592a964a7cb6b64385239398515c Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 18:52:45 +0300 Subject: [PATCH 16/35] segwit: witness tests from tx_invalid.json --- script/src/interpreter.rs | 174 +++++++++++++++++++++++++++++--------- 1 file changed, 136 insertions(+), 38 deletions(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 5ef8a42d..4e5fed09 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -405,11 +405,11 @@ fn verify_witness_program( return Ok(false); } - let success = !stack.is_empty() && { - let last = stack.last()?; - cast_to_bool(last) - }; + if stack.len() != 1 { + return Err(Error::EvalFalse); + } + let success = cast_to_bool(stack.last().expect("stack.len() == 1; last() only returns errors when stack is empty; qed")); Ok(success) } @@ -2206,7 +2206,7 @@ mod tests { } } - fn run_witness_test(script_sig: Script, script_pubkey: Script, script_witness: Vec, flags: VerificationFlags, sigver: SignatureVersion, amount: u64) -> Result<(), Error> { + fn run_witness_test(script_sig: Script, script_pubkey: Script, script_witness: Vec, flags: VerificationFlags, amount: u64) -> Result<(), Error> { use chain::{TransactionInput, OutPoint, TransactionOutput}; let tx1 = Transaction { @@ -2255,7 +2255,7 @@ mod tests { &script_witness, &flags, &checker, - sigver) + SignatureVersion::Base) } // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/script_tests.json#L1257 @@ -2266,7 +2266,6 @@ mod tests { "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), vec!["00".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2279,7 +2278,6 @@ mod tests { "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), vec!["51".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2292,7 +2290,6 @@ mod tests { "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), vec!["00".into()], VerificationFlags::default(), - SignatureVersion::Base, 0, )); } @@ -2305,7 +2302,6 @@ mod tests { "00206e340b9cffb37a989ca544e6bb780a2c78901d3fb33738768511a30617afa01d".into(), vec!["51".into()], VerificationFlags::default(), - SignatureVersion::Base, 0, )); } @@ -2319,7 +2315,6 @@ mod tests { vec!["304402200d461c140cfdfcf36b94961db57ae8c18d1cb80e9d95a9e47ac22470c1bf125502201c8dc1cbfef6a3ef90acbbb992ca22fe9466ee6f9d4898eda277a7ac3ab4b25101".into(), "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2333,7 +2328,6 @@ mod tests { vec!["304402201e7216e5ccb3b61d46946ec6cc7e8c4e0117d13ac2fd4b152197e4805191c74202203e9903e33e84d9ee1dd13fb057afb7ccfb47006c23f6a067185efbc9dd780fc501".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2347,7 +2341,6 @@ mod tests { vec!["3044022066e02c19a513049d49349cf5311a1b012b7c4fae023795a18ab1d91c23496c22022025e216342c8e07ce8ef51e8daee88f84306a9de66236cab230bb63067ded1ad301".into(), "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2361,7 +2354,6 @@ mod tests { vec!["304402200929d11561cd958460371200f82e9cae64c727a495715a31828e27a7ad57b36d0220361732ced04a6f97351ecca21a56d0b8cd4932c1da1f8f569a2b68e5e48aed7801".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2375,7 +2367,6 @@ mod tests { vec!["304402202589f0512cb2408fb08ed9bd24f85eb3059744d9e4f2262d0b7f1338cff6e8b902206c0978f449693e0578c71bc543b11079fd0baae700ee5e9a6bee94db490af9fc01".into(), "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2389,7 +2380,6 @@ mod tests { vec!["304402206ef7fdb2986325d37c6eb1a8bb24aeb46dede112ed8fc76c7d7500b9b83c0d3d02201edc2322c794fe2d6b0bd73ed319e714aa9b86d8891961530d5c9b7156b60d4e01".into(), "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2403,7 +2393,6 @@ mod tests { vec!["30440220069ea3581afaf8187f63feee1fd2bd1f9c0dc71ea7d6e8a8b07ee2ebcf824bf402201a4fdef4c532eae59223be1eda6a397fc835142d4ddc6c74f4aa85b766a5c16f01".into(), "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2417,7 +2406,6 @@ mod tests { vec!["304402204209e49457c2358f80d0256bc24535b8754c14d08840fc4be762d6f5a0aed80b02202eaf7d8fc8d62f60c67adcd99295528d0e491ae93c195cec5a67e7a09532a88001".into(), "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2431,7 +2419,6 @@ mod tests { vec!["304402202589f0512cb2408fb08ed9bd24f85eb3059744d9e4f2262d0b7f1338cff6e8b902206c0978f449693e0578c71bc543b11079fd0baae700ee5e9a6bee94db490af9fc01".into(), "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], VerificationFlags::default().verify_p2sh(true), - SignatureVersion::Base, 0, )); } @@ -2445,7 +2432,6 @@ mod tests { vec!["304402206ef7fdb2986325d37c6eb1a8bb24aeb46dede112ed8fc76c7d7500b9b83c0d3d02201edc2322c794fe2d6b0bd73ed319e714aa9b86d8891961530d5c9b7156b60d4e01".into(), "4104828048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf2263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], VerificationFlags::default().verify_p2sh(true), - SignatureVersion::Base, 0, )); } @@ -2459,7 +2445,6 @@ mod tests { vec!["30440220069ea3581afaf8187f63feee1fd2bd1f9c0dc71ea7d6e8a8b07ee2ebcf824bf402201a4fdef4c532eae59223be1eda6a397fc835142d4ddc6c74f4aa85b766a5c16f01".into(), "41048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26cafac".into()], VerificationFlags::default().verify_p2sh(true), - SignatureVersion::Base, 0, )); } @@ -2473,7 +2458,6 @@ mod tests { vec!["304402204209e49457c2358f80d0256bc24535b8754c14d08840fc4be762d6f5a0aed80b02202eaf7d8fc8d62f60c67adcd99295528d0e491ae93c195cec5a67e7a09532a88001".into(), "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], VerificationFlags::default().verify_p2sh(true), - SignatureVersion::Base, 0, )); } @@ -2487,7 +2471,6 @@ mod tests { vec!["3044022066faa86e74e8b30e82691b985b373de4f9e26dc144ec399c4f066aa59308e7c202204712b86f28c32503faa051dbeabff2c238ece861abc36c5e0b40b1139ca222f001".into(), "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2501,7 +2484,6 @@ mod tests { vec!["304402203b3389b87448d7dfdb5e82fb854fcf92d7925f9938ea5444e36abef02c3d6a9602202410bc3265049abb07fd2e252c65ab7034d95c9d5acccabe9fadbdc63a52712601".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2515,7 +2497,6 @@ mod tests { vec!["3044022000a30c4cfc10e4387be528613575434826ad3c15587475e0df8ce3b1746aa210022008149265e4f8e9dafe1f3ea50d90cb425e9e40ea7ebdd383069a7cfa2b77004701".into(), "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2529,7 +2510,6 @@ mod tests { vec!["304402204fc3a2cd61a47913f2a5f9107d0ad4a504c7b31ee2d6b3b2f38c2b10ee031e940220055d58b7c3c281aaa381d8f486ac0f3e361939acfd568046cb6a311cdfa974cf01".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2543,7 +2523,6 @@ mod tests { vec!["304402205ae57ae0534c05ca9981c8a6cdf353b505eaacb7375f96681a2d1a4ba6f02f84022056248e68643b7d8ce7c7d128c9f1f348bcab8be15d094ad5cadd24251a28df8001".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true).verify_discourage_upgradable_witness_program(true), - SignatureVersion::Base, 0, )); } @@ -2557,7 +2536,6 @@ mod tests { vec!["3044022064100ca0e2a33332136775a86cd83d0230e58b9aebb889c5ac952abff79a46ef02205f1bf900e022039ad3091bdaf27ac2aef3eae9ed9f190d821d3e508405b9513101".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2570,7 +2548,6 @@ mod tests { "0020b95237b48faaa69eb078e1170be3b5cbb3fddf16d0a991e14ad274f7b33a4f64".into(), vec![], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2584,7 +2561,6 @@ mod tests { vec!["3044022039105b995a5f448639a997a5c90fda06f50b49df30c3bdb6663217bf79323db002206fecd54269dec569fcc517178880eb58bb40f381a282bb75766ff3637d5f4b4301".into(), "400479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2599,7 +2575,6 @@ mod tests { "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into(), "".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2613,7 +2588,6 @@ mod tests { vec!["304402201a96950593cb0af32d080b0f193517f4559241a8ebd1e95e414533ad64a3f423022047f4f6d3095c23235bdff3aeff480d0529c027a3f093cb265b7cbf148553b85101".into(), "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2627,7 +2601,6 @@ mod tests { vec!["304402204209e49457c2358f80d0256bc24535b8754c14d08840fc4be762d6f5a0aed80b02202eaf7d8fc8d62f60c67adcd99295528d0e491ae93c195cec5a67e7a09532a88001".into(), "048282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f5150811f8a8098557dfe45e8256e830b60ace62d613ac2f7b17bed31b6eaff6e26caf".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2640,7 +2613,6 @@ mod tests { "410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac".into(), vec!["".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 0, )); } @@ -2655,7 +2627,6 @@ mod tests { "304402202d092ededd1f060609dbf8cb76950634ff42b3e62cf4adb69ab92397b07d742302204ff886f8d0817491a96d1daccdcc820f6feb122ee6230143303100db37dfa79f01".into(), "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2670,7 +2641,6 @@ mod tests { "304402202dd7e91243f2235481ffb626c3b7baf2c859ae3a5a77fb750ef97b99a8125dc002204960de3d3c3ab9496e218ec57e5240e0e10a6f9546316fe240c216d45116d29301".into(), "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2685,7 +2655,6 @@ mod tests { "304402201e9e6f7deef5b2f21d8223c5189b7d5e82d237c10e97165dd08f547c4e5ce6ed02206796372eb1cc6acb52e13ee2d7f45807780bf96b132cb6697f69434be74b1af901".into(), "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } @@ -2700,8 +2669,137 @@ mod tests { "3044022045e667f3f0f3147b95597a24babe9afecea1f649fd23637dfa7ed7e9f3ac18440220295748e81005231135289fe3a88338dabba55afa1bdb4478691337009d82b68d01".into(), "5121038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b852ae".into()], VerificationFlags::default().verify_p2sh(true).verify_witness(true), - SignatureVersion::Base, 1, )); } + + fn run_witness_test_tx_test(script_pubkey: Script, tx: &Transaction, flags: &VerificationFlags, amount: u64, index: usize) -> Result<(), Error> { + let checker = TransactionSignatureChecker { + input_index: index, + input_amount: amount, + signer: tx.clone().into(), + }; + + verify_script(&tx.inputs[index].script_sig.clone().into(), + &script_pubkey, + &tx.inputs[index].script_witness, + flags, + &checker, + SignatureVersion::Base) + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L254 + #[test] + fn witness_unknown_program_version() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true).verify_discourage_upgradable_witness_program(true); + assert_eq!(Err(Error::DiscourageUpgradableWitnessProgram), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("60144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L260 + #[test] + fn witness_unknown_program0_lengh() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::WitnessProgramWrongLength), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00154c9c3dfac4207d5d8cb89df5722cb3d712385e3fff".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L260 + #[test] + fn witness_single_anyone_same_index_value_changed() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e80300000000000001516c070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L272 + #[test] + fn witness_none_anyone_same_index_value_changed() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff000100000000000000000000000000000000000000000000000000000000000001000000000100000000010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L278 + #[test] + fn witness_all_anyone_third_value_changed() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151540b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L284 + #[test] + fn witness_with_push_of_521_bytes() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd0902000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::PushSize), run_witness_test_tx_test("002033198a9bfef674ebddb9ffaa52928017b8472791e54c609cb95f278ac6b1e349".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L288 + #[test] + fn witness_unknown_version_with_false_on_stack() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015101010100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("60020000".into(), &tx, &flags, 2000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L292 + #[test] + fn witness_unknown_version_with_non_empty_stack() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01000000000000000001510102515100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("00202f04a3aa051f1f60d695f6c44c0c3d383973dfd446ace8962664a76bb10e31a8".into(), &tx, &flags, 2000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L296 + #[test] + fn witness_program0_with_push_of_2_bytes() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015101040002000100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::WitnessProgramWrongLength), run_witness_test_tx_test("00020001".into(), &tx, &flags, 2000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L300 + #[test] + fn witness_unknown_version_with_non_empty_script_sig() { + let tx = "01000000010001000000000000000000000000000000000000000000000000000000000000000000000151ffffffff010000000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::WitnessMalleated), run_witness_test_tx_test("60020001".into(), &tx, &flags, 2000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L304 + #[test] + fn witness_non_witness_single_anyone_hash_input_position() { + let tx = "010000000200010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff0001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff02e9030000000000000151e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac".into(), &tx, &flags, 1001, 1))); + } + +/* // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L309 + #[test] + fn witness_p2wsh_with_redeem_witness_script_pubkey() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0001045102010100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("002034b6c399093e06cf9f0f7f660a1abcfe78fcf7b576f43993208edd9518a0ae9b".into(), &tx, &flags, 1000, 0)); + } +*/ + // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L313 + #[test] + fn witness_33_bytes_witness_script_pubkey() { + let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true).verify_discourage_upgradable_witness_program(true); + assert_eq!(Err(Error::DiscourageUpgradableWitnessProgram), run_witness_test_tx_test("6021ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbff".into(), &tx, &flags, 1000, 0)); + } } From 98db437162d63dd78e4ed0d23bca9efea1b2f6c2 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 21 Aug 2017 20:38:31 +0300 Subject: [PATCH 17/35] segwit: tx_valid tests --- script/src/interpreter.rs | 344 +++++++++++++++++++++++++++++++++++--- script/src/sign.rs | 16 +- 2 files changed, 329 insertions(+), 31 deletions(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 4e5fed09..9fd0a3a7 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -465,8 +465,8 @@ pub fn eval_script( return Err(Error::DisabledOpcode(opcode)); } + pc += instruction.step; if !(executing || (Opcode::OP_IF <= opcode && opcode <= Opcode::OP_ENDIF)) { - pc += instruction.step; continue; } @@ -1012,8 +1012,6 @@ pub fn eval_script( if stack.len() + altstack.len() > 1000 { return Err(Error::StackSize); } - - pc += instruction.step; } if !exec_stack.is_empty() { @@ -2688,7 +2686,7 @@ mod tests { SignatureVersion::Base) } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L254 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L254 #[test] fn witness_unknown_program_version() { let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); @@ -2698,7 +2696,7 @@ mod tests { .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L260 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L260 #[test] fn witness_unknown_program0_lengh() { let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); @@ -2708,7 +2706,7 @@ mod tests { .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L260 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L260 #[test] fn witness_single_anyone_same_index_value_changed() { let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e80300000000000001516c070000000000000151b80b0000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); @@ -2718,7 +2716,7 @@ mod tests { .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L272 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L272 #[test] fn witness_none_anyone_same_index_value_changed() { let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff000100000000000000000000000000000000000000000000000000000000000001000000000100000000010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); @@ -2728,7 +2726,7 @@ mod tests { .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L278 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L278 #[test] fn witness_all_anyone_third_value_changed() { let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151540b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); @@ -2738,7 +2736,7 @@ mod tests { .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L284 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L284 #[test] fn witness_with_push_of_521_bytes() { let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd0902000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000".into(); @@ -2746,7 +2744,7 @@ mod tests { assert_eq!(Err(Error::PushSize), run_witness_test_tx_test("002033198a9bfef674ebddb9ffaa52928017b8472791e54c609cb95f278ac6b1e349".into(), &tx, &flags, 1000, 0)); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L288 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L288 #[test] fn witness_unknown_version_with_false_on_stack() { let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015101010100000000".into(); @@ -2754,7 +2752,7 @@ mod tests { assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("60020000".into(), &tx, &flags, 2000, 0)); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L292 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L292 #[test] fn witness_unknown_version_with_non_empty_stack() { let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01000000000000000001510102515100000000".into(); @@ -2762,7 +2760,7 @@ mod tests { assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("00202f04a3aa051f1f60d695f6c44c0c3d383973dfd446ace8962664a76bb10e31a8".into(), &tx, &flags, 2000, 0)); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L296 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L296 #[test] fn witness_program0_with_push_of_2_bytes() { let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015101040002000100000000".into(); @@ -2770,7 +2768,7 @@ mod tests { assert_eq!(Err(Error::WitnessProgramWrongLength), run_witness_test_tx_test("00020001".into(), &tx, &flags, 2000, 0)); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L300 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L300 #[test] fn witness_unknown_version_with_non_empty_script_sig() { let tx = "01000000010001000000000000000000000000000000000000000000000000000000000000000000000151ffffffff010000000000000000015100000000".into(); @@ -2778,7 +2776,7 @@ mod tests { assert_eq!(Err(Error::WitnessMalleated), run_witness_test_tx_test("60020001".into(), &tx, &flags, 2000, 0)); } - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L304 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L304 #[test] fn witness_non_witness_single_anyone_hash_input_position() { let tx = "010000000200010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff0001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff02e9030000000000000151e803000000000000015100000000".into(); @@ -2787,19 +2785,319 @@ mod tests { .and_then(|_| run_witness_test_tx_test("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac".into(), &tx, &flags, 1001, 1))); } -/* // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L309 - #[test] - fn witness_p2wsh_with_redeem_witness_script_pubkey() { - let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff0001045102010100000000".into(); - let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); - assert_eq!(Err(Error::EvalFalse), run_witness_test_tx_test("002034b6c399093e06cf9f0f7f660a1abcfe78fcf7b576f43993208edd9518a0ae9b".into(), &tx, &flags, 1000, 0)); - } -*/ - // https://github.com/bitcoin/bitcoin/blob/master/src/test/data/tx_invalid.json#L313 + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_invalid.json#L313 #[test] fn witness_33_bytes_witness_script_pubkey() { let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true).verify_discourage_upgradable_witness_program(true); assert_eq!(Err(Error::DiscourageUpgradableWitnessProgram), run_witness_test_tx_test("6021ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbff".into(), &tx, &flags, 1000, 0)); } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L320 + #[test] + fn witness_valid_p2wpkh() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L324 + #[test] + fn witness_valid_p2wsh() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("0020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3db".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L328 + #[test] + fn witness_valid_p2sh_p2wpkh() { + let tx = "01000000000101000100000000000000000000000000000000000000000000000000000000000000000000171600144c9c3dfac4207d5d8cb89df5722cb3d712385e3fffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100cfb07164b36ba64c1b1e8c7720a56ad64d96f6ef332d3d37f9cb3c96477dc44502200a464cd7a9cf94cd70f66ce4f4f0625ef650052c7afcfe29d7d7e01830ff91ed012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("a914fe9c7dacc9fcfbf7e3b7d5ad06aa2b28c5a7b7e387".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L328 + #[test] + fn witness_valid_p2sh_p2wsh() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000023220020ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbffffffff01e8030000000000001976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac02483045022100aa5d8aa40a90f23ce2c3d11bc845ca4a12acd99cbea37de6b9f6d86edebba8cb022022dedc2aa0a255f74d04c0b76ece2d7c691f9dd11a64a8ac49f62a99c3a05f9d01232103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("a9142135ab4f0981830311e35600eebc7376dce3a91487".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L328 + #[test] + fn witness_valid_single_anyoune() { + let tx = "0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 3100, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 1100, 2)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 4100, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L343 + #[test] + fn witness_valid_single_anyoune_same_signature() { + let tx = "0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff05540b0000000000000151d0070000000000000151840300000000000001513c0f00000000000001512c010000000000000151000248304502210092f4777a0f17bf5aeb8ae768dec5f2c14feabf9d1fe2c89c78dfed0f13fdb86902206da90a86042e252bcd1e80a168c719e4a1ddcc3cebea24b9812c5453c79107e9832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71000000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 3100, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 1100, 2)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 4100, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L349 + #[test] + fn witness_valid_single() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff0484030000000000000151d0070000000000000151540b0000000000000151c800000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L355 + #[test] + fn witness_valid_single_same_signature() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b000000000000015100024730440220699e6b0cfe015b64ca3283e6551440a34f901ba62dd4c72fe1cb815afb2e6761022021cc5e84db498b1479de14efda49093219441adc6c543e5534979605e273d80b032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L361 + #[test] + fn witness_valid_none_anyone() { + let tx = "0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff04b60300000000000001519e070000000000000151860b00000000000001009600000000000000015100000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 3100, 0) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 1100, 1)) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 2)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 4100, 3))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L368 + #[test] + fn witness_valid_none_anyone_same_signature() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000248304502210091b32274295c2a3fa02f5bce92fb2789e3fc6ea947fbe1a76e52ea3f4ef2381a022079ad72aefa3837a2e0c033a8652a59731da05fa4a813f4fc48e87c075037256b822103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L374 + #[test] + fn witness_none() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff04b60300000000000001519e070000000000000151860b0000000000000100960000000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L380 + #[test] + fn witness_none_same_signature() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L386 + #[test] + fn witness_none_same_signature_sequence_changed() { + let tx = "01000000000103000100000000000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000100000000ffffffff000100000000000000000000000000000000000000000000000000000000000002000000000200000003e8030000000000000151d0070000000000000151b80b00000000000001510002473044022022fceb54f62f8feea77faac7083c3b56c4676a78f93745adc8a35800bc36adfa022026927df9abcf0a8777829bcfcce3ff0a385fa54c3f9df577405e3ef24ee56479022103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L392 + #[test] + fn witness_all_anyone() { + let tx = "0100000000010400010000000000000000000000000000000000000000000000000000000000000200000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000300000000ffffffff03e8030000000000000151d0070000000000000151b80b0000000000000151000002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 3100, 0) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 1100, 1)) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 2)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 4100, 3))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L399 + #[test] + fn witness_all_anyone_same_signature() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623eeef89e0ba1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L405 + #[test] + fn witness_unknown_witness_program_version() { + let tx = "0100000000010300010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000200000000ffffffff03e8030000000000000151d0070000000000000151b80b00000000000001510002483045022100a3cec69b52cba2d2de623ffffffffff1606184ea55476c0f8189fda231bc9cbb022003181ad597f7c380a7d1c740286b1d022b8b04ded028b833282e055e03b8efef812103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("60144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 2000, 1)) + .and_then(|_| run_witness_test_tx_test("51".into(), &tx, &flags, 3000, 2))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L411 + #[test] + fn witness_push_520_bytes() { + let tx = "0100000000010100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff010000000000000000015102fd08020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002755100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("002033198a9bfef674ebddb9ffaa52928017b8472791e54c609cb95f278ac6b1e349".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L415 + #[test] + fn witness_mixed_transaction() { + let tx = "0100000000010c00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff0001000000000000000000000000000000000000000000000000000000000000020000006a473044022026c2e65b33fcd03b2a3b0f25030f0244bd23cc45ae4dec0f48ae62255b1998a00220463aa3982b718d593a6b9e0044513fd67a5009c2fdccc59992cffc2b167889f4012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000030000006a4730440220008bd8382911218dcb4c9f2e75bf5c5c3635f2f2df49b36994fde85b0be21a1a02205a539ef10fb4c778b522c1be852352ea06c67ab74200977c722b0bc68972575a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000040000006b483045022100d9436c32ff065127d71e1a20e319e4fe0a103ba0272743dbd8580be4659ab5d302203fd62571ee1fe790b182d078ecfd092a509eac112bea558d122974ef9cc012c7012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000050000006a47304402200e2c149b114ec546015c13b2b464bbcb0cdc5872e6775787527af6cbc4830b6c02207e9396c6979fb15a9a2b96ca08a633866eaf20dc0ff3c03e512c1d5a1654f148012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0001000000000000000000000000000000000000000000000000000000000000060000006b483045022100b20e70d897dc15420bccb5e0d3e208d27bdd676af109abbd3f88dbdb7721e6d6022005836e663173fbdfe069f54cde3c2decd3d0ea84378092a5d9d85ec8642e8a41012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff00010000000000000000000000000000000000000000000000000000000000000700000000ffffffff00010000000000000000000000000000000000000000000000000000000000000800000000ffffffff00010000000000000000000000000000000000000000000000000000000000000900000000ffffffff00010000000000000000000000000000000000000000000000000000000000000a00000000ffffffff00010000000000000000000000000000000000000000000000000000000000000b0000006a47304402206639c6e05e3b9d2675a7f3876286bdf7584fe2bbd15e0ce52dd4e02c0092cdc60220757d60b0a61fc95ada79d23746744c72bac1545a75ff6c2c7cdb6ae04e7e9592012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ffffffff0ce8030000000000000151e9030000000000000151ea030000000000000151eb030000000000000151ec030000000000000151ed030000000000000151ee030000000000000151ef030000000000000151f0030000000000000151f1030000000000000151f2030000000000000151f30300000000000001510248304502210082219a54f61bf126bfc3fa068c6e33831222d1d7138c6faa9d33ca87fd4202d6022063f9902519624254d7c2c8ea7ba2d66ae975e4e229ae38043973ec707d5d4a83012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022017fb58502475848c1b09f162cb1688d0920ff7f142bed0ef904da2ccc88b168f02201798afa61850c65e77889cbcd648a5703b487895517c88f85cdd18b021ee246a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000000247304402202830b7926e488da75782c81a54cd281720890d1af064629ebf2e31bf9f5435f30220089afaa8b455bbeb7d9b9c3fe1ed37d07685ade8455c76472cda424d93e4074a012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7102473044022026326fcdae9207b596c2b05921dbac11d81040c4d40378513670f19d9f4af893022034ecd7a282c0163b89aaa62c22ec202cef4736c58cd251649bad0d8139bcbf55012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71024730440220214978daeb2f38cd426ee6e2f44131a33d6b191af1c216247f1dd7d74c16d84a02205fdc05529b0bc0c430b4d5987264d9d075351c4f4484c16e91662e90a72aab24012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402204a6e9f199dc9672cf2ff8094aaa784363be1eb62b679f7ff2df361124f1dca3302205eeb11f70fab5355c9c8ad1a0700ea355d315e334822fa182227e9815308ee8f012103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710000000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1001, 1)) + .and_then(|_| run_witness_test_tx_test("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac".into(), &tx, &flags, 1002, 2)) + .and_then(|_| run_witness_test_tx_test("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac".into(), &tx, &flags, 1003, 3)) + .and_then(|_| run_witness_test_tx_test("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac".into(), &tx, &flags, 1004, 4)) + .and_then(|_| run_witness_test_tx_test("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac".into(), &tx, &flags, 1005, 5)) + .and_then(|_| run_witness_test_tx_test("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac".into(), &tx, &flags, 1006, 6)) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1007, 7)) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1008, 8)) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1009, 9)) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1010, 10)) + .and_then(|_| run_witness_test_tx_test("76a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac".into(), &tx, &flags, 1011, 11))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L430 + #[test] + fn witness_unknown_version_with_empty_witness() { + let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("60144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L434 + #[test] + fn witness_single_output_oob() { + let tx = "0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff01d00700000000000001510003483045022100e078de4e96a0e05dcdc0a414124dd8475782b5f3f0ed3f607919e9a5eeeb22bf02201de309b3a3109adb3de8074b3610d4cf454c49b61247a2779a0bcbf31c889333032103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc711976a9144c9c3dfac4207d5d8cb89df5722cb3d712385e3f88ac00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("51".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00204d6c2a32c87821d68fc016fca70797abdb80df6cd84651d40a9300c6bad79e62".into(), &tx, &flags, 1000, 1))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L439 + #[test] + fn witness_1_byte_push_not_witness_script_pubkey() { + let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("600101".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L443 + #[test] + fn witness_41_byte_push_not_witness_script_pubkey() { + let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("6029ff25429251b5a84f452230a3c75fd886b7fc5a7865ce4a7bb7a9d7c5be6da3dbff0000000000000000".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L447 + #[test] + fn witness_version_must_use_op1_to_op16() { + let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("0110020001".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L451 + #[test] + fn witness_program_push_must_be_canonical() { + let tx = "010000000100010000000000000000000000000000000000000000000000000000000000000000000000ffffffff01e803000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("604c020001".into(), &tx, &flags, 1000, 0)); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L455 + #[test] + fn witness_single_anyone_does_not_hash_input_position() { + let tx = "0100000000010200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00010000000000000000000000000000000000000000000000000000000000000100000000ffffffff02e8030000000000000151e90300000000000001510247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1001, 1))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L460 + #[test] + fn witness_single_anyone_does_not_hash_input_position_permutation() { + let tx = "0100000000010200010000000000000000000000000000000000000000000000000000000000000100000000ffffffff00010000000000000000000000000000000000000000000000000000000000000000000000ffffffff02e9030000000000000151e80300000000000001510248304502210085001a820bfcbc9f9de0298af714493f8a37b3b354bfd21a7097c3e009f2018c022050a8b4dbc8155d4d04da2f5cdd575dcf8dd0108de8bec759bd897ea01ecb3af7832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc710247304402206d59682663faab5e4cb733c562e22cdae59294895929ec38d7c016621ff90da0022063ef0af5f970afe8a45ea836e3509b8847ed39463253106ac17d19c437d3d56b832103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc7100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1001, 0) + .and_then(|_| run_witness_test_tx_test("00144c9c3dfac4207d5d8cb89df5722cb3d712385e3f".into(), &tx, &flags, 1000, 1))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L465 + #[test] + fn witness_non_witness_single_anyone_hash_input_position_ok() { + let tx = "01000000020001000000000000000000000000000000000000000000000000000000000000000000004847304402202a0b4b1294d70540235ae033d78e64b4897ec859c7b6f1b2b1d8a02e1d46006702201445e756d2254b0f1dfda9ab8e1e1bc26df9668077403204f32d16a49a36eb6983ffffffff00010000000000000000000000000000000000000000000000000000000000000100000049483045022100acb96cfdbda6dc94b489fd06f2d720983b5f350e31ba906cdbd800773e80b21c02200d74ea5bdf114212b4bbe9ed82c36d2e369e302dff57cb60d01c428f0bd3daab83ffffffff02e8030000000000000151e903000000000000015100000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac".into(), &tx, &flags, 1000, 0) + .and_then(|_| run_witness_test_tx_test("2103596d3451025c19dbbdeb932d6bf8bfb4ad499b95b6f88db8899efac102e5fc71ac".into(), &tx, &flags, 1001, 1))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L471 + #[test] + fn witness_bip143_example1() { + let tx = "01000000000102fe3dc9208094f3ffd12645477b3dc56f60ec4fa8e6f5d67c565d1c6b9216b36e000000004847304402200af4e47c9b9629dbecc21f73af989bdaa911f7e6f6c2e9394588a3aa68f81e9902204f3fcf6ade7e5abb1295b6774c8e0abd94ae62217367096bc02ee5e435b67da201ffffffff0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f80000000000ffffffff0100f2052a010000001976a914a30741f8145e5acadf23f751864167f32e0963f788ac000347304402200de66acf4527789bfda55fc5459e214fa6083f936b430a762c629656216805ac0220396f550692cd347171cbc1ef1f51e15282e837bb2b30860dc77c8f78bc8501e503473044022027dc95ad6b740fe5129e7e62a75dd00f291a2aeb1200b84b09d9e3789406b6c002201a9ecd315dd6a0e632ab20bbb98948bc0c6fb204f2c286963bb48517a7058e27034721026dccc749adc2a9d0d89497ac511f760f45c47dc5ed9cf352a58ac706453880aeadab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("21036d5c20fa14fb2f635474c1dc4ef5909d4568e5569b79fc94d3448486e14685f8ac".into(), &tx, &flags, 156250000, 0) + .and_then(|_| run_witness_test_tx_test("00205d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0".into(), &tx, &flags, 4900000000, 1))); +/* +pbtc: +=== version = 1 +=== hash_prevouts = ef546acf4a020de3898d1b8956176bb507e6211b5ed3619cd08b6ea7e2a09d41 +=== hash_sequence = 0000000000000000000000000000000000000000000000000000000000000000 +=== previous_output = OutPoint { hash: 0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f8, index: 0 } +=== script_pubkey = Script { data: ab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac } +=== input_amount = 4900000000 +=== sequence = 4294967295 +=== hash_outputs = 0000000000000000000000000000000000000000000000000000000000000000 +=== lock_time = 0 +=== sighashtype = 3 +=== OP_CHECKSIG: false/WitnessV0 +*/ + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L476 + #[test] + fn witness_bip143_example2() { + let tx = "01000000000102e9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff80e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffff0280969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac80969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("0020ba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d".into(), &tx, &flags, 16777215, 0) + .and_then(|_| run_witness_test_tx_test("0020d9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537".into(), &tx, &flags, 16777215, 1))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L481 + #[test] + fn witness_bip143_example3() { + let tx = "0100000000010280e68831516392fcd100d186b3c2c7b95c80b53c77e77c35ba03a66b429a2a1b0000000000ffffffffe9b542c5176808107ff1df906f46bb1f2583b16112b95ee5380665ba7fcfc0010000000000ffffffff0280969800000000001976a9146648a8cd4531e1ec47f35916de8e259237294d1e88ac80969800000000001976a914de4b231626ef508c9a74a8517e6783c0546d6b2888ac024730440220032521802a76ad7bf74d0e2c218b72cf0cbc867066e2e53db905ba37f130397e02207709e2188ed7f08f4c952d9d13986da504502b8c3be59617e043552f506c46ff83275163ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac02483045022100f6a10b8604e6dc910194b79ccfc93e1bc0ec7c03453caaa8987f7d6c3413566002206216229ede9b4d6ec2d325be245c5b508ff0339bf1794078e20bfe0babc7ffe683270063ab68210392972e2eb617b2388771abe27235fd5ac44af8e61693261550447a4c3e39da98ac00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("0020d9bbfbe56af7c4b7f960a70d7ea107156913d9e5a26b0a71429df5e097ca6537".into(), &tx, &flags, 16777215, 0) + .and_then(|_| run_witness_test_tx_test("0020ba468eea561b26301e4cf69fa34bde4ad60c81e70f059f045ca9a79931004a4d".into(), &tx, &flags, 16777215, 1))); + } + + // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L486 + #[test] + fn witness_bip143_example4() { + let tx = "0100000000010136641869ca081e70f394c6948e8af409e18b619df2ed74aa106c1ca29787b96e0100000023220020a16b5755f7f6f96dbd65f5f0d6ab9418b89af4b1f14a1bb8a09062c35f0dcb54ffffffff0200e9a435000000001976a914389ffce9cd9ae88dcc0631e88a821ffdbe9bfe2688acc0832f05000000001976a9147480a33f950689af511e6e84c138dbbd3c3ee41588ac080047304402206ac44d672dac41f9b00e28f4df20c52eeb087207e8d758d76d92c6fab3b73e2b0220367750dbbe19290069cba53d096f44530e4f98acaa594810388cf7409a1870ce01473044022068c7946a43232757cbdf9176f009a928e1cd9a1a8c212f15c1e11ac9f2925d9002205b75f937ff2f9f3c1246e547e54f62e027f64eefa2695578cc6432cdabce271502473044022059ebf56d98010a932cf8ecfec54c48e6139ed6adb0728c09cbe1e4fa0915302e022007cd986c8fa870ff5d2b3a89139c9fe7e499259875357e20fcbb15571c76795403483045022100fbefd94bd0a488d50b79102b5dad4ab6ced30c4069f1eaa69a4b5a763414067e02203156c6a5c9cf88f91265f5a942e96213afae16d83321c8b31bb342142a14d16381483045022100a5263ea0553ba89221984bd7f0b13613db16e7a70c549a86de0cc0444141a407022005c360ef0ae5a5d4f9f2f87a56c1546cc8268cab08c73501d6b3be2e1e1a8a08824730440220525406a1482936d5a21888260dc165497a90a15669636d8edca6b9fe490d309c022032af0c646a34a44d1f4576bf6a4a74b67940f8faa84c7df9abe12a01a11e2b4783cf56210307b8ae49ac90a048e9b53357a2354b3334e9c8bee813ecb98e99a7e07e8c3ba32103b28f0c28bfab54554ae8c658ac5c3e0ce6e79ad336331f78c428dd43eea8449b21034b8113d703413d57761b8b9781957b8c0ac1dfe69f492580ca4195f50376ba4a21033400f6afecb833092a9a21cfdf1ed1376e58c5d1f47de74683123987e967a8f42103a6d48b1131e94ba04d9737d61acdaa1322008af9602b3b14862c07a1789aac162102d8b661b0b3302ee2f162b09e07a55ad5dfbe673a9f01d9f0c19617681024306b56ae00000000".into(); + let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); + assert_eq!(Ok(()), run_witness_test_tx_test("a9149993a429037b5d912407a71c252019287b8d27a587".into(), &tx, &flags, 987654321, 0)); + } } diff --git a/script/src/sign.rs b/script/src/sign.rs index f3accc81..f9ff94b9 100644 --- a/script/src/sign.rs +++ b/script/src/sign.rs @@ -132,14 +132,6 @@ impl From for TransactionInputSigner { impl TransactionInputSigner { pub fn signature_hash(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sigversion: SignatureVersion, sighashtype: u32) -> H256 { let sighash = Sighash::from_u32(sigversion, sighashtype); - if input_index >= self.inputs.len() { - return 1u8.into(); - } - - if sighash.base == SighashBase::Single && input_index >= self.outputs.len() { - return 1u8.into(); - } - match sigversion { SignatureVersion::ForkId if sighash.fork_id => self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash), SignatureVersion::Base | SignatureVersion::ForkId => self.signature_hash_original(input_index, script_pubkey, sighashtype, sighash), @@ -177,6 +169,14 @@ impl TransactionInputSigner { } pub fn signature_hash_original(&self, input_index: usize, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 { + if input_index >= self.inputs.len() { + return 1u8.into(); + } + + if sighash.base == SighashBase::Single && input_index >= self.outputs.len() { + return 1u8.into(); + } + let script_pubkey = script_pubkey.without_separators(); let inputs = if sighash.anyone_can_pay { From 51d535968b8996f21a2d72de62abebc9fe8ed874 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 09:38:31 +0300 Subject: [PATCH 18/35] fixed csv deployment constants --- network/src/consensus.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index a59d9830..945d39ab 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -84,7 +84,7 @@ impl ConsensusParams { bit: 0, start_time: 1462060800, timeout: 1493596800, - activation: Some(770112), + activation: Some(419328), }), segwit_deployment: match fork { ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { @@ -111,7 +111,7 @@ impl ConsensusParams { bit: 0, start_time: 1456790400, timeout: 1493596800, - activation: Some(419328), + activation: Some(770112), }), segwit_deployment: match fork { ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => Some(Deployment { From 07e5064755773efba187b3f577801761170716f1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 11:46:46 +0300 Subject: [PATCH 19/35] temp fix for deployments panic --- verification/src/deployments.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/verification/src/deployments.rs b/verification/src/deployments.rs index d06c0e93..4a6902e3 100644 --- a/verification/src/deployments.rs +++ b/verification/src/deployments.rs @@ -142,7 +142,14 @@ fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, num let from_block = deployment_state.block_number + consensus.miner_confirmation_window; let threshold_state = deployment_state.state; let deployment_iter = ThresholdIterator::new(deployment, headers, from_block, consensus, threshold_state); - let state = deployment_iter.last().expect("iter must have at least one item"); + let state = match deployment_iter.last() { + Some(state) => state, + None => DeploymentState { + block_number: number, + block_hash: hash, + state: deployment_state.state, + }, + }; let result = state.state; entry.insert(state); result From 4f9811fe6f854ee602360848ada56bcd6be12418 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 12:49:33 +0300 Subject: [PATCH 20/35] segwit2x: get rid of hardcoded SegWit constants --- network/src/consensus.rs | 39 ++++++++++++++++++++------------ network/src/lib.rs | 2 +- verification/src/accept_block.rs | 6 ++--- verification/src/sigops.rs | 5 ++-- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 945d39ab..0660b00b 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -7,16 +7,6 @@ pub const SEGWIT2X_FORK_BLOCK: u32 = 0xFFFFFFFF; // not known (yet?) /// First block of BitcoinCash fork. pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitcoin-cash/block/478559 -/// Segwit-related constants. -pub mod segwit { - /// The maximum allowed weight for a block, see BIP 141 (network rule). - pub const MAX_BLOCK_WEIGHT: usize = 4_000_000; - /// The maximum allowed number of signature check operations in a block (network rule). - pub const MAX_BLOCK_SIGOPS_COST: usize = 80_000; - /// Witness scale factor. - pub const WITNESS_SCALE_FACTOR: usize = 4; -} - #[derive(Debug, Clone)] /// Parameters that influence chain consensus. pub struct ConsensusParams { @@ -57,6 +47,7 @@ pub enum ConsensusFork { /// Technical specification: /// Segregated Witness (Consensus layer) - https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki /// Block size increase to 2MB - https://github.com/bitcoin/bips/blob/master/bip-0102.mediawiki + /// Readiness checklist - https://segwit2x.github.io/segwit2x-announce.html SegWit2x(u32), /// Bitcoin Cash (aka UAHF). /// `u32` is height of the first block, for which new consensus rules are applied. @@ -171,6 +162,11 @@ impl ConsensusFork { 160_000 } + /// Witness scale factor (equal among all forks) + pub fn witness_scale_factor() -> usize { + 4 + } + pub fn max_transaction_size(&self) -> usize { // BitcoinCash: according to REQ-5: max size of tx is still 1_000_000 // SegWit: size * 4 <= 4_000_000 ===> max size of tx is still 1_000_000 @@ -198,6 +194,8 @@ impl ConsensusFork { // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) ConsensusFork::BitcoinCash(fork_height) if height >= fork_height && block_size > 1_000_000 => 20_000 * (max(block_size, 1_000_000) / 1_000_000), + ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + 40_000, ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => 20_000, } } @@ -205,9 +203,22 @@ impl ConsensusFork { pub fn max_block_sigops_cost(&self, height: u32, block_size: usize) -> usize { match *self { ConsensusFork::BitcoinCash(_) => - self.max_block_sigops(height, block_size) * segwit::WITNESS_SCALE_FACTOR, + self.max_block_sigops(height, block_size) * Self::witness_scale_factor(), + ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + 160_000, ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => - segwit::MAX_BLOCK_SIGOPS_COST, + 80_000, + } + } + + pub fn max_block_weight(&self, height: u32) -> usize { + match *self { + ConsensusFork::SegWit2x(fork_height) if height >= fork_height => + 8_000_000, + ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) => + 4_000_000, + ConsensusFork::BitcoinCash(_) => + unreachable!("BitcoinCash has no SegWit; weight is only checked with SegWit activated; qed"), } } } @@ -272,8 +283,8 @@ mod tests { fn test_consensus_fork_max_block_sigops() { assert_eq!(ConsensusFork::NoFork.max_block_sigops(0, 1_000_000), 20_000); assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(0, 1_000_000), 20_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(100, 2_000_000), 20_000); - assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(200, 3_000_000), 20_000); + assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(100, 2_000_000), 40_000); + assert_eq!(ConsensusFork::SegWit2x(100).max_block_sigops(200, 3_000_000), 40_000); assert_eq!(ConsensusFork::BitcoinCash(100).max_block_sigops(0, 1_000_000), 20_000); assert_eq!(ConsensusFork::BitcoinCash(100).max_block_sigops(100, 2_000_000), 40_000); assert_eq!(ConsensusFork::BitcoinCash(100).max_block_sigops(200, 3_000_000), 60_000); diff --git a/network/src/lib.rs b/network/src/lib.rs index 8c562948..7b97f698 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -8,7 +8,7 @@ mod magic; pub use primitives::{hash, compact}; -pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK, segwit}; +pub use consensus::{ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK}; pub use deployments::Deployment; pub use magic::Magic; diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index 808c8d87..4f169bf2 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -1,4 +1,4 @@ -use network::{ConsensusParams, segwit}; +use network::{ConsensusParams, ConsensusFork}; use crypto::dhash256; use db::{TransactionOutputProvider, BlockHeaderProvider}; use script; @@ -113,8 +113,8 @@ impl<'a> BlockSerializedSize<'a> { if self.segwit_active { let size_with_witness = self.block.size_with_witness(); - let weight = size * (segwit::WITNESS_SCALE_FACTOR - 1) + size_with_witness; - if weight > segwit::MAX_BLOCK_WEIGHT { + let weight = size * (ConsensusFork::witness_scale_factor() - 1) + size_with_witness; + if weight > self.consensus.fork.max_block_weight(self.height) { return Err(Error::Weight); } } diff --git a/verification/src/sigops.rs b/verification/src/sigops.rs index d24340a0..e2181f32 100644 --- a/verification/src/sigops.rs +++ b/verification/src/sigops.rs @@ -1,5 +1,4 @@ -// TODO: excess clones -use network::segwit; +use network::ConsensusFork; use chain::Transaction; use db::TransactionOutputProvider; use script::{Script, ScriptWitness}; @@ -47,7 +46,7 @@ pub fn transaction_sigops_cost( store: &TransactionOutputProvider, sigops: usize, ) -> usize { - let sigops_cost = sigops * segwit::WITNESS_SCALE_FACTOR; + let sigops_cost = sigops * ConsensusFork::witness_scale_factor(); let witness_sigops_cost: usize = transaction.inputs.iter() .map(|input| store.transaction_output(&input.previous_output, usize::max_value()) .map(|output| witness_sigops(&Script::new(input.script_sig.clone()), &Script::new(output.script_pubkey.clone()), &input.script_witness,)) From 42c6ca77f0b3e894bdade5d0a787b1712738a6c5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 12:56:39 +0300 Subject: [PATCH 21/35] segwit2x: fork height + seednodes --- network/src/consensus.rs | 2 +- pbtc/config.rs | 8 ++++++-- pbtc/seednodes.rs | 9 +++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 0660b00b..48afe0ea 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -3,7 +3,7 @@ use hash::H256; use {Magic, Deployment}; /// First block of SegWit2x fork. -pub const SEGWIT2X_FORK_BLOCK: u32 = 0xFFFFFFFF; // not known (yet?) +pub const SEGWIT2X_FORK_BLOCK: u32 = 494784; // https://segwit2x.github.io/segwit2x-announce.html /// First block of BitcoinCash fork. pub const BITCOIN_CASH_FORK_BLOCK: u32 = 478559; // https://blockchair.com/bitcoin-cash/block/478559 diff --git a/pbtc/config.rs b/pbtc/config.rs index e960cc5d..542a2061 100644 --- a/pbtc/config.rs +++ b/pbtc/config.rs @@ -3,7 +3,7 @@ use clap; use message::Services; use network::{Magic, ConsensusParams, ConsensusFork, SEGWIT2X_FORK_BLOCK, BITCOIN_CASH_FORK_BLOCK}; use p2p::InternetProtocol; -use seednodes::{mainnet_seednodes, testnet_seednodes}; +use seednodes::{mainnet_seednodes, testnet_seednodes, segwit2x_seednodes}; use rpc_apis::ApiSet; use {USER_AGENT, REGTEST_USER_AGENT}; use primitives::hash::H256; @@ -81,7 +81,7 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { None => None, }; - let seednodes = match matches.value_of("seednode") { + let mut seednodes: Vec = match matches.value_of("seednode") { Some(s) => vec![s.parse().map_err(|_| "Invalid seednode".to_owned())?], None => match magic { Magic::Mainnet => mainnet_seednodes().into_iter().map(Into::into).collect(), @@ -89,6 +89,10 @@ pub fn parse(matches: &clap::ArgMatches) -> Result { Magic::Other(_) | Magic::Regtest | Magic::Unitest => Vec::new(), }, }; + match consensus_fork { + ConsensusFork::SegWit2x(_) => seednodes.extend(segwit2x_seednodes().into_iter().map(Into::into)), + _ => (), + } let db_cache = match matches.value_of("db-cache") { Some(s) => s.parse().map_err(|_| "Invalid cache size - should be number in MB".to_owned())?, diff --git a/pbtc/seednodes.rs b/pbtc/seednodes.rs index 7aded9e8..92c1da75 100644 --- a/pbtc/seednodes.rs +++ b/pbtc/seednodes.rs @@ -27,3 +27,12 @@ pub fn testnet_seednodes() -> Vec<&'static str> { "testnet-seed.voskuil.org:18333", ] } + +pub fn segwit2x_seednodes() -> Vec<&'static str> { + vec![ + "seed.mainnet.b-pay.net:8333", + "seed.ob1.io:8333", + "seed.blockchain.info:8333", + "bitcoin.bloqseeds.net:8333", + ] +} From a1034cf62344fd6f86d28fa12c0832487444b9e4 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 13:36:35 +0300 Subject: [PATCH 22/35] fixed median_timestamp calculation --- verification/src/timestamp.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/verification/src/timestamp.rs b/verification/src/timestamp.rs index 3aef250e..5c1487ad 100644 --- a/verification/src/timestamp.rs +++ b/verification/src/timestamp.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeSet; use chain::BlockHeader; use db::{BlockHeaderProvider, BlockAncestors}; use primitives::hash::H256; @@ -14,7 +13,7 @@ pub fn median_timestamp(header: &BlockHeader, store: &BlockHeaderProvider) -> u3 /// The header should be later expected to have higher timestamp /// than this median timestamp pub fn median_timestamp_inclusive(previous_header_hash: H256, store: &BlockHeaderProvider) -> u32 { - let timestamps: BTreeSet<_> = BlockAncestors::new(previous_header_hash.clone().into(), store) + let mut timestamps: Vec<_> = BlockAncestors::new(previous_header_hash.clone().into(), store) .take(11) .map(|header| header.time) .collect(); @@ -23,6 +22,6 @@ pub fn median_timestamp_inclusive(previous_header_hash: H256, store: &BlockHeade return 0; } - let timestamps = timestamps.into_iter().collect::>(); + timestamps.sort(); timestamps[timestamps.len() / 2] } From f4fb8fde23f6c64d9048cd2cc1f296604565656b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 14:28:06 +0300 Subject: [PATCH 23/35] use previous block index in deployments state --- verification/src/deployments.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/verification/src/deployments.rs b/verification/src/deployments.rs index 4a6902e3..6077b8b8 100644 --- a/verification/src/deployments.rs +++ b/verification/src/deployments.rs @@ -112,6 +112,8 @@ impl<'a> BlockDeployments<'a> { /// Calculates threshold state of given deployment fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, number: u32, headers: &BlockHeaderProvider, consensus: &ConsensusParams) -> ThresholdState { + // deployments are checked using previous block index + let number = number - 1; if let Some(activation) = deployment.activation { if activation <= number { return ThresholdState::Active; @@ -162,7 +164,6 @@ fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, num result }, } - } fn first_of_the_period(block: u32, miner_confirmation_window: u32) -> u32 { From 1a7b0e13ea7049d5016b8a5fb5e62fe3eb6378a3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 14:47:19 +0300 Subject: [PATCH 24/35] removed comments --- script/src/interpreter.rs | 14 -------------- script/src/sign.rs | 11 +---------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index 9fd0a3a7..a110a2e9 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -3059,20 +3059,6 @@ mod tests { let flags = VerificationFlags::default().verify_witness(true).verify_p2sh(true); assert_eq!(Ok(()), run_witness_test_tx_test("21036d5c20fa14fb2f635474c1dc4ef5909d4568e5569b79fc94d3448486e14685f8ac".into(), &tx, &flags, 156250000, 0) .and_then(|_| run_witness_test_tx_test("00205d1b56b63d714eebe542309525f484b7e9d6f686b3781b6f61ef925d66d6f6a0".into(), &tx, &flags, 4900000000, 1))); -/* -pbtc: -=== version = 1 -=== hash_prevouts = ef546acf4a020de3898d1b8956176bb507e6211b5ed3619cd08b6ea7e2a09d41 -=== hash_sequence = 0000000000000000000000000000000000000000000000000000000000000000 -=== previous_output = OutPoint { hash: 0815cf020f013ed6cf91d29f4202e8a58726b1ac6c79da47c23d1bee0a6925f8, index: 0 } -=== script_pubkey = Script { data: ab210255a9626aebf5e29c0e6538428ba0d1dcf6ca98ffdf086aa8ced5e0d0215ea465ac } -=== input_amount = 4900000000 -=== sequence = 4294967295 -=== hash_outputs = 0000000000000000000000000000000000000000000000000000000000000000 -=== lock_time = 0 -=== sighashtype = 3 -=== OP_CHECKSIG: false/WitnessV0 -*/ } // https://github.com/bitcoin/bitcoin/blob/7ee6c434ce8df9441abcf1718555cc7728a4c575/src/test/data/tx_valid.json#L476 diff --git a/script/src/sign.rs b/script/src/sign.rs index f9ff94b9..1c738a5b 100644 --- a/script/src/sign.rs +++ b/script/src/sign.rs @@ -242,16 +242,7 @@ impl TransactionInputSigner { let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs); let hash_sequence = compute_hash_sequence(sighash, &self.inputs); let hash_outputs = compute_hash_outputs(sighash, input_index, &self.outputs); -/*println!("=== version = {:?}", self.version); -println!("=== hash_prevouts = {:?}", hash_prevouts); -println!("=== hash_sequence = {:?}", hash_sequence); -println!("=== previous_output = {:?}", self.inputs[input_index].previous_output); -println!("=== script_pubkey = {:?}", script_pubkey); -println!("=== input_amount = {:?}", input_amount); -println!("=== sequence = {:?}", self.inputs[input_index].sequence); -println!("=== hash_outputs = {:?}", hash_outputs); -println!("=== lock_time = {:?}", self.lock_time); -println!("=== sighashtype = {:?}", sighashtype);*/ + let mut stream = Stream::default(); stream.append(&self.version); stream.append(&hash_prevouts); From 15bc6f4f45b29eb35f85b0280a47117ed4a101d3 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 22 Aug 2017 14:59:46 +0300 Subject: [PATCH 25/35] fixed tests --- verification/src/deployments.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/verification/src/deployments.rs b/verification/src/deployments.rs index 6077b8b8..7aaf6157 100644 --- a/verification/src/deployments.rs +++ b/verification/src/deployments.rs @@ -113,7 +113,6 @@ impl<'a> BlockDeployments<'a> { /// Calculates threshold state of given deployment fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, number: u32, headers: &BlockHeaderProvider, consensus: &ConsensusParams) -> ThresholdState { // deployments are checked using previous block index - let number = number - 1; if let Some(activation) = deployment.activation { if activation <= number { return ThresholdState::Active; @@ -123,7 +122,7 @@ fn threshold_state(cache: &mut DeploymentStateCache, deployment: Deployment, num } // get number of the first block in the period - let number = first_of_the_period(number, consensus.miner_confirmation_window); + let number = first_of_the_period(number.saturating_sub(1), consensus.miner_confirmation_window); let hash = match headers.block_header(BlockRef::Number(number)) { Some(header) => header.hash(), From db46633ef8b3c35046c45cec90d400027f273d01 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 12:49:22 +0300 Subject: [PATCH 26/35] segwit: optimized size_with_witness --- chain/src/indexed_block.rs | 11 +++++------ serialization/src/lib.rs | 2 +- serialization/src/stream.rs | 11 +++++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/chain/src/indexed_block.rs b/chain/src/indexed_block.rs index 945d5770..22ac74b0 100644 --- a/chain/src/indexed_block.rs +++ b/chain/src/indexed_block.rs @@ -1,7 +1,7 @@ use std::cmp; use hash::H256; use hex::FromHex; -use ser::{Serializable, serialized_list_size, deserialize, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; +use ser::{Serializable, serialized_list_size, serialized_list_size_with_flags, deserialize, SERIALIZE_TRANSACTION_WITNESS}; use block::Block; use transaction::Transaction; use merkle_root::merkle_root; @@ -55,11 +55,10 @@ impl IndexedBlock { } pub fn size_with_witness(&self) -> usize { - // TODO: optimize me - serialize_with_flags(&Block { - block_header: self.header.raw.clone(), - transactions: self.transactions.iter().map(|tx| tx.raw.clone()).collect(), - }, SERIALIZE_TRANSACTION_WITNESS).len() + let header_size = self.header.raw.serialized_size(); + let transactions = self.transactions.iter().map(|tx| &tx.raw).collect::>(); + let txs_size = serialized_list_size_with_flags::(&transactions, SERIALIZE_TRANSACTION_WITNESS); + header_size + txs_size } pub fn merkle_root(&self) -> H256 { diff --git a/serialization/src/lib.rs b/serialization/src/lib.rs index 83bbbaeb..cc2038d5 100644 --- a/serialization/src/lib.rs +++ b/serialization/src/lib.rs @@ -14,6 +14,6 @@ pub use list::List; pub use reader::{Reader, Deserializable, deserialize, deserialize_iterator, ReadIterator, Error}; pub use stream::{ Stream, Serializable, serialize, serialize_with_flags, serialize_list, serialized_list_size, - SERIALIZE_TRANSACTION_WITNESS, + serialized_list_size_with_flags, SERIALIZE_TRANSACTION_WITNESS, }; diff --git a/serialization/src/stream.rs b/serialization/src/stream.rs index 076a9ecc..09e9038c 100644 --- a/serialization/src/stream.rs +++ b/serialization/src/stream.rs @@ -30,6 +30,11 @@ pub fn serialized_list_size(t: &[K]) -> usize where T: Serializable, K: Bo t.iter().map(Borrow::borrow).map(Serializable::serialized_size).sum::() } +pub fn serialized_list_size_with_flags(t: &[K], flags: u32) -> usize where T: Serializable, K: Borrow { + CompactInteger::from(t.len()).serialized_size() + + t.iter().map(Borrow::borrow).map(|i| Serializable::serialized_size_with_flags(i, flags)).sum::() +} + pub trait Serializable { /// Serialize the struct and appends it to the end of stream. fn serialize(&self, s: &mut Stream); @@ -39,6 +44,12 @@ pub trait Serializable { // fallback implementation serialize(self).len() } + + /// Hint about the size of serialized struct with given flags. + fn serialized_size_with_flags(&self, flags: u32) -> usize where Self: Sized { + // fallback implementation + serialize_with_flags(self, flags).len() + } } /// Stream used for serialization of Bitcoin structures From 5fb35020f6ada8d5803c49e83af6218d84b3a0a1 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 13:14:19 +0300 Subject: [PATCH 27/35] segwit: basic serialization tests --- chain/src/indexed_block.rs | 15 +++++++++++++++ chain/src/transaction.rs | 11 ++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/chain/src/indexed_block.rs b/chain/src/indexed_block.rs index 22ac74b0..7297bcf5 100644 --- a/chain/src/indexed_block.rs +++ b/chain/src/indexed_block.rs @@ -84,3 +84,18 @@ impl From<&'static str> for IndexedBlock { deserialize(&s.from_hex().unwrap() as &[u8]).unwrap() } } + +#[cfg(test)] +mod tests { + use super::IndexedBlock; + + #[test] + fn size_with_witness_not_equal_to_size() { + let block_without_witness: IndexedBlock = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(); + assert_eq!(block_without_witness.size(), block_without_witness.size_with_witness()); + + // bip143 block + let block_with_witness: IndexedBlock = "0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000001010000000000".into(); + assert!(block_with_witness.size() != block_with_witness.size_with_witness()); + } +} diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index 2fcf87c7..b84dd0ed 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -262,7 +262,7 @@ impl Deserializable for Transaction { #[cfg(test)] mod tests { use hash::H256; - use ser::Serializable; + use ser::{Serializable, serialize_with_flags, SERIALIZE_TRANSACTION_WITNESS}; use super::{Transaction, TransactionInput, OutPoint, TransactionOutput}; // real transaction from block 80000 @@ -335,4 +335,13 @@ mod tests { }; assert_eq!(actual, expected); } + + #[test] + fn test_serialization_with_flags() { + let transaction_without_witness: Transaction = "000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(); + assert_eq!(serialize_with_flags(&transaction_without_witness, 0), serialize_with_flags(&transaction_without_witness, SERIALIZE_TRANSACTION_WITNESS)); + + let transaction_with_witness: Transaction = "0000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000001010000000000".into(); + assert!(serialize_with_flags(&transaction_with_witness, 0) != serialize_with_flags(&transaction_with_witness, SERIALIZE_TRANSACTION_WITNESS)); + } } From 1b1cebdaac3a3e98ad288fef02331ac7a86547f7 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 13:16:52 +0300 Subject: [PATCH 28/35] segwit: test_witness_hash_differs --- chain/src/transaction.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/chain/src/transaction.rs b/chain/src/transaction.rs index b84dd0ed..507f0f1f 100644 --- a/chain/src/transaction.rs +++ b/chain/src/transaction.rs @@ -344,4 +344,13 @@ mod tests { let transaction_with_witness: Transaction = "0000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000001010000000000".into(); assert!(serialize_with_flags(&transaction_with_witness, 0) != serialize_with_flags(&transaction_with_witness, SERIALIZE_TRANSACTION_WITNESS)); } + + #[test] + fn test_witness_hash_differs() { + let transaction_without_witness: Transaction = "000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000".into(); + assert_eq!(transaction_without_witness.hash(), transaction_without_witness.witness_hash()); + + let transaction_with_witness: Transaction = "0000000000010100000000000000000000000000000000000000000000000000000000000000000000000000000000000001010000000000".into(); + assert!(transaction_with_witness.hash() != transaction_with_witness.witness_hash()); + } } From c0ac8c93cb475e7b25ee87d162bc9cb6f3b70f76 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 13:25:11 +0300 Subject: [PATCH 29/35] uahf: fixed max block sigops calculation --- network/src/consensus.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index 48afe0ea..a25ed609 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -192,8 +192,8 @@ impl ConsensusFork { pub fn max_block_sigops(&self, height: u32, block_size: usize) -> usize { match *self { // according to REQ-5: max_block_sigops = 20000 * ceil((max(blocksize_bytes, 1000000) / 1000000)) - ConsensusFork::BitcoinCash(fork_height) if height >= fork_height && block_size > 1_000_000 => - 20_000 * (max(block_size, 1_000_000) / 1_000_000), + ConsensusFork::BitcoinCash(fork_height) if height >= fork_height => + 20_000 * (1 + (block_size - 1) / 1_000_000), ConsensusFork::SegWit2x(fork_height) if height >= fork_height => 40_000, ConsensusFork::NoFork | ConsensusFork::SegWit2x(_) | ConsensusFork::BitcoinCash(_) => 20_000, From 806bd02fbde5de614811b27329d5a937f1da12c5 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 13:44:20 +0300 Subject: [PATCH 30/35] added some documentation for sigops && weight checks --- verification/src/accept_block.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs index 4f169bf2..501290ae 100644 --- a/verification/src/accept_block.rs +++ b/verification/src/accept_block.rs @@ -106,11 +106,18 @@ impl<'a> BlockSerializedSize<'a> { fn check(&self) -> Result<(), Error> { let size = self.block.size(); + + // block size (without witness) is valid for all forks: + // before SegWit: it is main check for size + // after SegWit: without witness data, block size should be <= 1_000_000 + // after BitcoinCash fork: block size is increased to 8_000_000 + // after SegWit2x fork: without witness data, block size should be <= 2_000_000 if size < self.consensus.fork.min_block_size(self.height) || size > self.consensus.fork.max_block_size(self.height) { return Err(Error::Size(size)); } + // there's no need to define weight for pre-SegWit blocks if self.segwit_active { let size_with_witness = self.block.size_with_witness(); let weight = size * (ConsensusFork::witness_scale_factor() - 1) + size_with_witness; @@ -153,13 +160,21 @@ impl<'a> BlockSigops<'a> { }) .fold((0, 0), |acc, (tx_sigops, tx_sigops_cost)| (acc.0 + tx_sigops, acc.1 + tx_sigops_cost)); - // check sigops + // sigops check is valid for all forks: + // before SegWit: 20_000 + // after SegWit: cost of sigops is sigops * 4 and max cost is 80_000 => max sigops is still 20_000 + // after BitcoinCash fork: 20_000 sigops for each full/partial 1_000_000 bytes of block + // after SegWit2x fork: cost of sigops is sigops * 4 and max cost is 160_000 => max sigops is 40_000 let size = self.block.size(); if sigops > self.consensus.fork.max_block_sigops(self.height, size) { return Err(Error::MaximumSigops); } - // check sigops cost + // sigops check is valid for all forks: + // before SegWit: no witnesses => cost is sigops * 4 and max cost is 80_000 + // after SegWit: it is main check for sigops + // after BitcoinCash fork: no witnesses => cost is sigops * 4 and max cost depends on block size + // after SegWit2x: it is basic check for sigops, limits are increased if sigops_cost > self.consensus.fork.max_block_sigops_cost(self.height, size) { Err(Error::MaximumSigopsCost) } else { From 7f14f9b40d79f4ca08cb1b1500d43d92559c6544 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 13:48:49 +0300 Subject: [PATCH 31/35] uahf: input_index check is back in SignatureHash --- network/src/consensus.rs | 1 - script/src/sign.rs | 16 ++++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/network/src/consensus.rs b/network/src/consensus.rs index a25ed609..0daaa027 100644 --- a/network/src/consensus.rs +++ b/network/src/consensus.rs @@ -1,4 +1,3 @@ -use std::cmp::max; use hash::H256; use {Magic, Deployment}; diff --git a/script/src/sign.rs b/script/src/sign.rs index 1c738a5b..33453194 100644 --- a/script/src/sign.rs +++ b/script/src/sign.rs @@ -235,10 +235,6 @@ impl TransactionInputSigner { } fn signature_hash_witness0(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 { - self.signature_hash_fork_id(input_index, input_amount, script_pubkey, sighashtype, sighash) - } - - fn signature_hash_fork_id(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 { let hash_prevouts = compute_hash_prevouts(sighash, &self.inputs); let hash_sequence = compute_hash_sequence(sighash, &self.inputs); let hash_outputs = compute_hash_outputs(sighash, input_index, &self.outputs); @@ -257,6 +253,18 @@ impl TransactionInputSigner { let out = stream.out(); dhash256(&out) } + + fn signature_hash_fork_id(&self, input_index: usize, input_amount: u64, script_pubkey: &Script, sighashtype: u32, sighash: Sighash) -> H256 { + if input_index >= self.inputs.len() { + return 1u8.into(); + } + + if sighash.base == SighashBase::Single && input_index >= self.outputs.len() { + return 1u8.into(); + } + + self.signature_hash_witness0(input_index, input_amount, script_pubkey, sighashtype, sighash) + } } fn compute_hash_prevouts(sighash: Sighash, inputs: &[UnsignedTransactionInput]) -> H256 { From b791b92414c208849e4fee52f44ba635d1d18392 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 17:26:19 +0300 Subject: [PATCH 32/35] add script error to TransactionError::Signature --- sync/src/synchronization_verifier.rs | 3 ++- verification/src/accept_transaction.rs | 2 +- verification/src/error.rs | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/sync/src/synchronization_verifier.rs b/sync/src/synchronization_verifier.rs index 67550801..2eb42716 100644 --- a/sync/src/synchronization_verifier.rs +++ b/sync/src/synchronization_verifier.rs @@ -271,6 +271,7 @@ pub mod tests { use chain::{IndexedBlock, IndexedTransaction}; use super::{Verifier, BlockVerificationSink, TransactionVerificationSink, AsyncVerifier, VerificationTask, ChainVerifierWrapper}; use types::{BlockHeight, StorageRef, MemoryPoolRef}; + use script::Error as ScriptError; use VerificationParameters; #[derive(Default)] @@ -417,7 +418,7 @@ pub mod tests { verification_level: VerificationLevel::Full, verification_edge: 1.into(), }); - assert_eq!(wrapper.verify_block(&bad_transaction_block), Err(VerificationError::Transaction(1, TransactionError::Signature(0)))); + assert_eq!(wrapper.verify_block(&bad_transaction_block), Err(VerificationError::Transaction(1, TransactionError::Signature(0, ScriptError::InvalidStackOperation)))); } #[test] diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs index d86e1ffd..3bfcd1e9 100644 --- a/verification/src/accept_transaction.rs +++ b/verification/src/accept_transaction.rs @@ -374,7 +374,7 @@ impl<'a> TransactionEval<'a> { .verify_witness(self.verify_witness); try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version) - .map_err(|_| TransactionError::Signature(index))); + .map_err(|e| TransactionError::Signature(index, e))); } Ok(()) diff --git a/verification/src/error.rs b/verification/src/error.rs index bc312260..98ec34a2 100644 --- a/verification/src/error.rs +++ b/verification/src/error.rs @@ -1,6 +1,7 @@ use hash::H256; use compact::Compact; use db::Error as DBError; +use script::Error as SignatureError; #[derive(Debug, PartialEq)] /// All possible verification errors @@ -86,7 +87,7 @@ pub enum TransactionError { /// Referenced coinbase output for the transaction input is not mature enough Maturity, /// Signature invalid for given input - Signature(usize), + Signature(usize, SignatureError), /// Unknown previous transaction referenced UnknownReference(H256), /// Spends more than claims From c10214c5ed7c04673a8e426e1468f128cb548b7b Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Wed, 23 Aug 2017 17:26:38 +0300 Subject: [PATCH 33/35] fixed OP_NOP --- script/src/interpreter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index a110a2e9..24dcebf4 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -580,7 +580,7 @@ pub fn eval_script( Opcode::OP_MOD | Opcode::OP_LSHIFT | Opcode::OP_RSHIFT => { return Err(Error::DisabledOpcode(opcode)); }, - Opcode::OP_NOP => break, + Opcode::OP_NOP => (), Opcode::OP_CHECKLOCKTIMEVERIFY => { if flags.verify_locktime { // Note that elsewhere numeric opcodes are limited to From 1f1e274dd16174b49ac5c1d4646010b6c108deed Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 28 Aug 2017 09:10:49 +0300 Subject: [PATCH 34/35] fixed grumbles --- chain/src/block.rs | 14 ++++++++------ chain/src/indexed_block.rs | 15 +++++++++------ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/chain/src/block.rs b/chain/src/block.rs index 09dbd945..f7851439 100644 --- a/chain/src/block.rs +++ b/chain/src/block.rs @@ -34,12 +34,14 @@ impl Block { /// Returns block's witness merkle root. pub fn witness_merkle_root(&self) -> H256 { - let hashes = self.transactions.iter() - .enumerate() - .map(|(i, tx)| match i { - 0 => H256::from(0), - _ => tx.witness_hash(), - }).collect::>(); + let hashes = match self.transactions.split_first() { + None => vec![], + Some((_, rest)) => { + let mut hashes = vec![H256::from(0)]; + hashes.extend(rest.iter().map(Transaction::witness_hash)); + hashes + }, + }; merkle_root(&hashes) } diff --git a/chain/src/indexed_block.rs b/chain/src/indexed_block.rs index 7297bcf5..961ef0b9 100644 --- a/chain/src/indexed_block.rs +++ b/chain/src/indexed_block.rs @@ -66,12 +66,15 @@ impl IndexedBlock { } pub fn witness_merkle_root(&self) -> H256 { - merkle_root(&self.transactions.iter() - .enumerate() - .map(|(i, tx)| match i { - 0 => H256::from(0), - _ => tx.raw.witness_hash(), - }).collect::>()) + let hashes = match self.transactions.split_first() { + None => vec![], + Some((_, rest)) => { + let mut hashes = vec![H256::from(0)]; + hashes.extend(rest.iter().map(|tx| tx.raw.witness_hash())); + hashes + }, + }; + merkle_root(&hashes) } pub fn is_final(&self, height: u32) -> bool { From 65e1ec068cf8864d14eafea447150b359ec2cc8a Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 28 Aug 2017 09:14:55 +0300 Subject: [PATCH 35/35] moved inv_type out of the loop --- sync/src/synchronization_client_core.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sync/src/synchronization_client_core.rs b/sync/src/synchronization_client_core.rs index 719540c8..e0f856e8 100644 --- a/sync/src/synchronization_client_core.rs +++ b/sync/src/synchronization_client_core.rs @@ -970,6 +970,8 @@ impl SynchronizationClientCore where T: TaskExecutor { let chunk_size = min(limits.max_blocks_in_request, max(hashes.len() as BlockHeight, limits.min_blocks_in_request)); let last_peer_index = peers.len() - 1; let mut tasks: Vec = Vec::new(); + let is_segwit_active = self.chain.is_segwit_active(); + let inv_type = if is_segwit_active { InventoryType::MessageWitnessBlock } else { InventoryType::MessageBlock }; for (peer_index, peer) in peers.into_iter().enumerate() { // we have to request all blocks => we will request last peer for all remaining blocks let peer_chunk_size = if peer_index == last_peer_index { hashes.len() } else { min(hashes.len(), chunk_size as usize) }; @@ -984,10 +986,9 @@ impl SynchronizationClientCore where T: TaskExecutor { self.peers_tasks.on_blocks_requested(peer, &chunk_hashes); // request blocks. If block is believed to have witness - ask for witness - let is_segwit_active = self.chain.is_segwit_active(); let getdata = types::GetData { inventory: chunk_hashes.into_iter().map(|h| InventoryVector { - inv_type: if is_segwit_active { InventoryType::MessageWitnessBlock } else { InventoryType::MessageBlock }, + inv_type: inv_type, hash: h, }).collect(), };