Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BCH Nov2018 HF: enforce push-only restriction for scriptsig #528

Merged
merged 4 commits into from
Nov 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 87 additions & 7 deletions miner/src/block_assembler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ use primitives::hash::H256;
use primitives::compact::Compact;
use chain::{OutPoint, TransactionOutput, IndexedTransaction};
use storage::{SharedStore, TransactionOutputProvider};
use network::ConsensusParams;
use network::{ConsensusParams, ConsensusFork, TransactionOrdering};
use memory_pool::{MemoryPool, OrderingStrategy, Entry};
use verification::{work_required, block_reward_satoshi, transaction_sigops};
use verification::{work_required, block_reward_satoshi, transaction_sigops, median_timestamp_inclusive};

const BLOCK_VERSION: u32 = 0x20000000;
const BLOCK_HEADER_SIZE: u32 = 4 + 32 + 32 + 4 + 4 + 4;
Expand Down Expand Up @@ -116,7 +116,9 @@ impl SizePolicy {

/// Block assembler
pub struct BlockAssembler {
/// Maximal block size.
pub max_block_size: u32,
/// Maximal # of sigops in the block.
pub max_block_sigops: u32,
}

Expand All @@ -130,6 +132,8 @@ struct FittingTransactionsIterator<'a, T> {
block_height: u32,
/// New block time
block_time: u32,
/// Are OP_CHECKDATASIG && OP_CHECKDATASIGVERIFY enabled for this block.
checkdatasig_active: bool,
/// Size policy decides if transactions size fits the block
block_size: SizePolicy,
/// Sigops policy decides if transactions sigops fits the block
Expand All @@ -143,12 +147,21 @@ struct FittingTransactionsIterator<'a, T> {
}

impl<'a, T> FittingTransactionsIterator<'a, T> where T: Iterator<Item = &'a Entry> {
fn new(store: &'a TransactionOutputProvider, iter: T, max_block_size: u32, max_block_sigops: u32, block_height: u32, block_time: u32) -> Self {
fn new(
store: &'a TransactionOutputProvider,
iter: T,
max_block_size: u32,
max_block_sigops: u32,
block_height: u32,
block_time: u32,
checkdatasig_active: bool,
) -> Self {
FittingTransactionsIterator {
store: store,
iter: iter,
block_height: block_height,
block_time: block_time,
checkdatasig_active,
// reserve some space for header and transations len field
block_size: SizePolicy::new(BLOCK_HEADER_SIZE + 4, max_block_size, 1_000, 50),
sigops: SizePolicy::new(0, max_block_sigops, 8, 50),
Expand Down Expand Up @@ -190,7 +203,7 @@ impl<'a, T> Iterator for FittingTransactionsIterator<'a, T> where T: Iterator<It

let transaction_size = entry.size as u32;
let bip16_active = true;
let sigops_count = transaction_sigops(&entry.transaction, self, bip16_active) as u32;
let sigops_count = transaction_sigops(&entry.transaction, self, bip16_active, self.checkdatasig_active) as u32;

let size_step = self.block_size.decide(transaction_size);
let sigops_step = self.sigops.decide(sigops_count);
Expand Down Expand Up @@ -233,7 +246,7 @@ impl<'a, T> Iterator for FittingTransactionsIterator<'a, T> where T: Iterator<It
}

impl BlockAssembler {
pub fn create_new_block(&self, store: &SharedStore, mempool: &MemoryPool, time: u32, consensus: &ConsensusParams) -> BlockTemplate {
pub fn create_new_block(&self, store: &SharedStore, mempool: &MemoryPool, time: u32, median_timestamp: u32, consensus: &ConsensusParams) -> BlockTemplate {
// get best block
// take it's hash && height
let best_block = store.best_block();
Expand All @@ -242,11 +255,23 @@ impl BlockAssembler {
let bits = work_required(previous_header_hash.clone(), time, height, store.as_block_header_provider(), consensus);
let version = BLOCK_VERSION;

let checkdatasig_active = match consensus.fork {
ConsensusFork::BitcoinCash(ref fork) => median_timestamp >= fork.magnetic_anomaly_time,
_ => false
};

let mut coinbase_value = block_reward_satoshi(height);
let mut transactions = Vec::new();

let mempool_iter = mempool.iter(OrderingStrategy::ByTransactionScore);
let tx_iter = FittingTransactionsIterator::new(store.as_transaction_output_provider(), mempool_iter, self.max_block_size, self.max_block_sigops, height, time);
let tx_iter = FittingTransactionsIterator::new(
store.as_transaction_output_provider(),
mempool_iter,
self.max_block_size,
self.max_block_sigops,
height,
time,
checkdatasig_active);
for entry in tx_iter {
// miner_fee is i64, but we can safely cast it to u64
// memory pool should restrict miner fee to be positive
Expand All @@ -255,6 +280,15 @@ impl BlockAssembler {
transactions.push(tx);
}

// sort block transactions
let median_time_past = median_timestamp_inclusive(previous_header_hash.clone(), store.as_block_header_provider());
match consensus.fork.transaction_ordering(median_time_past) {
TransactionOrdering::Canonical => transactions.sort_unstable_by(|tx1, tx2|
tx1.hash.cmp(&tx2.hash)),
// memory pool iter returns transactions in topological order
TransactionOrdering::Topological => (),
}

BlockTemplate {
version: version,
previous_header_hash: previous_header_hash,
Expand All @@ -271,7 +305,16 @@ impl BlockAssembler {

#[cfg(test)]
mod tests {
use super::{SizePolicy, NextStep};
extern crate test_data;

use std::sync::Arc;
use db::BlockChainDatabase;
use primitives::hash::H256;
use storage::SharedStore;
use network::{ConsensusParams, ConsensusFork, Network, BitcoinCashConsensusParams};
use memory_pool::MemoryPool;
use self::test_data::{ChainBuilder, TransactionBuilder};
use super::{BlockAssembler, SizePolicy, NextStep, BlockTemplate};

#[test]
fn test_size_policy() {
Expand Down Expand Up @@ -317,4 +360,41 @@ mod tests {
fn test_fitting_transactions_iterator_locked_transaction() {
// TODO
}

#[test]
fn block_assembler_transaction_order() {
fn construct_block(consensus: ConsensusParams) -> (BlockTemplate, H256, H256) {
let chain = &mut ChainBuilder::new();
TransactionBuilder::with_default_input(0).set_output(30).store(chain) // transaction0
.into_input(0).set_output(50).store(chain); // transaction0 -> transaction1
let hash0 = chain.at(0).hash();
let hash1 = chain.at(1).hash();

let mut pool = MemoryPool::new();
let storage: SharedStore = Arc::new(BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()]));
pool.insert_verified(chain.at(0).into());
pool.insert_verified(chain.at(1).into());

(BlockAssembler {
max_block_size: 0xffffffff,
max_block_sigops: 0xffffffff,
}.create_new_block(&storage, &pool, 0, 0, &consensus), hash0, hash1)
}

// when topological consensus is used
let topological_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCore);
let (block, hash0, hash1) = construct_block(topological_consensus);
assert!(hash1 < hash0);
assert_eq!(block.transactions[0].hash, hash0);
assert_eq!(block.transactions[1].hash, hash1);

// when canonocal consensus is used
let mut canonical_fork = BitcoinCashConsensusParams::new(Network::Mainnet);
canonical_fork.magnetic_anomaly_time = 0;
let canonical_consensus = ConsensusParams::new(Network::Mainnet, ConsensusFork::BitcoinCash(canonical_fork));
let (block, hash0, hash1) = construct_block(canonical_consensus);
assert!(hash1 < hash0);
assert_eq!(block.transactions[0].hash, hash1);
assert_eq!(block.transactions[1].hash, hash0);
}
}
40 changes: 40 additions & 0 deletions network/src/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub struct BitcoinCashConsensusParams {
/// Time of monolith (aka May 2018) hardfork.
/// https://github.com/bitcoincashorg/spec/blob/4fbb0face661e293bcfafe1a2a4744dcca62e50d/may-2018-hardfork.md
pub monolith_time: u32,
/// Time of magnetic anomaly (aka Nov 2018) hardfork.
/// https://github.com/bitcoincashorg/bitcoincash.org/blob/f92f5412f2ed60273c229f68dd8703b6d5d09617/spec/2018-nov-upgrade.md
pub magnetic_anomaly_time: u32,
}

#[derive(Debug, Clone)]
Expand All @@ -57,6 +60,17 @@ pub enum ConsensusFork {
BitcoinCash(BitcoinCashConsensusParams),
}

#[derive(Debug, Clone, Copy)]
/// Describes the ordering of transactions within single block.
pub enum TransactionOrdering {
/// Topological tranasaction ordering: if tx TX2 depends on tx TX1,
/// it should come AFTER TX1 (not necessary **right** after it).
Topological,
/// Canonical transaction ordering: transactions are ordered by their
/// hash (in ascending order).
Canonical,
}

impl ConsensusParams {
pub fn new(network: Network, fork: ConsensusFork) -> Self {
match network {
Expand Down Expand Up @@ -177,6 +191,13 @@ impl ConsensusFork {
}
}

pub fn min_transaction_size(&self, median_time_past: u32) -> usize {
match *self {
ConsensusFork::BitcoinCash(ref fork) if median_time_past >= fork.magnetic_anomaly_time => 100,
_ => 0,
}
}

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
Expand Down Expand Up @@ -225,6 +246,14 @@ impl ConsensusFork {
unreachable!("BitcoinCash has no SegWit; weight is only checked with SegWit activated; qed"),
}
}

pub fn transaction_ordering(&self, median_time_past: u32) -> TransactionOrdering {
match *self {
ConsensusFork::BitcoinCash(ref fork) if median_time_past >= fork.magnetic_anomaly_time
=> TransactionOrdering::Canonical,
_ => TransactionOrdering::Topological,
}
}
}

impl BitcoinCashConsensusParams {
Expand All @@ -234,16 +263,19 @@ impl BitcoinCashConsensusParams {
height: 478559,
difficulty_adjustion_height: 504031,
monolith_time: 1526400000,
magnetic_anomaly_time: 1542300000,
},
Network::Testnet => BitcoinCashConsensusParams {
height: 1155876,
difficulty_adjustion_height: 1188697,
monolith_time: 1526400000,
magnetic_anomaly_time: 1542300000,
},
Network::Regtest | Network::Unitest => BitcoinCashConsensusParams {
height: 0,
difficulty_adjustion_height: 0,
monolith_time: 1526400000,
magnetic_anomaly_time: 1542300000,
},
}
}
Expand Down Expand Up @@ -303,6 +335,14 @@ mod tests {
assert_eq!(ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Mainnet)).max_transaction_size(), 1_000_000);
}

#[test]
fn test_consensus_fork_min_transaction_size() {
assert_eq!(ConsensusFork::BitcoinCore.min_transaction_size(0), 0);
assert_eq!(ConsensusFork::BitcoinCore.min_transaction_size(2000000000), 0);
assert_eq!(ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Mainnet)).min_transaction_size(0), 0);
assert_eq!(ConsensusFork::BitcoinCash(BitcoinCashConsensusParams::new(Network::Mainnet)).min_transaction_size(2000000000), 100);
}

#[test]
fn test_consensus_fork_max_block_sigops() {
assert_eq!(ConsensusFork::BitcoinCore.max_block_sigops(0, 1_000_000), 20_000);
Expand Down
2 changes: 1 addition & 1 deletion network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ mod network;

pub use primitives::{hash, compact};

pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams};
pub use consensus::{ConsensusParams, ConsensusFork, BitcoinCashConsensusParams, TransactionOrdering};
pub use deployments::Deployment;
pub use network::{Magic, Network};
6 changes: 4 additions & 2 deletions script/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ pub enum Error {
NumberNotMinimallyEncoded,
SigCount,
PubkeyCount,
InvalidOperandSize,

// Failed verify operations
Verify,
EqualVerify,
CheckSigVerify,
CheckDataSigVerify,
NumEqualVerify,

// Logical/Format/Canonical errors.
Expand All @@ -33,7 +35,6 @@ pub enum Error {
InvalidAltstackOperation,
UnbalancedConditional,
InvalidSplitRange,
InvalidBitwiseOperation,
DivisionByZero,
ImpossibleEncoding,

Expand Down Expand Up @@ -78,6 +79,7 @@ impl fmt::Display for Error {
Error::Verify => "Failed verify operation".fmt(f),
Error::EqualVerify => "Failed equal verify operation".fmt(f),
Error::CheckSigVerify => "Failed signature check".fmt(f),
Error::CheckDataSigVerify => "Failed data signature check".fmt(f),
Error::NumEqualVerify => "Failed num equal verify operation".fmt(f),
Error::SigCount => "Maximum number of signature exceeded".fmt(f),
Error::PubkeyCount => "Maximum number of pubkeys per multisig exceeded".fmt(f),
Expand All @@ -97,7 +99,7 @@ impl fmt::Display for Error {
Error::InvalidAltstackOperation => "Invalid altstack operation".fmt(f),
Error::UnbalancedConditional => "Unbalanced conditional".fmt(f),
Error::InvalidSplitRange => "Invalid OP_SPLIT range".fmt(f),
Error::InvalidBitwiseOperation => "Invalid bitwise operation (check length of inputs)".fmt(f),
Error::InvalidOperandSize => "Invalid operand size".fmt(f),
Error::DivisionByZero => "Invalid division operation".fmt(f),
Error::ImpossibleEncoding => "The requested encoding is impossible to satisfy".fmt(f),

Expand Down
13 changes: 13 additions & 0 deletions script/src/flags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ pub struct VerificationFlags {
///
/// This opcode replaces OP_LEFT => enabling both OP_NUM2BIN && OP_LEFT would be an error
pub verify_num2bin: bool,

/// Support OP_CHECKDATASIG and OP_CHECKDATASIGVERIFY opcodes.
pub verify_checkdatasig: bool,
}

impl VerificationFlags {
Expand Down Expand Up @@ -136,6 +139,11 @@ impl VerificationFlags {
self
}

pub fn verify_sigpushonly(mut self, value: bool) -> Self {
self.verify_sigpushonly = value;
self
}

pub fn verify_discourage_upgradable_witness_program(mut self, value: bool) -> Self {
self.verify_discourage_upgradable_witness_program = value;
self
Expand Down Expand Up @@ -185,4 +193,9 @@ impl VerificationFlags {
self.verify_num2bin = value;
self
}

pub fn verify_checkdatasig(mut self, value: bool) -> Self {
self.verify_checkdatasig = value;
self
}
}
Loading