diff --git a/message/src/common/consensus.rs b/message/src/common/consensus.rs new file mode 100644 index 00000000..c92c14de --- /dev/null +++ b/message/src/common/consensus.rs @@ -0,0 +1,38 @@ +use super::Magic; + +#[derive(Debug, Clone)] +/// Parameters that influence chain consensus. +pub struct ConsensusParams { + /// Block height at which BIP65 becomes active. + /// See https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki + pub bip65_height: u32, +} + +impl ConsensusParams { + pub fn with_magic(magic: Magic) -> Self { + match magic { + Magic::Mainnet => ConsensusParams { + bip65_height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0 + }, + Magic::Testnet => ConsensusParams { + bip65_height: 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6 + }, + Magic::Regtest => ConsensusParams { + bip65_height: 1351, + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::super::Magic; + use super::ConsensusParams; + + #[test] + fn test_consensus_params_bip65_height() { + assert_eq!(ConsensusParams::with_magic(Magic::Mainnet).bip65_height, 388381); + assert_eq!(ConsensusParams::with_magic(Magic::Testnet).bip65_height, 581885); + assert_eq!(ConsensusParams::with_magic(Magic::Regtest).bip65_height, 1351); + } +} diff --git a/message/src/common/magic.rs b/message/src/common/magic.rs index c600da2d..55297061 100644 --- a/message/src/common/magic.rs +++ b/message/src/common/magic.rs @@ -4,6 +4,7 @@ use ser::{Stream, Serializable}; use chain::Block; use Error; +use super::ConsensusParams; const MAGIC_MAINNET: u32 = 0xD9B4BEF9; const MAGIC_TESTNET: u32 = 0x0709110B; @@ -62,6 +63,10 @@ impl Magic { Magic::Regtest => "0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f20020000000101000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000".into(), } } + + pub fn consensus_params(&self) -> ConsensusParams { + ConsensusParams::with_magic(*self) + } } impl Serializable for Magic { diff --git a/message/src/common/mod.rs b/message/src/common/mod.rs index 08bbc1e3..e58dbad4 100644 --- a/message/src/common/mod.rs +++ b/message/src/common/mod.rs @@ -3,6 +3,7 @@ mod block_header_and_ids; mod block_transactions; mod block_transactions_request; mod command; +mod consensus; mod inventory; mod ip; mod magic; @@ -15,6 +16,7 @@ pub use self::block_header_and_ids::BlockHeaderAndIDs; pub use self::block_transactions::BlockTransactions; pub use self::block_transactions_request::BlockTransactionsRequest; pub use self::command::Command; +pub use self::consensus::ConsensusParams; pub use self::inventory::{InventoryVector, InventoryType}; pub use self::ip::IpAddress; pub use self::magic::Magic; diff --git a/pbtc/commands/start.rs b/pbtc/commands/start.rs index 5860893d..890be31d 100644 --- a/pbtc/commands/start.rs +++ b/pbtc/commands/start.rs @@ -30,7 +30,7 @@ pub fn start(cfg: config::Config) -> Result<(), String> { }; let sync_handle = el.handle(); - let sync_connection_factory = create_sync_connection_factory(&sync_handle, db); + let sync_connection_factory = create_sync_connection_factory(&sync_handle, cfg.magic.consensus_params(), db); let p2p = try!(p2p::P2P::new(p2p_cfg, sync_connection_factory, el.handle()).map_err(|x| x.to_string())); try!(p2p.run().map_err(|_| "Failed to start p2p module")); diff --git a/script/src/flags.rs b/script/src/flags.rs index cf7be871..9b7f6ca6 100644 --- a/script/src/flags.rs +++ b/script/src/flags.rs @@ -71,5 +71,10 @@ impl VerificationFlags { self.verify_p2sh = value; self } + + pub fn verify_clocktimeverify(mut self, value: bool) -> Self { + self.verify_clocktimeverify = value; + self + } } diff --git a/script/src/interpreter.rs b/script/src/interpreter.rs index f184c82c..097a231d 100644 --- a/script/src/interpreter.rs +++ b/script/src/interpreter.rs @@ -447,35 +447,37 @@ pub fn eval_script( }, Opcode::OP_NOP => break, Opcode::OP_CHECKLOCKTIMEVERIFY => { - if !flags.verify_clocktimeverify && flags.verify_discourage_upgradable_nops { + if flags.verify_discourage_upgradable_nops { return Err(Error::DiscourageUpgradableNops); } - // Note that elsewhere numeric opcodes are limited to - // operands in the range -2**31+1 to 2**31-1, however it is - // legal for opcodes to produce results exceeding that - // range. This limitation is implemented by CScriptNum's - // default 4-byte limit. - // - // If we kept to that limit we'd have a year 2038 problem, - // even though the nLockTime field in transactions - // themselves is uint32 which only becomes meaningless - // after the year 2106. - // - // Thus as a special case we tell CScriptNum to accept up - // to 5-byte bignums, which are good until 2**39-1, well - // beyond the 2**32-1 limit of the nLockTime field itself. - let lock_time = try!(Num::from_slice(try!(stack.last()), flags.verify_minimaldata, 5)); + if flags.verify_clocktimeverify { + // Note that elsewhere numeric opcodes are limited to + // operands in the range -2**31+1 to 2**31-1, however it is + // legal for opcodes to produce results exceeding that + // range. This limitation is implemented by CScriptNum's + // default 4-byte limit. + // + // If we kept to that limit we'd have a year 2038 problem, + // even though the nLockTime field in transactions + // themselves is uint32 which only becomes meaningless + // after the year 2106. + // + // Thus as a special case we tell CScriptNum to accept up + // to 5-byte bignums, which are good until 2**39-1, well + // beyond the 2**32-1 limit of the nLockTime field itself. + let lock_time = try!(Num::from_slice(try!(stack.last()), flags.verify_minimaldata, 5)); - // In the rare event that the argument may be < 0 due to - // some arithmetic being done first, you can always use - // 0 MAX CHECKLOCKTIMEVERIFY. - if lock_time.is_negative() { - return Err(Error::NegativeLocktime); - } + // In the rare event that the argument may be < 0 due to + // some arithmetic being done first, you can always use + // 0 MAX CHECKLOCKTIMEVERIFY. + if lock_time.is_negative() { + return Err(Error::NegativeLocktime); + } - if !checker.check_lock_time(lock_time) { - return Err(Error::UnsatisfiedLocktime); + if !checker.check_lock_time(lock_time) { + return Err(Error::UnsatisfiedLocktime); + } } }, Opcode::OP_CHECKSEQUENCEVERIFY => { @@ -1870,4 +1872,26 @@ mod tests { .verify_p2sh(true); assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(())); } + + // https://blockchain.info/rawtx/eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb + #[test] + fn test_transaction_bip65() { + let tx: Transaction = "01000000024de8b0c4c2582db95fa6b3567a989b664484c7ad6672c85a3da413773e63fdb8000000006b48304502205b282fbc9b064f3bc823a23edcc0048cbb174754e7aa742e3c9f483ebe02911c022100e4b0b3a117d36cab5a67404dddbf43db7bea3c1530e0fe128ebc15621bd69a3b0121035aa98d5f77cd9a2d88710e6fc66212aff820026f0dad8f32d1f7ce87457dde50ffffffff4de8b0c4c2582db95fa6b3567a989b664484c7ad6672c85a3da413773e63fdb8010000006f004730440220276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea64660202200f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee01ab51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51aeffffffff02e0fd1c00000000001976a914380cb3c594de4e7e9b8e18db182987bebb5a4f7088acc0c62d000000000017142a9bc5447d664c1d0141392a842d23dba45c4f13b17500000000".into(); + let signer: TransactionInputSigner = tx.into(); + let checker = TransactionSignatureChecker { + signer: signer, + input_index: 1, + }; + let input: Script = "004730440220276d6dad3defa37b5f81add3992d510d2f44a317fd85e04f93a1e2daea64660202200f862a0da684249322ceb8ed842fb8c859c0cb94c81e1c5308b4868157a428ee01ab51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51ae".into(); + let output: Script = "142a9bc5447d664c1d0141392a842d23dba45c4f13b175".into(); + + let flags = VerificationFlags::default() + .verify_p2sh(true); + assert_eq!(verify_script(&input, &output, &flags, &checker), Ok(())); + + let flags = VerificationFlags::default() + .verify_p2sh(true) + .verify_clocktimeverify(true); + assert_eq!(verify_script(&input, &output, &flags, &checker), Err(Error::NumberOverflow)); + } } diff --git a/sync/src/lib.rs b/sync/src/lib.rs index 34cec017..f5d2f622 100644 --- a/sync/src/lib.rs +++ b/sync/src/lib.rs @@ -33,6 +33,7 @@ mod synchronization_server; use std::sync::Arc; use parking_lot::RwLock; use tokio_core::reactor::Handle; +use message::common::ConsensusParams; /// Sync errors. #[derive(Debug)] @@ -51,7 +52,7 @@ pub fn create_sync_blocks_writer(db: Arc) -> blocks_writer::BlocksWri } /// Create inbound synchronization connections factory for given `db`. -pub fn create_sync_connection_factory(handle: &Handle, db: Arc) -> p2p::LocalSyncNodeRef { +pub fn create_sync_connection_factory(handle: &Handle, consensus_params: ConsensusParams, db: Arc) -> p2p::LocalSyncNodeRef { use synchronization_chain::Chain as SyncChain; use synchronization_executor::LocalSynchronizationTaskExecutor as SyncExecutor; use local_node::LocalNode as SyncNode; @@ -62,7 +63,7 @@ pub fn create_sync_connection_factory(handle: &Handle, db: Arc) -> p2 let sync_chain = Arc::new(RwLock::new(SyncChain::new(db))); let sync_executor = SyncExecutor::new(sync_chain.clone()); let sync_server = Arc::new(SynchronizationServer::new(sync_chain.clone(), sync_executor.clone())); - let sync_client = SynchronizationClient::new(SynchronizationConfig::default(), handle, sync_executor.clone(), sync_chain); + let sync_client = SynchronizationClient::new(SynchronizationConfig::with_consensus_params(consensus_params), handle, sync_executor.clone(), sync_chain); let sync_node = Arc::new(SyncNode::new(sync_server, sync_client, sync_executor)); SyncConnectionFactory::with_local_node(sync_node) } diff --git a/sync/src/local_node.rs b/sync/src/local_node.rs index 6e5ef35c..b30adc96 100644 --- a/sync/src/local_node.rs +++ b/sync/src/local_node.rs @@ -215,7 +215,7 @@ mod tests { use synchronization_chain::Chain; use p2p::{event_loop, OutboundSyncConnection, OutboundSyncConnectionRef}; use message::types; - use message::common::{InventoryVector, InventoryType}; + use message::common::{Magic, ConsensusParams, InventoryVector, InventoryType}; use db; use super::LocalNode; use test_data; @@ -259,7 +259,7 @@ mod tests { let chain = Arc::new(RwLock::new(Chain::new(Arc::new(db::TestStorage::with_genesis_block())))); let executor = DummyTaskExecutor::new(); let server = Arc::new(DummyServer::new()); - let config = Config { threads_num: 1, skip_verification: true }; + let config = Config { consensus_params: ConsensusParams::with_magic(Magic::Mainnet), threads_num: 1, skip_verification: true }; let client = SynchronizationClient::new(config, &handle, executor.clone(), chain); let local_node = LocalNode::new(server.clone(), client, executor.clone()); (event_loop, handle, executor, server, local_node) diff --git a/sync/src/synchronization_client.rs b/sync/src/synchronization_client.rs index 5128bcb4..fbf87f22 100644 --- a/sync/src/synchronization_client.rs +++ b/sync/src/synchronization_client.rs @@ -12,6 +12,7 @@ use futures_cpupool::CpuPool; use linked_hash_map::LinkedHashMap; use db; use chain::{Block, BlockHeader, RepresentH256}; +use message::common::ConsensusParams; use primitives::hash::H256; use synchronization_peers::Peers; #[cfg(test)] use synchronization_peers::{Information as PeersInformation}; @@ -206,6 +207,8 @@ pub struct PeersBlocksWaiter { /// Synchronization client configuration options. pub struct Config { + /// Consensus-related parameters. + pub consensus_params: ConsensusParams, /// Number of threads to allocate in synchronization CpuPool. pub threads_num: usize, /// Do not verify incoming blocks before inserting to db. @@ -214,6 +217,8 @@ pub struct Config { /// Synchronization client. pub struct SynchronizationClient { + /// Synchronization configuration. + config: Config, /// Synchronization state. state: State, /// Cpu pool. @@ -240,9 +245,10 @@ pub struct SynchronizationClient { verifying_blocks_waiters: HashMap, Option>)>, } -impl Default for Config { - fn default() -> Self { +impl Config { + pub fn with_consensus_params(consensus_params: ConsensusParams) -> Self { Config { + consensus_params: consensus_params, threads_num: 4, skip_verification: false, } @@ -458,6 +464,7 @@ impl Client for SynchronizationClient where T: TaskExecutor { impl SynchronizationClient where T: TaskExecutor { /// Create new synchronization window pub fn new(config: Config, handle: &Handle, executor: Arc>, chain: ChainRef) -> Arc> { + let skip_verification = config.skip_verification; let sync = Arc::new(Mutex::new( SynchronizationClient { state: State::Saturated, @@ -472,19 +479,21 @@ impl SynchronizationClient where T: TaskExecutor { verification_worker_thread: None, verifying_blocks_by_peer: HashMap::new(), verifying_blocks_waiters: HashMap::new(), + config: config, } )); - if !config.skip_verification { + if !skip_verification { let (verification_work_sender, verification_work_receiver) = channel(); let csync = sync.clone(); let mut lsync = sync.lock(); let storage = chain.read().storage(); + let verifier = ChainVerifier::new(storage); lsync.verification_work_sender = Some(verification_work_sender); lsync.verification_worker_thread = Some(thread::Builder::new() .name("Sync verification thread".to_string()) .spawn(move || { - SynchronizationClient::verification_worker_proc(csync, storage, verification_work_receiver) + SynchronizationClient::verification_worker_proc(csync, verifier, verification_work_receiver) }) .expect("Error creating verification thread")); } @@ -536,6 +545,11 @@ impl SynchronizationClient where T: TaskExecutor { } } + /// Get configuration parameters. + pub fn config<'a>(&'a self) -> &'a Config { + &self.config + } + /// Process new blocks inventory fn process_new_blocks_headers(&mut self, peer_index: usize, mut hashes: Vec, mut headers: Vec) { assert_eq!(hashes.len(), headers.len()); @@ -895,11 +909,32 @@ impl SynchronizationClient where T: TaskExecutor { } /// Thread procedure for handling verification tasks - fn verification_worker_proc(sync: Arc>, storage: Arc, work_receiver: Receiver) { - let verifier = ChainVerifier::new(storage); + fn verification_worker_proc(sync: Arc>, mut verifier: ChainVerifier, work_receiver: Receiver) { + let mut parameters_change_steps = Some(0); while let Ok(task) = work_receiver.recv() { match task { VerificationTask::VerifyBlock(block) => { + // change verifier parameters, if needed + if let Some(steps_left) = parameters_change_steps { + if steps_left == 0 { + let sync = sync.lock(); + let config = sync.config(); + let best_storage_block = sync.chain.read().best_storage_block(); + + let is_bip65_active = best_storage_block.number >= config.consensus_params.bip65_height; + verifier = verifier.verify_clocktimeverify(is_bip65_active); + + if is_bip65_active { + parameters_change_steps = None; + } else { + parameters_change_steps = Some(config.consensus_params.bip65_height - best_storage_block.number); + } + } else { + parameters_change_steps = Some(steps_left - 1); + } + } + + // verify block match verifier.verify(&block) { Ok(_chain) => { sync.lock().on_block_verification_success(block) @@ -938,6 +973,7 @@ pub mod tests { use parking_lot::{Mutex, RwLock}; use tokio_core::reactor::{Core, Handle}; use chain::{Block, RepresentH256}; + use message::common::{Magic, ConsensusParams}; use super::{Client, Config, SynchronizationClient}; use synchronization_executor::Task; use synchronization_chain::{Chain, ChainRef}; @@ -961,7 +997,7 @@ pub mod tests { }; let chain = ChainRef::new(RwLock::new(Chain::new(storage.clone()))); let executor = DummyTaskExecutor::new(); - let config = Config { threads_num: 1, skip_verification: true }; + let config = Config { consensus_params: ConsensusParams::with_magic(Magic::Mainnet), threads_num: 1, skip_verification: true }; let client = SynchronizationClient::new(config, &handle, executor.clone(), chain.clone()); (event_loop, handle, executor, chain, client) diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs index 4a31141e..4b1d9f4f 100644 --- a/verification/src/chain_verifier.rs +++ b/verification/src/chain_verifier.rs @@ -14,13 +14,19 @@ const MAX_BLOCK_SIZE: usize = 1000000; pub struct ChainVerifier { store: Arc, + verify_clocktimeverify: bool, skip_pow: bool, skip_sig: bool, } impl ChainVerifier { pub fn new(store: Arc) -> Self { - ChainVerifier { store: store, skip_pow: false, skip_sig: false } + ChainVerifier { + store: store, + verify_clocktimeverify: false, + skip_pow: false, + skip_sig: false + } } #[cfg(test)] @@ -35,6 +41,11 @@ impl ChainVerifier { self } + pub fn verify_clocktimeverify(mut self, verify: bool) -> Self { + self.verify_clocktimeverify = verify; + self + } + fn ordered_verify(&self, block: &chain::Block, at_height: u32) -> Result<(), Error> { let coinbase_spends = block.transactions()[0].total_spends(); @@ -129,7 +140,9 @@ impl ChainVerifier { let input: Script = input.script_sig().to_vec().into(); let output: Script = paired_output.script_pubkey.to_vec().into(); - let flags = VerificationFlags::default().verify_p2sh(true); + let flags = VerificationFlags::default() + .verify_p2sh(true) + .verify_clocktimeverify(self.verify_clocktimeverify); // for tests only, skips as late as possible if self.skip_sig { continue; }