bip9, bip112, bip113
This commit is contained in:
parent
95255d0f58
commit
9a389f9746
|
@ -6,6 +6,7 @@ dependencies = [
|
||||||
"db 0.1.0",
|
"db 0.1.0",
|
||||||
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"network 0.1.0",
|
"network 0.1.0",
|
||||||
|
"parking_lot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"primitives 0.1.0",
|
"primitives 0.1.0",
|
||||||
"rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"rayon 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"script 0.1.0",
|
"script 0.1.0",
|
||||||
|
|
|
@ -7,7 +7,7 @@ BIPs that are implemented by pbtc
|
||||||
|
|
||||||
| BIPs | pbtc | core | unlimited |
|
| BIPs | pbtc | core | unlimited |
|
||||||
| ------ | ------ | ------ | ------ |
|
| ------ | ------ | ------ | ------ |
|
||||||
| [BIP 9][BIP9] | - | + | ? |
|
| [BIP 9][BIP9] | + | + | ? |
|
||||||
| [BIP 11][BIP11] | a | + | ? |
|
| [BIP 11][BIP11] | a | + | ? |
|
||||||
| [BIP 13][BIP13] | a | + | ? |
|
| [BIP 13][BIP13] | a | + | ? |
|
||||||
| [BIP 14][BIP14] | - | + | ? |
|
| [BIP 14][BIP14] | - | + | ? |
|
||||||
|
@ -25,14 +25,14 @@ BIPs that are implemented by pbtc
|
||||||
| [BIP 61][BIP61] | ? | + | ? |
|
| [BIP 61][BIP61] | ? | + | ? |
|
||||||
| [BIP 65][BIP65] | + | + | ? |
|
| [BIP 65][BIP65] | + | + | ? |
|
||||||
| [BIP 66][BIP66] | + | + | ? |
|
| [BIP 66][BIP66] | + | + | ? |
|
||||||
| [BIP 68][BIP68] | - | + | ? |
|
| [BIP 68][BIP68] | + | + | ? |
|
||||||
| [BIP 70][BIP70] | a | + | ? |
|
| [BIP 70][BIP70] | a | + | ? |
|
||||||
| [BIP 71][BIP71] | a | + | ? |
|
| [BIP 71][BIP71] | a | + | ? |
|
||||||
| [BIP 72][BIP72] | a | + | ? |
|
| [BIP 72][BIP72] | a | + | ? |
|
||||||
| [BIP 90][BIP90] | + | + | ? |
|
| [BIP 90][BIP90] | + | + | ? |
|
||||||
| [BIP 111][BIP111] | ? | + | ? |
|
| [BIP 111][BIP111] | ? | + | ? |
|
||||||
| [BIP 112][BIP112] | - | + | ? |
|
| [BIP 112][BIP112] | + | + | ? |
|
||||||
| [BIP 113][BIP113] | - | + | ? |
|
| [BIP 113][BIP113] | + | + | ? |
|
||||||
| [BIP 125][BIP125] | a | + | ? |
|
| [BIP 125][BIP125] | a | + | ? |
|
||||||
| [BIP 130][BIP130] | ? | + | ? |
|
| [BIP 130][BIP130] | ? | + | ? |
|
||||||
| [BIP 133][BIP133] | ? | + | ? |
|
| [BIP 133][BIP133] | ? | + | ? |
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use hash::H256;
|
use hash::H256;
|
||||||
use super::Magic;
|
use {Magic, Deployment};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Parameters that influence chain consensus.
|
/// Parameters that influence chain consensus.
|
||||||
|
@ -16,6 +16,14 @@ pub struct ConsensusParams {
|
||||||
/// Block height at which BIP65 becomes active.
|
/// Block height at which BIP65 becomes active.
|
||||||
/// See https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
|
/// See https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki
|
||||||
pub bip66_height: u32,
|
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 {
|
impl ConsensusParams {
|
||||||
|
@ -26,18 +34,48 @@ impl ConsensusParams {
|
||||||
bip34_height: 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
|
bip34_height: 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
|
||||||
bip65_height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
|
bip65_height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
|
||||||
bip66_height: 363725, // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931
|
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 {
|
Magic::Testnet => ConsensusParams {
|
||||||
bip16_time: 1333238400, // Apr 1 2012
|
bip16_time: 1333238400, // Apr 1 2012
|
||||||
bip34_height: 21111, // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
|
bip34_height: 21111, // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
|
||||||
bip65_height: 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
|
bip65_height: 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
|
||||||
bip66_height: 330776, // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182
|
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 {
|
Magic::Regtest | Magic::Unitest => ConsensusParams {
|
||||||
bip16_time: 1333238400, // Apr 1 2012
|
bip16_time: 1333238400, // Apr 1 2012
|
||||||
bip34_height: 100000000, // not activated on regtest
|
bip34_height: 100000000, // not activated on regtest
|
||||||
bip65_height: 1351,
|
bip65_height: 1351,
|
||||||
bip66_height: 1251, // used only in rpc tests
|
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::Testnet).bip66_height, 330776);
|
||||||
assert_eq!(ConsensusParams::with_magic(Magic::Regtest).bip66_height, 1251);
|
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;
|
extern crate serialization as ser;
|
||||||
|
|
||||||
mod consensus;
|
mod consensus;
|
||||||
|
mod deployments;
|
||||||
mod magic;
|
mod magic;
|
||||||
|
|
||||||
pub use primitives::{hash, compact};
|
pub use primitives::{hash, compact};
|
||||||
|
|
||||||
pub use consensus::ConsensusParams;
|
pub use consensus::ConsensusParams;
|
||||||
|
pub use deployments::Deployment;
|
||||||
pub use magic::Magic;
|
pub use magic::Magic;
|
||||||
|
|
||||||
|
|
|
@ -75,11 +75,9 @@ impl BlockChainClientCoreApi for BlockChainClientCore {
|
||||||
None => -1,
|
None => -1,
|
||||||
};
|
};
|
||||||
let block_size = block.size();
|
let block_size = block.size();
|
||||||
// TODO: use real network
|
|
||||||
let median_time = verification::median_timestamp(
|
let median_time = verification::median_timestamp(
|
||||||
&block.header.raw,
|
&block.header.raw,
|
||||||
self.storage.as_block_header_provider(),
|
self.storage.as_block_header_provider()
|
||||||
Magic::Mainnet,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
VerboseBlock {
|
VerboseBlock {
|
||||||
|
|
|
@ -53,12 +53,12 @@ pub struct VerificationFlags {
|
||||||
/// Verify CHECKLOCKTIMEVERIFY
|
/// Verify CHECKLOCKTIMEVERIFY
|
||||||
///
|
///
|
||||||
/// See BIP65 for details.
|
/// See BIP65 for details.
|
||||||
pub verify_clocktimeverify: bool,
|
pub verify_locktime: bool,
|
||||||
|
|
||||||
/// support CHECKSEQUENCEVERIFY opcode
|
/// support CHECKSEQUENCEVERIFY opcode
|
||||||
///
|
///
|
||||||
/// See BIP112 for details
|
/// See BIP112 for details
|
||||||
pub verify_checksequenceverify: bool,
|
pub verify_checksequence: bool,
|
||||||
|
|
||||||
/// Support segregated witness
|
/// Support segregated witness
|
||||||
pub verify_witness: bool,
|
pub verify_witness: bool,
|
||||||
|
@ -73,8 +73,13 @@ impl VerificationFlags {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verify_clocktimeverify(mut self, value: bool) -> Self {
|
pub fn verify_locktime(mut self, value: bool) -> Self {
|
||||||
self.verify_clocktimeverify = value;
|
self.verify_locktime = value;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_checksequence(mut self, value: bool) -> Self {
|
||||||
|
self.verify_checksequence = value;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -456,7 +456,7 @@ pub fn eval_script(
|
||||||
},
|
},
|
||||||
Opcode::OP_NOP => break,
|
Opcode::OP_NOP => break,
|
||||||
Opcode::OP_CHECKLOCKTIMEVERIFY => {
|
Opcode::OP_CHECKLOCKTIMEVERIFY => {
|
||||||
if flags.verify_clocktimeverify {
|
if flags.verify_locktime {
|
||||||
// Note that elsewhere numeric opcodes are limited to
|
// Note that elsewhere numeric opcodes are limited to
|
||||||
// operands in the range -2**31+1 to 2**31-1, however it is
|
// operands in the range -2**31+1 to 2**31-1, however it is
|
||||||
// legal for opcodes to produce results exceeding that
|
// legal for opcodes to produce results exceeding that
|
||||||
|
@ -488,7 +488,7 @@ pub fn eval_script(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Opcode::OP_CHECKSEQUENCEVERIFY => {
|
Opcode::OP_CHECKSEQUENCEVERIFY => {
|
||||||
if flags.verify_checksequenceverify {
|
if flags.verify_checksequence {
|
||||||
let sequence = try!(Num::from_slice(try!(stack.last()), flags.verify_minimaldata, 5));
|
let sequence = try!(Num::from_slice(try!(stack.last()), flags.verify_minimaldata, 5));
|
||||||
|
|
||||||
if sequence.is_negative() {
|
if sequence.is_negative() {
|
||||||
|
@ -1916,7 +1916,7 @@ mod tests {
|
||||||
|
|
||||||
let flags = VerificationFlags::default()
|
let flags = VerificationFlags::default()
|
||||||
.verify_p2sh(true)
|
.verify_p2sh(true)
|
||||||
.verify_clocktimeverify(true);
|
.verify_locktime(true);
|
||||||
assert_eq!(verify_script(&input, &output, &flags, &checker), Err(Error::NumberOverflow));
|
assert_eq!(verify_script(&input, &output, &flags, &checker), Err(Error::NumberOverflow));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ authors = ["Nikolay Volf <nikvolf@gmail.com>"]
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
rayon = "0.6"
|
rayon = "0.6"
|
||||||
|
parking_lot = "0.4"
|
||||||
primitives = { path = "../primitives" }
|
primitives = { path = "../primitives" }
|
||||||
chain = { path = "../chain" }
|
chain = { path = "../chain" }
|
||||||
serialization = { path = "../serialization" }
|
serialization = { path = "../serialization" }
|
||||||
|
|
|
@ -6,6 +6,7 @@ use canon::CanonBlock;
|
||||||
use accept_block::BlockAcceptor;
|
use accept_block::BlockAcceptor;
|
||||||
use accept_header::HeaderAcceptor;
|
use accept_header::HeaderAcceptor;
|
||||||
use accept_transaction::TransactionAcceptor;
|
use accept_transaction::TransactionAcceptor;
|
||||||
|
use deployments::Deployments;
|
||||||
use duplex_store::DuplexTransactionOutputProvider;
|
use duplex_store::DuplexTransactionOutputProvider;
|
||||||
|
|
||||||
pub struct ChainAcceptor<'a> {
|
pub struct ChainAcceptor<'a> {
|
||||||
|
@ -15,12 +16,13 @@ pub struct ChainAcceptor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> 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());
|
trace!(target: "verification", "Block verification {}", block.hash().to_reversed_str());
|
||||||
let output_store = DuplexTransactionOutputProvider::new(store.as_transaction_output_provider(), block.raw());
|
let output_store = DuplexTransactionOutputProvider::new(store.as_transaction_output_provider(), block.raw());
|
||||||
|
let headers = store.as_block_header_provider();
|
||||||
ChainAcceptor {
|
ChainAcceptor {
|
||||||
block: BlockAcceptor::new(store.as_transaction_output_provider(), network, block, height),
|
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()
|
transactions: block.transactions()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
@ -32,7 +34,9 @@ impl<'a> ChainAcceptor<'a> {
|
||||||
block.hash(),
|
block.hash(),
|
||||||
height,
|
height,
|
||||||
block.header.raw.time,
|
block.header.raw.time,
|
||||||
tx_index
|
tx_index,
|
||||||
|
deployments,
|
||||||
|
headers,
|
||||||
))
|
))
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ use db::BlockHeaderProvider;
|
||||||
use canon::CanonHeader;
|
use canon::CanonHeader;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use work::work_required;
|
use work::work_required;
|
||||||
|
use deployments::Deployments;
|
||||||
use timestamp::median_timestamp;
|
use timestamp::median_timestamp;
|
||||||
|
|
||||||
pub struct HeaderAcceptor<'a> {
|
pub struct HeaderAcceptor<'a> {
|
||||||
|
@ -12,13 +13,18 @@ pub struct HeaderAcceptor<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> 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();
|
let params = network.consensus_params();
|
||||||
HeaderAcceptor {
|
HeaderAcceptor {
|
||||||
// TODO: check last 1000 blocks instead of hardcoding the value
|
version: HeaderVersion::new(header, height, params.clone()),
|
||||||
version: HeaderVersion::new(header, height, params),
|
|
||||||
work: HeaderWork::new(header, store, height, network),
|
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> {
|
pub struct HeaderMedianTimestamp<'a> {
|
||||||
header: CanonHeader<'a>,
|
header: CanonHeader<'a>,
|
||||||
store: &'a BlockHeaderProvider,
|
store: &'a BlockHeaderProvider,
|
||||||
network: Magic,
|
active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> HeaderMedianTimestamp<'a> {
|
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 {
|
HeaderMedianTimestamp {
|
||||||
header: header,
|
header: header,
|
||||||
store: store,
|
store: store,
|
||||||
network: network,
|
active: active,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(&self) -> Result<(), Error> {
|
fn check(&self) -> Result<(), Error> {
|
||||||
let median = median_timestamp(&self.header.raw, self.store, self.network);
|
if self.active && self.header.raw.time <= median_timestamp(&self.header.raw, self.store) {
|
||||||
if self.header.raw.time <= median {
|
|
||||||
Err(Error::Timestamp)
|
Err(Error::Timestamp)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
use primitives::hash::H256;
|
use primitives::hash::H256;
|
||||||
use db::{TransactionMetaProvider, TransactionOutputProvider};
|
use db::{TransactionMetaProvider, TransactionOutputProvider, BlockHeaderProvider};
|
||||||
use network::{Magic, ConsensusParams};
|
use network::{Magic, ConsensusParams};
|
||||||
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner};
|
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner};
|
||||||
use duplex_store::DuplexTransactionOutputProvider;
|
use duplex_store::DuplexTransactionOutputProvider;
|
||||||
|
use deployments::Deployments;
|
||||||
use sigops::transaction_sigops;
|
use sigops::transaction_sigops;
|
||||||
use canon::CanonTransaction;
|
use canon::CanonTransaction;
|
||||||
use constants::{COINBASE_MATURITY, MAX_BLOCK_SIGOPS};
|
use constants::{COINBASE_MATURITY, MAX_BLOCK_SIGOPS};
|
||||||
|
@ -30,6 +31,8 @@ impl<'a> TransactionAcceptor<'a> {
|
||||||
height: u32,
|
height: u32,
|
||||||
time: u32,
|
time: u32,
|
||||||
transaction_index: usize,
|
transaction_index: usize,
|
||||||
|
deployments: &'a Deployments,
|
||||||
|
headers: &'a BlockHeaderProvider,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str());
|
trace!(target: "verification", "Tx verification {}", transaction.hash.to_reversed_str());
|
||||||
let params = network.consensus_params();
|
let params = network.consensus_params();
|
||||||
|
@ -39,7 +42,7 @@ impl<'a> TransactionAcceptor<'a> {
|
||||||
maturity: TransactionMaturity::new(transaction, meta_store, height),
|
maturity: TransactionMaturity::new(transaction, meta_store, height),
|
||||||
overspent: TransactionOverspent::new(transaction, output_store),
|
overspent: TransactionOverspent::new(transaction, output_store),
|
||||||
double_spent: TransactionDoubleSpend::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>,
|
transaction: CanonTransaction<'a>,
|
||||||
height: u32,
|
height: u32,
|
||||||
time: u32,
|
time: u32,
|
||||||
|
deployments: &'a Deployments,
|
||||||
|
headers: &'a BlockHeaderProvider,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
trace!(target: "verification", "Mempool-Tx verification {}", transaction.hash.to_reversed_str());
|
trace!(target: "verification", "Mempool-Tx verification {}", transaction.hash.to_reversed_str());
|
||||||
let params = network.consensus_params();
|
let params = network.consensus_params();
|
||||||
|
@ -83,7 +88,7 @@ impl<'a> MemoryPoolTransactionAcceptor<'a> {
|
||||||
overspent: TransactionOverspent::new(transaction, output_store),
|
overspent: TransactionOverspent::new(transaction, output_store),
|
||||||
sigops: TransactionSigops::new(transaction, output_store, params.clone(), MAX_BLOCK_SIGOPS, time),
|
sigops: TransactionSigops::new(transaction, output_store, params.clone(), MAX_BLOCK_SIGOPS, time),
|
||||||
double_spent: TransactionDoubleSpend::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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +274,8 @@ pub struct TransactionEval<'a> {
|
||||||
transaction: CanonTransaction<'a>,
|
transaction: CanonTransaction<'a>,
|
||||||
store: DuplexTransactionOutputProvider<'a>,
|
store: DuplexTransactionOutputProvider<'a>,
|
||||||
verify_p2sh: bool,
|
verify_p2sh: bool,
|
||||||
verify_clocktime: bool,
|
verify_locktime: bool,
|
||||||
|
verify_checksequence: bool,
|
||||||
verify_dersig: bool,
|
verify_dersig: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,16 +286,21 @@ impl<'a> TransactionEval<'a> {
|
||||||
params: ConsensusParams,
|
params: ConsensusParams,
|
||||||
height: u32,
|
height: u32,
|
||||||
time: u32,
|
time: u32,
|
||||||
|
deployments: &'a Deployments,
|
||||||
|
headers: &'a BlockHeaderProvider,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let verify_p2sh = time >= params.bip16_time;
|
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_dersig = height >= params.bip66_height;
|
||||||
|
|
||||||
|
let verify_checksequence = deployments.csv(height, headers, params);
|
||||||
|
|
||||||
TransactionEval {
|
TransactionEval {
|
||||||
transaction: transaction,
|
transaction: transaction,
|
||||||
store: store,
|
store: store,
|
||||||
verify_p2sh: verify_p2sh,
|
verify_p2sh: verify_p2sh,
|
||||||
verify_clocktime: verify_clocktime,
|
verify_locktime: verify_locktime,
|
||||||
|
verify_checksequence: verify_checksequence,
|
||||||
verify_dersig: verify_dersig,
|
verify_dersig: verify_dersig,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,7 +328,8 @@ impl<'a> TransactionEval<'a> {
|
||||||
|
|
||||||
let flags = VerificationFlags::default()
|
let flags = VerificationFlags::default()
|
||||||
.verify_p2sh(self.verify_p2sh)
|
.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);
|
.verify_dersig(self.verify_dersig);
|
||||||
|
|
||||||
try!(verify_script(&input, &output, &flags, &checker).map_err(|_| TransactionError::Signature(index)));
|
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 verify_transaction::MemoryPoolTransactionVerifier;
|
||||||
use accept_chain::ChainAcceptor;
|
use accept_chain::ChainAcceptor;
|
||||||
use accept_transaction::MemoryPoolTransactionAcceptor;
|
use accept_transaction::MemoryPoolTransactionAcceptor;
|
||||||
|
use deployments::Deployments;
|
||||||
use Verify;
|
use Verify;
|
||||||
|
|
||||||
pub struct BackwardsCompatibleChainVerifier {
|
pub struct BackwardsCompatibleChainVerifier {
|
||||||
store: SharedStore,
|
store: SharedStore,
|
||||||
network: Magic,
|
network: Magic,
|
||||||
|
deployments: Deployments,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackwardsCompatibleChainVerifier {
|
impl BackwardsCompatibleChainVerifier {
|
||||||
|
@ -24,6 +26,7 @@ impl BackwardsCompatibleChainVerifier {
|
||||||
BackwardsCompatibleChainVerifier {
|
BackwardsCompatibleChainVerifier {
|
||||||
store: store,
|
store: store,
|
||||||
network: network,
|
network: network,
|
||||||
|
deployments: Deployments::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,21 +46,21 @@ impl BackwardsCompatibleChainVerifier {
|
||||||
},
|
},
|
||||||
BlockOrigin::CanonChain { block_number } => {
|
BlockOrigin::CanonChain { block_number } => {
|
||||||
let canon_block = CanonBlock::new(block);
|
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()?;
|
chain_acceptor.check()?;
|
||||||
},
|
},
|
||||||
BlockOrigin::SideChain(origin) => {
|
BlockOrigin::SideChain(origin) => {
|
||||||
let block_number = origin.block_number;
|
let block_number = origin.block_number;
|
||||||
let fork = self.store.fork(origin)?;
|
let fork = self.store.fork(origin)?;
|
||||||
let canon_block = CanonBlock::new(block);
|
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()?;
|
chain_acceptor.check()?;
|
||||||
},
|
},
|
||||||
BlockOrigin::SideChainBecomingCanonChain(origin) => {
|
BlockOrigin::SideChainBecomingCanonChain(origin) => {
|
||||||
let block_number = origin.block_number;
|
let block_number = origin.block_number;
|
||||||
let fork = self.store.fork(origin)?;
|
let fork = self.store.fork(origin)?;
|
||||||
let canon_block = CanonBlock::new(block);
|
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()?;
|
chain_acceptor.check()?;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -102,7 +105,9 @@ impl BackwardsCompatibleChainVerifier {
|
||||||
self.network,
|
self.network,
|
||||||
canon_tx,
|
canon_tx,
|
||||||
height,
|
height,
|
||||||
time
|
time,
|
||||||
|
&self.deployments,
|
||||||
|
self.store.as_block_header_provider()
|
||||||
);
|
);
|
||||||
tx_acceptor.check()
|
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;
|
extern crate time;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
extern crate parking_lot;
|
||||||
extern crate rayon;
|
extern crate rayon;
|
||||||
|
|
||||||
extern crate db;
|
extern crate db;
|
||||||
|
@ -65,11 +66,11 @@ extern crate script;
|
||||||
|
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
mod canon;
|
mod canon;
|
||||||
|
mod deployments;
|
||||||
mod duplex_store;
|
mod duplex_store;
|
||||||
mod error;
|
mod error;
|
||||||
mod sigops;
|
mod sigops;
|
||||||
mod timestamp;
|
mod timestamp;
|
||||||
mod versionbits;
|
|
||||||
mod work;
|
mod work;
|
||||||
|
|
||||||
// pre-verification
|
// pre-verification
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use chain::BlockHeader;
|
use chain::BlockHeader;
|
||||||
use db::{BlockHeaderProvider, BlockAncestors};
|
use db::{BlockHeaderProvider, BlockAncestors};
|
||||||
use network::Magic;
|
|
||||||
|
|
||||||
/// Returns median timestamp, of given header ancestors.
|
/// Returns median timestamp, of given header ancestors.
|
||||||
/// The header should be later expected to have higher timestamp
|
/// The header should be later expected to have higher timestamp
|
||||||
/// than this median timestamp
|
/// than this median timestamp
|
||||||
pub fn median_timestamp(header: &BlockHeader, store: &BlockHeaderProvider, network: Magic) -> u32 {
|
pub fn median_timestamp(header: &BlockHeader, store: &BlockHeaderProvider) -> u32 {
|
||||||
// TODO: timestamp validation on testnet is broken
|
|
||||||
if network == Magic::Testnet {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let timestamps: BTreeSet<_> = BlockAncestors::new(header.previous_header_hash.clone().into(), store)
|
let timestamps: BTreeSet<_> = BlockAncestors::new(header.previous_header_hash.clone().into(), store)
|
||||||
.take(11)
|
.take(11)
|
||||||
.map(|header| header.time)
|
.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