bip9, bip112, bip113

This commit is contained in:
debris 2017-05-03 14:08:53 +02:00
parent 95255d0f58
commit 9a389f9746
17 changed files with 372 additions and 233 deletions

1
Cargo.lock generated
View File

@ -6,6 +6,7 @@ dependencies = [
"db 0.1.0",
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
"network 0.1.0",
"parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"primitives 0.1.0",
"rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
"script 0.1.0",

View File

@ -7,7 +7,7 @@ BIPs that are implemented by pbtc
| BIPs | pbtc | core | unlimited |
| ------ | ------ | ------ | ------ |
| [BIP 9][BIP9] | - | + | ? |
| [BIP 9][BIP9] | + | + | ? |
| [BIP 11][BIP11] | a | + | ? |
| [BIP 13][BIP13] | a | + | ? |
| [BIP 14][BIP14] | - | + | ? |
@ -25,14 +25,14 @@ BIPs that are implemented by pbtc
| [BIP 61][BIP61] | ? | + | ? |
| [BIP 65][BIP65] | + | + | ? |
| [BIP 66][BIP66] | + | + | ? |
| [BIP 68][BIP68] | - | + | ? |
| [BIP 68][BIP68] | + | + | ? |
| [BIP 70][BIP70] | a | + | ? |
| [BIP 71][BIP71] | a | + | ? |
| [BIP 72][BIP72] | a | + | ? |
| [BIP 90][BIP90] | + | + | ? |
| [BIP 111][BIP111] | ? | + | ? |
| [BIP 112][BIP112] | - | + | ? |
| [BIP 113][BIP113] | - | + | ? |
| [BIP 112][BIP112] | + | + | ? |
| [BIP 113][BIP113] | + | + | ? |
| [BIP 125][BIP125] | a | + | ? |
| [BIP 130][BIP130] | ? | + | ? |
| [BIP 133][BIP133] | ? | + | ? |

View File

@ -1,5 +1,5 @@
use hash::H256;
use super::Magic;
use {Magic, Deployment};
#[derive(Debug, Clone)]
/// Parameters that influence chain consensus.
@ -16,6 +16,14 @@ pub struct ConsensusParams {
/// Block height at which BIP65 becomes active.
/// See https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
pub bip66_height: u32,
/// Version bits activation
pub rule_change_activation_threshold: u32,
/// Number of blocks with the same set of rules
pub miner_confirmation_window: u32,
/// BIP68, BIP112, BIP113 deployment
pub csv_deployment: Option<Deployment>,
/// BIP141, BIP143, BIP147 deployment
pub segwit_deployment: Option<Deployment>,
}
impl ConsensusParams {
@ -26,18 +34,48 @@ impl ConsensusParams {
bip34_height: 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
bip65_height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
bip66_height: 363725, // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931
rule_change_activation_threshold: 1916, // 95%
miner_confirmation_window: 2016,
csv_deployment: Some(Deployment {
name: "csv",
bit: 0,
start_time: 1462060800,
timeout: 1493596800,
activation: Some(770112),
}),
segwit_deployment: None,
},
Magic::Testnet => ConsensusParams {
bip16_time: 1333238400, // Apr 1 2012
bip34_height: 21111, // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
bip65_height: 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
bip66_height: 330776, // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182
rule_change_activation_threshold: 1512, // 75%
miner_confirmation_window: 2016,
csv_deployment: Some(Deployment {
name: "csv",
bit: 0,
start_time: 1456790400,
timeout: 1493596800,
activation: Some(419328),
}),
segwit_deployment: None,
},
Magic::Regtest | Magic::Unitest => ConsensusParams {
bip16_time: 1333238400, // Apr 1 2012
bip34_height: 100000000, // not activated on regtest
bip65_height: 1351,
bip66_height: 1251, // used only in rpc tests
rule_change_activation_threshold: 108, // 75%
miner_confirmation_window: 144,
csv_deployment: Some(Deployment {
name: "csv",
bit: 0,
start_time: 0,
timeout: 0,
activation: Some(0),
}),
segwit_deployment: None,
},
}
}
@ -73,4 +111,18 @@ mod tests {
assert_eq!(ConsensusParams::with_magic(Magic::Testnet).bip66_height, 330776);
assert_eq!(ConsensusParams::with_magic(Magic::Regtest).bip66_height, 1251);
}
#[test]
fn test_consensus_activation_threshold() {
assert_eq!(ConsensusParams::with_magic(Magic::Mainnet).rule_change_activation_threshold, 1916);
assert_eq!(ConsensusParams::with_magic(Magic::Testnet).rule_change_activation_threshold, 1512);
assert_eq!(ConsensusParams::with_magic(Magic::Regtest).rule_change_activation_threshold, 108);
}
#[test]
fn test_consensus_miner_confirmation_window() {
assert_eq!(ConsensusParams::with_magic(Magic::Mainnet).miner_confirmation_window, 2016);
assert_eq!(ConsensusParams::with_magic(Magic::Testnet).miner_confirmation_window, 2016);
assert_eq!(ConsensusParams::with_magic(Magic::Regtest).miner_confirmation_window, 144);
}
}

View File

@ -0,0 +1,23 @@
const VERSIONBITS_TOP_MASK: u32 = 0xe0000000;
const VERSIONBITS_TOP_BITS: u32 = 0x20000000;
#[derive(Debug, Clone, Copy)]
pub struct Deployment {
/// Deployment's name
pub name: &'static str,
/// Bit
pub bit: u8,
/// Start time
pub start_time: u32,
/// Timeout
pub timeout: u32,
/// Activation block number (if already activated)
pub activation: Option<u32>,
}
impl Deployment {
pub fn matches(&self, version: u32) -> bool {
(version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS && (version & (1 << self.bit)) != 0
}
}

View File

@ -3,10 +3,12 @@ extern crate primitives;
extern crate serialization as ser;
mod consensus;
mod deployments;
mod magic;
pub use primitives::{hash, compact};
pub use consensus::ConsensusParams;
pub use deployments::Deployment;
pub use magic::Magic;

View File

@ -75,11 +75,9 @@ impl BlockChainClientCoreApi for BlockChainClientCore {
None => -1,
};
let block_size = block.size();
// TODO: use real network
let median_time = verification::median_timestamp(
&block.header.raw,
self.storage.as_block_header_provider(),
Magic::Mainnet,
self.storage.as_block_header_provider()
);
VerboseBlock {

View File

@ -53,12 +53,12 @@ pub struct VerificationFlags {
/// Verify CHECKLOCKTIMEVERIFY
///
/// See BIP65 for details.
pub verify_clocktimeverify: bool,
pub verify_locktime: bool,
/// support CHECKSEQUENCEVERIFY opcode
///
/// See BIP112 for details
pub verify_checksequenceverify: bool,
pub verify_checksequence: bool,
/// Support segregated witness
pub verify_witness: bool,
@ -73,8 +73,13 @@ impl VerificationFlags {
self
}
pub fn verify_clocktimeverify(mut self, value: bool) -> Self {
self.verify_clocktimeverify = value;
pub fn verify_locktime(mut self, value: bool) -> Self {
self.verify_locktime = value;
self
}
pub fn verify_checksequence(mut self, value: bool) -> Self {
self.verify_checksequence = value;
self
}

View File

@ -456,7 +456,7 @@ pub fn eval_script(
},
Opcode::OP_NOP => break,
Opcode::OP_CHECKLOCKTIMEVERIFY => {
if flags.verify_clocktimeverify {
if flags.verify_locktime {
// 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
@ -488,7 +488,7 @@ pub fn eval_script(
}
},
Opcode::OP_CHECKSEQUENCEVERIFY => {
if flags.verify_checksequenceverify {
if flags.verify_checksequence {
let sequence = try!(Num::from_slice(try!(stack.last()), flags.verify_minimaldata, 5));
if sequence.is_negative() {
@ -1916,7 +1916,7 @@ mod tests {
let flags = VerificationFlags::default()
.verify_p2sh(true)
.verify_clocktimeverify(true);
.verify_locktime(true);
assert_eq!(verify_script(&input, &output, &flags, &checker), Err(Error::NumberOverflow));
}

View File

@ -7,6 +7,7 @@ authors = ["Nikolay Volf <nikvolf@gmail.com>"]
time = "0.1"
log = "0.3"
rayon = "0.6"
parking_lot = "0.4"
primitives = { path = "../primitives" }
chain = { path = "../chain" }
serialization = { path = "../serialization" }

View File

@ -6,6 +6,7 @@ use canon::CanonBlock;
use accept_block::BlockAcceptor;
use accept_header::HeaderAcceptor;
use accept_transaction::TransactionAcceptor;
use deployments::Deployments;
use duplex_store::DuplexTransactionOutputProvider;
pub struct ChainAcceptor<'a> {
@ -15,12 +16,13 @@ pub struct ChainAcceptor<'a> {
}
impl<'a> ChainAcceptor<'a> {
pub fn new(store: &'a Store, network: Magic, block: CanonBlock<'a>, height: u32) -> Self {
pub fn new(store: &'a Store, network: Magic, block: CanonBlock<'a>, height: u32, deployments: &'a Deployments) -> 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(), network, block, height),
header: HeaderAcceptor::new(store.as_block_header_provider(), network, block.header(), height),
header: HeaderAcceptor::new(headers, network, block.header(), height, deployments),
transactions: block.transactions()
.into_iter()
.enumerate()
@ -32,7 +34,9 @@ impl<'a> ChainAcceptor<'a> {
block.hash(),
height,
block.header.raw.time,
tx_index
tx_index,
deployments,
headers,
))
.collect(),
}

View File

@ -3,6 +3,7 @@ use db::BlockHeaderProvider;
use canon::CanonHeader;
use error::Error;
use work::work_required;
use deployments::Deployments;
use timestamp::median_timestamp;
pub struct HeaderAcceptor<'a> {
@ -12,13 +13,18 @@ pub struct HeaderAcceptor<'a> {
}
impl<'a> HeaderAcceptor<'a> {
pub fn new(store: &'a BlockHeaderProvider, network: Magic, header: CanonHeader<'a>, height: u32) -> Self {
pub fn new(
store: &'a BlockHeaderProvider,
network: Magic,
header: CanonHeader<'a>,
height: u32,
deployments: &'a Deployments,
) -> Self {
let params = network.consensus_params();
HeaderAcceptor {
// TODO: check last 1000 blocks instead of hardcoding the value
version: HeaderVersion::new(header, height, params),
version: HeaderVersion::new(header, height, params.clone()),
work: HeaderWork::new(header, store, height, network),
median_timestamp: HeaderMedianTimestamp::new(header, store, network),
median_timestamp: HeaderMedianTimestamp::new(header, store, height, deployments, params),
}
}
@ -90,21 +96,21 @@ impl<'a> HeaderWork<'a> {
pub struct HeaderMedianTimestamp<'a> {
header: CanonHeader<'a>,
store: &'a BlockHeaderProvider,
network: Magic,
active: bool,
}
impl<'a> HeaderMedianTimestamp<'a> {
fn new(header: CanonHeader<'a>, store: &'a BlockHeaderProvider, network: Magic) -> Self {
fn new(header: CanonHeader<'a>, store: &'a BlockHeaderProvider, height: u32, deployments: &'a Deployments, params: ConsensusParams) -> Self {
let active = deployments.csv(height, store, params);
HeaderMedianTimestamp {
header: header,
store: store,
network: network,
active: active,
}
}
fn check(&self) -> Result<(), Error> {
let median = median_timestamp(&self.header.raw, self.store, self.network);
if self.header.raw.time <= median {
if self.active && self.header.raw.time <= median_timestamp(&self.header.raw, self.store) {
Err(Error::Timestamp)
} else {
Ok(())

View File

@ -1,8 +1,9 @@
use primitives::hash::H256;
use db::{TransactionMetaProvider, TransactionOutputProvider};
use db::{TransactionMetaProvider, TransactionOutputProvider, BlockHeaderProvider};
use network::{Magic, ConsensusParams};
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner};
use duplex_store::DuplexTransactionOutputProvider;
use deployments::Deployments;
use sigops::transaction_sigops;
use canon::CanonTransaction;
use constants::{COINBASE_MATURITY, MAX_BLOCK_SIGOPS};
@ -30,6 +31,8 @@ impl<'a> TransactionAcceptor<'a> {
height: u32,
time: u32,
transaction_index: usize,
deployments: &'a Deployments,
headers: &'a BlockHeaderProvider,
) -> Self {
trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str());
let params = network.consensus_params();
@ -39,7 +42,7 @@ impl<'a> TransactionAcceptor<'a> {
maturity: TransactionMaturity::new(transaction, meta_store, height),
overspent: TransactionOverspent::new(transaction, output_store),
double_spent: TransactionDoubleSpend::new(transaction, output_store),
eval: TransactionEval::new(transaction, output_store, params, height, time),
eval: TransactionEval::new(transaction, output_store, params, height, time, deployments, headers),
}
}
@ -73,6 +76,8 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> {
transaction: CanonTransaction<'a>,
height: u32,
time: u32,
deployments: &'a Deployments,
headers: &'a BlockHeaderProvider,
) -> Self {
trace!(target: "verification", "Mempool-Tx verification {}", transaction.hash.to_reversed_str());
let params = network.consensus_params();
@ -83,7 +88,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> {
overspent: TransactionOverspent::new(transaction, output_store),
sigops: TransactionSigops::new(transaction, output_store, params.clone(), MAX_BLOCK_SIGOPS, time),
double_spent: TransactionDoubleSpend::new(transaction, output_store),
eval: TransactionEval::new(transaction, output_store, params, height, time),
eval: TransactionEval::new(transaction, output_store, params, height, time, deployments, headers),
}
}
@ -269,7 +274,8 @@ pub struct TransactionEval<'a> {
transaction: CanonTransaction<'a>,
store: DuplexTransactionOutputProvider<'a>,
verify_p2sh: bool,
verify_clocktime: bool,
verify_locktime: bool,
verify_checksequence: bool,
verify_dersig: bool,
}
@ -280,16 +286,21 @@ impl<'a> TransactionEval<'a> {
params: ConsensusParams,
height: u32,
time: u32,
deployments: &'a Deployments,
headers: &'a BlockHeaderProvider,
) -> Self {
let verify_p2sh = time >= params.bip16_time;
let verify_clocktime = height >= params.bip65_height;
let verify_locktime = height >= params.bip65_height;
let verify_dersig = height >= params.bip66_height;
let verify_checksequence = deployments.csv(height, headers, params);
TransactionEval {
transaction: transaction,
store: store,
verify_p2sh: verify_p2sh,
verify_clocktime: verify_clocktime,
verify_locktime: verify_locktime,
verify_checksequence: verify_checksequence,
verify_dersig: verify_dersig,
}
}
@ -317,7 +328,8 @@ impl<'a> TransactionEval<'a> {
let flags = VerificationFlags::default()
.verify_p2sh(self.verify_p2sh)
.verify_clocktimeverify(self.verify_clocktime)
.verify_locktime(self.verify_locktime)
.verify_checksequence(self.verify_checksequence)
.verify_dersig(self.verify_dersig);
try!(verify_script(&input, &output, &flags, &checker).map_err(|_| TransactionError::Signature(index)));

View File

@ -12,11 +12,13 @@ use verify_header::HeaderVerifier;
use verify_transaction::MemoryPoolTransactionVerifier;
use accept_chain::ChainAcceptor;
use accept_transaction::MemoryPoolTransactionAcceptor;
use deployments::Deployments;
use Verify;
pub struct BackwardsCompatibleChainVerifier {
store: SharedStore,
network: Magic,
deployments: Deployments,
}
impl BackwardsCompatibleChainVerifier {
@ -24,6 +26,7 @@ impl BackwardsCompatibleChainVerifier {
BackwardsCompatibleChainVerifier {
store: store,
network: network,
deployments: Deployments::new(),
}
}
@ -43,21 +46,21 @@ impl BackwardsCompatibleChainVerifier {
},
BlockOrigin::CanonChain { block_number } => {
let canon_block = CanonBlock::new(block);
let chain_acceptor = ChainAcceptor::new(self.store.as_store(), self.network, canon_block, block_number);
let chain_acceptor = ChainAcceptor::new(self.store.as_store(), self.network, canon_block, block_number, &self.deployments);
chain_acceptor.check()?;
},
BlockOrigin::SideChain(origin) => {
let block_number = origin.block_number;
let fork = self.store.fork(origin)?;
let canon_block = CanonBlock::new(block);
let chain_acceptor = ChainAcceptor::new(fork.store(), self.network, canon_block, block_number);
let chain_acceptor = ChainAcceptor::new(fork.store(), self.network, canon_block, block_number, &self.deployments);
chain_acceptor.check()?;
},
BlockOrigin::SideChainBecomingCanonChain(origin) => {
let block_number = origin.block_number;
let fork = self.store.fork(origin)?;
let canon_block = CanonBlock::new(block);
let chain_acceptor = ChainAcceptor::new(fork.store(), self.network, canon_block, block_number);
let chain_acceptor = ChainAcceptor::new(fork.store(), self.network, canon_block, block_number, &self.deployments);
chain_acceptor.check()?;
},
}
@ -102,7 +105,9 @@ impl BackwardsCompatibleChainVerifier {
self.network,
canon_tx,
height,
time
time,
&self.deployments,
self.store.as_block_header_provider()
);
tx_acceptor.check()
}

View File

@ -0,0 +1,222 @@
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use parking_lot::Mutex;
use network::{ConsensusParams, Deployment};
use hash::H256;
use db::{BlockHeaderProvider, BlockRef, BlockAncestors, BlockIterator};
use timestamp::median_timestamp;
#[derive(Debug, Clone, Copy)]
enum ThresholdState {
Defined,
Started,
LockedIn,
Active,
Failed,
}
impl Default for ThresholdState {
fn default() -> Self {
ThresholdState::Defined
}
}
impl ThresholdState {
fn is_final(&self) -> bool {
match *self {
ThresholdState::Active | ThresholdState::Failed => true,
ThresholdState::Defined | ThresholdState::Started | ThresholdState::LockedIn => false,
}
}
fn is_active(&self) -> bool {
match *self {
ThresholdState::Active => true,
_ => false,
}
}
}
/// Threshold state at given point of time
#[derive(Debug, Clone, Default)]
struct DeploymentState {
/// Block number
block_number: u32,
/// Block hash
block_hash: H256,
/// Threshold state for given block
state: ThresholdState,
}
/// Last known deployment states
type DeploymentStateCache = HashMap<&'static str, DeploymentState>;
#[derive(Default)]
pub struct Deployments {
cache: Mutex<DeploymentStateCache>,
}
impl Deployments {
pub fn new() -> Self {
Deployments::default()
}
/// Returns true if csv deployment is active
pub fn csv(&self, number: u32, headers: &BlockHeaderProvider, consensus: ConsensusParams) -> bool {
match consensus.csv_deployment {
Some(csv) => {
let mut cache = self.cache.lock();
threshold_state(&mut cache, csv, number, headers, consensus).is_active()
},
None => false
}
}
}
/// 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 {
if activation <= number {
return ThresholdState::Active;
} else {
return ThresholdState::Defined;
}
}
// get number of the first block in the period
let number = first_of_the_period(number, consensus.miner_confirmation_window);
let hash = match headers.block_header(BlockRef::Number(number)) {
Some(header) => header.hash(),
None => return ThresholdState::Defined,
};
match cache.entry(deployment.name) {
// by checking hash, we make sure we are on the same branch
Entry::Occupied(ref entry) if entry.get().block_number == number && entry.get().block_hash == hash => {
entry.get().state
},
// otherwise we need to recalculate threshold state
Entry::Occupied(mut entry) => {
let deployment_state = entry.get().clone();
if deployment_state.state.is_final() {
return deployment_state.state
}
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 result = state.state;
entry.insert(state);
result
},
Entry::Vacant(entry) => {
let deployment_iter = ThresholdIterator::new(deployment, headers, 0, consensus, ThresholdState::Defined);
let state = deployment_iter.last().unwrap_or_default();
let result = state.state;
entry.insert(state);
result
},
}
}
fn first_of_the_period(block: u32, miner_confirmation_window: u32) -> u32 {
if block < miner_confirmation_window - 1 {
0
} else {
block - ((block + 1) % miner_confirmation_window)
}
}
fn count_deployment_matches(block_number: u32, blocks: &BlockHeaderProvider, deployment: Deployment, window: u32) -> usize {
BlockAncestors::new(BlockRef::Number(block_number), blocks)
.take(window as usize)
.filter(|header| deployment.matches(header.version))
.count()
}
struct ThresholdIterator<'a> {
deployment: Deployment,
block_iterator: BlockIterator<'a>,
headers: &'a BlockHeaderProvider,
consensus: ConsensusParams,
last_state: ThresholdState,
}
impl<'a> ThresholdIterator<'a> {
fn new(deployment: Deployment, headers: &'a BlockHeaderProvider, to_check: u32, consensus: ConsensusParams, state: ThresholdState) -> Self {
ThresholdIterator {
deployment: deployment,
block_iterator: BlockIterator::new(to_check, consensus.miner_confirmation_window, headers),
headers: headers,
consensus: consensus,
last_state: state,
}
}
}
impl<'a> Iterator for ThresholdIterator<'a> {
type Item = DeploymentState;
fn next(&mut self) -> Option<Self::Item> {
let (block_number, header) = match self.block_iterator.next() {
Some(header) => header,
None => return None,
};
let median = median_timestamp(&header, self.headers);
match self.last_state {
ThresholdState::Defined => {
if median >= self.deployment.timeout {
self.last_state = ThresholdState::Failed;
} else if median >= self.deployment.start_time {
self.last_state = ThresholdState::Started;
}
},
ThresholdState::Started => {
if median >= self.deployment.timeout {
self.last_state = ThresholdState::Failed;
} else {
let count = count_deployment_matches(block_number, self.headers, self.deployment, self.consensus.miner_confirmation_window);
if count >= self.consensus.rule_change_activation_threshold as usize {
self.last_state = ThresholdState::LockedIn;
}
}
},
ThresholdState::LockedIn => {
self.last_state = ThresholdState::Active;
},
ThresholdState::Failed | ThresholdState::Active => {
return None
}
}
let result = DeploymentState {
block_number: block_number,
block_hash: header.hash(),
state: self.last_state,
};
Some(result)
}
}
#[cfg(test)]
mod tests {
use super::first_of_the_period;
#[test]
fn test_first_of_the_period() {
let window = 2016;
assert_eq!(0, first_of_the_period(0, window));
assert_eq!(0, first_of_the_period(1, window));
assert_eq!(0, first_of_the_period(2014, window));
assert_eq!(2015, first_of_the_period(2015, window));
assert_eq!(2015, first_of_the_period(2016, window));
assert_eq!(8063, first_of_the_period(8063, window));
assert_eq!(8063, first_of_the_period(10000, window));
assert_eq!(8063, first_of_the_period(10001, window));
}
}

View File

@ -54,6 +54,7 @@
extern crate time;
#[macro_use]
extern crate log;
extern crate parking_lot;
extern crate rayon;
extern crate db;
@ -65,11 +66,11 @@ extern crate script;
pub mod constants;
mod canon;
mod deployments;
mod duplex_store;
mod error;
mod sigops;
mod timestamp;
mod versionbits;
mod work;
// pre-verification

View File

@ -1,17 +1,11 @@
use std::collections::BTreeSet;
use chain::BlockHeader;
use db::{BlockHeaderProvider, BlockAncestors};
use network::Magic;
/// Returns median timestamp, of given header ancestors.
/// The header should be later expected to have higher timestamp
/// than this median timestamp
pub fn median_timestamp(header: &BlockHeader, store: &BlockHeaderProvider, network: Magic) -> u32 {
// TODO: timestamp validation on testnet is broken
if network == Magic::Testnet {
return 0;
}
pub fn median_timestamp(header: &BlockHeader, store: &BlockHeaderProvider) -> u32 {
let timestamps: BTreeSet<_> = BlockAncestors::new(header.previous_header_hash.clone().into(), store)
.take(11)
.map(|header| header.time)

View File

@ -1,187 +0,0 @@
use std::collections::HashMap;
use network::Magic;
use hash::H256;
use chain::BlockHeader;
use db::{BlockHeaderProvider, BlockProvider, BlockRef, BlockAncestors, BlockIterator};
use timestamp::median_timestamp;
const VERSIONBITS_TOP_MASK: u32 = 0xe0000000;
const VERSIONBITS_TOP_BITS: u32 = 0x20000000;
#[derive(Debug, Clone, Copy)]
pub enum ThresholdState {
Defined,
Started,
LockedIn,
Active,
Failed,
}
impl Default for ThresholdState {
fn default() -> Self {
ThresholdState::Defined
}
}
pub struct ThresholdConditionCache {
map: HashMap<&'static str, HashMap<u32, ThresholdState>>,
}
#[derive(Debug, Clone, Copy)]
pub struct Condition {
/// Deployment's name
pub name: &'static str,
/// Bit
pub bit: u8,
/// Start time
pub start_time: u32,
/// Timeout
pub timeout: u32,
/// Activation block number (if already activated)
pub activation: Option<u32>,
}
impl Condition {
pub fn csv(network: Magic) -> Option<Self> {
let (start_time, timeout, activation) = match network {
Magic::Mainnet => (1462060800, 1493596800, Some(770112)),
Magic::Testnet => (1456790400, 1493596800, Some(419328)),
_ => { return None }
};
let condition = Condition {
name: "csv",
bit: 0,
start_time: start_time,
timeout: timeout,
activation: activation,
};
Some(condition)
}
pub fn segwit(network: Magic) -> Option<Self> {
let (start_time, timeout, activation) = match network {
Magic::Mainnet => (1479168000, 1510704000, None),
Magic::Testnet => (1462060800, 1493596800, Some(834624)),
_ => { return None },
};
let condition = Condition {
name: "segwit",
bit: 1,
start_time: start_time,
timeout: timeout,
activation: activation,
};
Some(condition)
}
pub fn matches(&self, version: u32) -> bool {
(version & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS && (version & (1 << self.bit)) != 0
}
}
pub fn threshold_state(cache: &mut ThresholdConditionCache, condition: Condition, number: u32, headers: &BlockHeaderProvider, network: Magic) -> ThresholdState {
if let Some(activation) = condition.activation {
if activation <= number {
return ThresholdState::Active;
}
}
// A block's state is always the same as that of the first of its period, so it is computed based on a
// pindexPrev whose height equals a multiple of nPeriod - 1.
// get number of the first block in the period
let number = first_of_the_period(number);
{
let condition_cache = cache.map.get(condition.name).expect("condition cache expected to be known");
if let Some(state) = condition_cache.get(&number) {
return *state;
}
}
// TODO: get proper start number
let (block_number, state) = ThresholdConditionIterator::new(condition, headers, number, network)
.last()
.unwrap_or_else(|| (number, ThresholdState::default()));
state
}
fn first_of_the_period(block: u32) -> u32 {
block - ((block + 1) % 2016)
}
fn count_condition_matches(to_check: u32, blocks: &BlockHeaderProvider, condition: Condition) -> usize {
BlockAncestors::new(BlockRef::Number(to_check), blocks)
.take(2016)
.filter(|header| condition.matches(header.version))
.count()
}
struct ThresholdConditionIterator<'a> {
condition: Condition,
blocks: &'a BlockHeaderProvider,
to_check: u32,
network: Magic,
last_state: ThresholdState,
}
impl<'a> ThresholdConditionIterator<'a> {
pub fn new(condition: Condition, blocks: &'a BlockHeaderProvider, to_check: u32, network: Magic) -> Self {
ThresholdConditionIterator {
condition: condition,
blocks: blocks,
to_check: first_of_the_period(to_check),
network: network,
last_state: ThresholdState::Defined,
}
}
}
impl<'a> Iterator for ThresholdConditionIterator<'a> {
type Item = (u32, ThresholdState);
fn next(&mut self) -> Option<Self::Item> {
let header = match self.blocks.block_header(BlockRef::Number(self.to_check)) {
Some(header) => header,
None => return None,
};
let median = median_timestamp(&header, self.blocks, self.network);
match self.last_state {
ThresholdState::Defined => {
if median >= self.condition.timeout {
self.last_state = ThresholdState::Failed;
} else if median >= self.condition.start_time {
self.last_state = ThresholdState::Started;
}
},
ThresholdState::Started => {
if median >= self.condition.timeout {
self.last_state = ThresholdState::Failed;
} else {
let count = count_condition_matches(self.to_check, self.blocks, self.condition);
// TODO: call if threshold and move it to consensus params
if count >= 1916 {
self.last_state = ThresholdState::LockedIn;
}
}
},
ThresholdState::LockedIn => {
self.last_state = ThresholdState::Active;
},
ThresholdState::Failed | ThresholdState::Active => {
return None
}
}
let block_number = self.to_check;
self.to_check += 2016;
Some((block_number, self.last_state))
}
}