Browse Source
0.138c74ced
RPC test for BIP9 warning logic (Suhas Daftuar)7870deb
Test versionbits deployments (Suhas Daftuar)532cbb2
Add testing of ComputeBlockVersion (Suhas Daftuar)d23f6c6
Softfork status report in RPC (Pieter Wuille)732e774
Versionbits tests (Pieter Wuille)6851107
BIP9 Implementation (Pieter Wuille)
Wladimir J. van der Laan
9 years ago
17 changed files with 864 additions and 20 deletions
@ -0,0 +1,160 @@ |
|||||||
|
#!/usr/bin/env python2 |
||||||
|
# Copyright (c) 2016 The Bitcoin Core developers |
||||||
|
# Distributed under the MIT/X11 software license, see the accompanying |
||||||
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
||||||
|
# |
||||||
|
|
||||||
|
from test_framework.mininode import * |
||||||
|
from test_framework.test_framework import BitcoinTestFramework |
||||||
|
from test_framework.util import * |
||||||
|
import time |
||||||
|
from test_framework.blocktools import create_block, create_coinbase |
||||||
|
|
||||||
|
''' |
||||||
|
Test version bits' warning system. |
||||||
|
|
||||||
|
Generate chains with block versions that appear to be signalling unknown |
||||||
|
soft-forks, and test that warning alerts are generated. |
||||||
|
''' |
||||||
|
|
||||||
|
VB_PERIOD = 144 # versionbits period length for regtest |
||||||
|
VB_THRESHOLD = 108 # versionbits activation threshold for regtest |
||||||
|
VB_TOP_BITS = 0x20000000 |
||||||
|
VB_UNKNOWN_BIT = 27 # Choose a bit unassigned to any deployment |
||||||
|
|
||||||
|
# TestNode: bare-bones "peer". Used mostly as a conduit for a test to sending |
||||||
|
# p2p messages to a node, generating the messages in the main testing logic. |
||||||
|
class TestNode(NodeConnCB): |
||||||
|
def __init__(self): |
||||||
|
NodeConnCB.__init__(self) |
||||||
|
self.connection = None |
||||||
|
self.ping_counter = 1 |
||||||
|
self.last_pong = msg_pong() |
||||||
|
|
||||||
|
def add_connection(self, conn): |
||||||
|
self.connection = conn |
||||||
|
|
||||||
|
def on_inv(self, conn, message): |
||||||
|
pass |
||||||
|
|
||||||
|
# Wrapper for the NodeConn's send_message function |
||||||
|
def send_message(self, message): |
||||||
|
self.connection.send_message(message) |
||||||
|
|
||||||
|
def on_pong(self, conn, message): |
||||||
|
self.last_pong = message |
||||||
|
|
||||||
|
# Sync up with the node after delivery of a block |
||||||
|
def sync_with_ping(self, timeout=30): |
||||||
|
self.connection.send_message(msg_ping(nonce=self.ping_counter)) |
||||||
|
received_pong = False |
||||||
|
sleep_time = 0.05 |
||||||
|
while not received_pong and timeout > 0: |
||||||
|
time.sleep(sleep_time) |
||||||
|
timeout -= sleep_time |
||||||
|
with mininode_lock: |
||||||
|
if self.last_pong.nonce == self.ping_counter: |
||||||
|
received_pong = True |
||||||
|
self.ping_counter += 1 |
||||||
|
return received_pong |
||||||
|
|
||||||
|
|
||||||
|
class VersionBitsWarningTest(BitcoinTestFramework): |
||||||
|
def setup_chain(self): |
||||||
|
initialize_chain_clean(self.options.tmpdir, 1) |
||||||
|
|
||||||
|
def setup_network(self): |
||||||
|
self.nodes = [] |
||||||
|
self.alert_filename = os.path.join(self.options.tmpdir, "alert.txt") |
||||||
|
# Open and close to create zero-length file |
||||||
|
with open(self.alert_filename, 'w') as f: |
||||||
|
pass |
||||||
|
self.node_options = ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""] |
||||||
|
self.nodes.append(start_node(0, self.options.tmpdir, self.node_options)) |
||||||
|
|
||||||
|
import re |
||||||
|
self.vb_pattern = re.compile("^Warning.*versionbit") |
||||||
|
|
||||||
|
# Send numblocks blocks via peer with nVersionToUse set. |
||||||
|
def send_blocks_with_version(self, peer, numblocks, nVersionToUse): |
||||||
|
tip = self.nodes[0].getbestblockhash() |
||||||
|
height = self.nodes[0].getblockcount() |
||||||
|
block_time = self.nodes[0].getblockheader(tip)["time"]+1 |
||||||
|
tip = int(tip, 16) |
||||||
|
|
||||||
|
for i in xrange(numblocks): |
||||||
|
block = create_block(tip, create_coinbase(height+1), block_time) |
||||||
|
block.nVersion = nVersionToUse |
||||||
|
block.solve() |
||||||
|
peer.send_message(msg_block(block)) |
||||||
|
block_time += 1 |
||||||
|
height += 1 |
||||||
|
tip = block.sha256 |
||||||
|
peer.sync_with_ping() |
||||||
|
|
||||||
|
def test_versionbits_in_alert_file(self): |
||||||
|
with open(self.alert_filename, 'r') as f: |
||||||
|
alert_text = f.read() |
||||||
|
assert(self.vb_pattern.match(alert_text)) |
||||||
|
|
||||||
|
def run_test(self): |
||||||
|
# Setup the p2p connection and start up the network thread. |
||||||
|
test_node = TestNode() |
||||||
|
|
||||||
|
connections = [] |
||||||
|
connections.append(NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node)) |
||||||
|
test_node.add_connection(connections[0]) |
||||||
|
|
||||||
|
NetworkThread().start() # Start up network handling in another thread |
||||||
|
|
||||||
|
# Test logic begins here |
||||||
|
test_node.wait_for_verack() |
||||||
|
|
||||||
|
# 1. Have the node mine one period worth of blocks |
||||||
|
self.nodes[0].generate(VB_PERIOD) |
||||||
|
|
||||||
|
# 2. Now build one period of blocks on the tip, with < VB_THRESHOLD |
||||||
|
# blocks signaling some unknown bit. |
||||||
|
nVersion = VB_TOP_BITS | (1<<VB_UNKNOWN_BIT) |
||||||
|
self.send_blocks_with_version(test_node, VB_THRESHOLD-1, nVersion) |
||||||
|
|
||||||
|
# Fill rest of period with regular version blocks |
||||||
|
self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD + 1) |
||||||
|
# Check that we're not getting any versionbit-related errors in |
||||||
|
# getinfo() |
||||||
|
assert(not self.vb_pattern.match(self.nodes[0].getinfo()["errors"])) |
||||||
|
|
||||||
|
# 3. Now build one period of blocks with >= VB_THRESHOLD blocks signaling |
||||||
|
# some unknown bit |
||||||
|
self.send_blocks_with_version(test_node, VB_THRESHOLD, nVersion) |
||||||
|
self.nodes[0].generate(VB_PERIOD - VB_THRESHOLD) |
||||||
|
# Might not get a versionbits-related alert yet, as we should |
||||||
|
# have gotten a different alert due to more than 51/100 blocks |
||||||
|
# being of unexpected version. |
||||||
|
# Check that getinfo() shows some kind of error. |
||||||
|
assert(len(self.nodes[0].getinfo()["errors"]) != 0) |
||||||
|
|
||||||
|
# Mine a period worth of expected blocks so the generic block-version warning |
||||||
|
# is cleared, and restart the node. This should move the versionbit state |
||||||
|
# to ACTIVE. |
||||||
|
self.nodes[0].generate(VB_PERIOD) |
||||||
|
stop_node(self.nodes[0], 0) |
||||||
|
wait_bitcoinds() |
||||||
|
# Empty out the alert file |
||||||
|
with open(self.alert_filename, 'w') as f: |
||||||
|
pass |
||||||
|
self.nodes[0] = start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""]) |
||||||
|
|
||||||
|
# Connecting one block should be enough to generate an error. |
||||||
|
self.nodes[0].generate(1) |
||||||
|
assert(len(self.nodes[0].getinfo()["errors"]) != 0) |
||||||
|
stop_node(self.nodes[0], 0) |
||||||
|
wait_bitcoinds() |
||||||
|
self.test_versionbits_in_alert_file() |
||||||
|
|
||||||
|
# Test framework expects the node to still be running... |
||||||
|
self.nodes[0] = start_node(0, self.options.tmpdir, ["-debug", "-logtimemicros=1", "-alertnotify=echo %s >> \"" + self.alert_filename + "\""]) |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
VersionBitsWarningTest().main() |
@ -0,0 +1,316 @@ |
|||||||
|
// Copyright (c) 2014-2015 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "chain.h" |
||||||
|
#include "random.h" |
||||||
|
#include "versionbits.h" |
||||||
|
#include "test/test_bitcoin.h" |
||||||
|
#include "chainparams.h" |
||||||
|
#include "main.h" |
||||||
|
#include "consensus/params.h" |
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp> |
||||||
|
|
||||||
|
/* Define a virtual block time, one block per 10 minutes after Nov 14 2014, 0:55:36am */ |
||||||
|
int32_t TestTime(int nHeight) { return 1415926536 + 600 * nHeight; } |
||||||
|
|
||||||
|
static const Consensus::Params paramsDummy = Consensus::Params(); |
||||||
|
|
||||||
|
class TestConditionChecker : public AbstractThresholdConditionChecker |
||||||
|
{ |
||||||
|
private: |
||||||
|
mutable ThresholdConditionCache cache; |
||||||
|
|
||||||
|
public: |
||||||
|
int64_t BeginTime(const Consensus::Params& params) const { return TestTime(10000); } |
||||||
|
int64_t EndTime(const Consensus::Params& params) const { return TestTime(20000); } |
||||||
|
int Period(const Consensus::Params& params) const { return 1000; } |
||||||
|
int Threshold(const Consensus::Params& params) const { return 900; } |
||||||
|
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const { return (pindex->nVersion & 0x100); } |
||||||
|
|
||||||
|
ThresholdState GetStateFor(const CBlockIndex* pindexPrev) const { return AbstractThresholdConditionChecker::GetStateFor(pindexPrev, paramsDummy, cache); } |
||||||
|
}; |
||||||
|
|
||||||
|
#define CHECKERS 6 |
||||||
|
|
||||||
|
class VersionBitsTester |
||||||
|
{ |
||||||
|
// A fake blockchain
|
||||||
|
std::vector<CBlockIndex*> vpblock; |
||||||
|
|
||||||
|
// 6 independent checkers for the same bit.
|
||||||
|
// The first one performs all checks, the second only 50%, the third only 25%, etc...
|
||||||
|
// This is to test whether lack of cached information leads to the same results.
|
||||||
|
TestConditionChecker checker[CHECKERS]; |
||||||
|
|
||||||
|
// Test counter (to identify failures)
|
||||||
|
int num; |
||||||
|
|
||||||
|
public: |
||||||
|
VersionBitsTester() : num(0) {} |
||||||
|
|
||||||
|
VersionBitsTester& Reset() { |
||||||
|
for (unsigned int i = 0; i < vpblock.size(); i++) { |
||||||
|
delete vpblock[i]; |
||||||
|
} |
||||||
|
for (unsigned int i = 0; i < CHECKERS; i++) { |
||||||
|
checker[i] = TestConditionChecker(); |
||||||
|
} |
||||||
|
vpblock.clear(); |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
~VersionBitsTester() { |
||||||
|
Reset(); |
||||||
|
} |
||||||
|
|
||||||
|
VersionBitsTester& Mine(unsigned int height, int32_t nTime, int32_t nVersion) { |
||||||
|
while (vpblock.size() < height) { |
||||||
|
CBlockIndex* pindex = new CBlockIndex(); |
||||||
|
pindex->nHeight = vpblock.size(); |
||||||
|
pindex->pprev = vpblock.size() > 0 ? vpblock.back() : NULL; |
||||||
|
pindex->nTime = nTime; |
||||||
|
pindex->nVersion = nVersion; |
||||||
|
pindex->BuildSkip(); |
||||||
|
vpblock.push_back(pindex); |
||||||
|
} |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
VersionBitsTester& TestDefined() { |
||||||
|
for (int i = 0; i < CHECKERS; i++) { |
||||||
|
if ((insecure_rand() & ((1 << i) - 1)) == 0) { |
||||||
|
BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? NULL : vpblock.back()) == THRESHOLD_DEFINED, strprintf("Test %i for DEFINED", num)); |
||||||
|
} |
||||||
|
} |
||||||
|
num++; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
VersionBitsTester& TestStarted() { |
||||||
|
for (int i = 0; i < CHECKERS; i++) { |
||||||
|
if ((insecure_rand() & ((1 << i) - 1)) == 0) { |
||||||
|
BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? NULL : vpblock.back()) == THRESHOLD_STARTED, strprintf("Test %i for STARTED", num)); |
||||||
|
} |
||||||
|
} |
||||||
|
num++; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
VersionBitsTester& TestLockedIn() { |
||||||
|
for (int i = 0; i < CHECKERS; i++) { |
||||||
|
if ((insecure_rand() & ((1 << i) - 1)) == 0) { |
||||||
|
BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? NULL : vpblock.back()) == THRESHOLD_LOCKED_IN, strprintf("Test %i for LOCKED_IN", num)); |
||||||
|
} |
||||||
|
} |
||||||
|
num++; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
VersionBitsTester& TestActive() { |
||||||
|
for (int i = 0; i < CHECKERS; i++) { |
||||||
|
if ((insecure_rand() & ((1 << i) - 1)) == 0) { |
||||||
|
BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? NULL : vpblock.back()) == THRESHOLD_ACTIVE, strprintf("Test %i for ACTIVE", num)); |
||||||
|
} |
||||||
|
} |
||||||
|
num++; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
VersionBitsTester& TestFailed() { |
||||||
|
for (int i = 0; i < CHECKERS; i++) { |
||||||
|
if ((insecure_rand() & ((1 << i) - 1)) == 0) { |
||||||
|
BOOST_CHECK_MESSAGE(checker[i].GetStateFor(vpblock.empty() ? NULL : vpblock.back()) == THRESHOLD_FAILED, strprintf("Test %i for FAILED", num)); |
||||||
|
} |
||||||
|
} |
||||||
|
num++; |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
CBlockIndex * Tip() { return vpblock.size() ? vpblock.back() : NULL; } |
||||||
|
}; |
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_SUITE(versionbits_tests, TestingSetup) |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(versionbits_test) |
||||||
|
{ |
||||||
|
for (int i = 0; i < 64; i++) { |
||||||
|
// DEFINED -> FAILED
|
||||||
|
VersionBitsTester().TestDefined() |
||||||
|
.Mine(1, TestTime(1), 0x100).TestDefined() |
||||||
|
.Mine(11, TestTime(11), 0x100).TestDefined() |
||||||
|
.Mine(989, TestTime(989), 0x100).TestDefined() |
||||||
|
.Mine(999, TestTime(20000), 0x100).TestDefined() |
||||||
|
.Mine(1000, TestTime(20000), 0x100).TestFailed() |
||||||
|
.Mine(1999, TestTime(30001), 0x100).TestFailed() |
||||||
|
.Mine(2000, TestTime(30002), 0x100).TestFailed() |
||||||
|
.Mine(2001, TestTime(30003), 0x100).TestFailed() |
||||||
|
.Mine(2999, TestTime(30004), 0x100).TestFailed() |
||||||
|
.Mine(3000, TestTime(30005), 0x100).TestFailed() |
||||||
|
|
||||||
|
// DEFINED -> STARTED -> FAILED
|
||||||
|
.Reset().TestDefined() |
||||||
|
.Mine(1, TestTime(1), 0).TestDefined() |
||||||
|
.Mine(1000, TestTime(10000) - 1, 0x100).TestDefined() // One second more and it would be defined
|
||||||
|
.Mine(2000, TestTime(10000), 0x100).TestStarted() // So that's what happens the next period
|
||||||
|
.Mine(2051, TestTime(10010), 0).TestStarted() // 51 old blocks
|
||||||
|
.Mine(2950, TestTime(10020), 0x100).TestStarted() // 899 new blocks
|
||||||
|
.Mine(3000, TestTime(20000), 0).TestFailed() // 50 old blocks (so 899 out of the past 1000)
|
||||||
|
.Mine(4000, TestTime(20010), 0x100).TestFailed() |
||||||
|
|
||||||
|
// DEFINED -> STARTED -> FAILED while threshold reached
|
||||||
|
.Reset().TestDefined() |
||||||
|
.Mine(1, TestTime(1), 0).TestDefined() |
||||||
|
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined() // One second more and it would be defined
|
||||||
|
.Mine(2000, TestTime(10000), 0x101).TestStarted() // So that's what happens the next period
|
||||||
|
.Mine(2999, TestTime(30000), 0x100).TestStarted() // 999 new blocks
|
||||||
|
.Mine(3000, TestTime(30000), 0x100).TestFailed() // 1 new block (so 1000 out of the past 1000 are new)
|
||||||
|
.Mine(3999, TestTime(30001), 0).TestFailed() |
||||||
|
.Mine(4000, TestTime(30002), 0).TestFailed() |
||||||
|
.Mine(14333, TestTime(30003), 0).TestFailed() |
||||||
|
.Mine(24000, TestTime(40000), 0).TestFailed() |
||||||
|
|
||||||
|
// DEFINED -> STARTED -> LOCKEDIN at the last minute -> ACTIVE
|
||||||
|
.Reset().TestDefined() |
||||||
|
.Mine(1, TestTime(1), 0).TestDefined() |
||||||
|
.Mine(1000, TestTime(10000) - 1, 0x101).TestDefined() // One second more and it would be defined
|
||||||
|
.Mine(2000, TestTime(10000), 0x101).TestStarted() // So that's what happens the next period
|
||||||
|
.Mine(2050, TestTime(10010), 0x200).TestStarted() // 50 old blocks
|
||||||
|
.Mine(2950, TestTime(10020), 0x100).TestStarted() // 900 new blocks
|
||||||
|
.Mine(2999, TestTime(19999), 0x200).TestStarted() // 49 old blocks
|
||||||
|
.Mine(3000, TestTime(29999), 0x200).TestLockedIn() // 1 old block (so 900 out of the past 1000)
|
||||||
|
.Mine(3999, TestTime(30001), 0).TestLockedIn() |
||||||
|
.Mine(4000, TestTime(30002), 0).TestActive() |
||||||
|
.Mine(14333, TestTime(30003), 0).TestActive() |
||||||
|
.Mine(24000, TestTime(40000), 0).TestActive(); |
||||||
|
} |
||||||
|
|
||||||
|
// Sanity checks of version bit deployments
|
||||||
|
const Consensus::Params &mainnetParams = Params(CBaseChainParams::MAIN).GetConsensus(); |
||||||
|
for (int i=0; i<(int) Consensus::MAX_VERSION_BITS_DEPLOYMENTS; i++) { |
||||||
|
uint32_t bitmask = VersionBitsMask(mainnetParams, (Consensus::DeploymentPos)i); |
||||||
|
// Make sure that no deployment tries to set an invalid bit.
|
||||||
|
BOOST_CHECK_EQUAL(bitmask & ~(uint32_t)VERSIONBITS_TOP_MASK, bitmask); |
||||||
|
|
||||||
|
// Verify that the deployment windows of different deployment using the
|
||||||
|
// same bit are disjoint.
|
||||||
|
// This test may need modification at such time as a new deployment
|
||||||
|
// is proposed that reuses the bit of an activated soft fork, before the
|
||||||
|
// end time of that soft fork. (Alternatively, the end time of that
|
||||||
|
// activated soft fork could be later changed to be earlier to avoid
|
||||||
|
// overlap.)
|
||||||
|
for (int j=i+1; j<(int) Consensus::MAX_VERSION_BITS_DEPLOYMENTS; j++) { |
||||||
|
if (VersionBitsMask(mainnetParams, (Consensus::DeploymentPos)j) == bitmask) { |
||||||
|
BOOST_CHECK(mainnetParams.vDeployments[j].nStartTime > mainnetParams.vDeployments[i].nTimeout || |
||||||
|
mainnetParams.vDeployments[i].nStartTime > mainnetParams.vDeployments[j].nTimeout); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(versionbits_computeblockversion) |
||||||
|
{ |
||||||
|
// Check that ComputeBlockVersion will set the appropriate bit correctly
|
||||||
|
// on mainnet.
|
||||||
|
const Consensus::Params &mainnetParams = Params(CBaseChainParams::MAIN).GetConsensus(); |
||||||
|
|
||||||
|
// Use the TESTDUMMY deployment for testing purposes.
|
||||||
|
int64_t bit = mainnetParams.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit; |
||||||
|
int64_t nStartTime = mainnetParams.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime; |
||||||
|
int64_t nTimeout = mainnetParams.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout; |
||||||
|
|
||||||
|
assert(nStartTime < nTimeout); |
||||||
|
|
||||||
|
// In the first chain, test that the bit is set by CBV until it has failed.
|
||||||
|
// In the second chain, test the bit is set by CBV while STARTED and
|
||||||
|
// LOCKED-IN, and then no longer set while ACTIVE.
|
||||||
|
VersionBitsTester firstChain, secondChain; |
||||||
|
|
||||||
|
// Start generating blocks before nStartTime
|
||||||
|
int64_t nTime = nStartTime - 1; |
||||||
|
|
||||||
|
// Before MedianTimePast of the chain has crossed nStartTime, the bit
|
||||||
|
// should not be set.
|
||||||
|
CBlockIndex *lastBlock = NULL; |
||||||
|
lastBlock = firstChain.Mine(2016, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0); |
||||||
|
|
||||||
|
// Mine 2011 more blocks at the old time, and check that CBV isn't setting the bit yet.
|
||||||
|
for (int i=1; i<2012; i++) { |
||||||
|
lastBlock = firstChain.Mine(2016+i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
// This works because VERSIONBITS_LAST_OLD_BLOCK_VERSION happens
|
||||||
|
// to be 4, and the bit we're testing happens to be bit 28.
|
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0); |
||||||
|
} |
||||||
|
// Now mine 5 more blocks at the start time -- MTP should not have passed yet, so
|
||||||
|
// CBV should still not yet set the bit.
|
||||||
|
nTime = nStartTime; |
||||||
|
for (int i=2012; i<=2016; i++) { |
||||||
|
lastBlock = firstChain.Mine(2016+i, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0); |
||||||
|
} |
||||||
|
|
||||||
|
// Advance to the next period and transition to STARTED,
|
||||||
|
lastBlock = firstChain.Mine(6048, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
// so ComputeBlockVersion should now set the bit,
|
||||||
|
BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0); |
||||||
|
// and should also be using the VERSIONBITS_TOP_BITS.
|
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); |
||||||
|
|
||||||
|
// Check that ComputeBlockVersion will set the bit until nTimeout
|
||||||
|
nTime += 600; |
||||||
|
int blocksToMine = 4032; // test blocks for up to 2 time periods
|
||||||
|
int nHeight = 6048; |
||||||
|
// These blocks are all before nTimeout is reached.
|
||||||
|
while (nTime < nTimeout && blocksToMine > 0) { |
||||||
|
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0); |
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS); |
||||||
|
blocksToMine--; |
||||||
|
nTime += 600; |
||||||
|
nHeight += 1; |
||||||
|
}; |
||||||
|
|
||||||
|
nTime = nTimeout; |
||||||
|
// FAILED is only triggered at the end of a period, so CBV should be setting
|
||||||
|
// the bit until the period transition.
|
||||||
|
for (int i=0; i<2015; i++) { |
||||||
|
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0); |
||||||
|
nHeight += 1; |
||||||
|
} |
||||||
|
// The next block should trigger no longer setting the bit.
|
||||||
|
lastBlock = firstChain.Mine(nHeight+1, nTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0); |
||||||
|
|
||||||
|
// On a new chain:
|
||||||
|
// verify that the bit will be set after lock-in, and then stop being set
|
||||||
|
// after activation.
|
||||||
|
nTime = nStartTime; |
||||||
|
|
||||||
|
// Mine one period worth of blocks, and check that the bit will be on for the
|
||||||
|
// next period.
|
||||||
|
lastBlock = secondChain.Mine(2016, nStartTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0); |
||||||
|
|
||||||
|
// Mine another period worth of blocks, signaling the new bit.
|
||||||
|
lastBlock = secondChain.Mine(4032, nStartTime, VERSIONBITS_TOP_BITS | (1<<bit)).Tip(); |
||||||
|
// After one period of setting the bit on each block, it should have locked in.
|
||||||
|
// We keep setting the bit for one more period though, until activation.
|
||||||
|
BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0); |
||||||
|
|
||||||
|
// Now check that we keep mining the block until the end of this period, and
|
||||||
|
// then stop at the beginning of the next period.
|
||||||
|
lastBlock = secondChain.Mine(6047, nStartTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK((ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit)) != 0); |
||||||
|
lastBlock = secondChain.Mine(6048, nStartTime, VERSIONBITS_LAST_OLD_BLOCK_VERSION).Tip(); |
||||||
|
BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & (1<<bit), 0); |
||||||
|
|
||||||
|
// Finally, verify that after a soft fork has activated, CBV no longer uses
|
||||||
|
// VERSIONBITS_LAST_OLD_BLOCK_VERSION.
|
||||||
|
//BOOST_CHECK_EQUAL(ComputeBlockVersion(lastBlock, mainnetParams) & VERSIONBITS_TOP_MASK, VERSIONBITS_TOP_BITS);
|
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_SUITE_END() |
@ -0,0 +1,133 @@ |
|||||||
|
// Copyright (c) 2016 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#include "versionbits.h" |
||||||
|
|
||||||
|
ThresholdState AbstractThresholdConditionChecker::GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const |
||||||
|
{ |
||||||
|
int nPeriod = Period(params); |
||||||
|
int nThreshold = Threshold(params); |
||||||
|
int64_t nTimeStart = BeginTime(params); |
||||||
|
int64_t nTimeTimeout = EndTime(params); |
||||||
|
|
||||||
|
// 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.
|
||||||
|
if (pindexPrev != NULL) { |
||||||
|
pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - ((pindexPrev->nHeight + 1) % nPeriod)); |
||||||
|
} |
||||||
|
|
||||||
|
// Walk backwards in steps of nPeriod to find a pindexPrev whose information is known
|
||||||
|
std::vector<const CBlockIndex*> vToCompute; |
||||||
|
while (cache.count(pindexPrev) == 0) { |
||||||
|
if (pindexPrev == NULL) { |
||||||
|
// The genesis block is by definition defined.
|
||||||
|
cache[pindexPrev] = THRESHOLD_DEFINED; |
||||||
|
break; |
||||||
|
} |
||||||
|
if (pindexPrev->GetMedianTimePast() < nTimeStart) { |
||||||
|
// Optimizaton: don't recompute down further, as we know every earlier block will be before the start time
|
||||||
|
cache[pindexPrev] = THRESHOLD_DEFINED; |
||||||
|
break; |
||||||
|
} |
||||||
|
vToCompute.push_back(pindexPrev); |
||||||
|
pindexPrev = pindexPrev->GetAncestor(pindexPrev->nHeight - nPeriod); |
||||||
|
} |
||||||
|
|
||||||
|
// At this point, cache[pindexPrev] is known
|
||||||
|
assert(cache.count(pindexPrev)); |
||||||
|
ThresholdState state = cache[pindexPrev]; |
||||||
|
|
||||||
|
// Now walk forward and compute the state of descendants of pindexPrev
|
||||||
|
while (!vToCompute.empty()) { |
||||||
|
ThresholdState stateNext = state; |
||||||
|
pindexPrev = vToCompute.back(); |
||||||
|
vToCompute.pop_back(); |
||||||
|
|
||||||
|
switch (state) { |
||||||
|
case THRESHOLD_DEFINED: { |
||||||
|
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) { |
||||||
|
stateNext = THRESHOLD_FAILED; |
||||||
|
} else if (pindexPrev->GetMedianTimePast() >= nTimeStart) { |
||||||
|
stateNext = THRESHOLD_STARTED; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
case THRESHOLD_STARTED: { |
||||||
|
if (pindexPrev->GetMedianTimePast() >= nTimeTimeout) { |
||||||
|
stateNext = THRESHOLD_FAILED; |
||||||
|
break; |
||||||
|
} |
||||||
|
// We need to count
|
||||||
|
const CBlockIndex* pindexCount = pindexPrev; |
||||||
|
int count = 0; |
||||||
|
for (int i = 0; i < nPeriod; i++) { |
||||||
|
if (Condition(pindexCount, params)) { |
||||||
|
count++; |
||||||
|
} |
||||||
|
pindexCount = pindexCount->pprev; |
||||||
|
} |
||||||
|
if (count >= nThreshold) { |
||||||
|
stateNext = THRESHOLD_LOCKED_IN; |
||||||
|
} |
||||||
|
break; |
||||||
|
} |
||||||
|
case THRESHOLD_LOCKED_IN: { |
||||||
|
// Always progresses into ACTIVE.
|
||||||
|
stateNext = THRESHOLD_ACTIVE; |
||||||
|
break; |
||||||
|
} |
||||||
|
case THRESHOLD_FAILED: |
||||||
|
case THRESHOLD_ACTIVE: { |
||||||
|
// Nothing happens, these are terminal states.
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
cache[pindexPrev] = state = stateNext; |
||||||
|
} |
||||||
|
|
||||||
|
return state; |
||||||
|
} |
||||||
|
|
||||||
|
namespace |
||||||
|
{ |
||||||
|
/**
|
||||||
|
* Class to implement versionbits logic. |
||||||
|
*/ |
||||||
|
class VersionBitsConditionChecker : public AbstractThresholdConditionChecker { |
||||||
|
private: |
||||||
|
const Consensus::DeploymentPos id; |
||||||
|
|
||||||
|
protected: |
||||||
|
int64_t BeginTime(const Consensus::Params& params) const { return params.vDeployments[id].nStartTime; } |
||||||
|
int64_t EndTime(const Consensus::Params& params) const { return params.vDeployments[id].nTimeout; } |
||||||
|
int Period(const Consensus::Params& params) const { return params.nMinerConfirmationWindow; } |
||||||
|
int Threshold(const Consensus::Params& params) const { return params.nRuleChangeActivationThreshold; } |
||||||
|
|
||||||
|
bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const |
||||||
|
{ |
||||||
|
return (((pindex->nVersion & VERSIONBITS_TOP_MASK) == VERSIONBITS_TOP_BITS) && (pindex->nVersion & Mask(params)) != 0); |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
VersionBitsConditionChecker(Consensus::DeploymentPos id_) : id(id_) {} |
||||||
|
uint32_t Mask(const Consensus::Params& params) const { return ((uint32_t)1) << params.vDeployments[id].bit; } |
||||||
|
}; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache) |
||||||
|
{ |
||||||
|
return VersionBitsConditionChecker(pos).GetStateFor(pindexPrev, params, cache.caches[pos]); |
||||||
|
} |
||||||
|
|
||||||
|
uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos) |
||||||
|
{ |
||||||
|
return VersionBitsConditionChecker(pos).Mask(params); |
||||||
|
} |
||||||
|
|
||||||
|
void VersionBitsCache::Clear() |
||||||
|
{ |
||||||
|
for (unsigned int d = 0; d < Consensus::MAX_VERSION_BITS_DEPLOYMENTS; d++) { |
||||||
|
caches[d].clear(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,59 @@ |
|||||||
|
// Copyright (c) 2016 The Bitcoin Core developers
|
||||||
|
// Distributed under the MIT software license, see the accompanying
|
||||||
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||||
|
|
||||||
|
#ifndef BITCOIN_CONSENSUS_VERSIONBITS |
||||||
|
#define BITCOIN_CONSENSUS_VERSIONBITS |
||||||
|
|
||||||
|
#include "chain.h" |
||||||
|
#include <map> |
||||||
|
|
||||||
|
/** What block version to use for new blocks (pre versionbits) */ |
||||||
|
static const int32_t VERSIONBITS_LAST_OLD_BLOCK_VERSION = 4; |
||||||
|
/** What bits to set in version for versionbits blocks */ |
||||||
|
static const int32_t VERSIONBITS_TOP_BITS = 0x20000000UL; |
||||||
|
/** What bitmask determines whether versionbits is in use */ |
||||||
|
static const int32_t VERSIONBITS_TOP_MASK = 0xE0000000UL; |
||||||
|
/** Total bits available for versionbits */ |
||||||
|
static const int32_t VERSIONBITS_NUM_BITS = 29; |
||||||
|
|
||||||
|
enum ThresholdState { |
||||||
|
THRESHOLD_DEFINED, |
||||||
|
THRESHOLD_STARTED, |
||||||
|
THRESHOLD_LOCKED_IN, |
||||||
|
THRESHOLD_ACTIVE, |
||||||
|
THRESHOLD_FAILED, |
||||||
|
}; |
||||||
|
|
||||||
|
// A map that gives the state for blocks whose height is a multiple of Period().
|
||||||
|
// The map is indexed by the block's parent, however, so all keys in the map
|
||||||
|
// will either be NULL or a block with (height + 1) % Period() == 0.
|
||||||
|
typedef std::map<const CBlockIndex*, ThresholdState> ThresholdConditionCache; |
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract class that implements BIP9-style threshold logic, and caches results. |
||||||
|
*/ |
||||||
|
class AbstractThresholdConditionChecker { |
||||||
|
protected: |
||||||
|
virtual bool Condition(const CBlockIndex* pindex, const Consensus::Params& params) const =0; |
||||||
|
virtual int64_t BeginTime(const Consensus::Params& params) const =0; |
||||||
|
virtual int64_t EndTime(const Consensus::Params& params) const =0; |
||||||
|
virtual int Period(const Consensus::Params& params) const =0; |
||||||
|
virtual int Threshold(const Consensus::Params& params) const =0; |
||||||
|
|
||||||
|
public: |
||||||
|
// Note that the function below takes a pindexPrev as input: they compute information for block B based on its parent.
|
||||||
|
ThresholdState GetStateFor(const CBlockIndex* pindexPrev, const Consensus::Params& params, ThresholdConditionCache& cache) const; |
||||||
|
}; |
||||||
|
|
||||||
|
struct VersionBitsCache |
||||||
|
{ |
||||||
|
ThresholdConditionCache caches[Consensus::MAX_VERSION_BITS_DEPLOYMENTS]; |
||||||
|
|
||||||
|
void Clear(); |
||||||
|
}; |
||||||
|
|
||||||
|
ThresholdState VersionBitsState(const CBlockIndex* pindexPrev, const Consensus::Params& params, Consensus::DeploymentPos pos, VersionBitsCache& cache); |
||||||
|
uint32_t VersionBitsMask(const Consensus::Params& params, Consensus::DeploymentPos pos); |
||||||
|
|
||||||
|
#endif |
Loading…
Reference in new issue