Repository: paritytech/parity-bitcoin (language: rust)

Issue:
Support Bitcoin Cash May 2018 HF
details: https://github.com/bitcoincashorg/spec/pull/53

important: https://github.com/bitcoincashorg/spec/pull/53#pullrequestreview-100735010


Discussion / hints:

Fix patch:
diff --git a/network/src/consensus.rs b/network/src/consensus.rs
index 9dd0268cb..44cdfa96f 100644
--- a/network/src/consensus.rs
+++ b/network/src/consensus.rs
@@ -35,9 +35,12 @@ pub struct ConsensusParams {
 pub struct BitcoinCashConsensusParams {
 	/// Initial BCH hard fork height.
 	pub height: u32,
-	/// Time when difficulty adjustment hardfork becomes active.
+	/// Height of difficulty adjustment hardfork.
 	/// https://reviews.bitcoinabc.org/D601
 	pub difficulty_adjustion_height: u32,
+	/// Time of monolith (aka May 2018) hardfork.
+	/// https://github.com/bitcoincashorg/spec/blob/4fbb0face661e293bcfafe1a2a4744dcca62e50d/may-2018-hardfork.md
+	pub monolith_time: u32,
 }
 
 #[derive(Debug, Clone)]
@@ -154,7 +157,7 @@ impl ConsensusParams {
 impl ConsensusFork {
 	/// Absolute (across all forks) maximum block size. Currently is 8MB for post-HF BitcoinCash
 	pub fn absolute_maximum_block_size() -> usize {
-		8_000_000
+		32_000_000
 	}
 
 	/// Absolute (across all forks) maximum number of sigops in single block. Currently is max(sigops) for 8MB post-HF BitcoinCash block
@@ -188,8 +191,9 @@ impl ConsensusFork {
 		}
 	}
 
-	pub fn max_block_size(&self, height: u32) -> usize {
+	pub fn max_block_size(&self, height: u32, median_time_past: u32) -> usize {
 		match *self {
+			ConsensusFork::BitcoinCash(ref fork) if median_time_past >= fork.monolith_time => 32_000_000,
 			ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => 8_000_000,
 			ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) => 1_000_000,
 		}
@@ -229,14 +233,17 @@ impl BitcoinCashConsensusParams {
 			Network::Mainnet | Network::Other(_) => BitcoinCashConsensusParams {
 				height: 478559,
 				difficulty_adjustion_height: 504031,
+				monolith_time: 1526400000,
 			},
 			Network::Testnet => BitcoinCashConsensusParams {
 				height: 1155876,
 				difficulty_adjustion_height: 1188697,
+				monolith_time: 1526400000,
 			},
 			Network::Regtest | Network::Unitest => BitcoinCashConsensusParams {
 				height: 0,
 				difficulty_adjustion_height: 0,
+				monolith_time: 1526400000,
 			},
 		}
 	}
diff --git a/sync/src/local_node.rs b/sync/src/local_node.rs
index a367fbbf7..b4b36c7fd 100644
--- a/sync/src/local_node.rs
+++ b/sync/src/local_node.rs
@@ -12,6 +12,7 @@ use synchronization_server::{Server, ServerTask};
 use synchronization_verifier::{TransactionVerificationSink};
 use primitives::hash::H256;
 use miner::BlockTemplate;
+use verification::median_timestamp_inclusive;
 use synchronization_peers::{TransactionAnnouncementType, BlockAnnouncementType};
 use types::{PeerIndex, RequestId, StorageRef, MemoryPoolRef, PeersRef, ExecutorRef,
 	ClientRef, ServerRef, SynchronizationStateRef, SyncListenerRef};
@@ -275,11 +276,14 @@ impl<T, U, V> LocalNode<T, U, V> where T: TaskExecutor, U: Server, V: Client {
 
 	/// Get block template for mining
 	pub fn get_block_template(&self) -> BlockTemplate {
-		let height = self.storage.best_block().number;
-		let max_block_size = self.consensus.fork.max_block_size(height);
+		let previous_block_height = self.storage.best_block().number;
+		let previous_block_header = self.storage.block_header(previous_block_height.into()).expect("best block is in db; qed");
+		let median_timestamp = median_timestamp_inclusive(previous_block_header.hash(), self.storage.as_block_header_provider());
+		let new_block_height = previous_block_height + 1;
+		let max_block_size = self.consensus.fork.max_block_size(new_block_height, median_timestamp);
 		let block_assembler = BlockAssembler {
 			max_block_size: max_block_size as u32,
-			max_block_sigops: self.consensus.fork.max_block_sigops(height, max_block_size) as u32,
+			max_block_sigops: self.consensus.fork.max_block_sigops(new_block_height, max_block_size) as u32,
 		};
 		let memory_pool = &*self.memory_pool.read();
 		block_assembler.create_new_block(&self.storage, memory_pool, time::get_time().sec as u32, &self.consensus)
diff --git a/verification/src/accept_block.rs b/verification/src/accept_block.rs
index f809e7eec..d656b5233 100644
--- a/verification/src/accept_block.rs
+++ b/verification/src/accept_block.rs
@@ -27,12 +27,13 @@ impl<'a> BlockAcceptor<'a> {
 		consensus: &'a ConsensusParams,
 		block: CanonBlock<'a>,
 		height: u32,
+		median_time_past: u32,
 		deployments: &'a BlockDeployments<'a>,
 		headers: &'a BlockHeaderProvider,
 	) -> Self {
 		BlockAcceptor {
 			finality: BlockFinality::new(block, height, deployments, headers),
-			serialized_size: BlockSerializedSize::new(block, consensus, deployments, height),
+			serialized_size: BlockSerializedSize::new(block, consensus, deployments, height, median_time_past),
 			coinbase_script: BlockCoinbaseScript::new(block, consensus, height),
 			coinbase_claim: BlockCoinbaseClaim::new(block, store, height),
 			sigops: BlockSigops::new(block, store, consensus, height),
@@ -89,17 +90,19 @@ pub struct BlockSerializedSize<'a> {
 	block: CanonBlock<'a>,
 	consensus: &'a ConsensusParams,
 	height: u32,
+	median_time_past: u32,
 	segwit_active: bool,
 }
 
 impl<'a> BlockSerializedSize<'a> {
-	fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, deployments: &'a BlockDeployments<'a>, height: u32) -> Self {
+	fn new(block: CanonBlock<'a>, consensus: &'a ConsensusParams, deployments: &'a BlockDeployments<'a>, height: u32, median_time_past: u32) -> Self {
 		let segwit_active = deployments.segwit();
 
 		BlockSerializedSize {
 			block: block,
 			consensus: consensus,
 			height: height,
+			median_time_past: median_time_past,
 			segwit_active: segwit_active,
 		}
 	}
@@ -112,7 +115,7 @@ impl<'a> BlockSerializedSize<'a> {
 		// after SegWit: without witness data, block size should be <= 1_000_000
 		// after BitcoinCash fork: block size is increased to 8_000_000
 		if size < self.consensus.fork.min_block_size(self.height) ||
-			size > self.consensus.fork.max_block_size(self.height) {
+			size > self.consensus.fork.max_block_size(self.height, self.median_time_past) {
 			return Err(Error::Size(size));
 		}
 
diff --git a/verification/src/accept_chain.rs b/verification/src/accept_chain.rs
index 9b9011df9..5b87d071c 100644
--- a/verification/src/accept_chain.rs
+++ b/verification/src/accept_chain.rs
@@ -17,13 +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 BlockDeployments) -> Self {
+	pub fn new(store: &'a Store, consensus: &'a ConsensusParams, verification_level: VerificationLevel, block: CanonBlock<'a>, height: u32, median_time_past: 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();
 
 		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, median_time_past, deployments, headers),
 			header: HeaderAcceptor::new(headers, consensus, block.header(), height, deployments),
 			transactions: block.transactions()
 				.into_iter()
@@ -37,6 +37,7 @@ impl<'a> ChainAcceptor<'a> {
 						block.hash(),
 						height,
 						block.header.raw.time,
+						median_time_past,
 						tx_index,
 						deployments,
 				))
diff --git a/verification/src/accept_transaction.rs b/verification/src/accept_transaction.rs
index 610226f69..f89cdcf87 100644
--- a/verification/src/accept_transaction.rs
+++ b/verification/src/accept_transaction.rs
@@ -36,6 +36,7 @@ impl<'a> TransactionAcceptor<'a> {
 		block_hash: &'a H256,
 		height: u32,
 		time: u32,
+		median_time_past: u32,
 		transaction_index: usize,
 		deployments: &'a BlockDeployments<'a>,
 	) -> Self {
@@ -48,7 +49,7 @@ impl<'a> TransactionAcceptor<'a> {
 			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),
+			eval: TransactionEval::new(transaction, output_store, consensus, verification_level, height, time, median_time_past, deployments),
 		}
 	}
 
@@ -85,11 +86,12 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> {
 		transaction: CanonTransaction<'a>,
 		height: u32,
 		time: u32,
+		median_time_past: u32,
 		deployments: &'a BlockDeployments<'a>,
 	) -> Self {
 		trace!(target: "verification", "Mempool-Tx verification {}", transaction.hash.to_reversed_str());
 		let transaction_index = 0;
-		let max_block_sigops = consensus.fork.max_block_sigops(height, consensus.fork.max_block_size(height));
+		let max_block_sigops = consensus.fork.max_block_sigops(height, consensus.fork.max_block_size(height, median_time_past));
 		MemoryPoolTransactionAcceptor {
 			missing_inputs: TransactionMissingInputs::new(transaction, output_store, transaction_index),
 			maturity: TransactionMaturity::new(transaction, meta_store, height),
@@ -97,7 +99,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),
+			eval: TransactionEval::new(transaction, output_store, consensus, VerificationLevel::Full, height, time, median_time_past, deployments),
 		}
 	}
 
@@ -291,6 +293,7 @@ pub struct TransactionEval<'a> {
 	verify_dersig: bool,
 	verify_witness: bool,
 	verify_nulldummy: bool,
+	verify_monolith_opcodes: bool,
 	signature_version: SignatureVersion,
 }
 
@@ -302,6 +305,7 @@ impl<'a> TransactionEval<'a> {
 		verification_level: VerificationLevel,
 		height: u32,
 		time: u32,
+		median_timestamp: u32,
 		deployments: &'a BlockDeployments,
 	) -> Self {
 		let verify_p2sh = time >= params.bip16_time;
@@ -311,6 +315,10 @@ impl<'a> TransactionEval<'a> {
 		};
 		let verify_locktime = height >= params.bip65_height;
 		let verify_dersig = height >= params.bip66_height;
+		let verify_monolith_opcodes = match params.fork {
+			ConsensusFork::BitcoinCash(ref fork) if median_timestamp >= fork.monolith_time => true,
+			_ => false,
+		};
 		let signature_version = match params.fork {
 			ConsensusFork::BitcoinCash(ref fork) if height >= fork.height => SignatureVersion::ForkId,
 			ConsensusFork::NoFork | ConsensusFork::BitcoinCash(_) => SignatureVersion::Base,
@@ -331,6 +339,7 @@ impl<'a> TransactionEval<'a> {
 			verify_dersig: verify_dersig,
 			verify_witness: verify_witness,
 			verify_nulldummy: verify_nulldummy,
+			verify_monolith_opcodes: verify_monolith_opcodes,
 			signature_version: signature_version,
 		}
 	}
@@ -371,7 +380,16 @@ impl<'a> TransactionEval<'a> {
 				.verify_checksequence(self.verify_checksequence)
 				.verify_dersig(self.verify_dersig)
 				.verify_nulldummy(self.verify_nulldummy)
-				.verify_witness(self.verify_witness);
+				.verify_witness(self.verify_witness)
+				.verify_concat(self.verify_monolith_opcodes)
+				.verify_split(self.verify_monolith_opcodes)
+				.verify_and(self.verify_monolith_opcodes)
+				.verify_or(self.verify_monolith_opcodes)
+				.verify_xor(self.verify_monolith_opcodes)
+				.verify_div(self.verify_monolith_opcodes)
+				.verify_mod(self.verify_monolith_opcodes)
+				.verify_bin2num(self.verify_monolith_opcodes)
+				.verify_num2bin(self.verify_monolith_opcodes);
 
 			try!(verify_script(&input, &output, &script_witness, &flags, &checker, self.signature_version)
 				.map_err(|e| TransactionError::Signature(index, e)));
diff --git a/verification/src/chain_verifier.rs b/verification/src/chain_verifier.rs
index 3f98a62d8..a60e5a060 100644
--- a/verification/src/chain_verifier.rs
+++ b/verification/src/chain_verifier.rs
@@ -13,6 +13,7 @@ use verify_transaction::MemoryPoolTransactionVerifier;
 use accept_chain::ChainAcceptor;
 use accept_transaction::MemoryPoolTransactionAcceptor;
 use deployments::{Deployments, BlockDeployments};
+use timestamp::median_timestamp_inclusive;
 use {Verify, VerificationLevel};
 
 pub struct BackwardsCompatibleChainVerifier {
@@ -43,6 +44,8 @@ impl BackwardsCompatibleChainVerifier {
 		assert_eq!(Some(self.store.best_block().hash), self.store.block_hash(self.store.best_block().number));
 		let block_origin = self.store.block_origin(&block.header)?;
 		trace!(target: "verification", "verify_block: {:?} best_block: {:?} block_origin: {:?}", block.hash().reversed(), self.store.best_block(), block_origin);
+
+		let median_time_past = median_timestamp_inclusive(block.header.raw.previous_header_hash.clone(), self.store.as_block_header_provider());
 		match block_origin {
 			BlockOrigin::KnownBlock => {
 				// there should be no known blocks at this point
@@ -52,7 +55,8 @@ impl BackwardsCompatibleChainVerifier {
 				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, &deployments);
+				let chain_acceptor = ChainAcceptor::new(self.store.as_store(), &self.consensus, verification_level,
+					canon_block, block_number, median_time_past, &deployments);
 				chain_acceptor.check()?;
 			},
 			BlockOrigin::SideChain(origin) => {
@@ -61,7 +65,8 @@ impl BackwardsCompatibleChainVerifier {
 				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, &deployments);
+				let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block,
+					block_number, median_time_past, &deployments);
 				chain_acceptor.check()?;
 			},
 			BlockOrigin::SideChainBecomingCanonChain(origin) => {
@@ -70,7 +75,8 @@ impl BackwardsCompatibleChainVerifier {
 				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, &deployments);
+				let chain_acceptor = ChainAcceptor::new(fork.store(), &self.consensus, verification_level, canon_block,
+					block_number, median_time_past, &deployments);
 				chain_acceptor.check()?;
 			},
 		}
@@ -111,6 +117,11 @@ impl BackwardsCompatibleChainVerifier {
 		// now let's do full verification
 		let noop = NoopStore;
 		let output_store = DuplexTransactionOutputProvider::new(prevout_provider, &noop);
+		let previous_block_number = height.checked_sub(1)
+			.expect("height is the height of future block of new tx; genesis block can't be in the future; qed");
+		let previous_block_header = block_header_provider.block_header(previous_block_number.into())
+			.expect("blocks up to height should be in db; qed");
+		let median_time_past = median_timestamp_inclusive(previous_block_header.hash(), block_header_provider);
 		let tx_acceptor = MemoryPoolTransactionAcceptor::new(
 			self.store.as_transaction_meta_provider(),
 			output_store,
@@ -118,6 +129,7 @@ impl BackwardsCompatibleChainVerifier {
 			canon_tx,
 			height,
 			time,
+			median_time_past,
 			&deployments,
 		);
 		tx_acceptor.check()
diff --git a/verification/src/lib.rs b/verification/src/lib.rs
index f977861b8..d1f82854c 100644
--- a/verification/src/lib.rs
+++ b/verification/src/lib.rs
@@ -110,7 +110,7 @@ pub use verify_transaction::{TransactionVerifier, MemoryPoolTransactionVerifier}
 pub use chain_verifier::BackwardsCompatibleChainVerifier;
 pub use error::{Error, TransactionError};
 pub use sigops::transaction_sigops;
-pub use timestamp::median_timestamp;
+pub use timestamp::{median_timestamp, median_timestamp_inclusive};
 pub use work::{work_required, is_valid_proof_of_work, is_valid_proof_of_work_hash, block_reward_satoshi};
 pub use deployments::Deployments;
 
diff --git a/verification/src/work_bch.rs b/verification/src/work_bch.rs
index 202be565a..7dbd3b4bd 100644
--- a/verification/src/work_bch.rs
+++ b/verification/src/work_bch.rs
@@ -217,6 +217,7 @@ mod tests {
 		let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams {
 			height: 1000,
 			difficulty_adjustion_height: 0xffffffff,
+			monolith_time: 0xffffffff,
 		}));
 		let mut header_provider = MemoryBlockHeaderProvider::default();
 		header_provider.insert(BlockHeader {
@@ -269,6 +270,7 @@ mod tests {
 		let uahf_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(BitcoinCashConsensusParams {
 			height: 1000,
 			difficulty_adjustion_height: 0xffffffff,
+			monolith_time: 0xffffffff,
 		}));
 
 


Test patch: