bip9, bip112, bip113
This commit is contained in:
parent
95255d0f58
commit
9a389f9746
|
@ -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",
|
||||
|
|
|
@ -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] | ? | + | ? |
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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" }
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue