diff --git a/qa/pull-tester/rpc-tests.py b/qa/pull-tester/rpc-tests.py index e7173fda080..d0cdf703581 100755 --- a/qa/pull-tester/rpc-tests.py +++ b/qa/pull-tester/rpc-tests.py @@ -126,6 +126,7 @@ testScriptsExt = [ 'mempool_packages.py', 'maxuploadtarget.py', 'replace-by-fee.py', + 'bigblocks.py', ] #Enable ZMQ tests diff --git a/qa/rpc-tests/bigblocks.py b/qa/rpc-tests/bigblocks.py new file mode 100755 index 00000000000..d461d91566c --- /dev/null +++ b/qa/rpc-tests/bigblocks.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python2 +# Copyright (c) 2014 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + +# +# Test mining and broadcast of larger-than-1MB-blocks +# +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +from decimal import Decimal + +CACHE_DIR = "cache_bigblock" + +# regression test / testnet fork params: +BASE_VERSION = 0x20000000 +FORK_BLOCK_BIT = 0x10000000 +FORK_DEADLINE = 1514764800 +FORK_GRACE_PERIOD = 60*60*24 +# Worst-case: fork happens close to the expiration time +FORK_TIME = FORK_DEADLINE-FORK_GRACE_PERIOD*4 + +class BigBlockTest(BitcoinTestFramework): + + def setup_chain(self): + print("Initializing test directory "+self.options.tmpdir) + + if not os.path.isdir(os.path.join(CACHE_DIR, "node0")): + print("Creating initial chain") + + for i in range(4): + initialize_datadir(CACHE_DIR, i) # Overwrite port/rpcport in bitcoin.conf + + first_block_time = FORK_TIME - 200 * 10*60 + + # Node 0 tries to create as-big-as-possible blocks. + # Node 1 creates really small, old-version blocks + # Node 2 creates empty up-version blocks + # Node 3 creates empty, old-version blocks + self.nodes = [] + # Use node0 to mine blocks for input splitting + self.nodes.append(start_node(0, CACHE_DIR, ["-blockmaxsize=2000000", "-debug=net", + "-mocktime=%d"%(first_block_time,)])) + self.nodes.append(start_node(1, CACHE_DIR, ["-blockmaxsize=50000", "-debug=net", + "-mocktime=%d"%(first_block_time,), + "-blockversion=%d"%(BASE_VERSION,)])) + self.nodes.append(start_node(2, CACHE_DIR, ["-blockmaxsize=1000", + "-mocktime=%d"%(first_block_time,)])) + self.nodes.append(start_node(3, CACHE_DIR, ["-blockmaxsize=1000", + "-mocktime=%d"%(first_block_time,), + "-blockversion=4"])) + + set_node_times(self.nodes, first_block_time) + + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + connect_nodes_bi(self.nodes, 2, 3) + connect_nodes_bi(self.nodes, 3, 0) + + self.is_network_split = False + self.sync_all() + + # Have node0 and node1 alternate finding blocks + # before the fork time, so it's 50% / 50% vote + block_time = first_block_time + for i in range(0,200): + miner = i%2 + set_node_times(self.nodes, block_time) + b1hash = self.nodes[miner].generate(1)[0] + b1 = self.nodes[miner].getblock(b1hash, True) + if miner % 2: assert(not (b1['version'] & FORK_BLOCK_BIT)) + else: assert(b1['version'] & FORK_BLOCK_BIT) + assert(self.sync_blocks(self.nodes[0:2])) + block_time = block_time + 10*60 + + # Generate 1200 addresses + addresses = [ self.nodes[3].getnewaddress() for i in range(0,1200) ] + + amount = Decimal("0.00125") + + send_to = { } + for address in addresses: + send_to[address] = amount + + tx_file = open(os.path.join(CACHE_DIR, "txdata"), "w") + + # Create four megabytes worth of transactions ready to be + # mined: + print("Creating 100 40K transactions (4MB)") + for node in range(0,2): + for i in range(0,50): + txid = self.nodes[node].sendmany("", send_to, 1) + txdata = self.nodes[node].getrawtransaction(txid) + tx_file.write(txdata+"\n") + tx_file.close() + + stop_nodes(self.nodes) + wait_bitcoinds() + self.nodes = [] + for i in range(4): + os.remove(log_filename(CACHE_DIR, i, "debug.log")) + os.remove(log_filename(CACHE_DIR, i, "db.log")) + os.remove(log_filename(CACHE_DIR, i, "peers.dat")) + os.remove(log_filename(CACHE_DIR, i, "fee_estimates.dat")) + + + for i in range(4): + from_dir = os.path.join(CACHE_DIR, "node"+str(i)) + to_dir = os.path.join(self.options.tmpdir, "node"+str(i)) + shutil.copytree(from_dir, to_dir) + initialize_datadir(self.options.tmpdir, i) # Overwrite port/rpcport in bitcoin.conf + + def sync_blocks(self, rpc_connections, wait=0.1, max_wait=30): + """ + Wait until everybody has the same block count + """ + for i in range(0,max_wait): + if i > 0: time.sleep(wait) + counts = [ x.getblockcount() for x in rpc_connections ] + if counts == [ counts[0] ]*len(counts): + return True + return False + + def setup_network(self): + self.nodes = [] + last_block_time = FORK_TIME - 10*60 + + self.nodes.append(start_node(0, self.options.tmpdir, ["-blockmaxsize=2000000", "-debug=net", + "-mocktime=%d"%(last_block_time,)])) + self.nodes.append(start_node(1, self.options.tmpdir, ["-blockmaxsize=50000", "-debug=net", + "-mocktime=%d"%(last_block_time,), + "-blockversion=%d"%(BASE_VERSION,)])) + self.nodes.append(start_node(2, self.options.tmpdir, ["-blockmaxsize=1000", + "-mocktime=%d"%(last_block_time,)])) + self.nodes.append(start_node(3, self.options.tmpdir, ["-blockmaxsize=1000", + "-mocktime=%d"%(last_block_time,), + "-blockversion=4"])) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 1, 2) + connect_nodes_bi(self.nodes, 2, 3) + connect_nodes_bi(self.nodes, 3, 0) + + # Populate node0's mempool with cached pre-created transactions: + with open(os.path.join(CACHE_DIR, "txdata"), "r") as f: + for line in f: + self.nodes[0].sendrawtransaction(line.rstrip()) + + def copy_mempool(self, from_node, to_node): + txids = from_node.getrawmempool() + for txid in txids: + txdata = from_node.getrawtransaction(txid) + to_node.sendrawtransaction(txdata) + + def TestMineBig(self, expect_big, expect_version=None): + # Test if node0 will mine big blocks. + b1hash = self.nodes[0].generate(1)[0] + b1 = self.nodes[0].getblock(b1hash, True) + assert(self.sync_blocks(self.nodes)) + + if expect_version: + assert b1['version'] & FORK_BLOCK_BIT + elif not expect_version==None: + assert not b1['version'] & FORK_BLOCK_BIT + + if expect_big: + assert(b1['size'] > 1000*1000) + + # Have node1 mine on top of the block, + # to make sure it goes along with the fork + b2hash = self.nodes[1].generate(1)[0] + b2 = self.nodes[1].getblock(b2hash, True) + assert(b2['previousblockhash'] == b1hash) + assert(self.sync_blocks(self.nodes)) + + else: + assert(b1['size'] <= 1000*1000) + + # Reset chain to before b1hash: + for node in self.nodes: + node.invalidateblock(b1hash) + assert(self.sync_blocks(self.nodes)) + + def run_test(self): + # nodes 0 and 1 have 50 mature 50-BTC coinbase transactions. + # Spend them with 50 transactions, each that has + # 1,200 outputs (so they're about 41K big). + + print("Testing fork conditions") + + # Fork is controlled by block timestamp and miner super-majority; + # large blocks may only be created after a supermajority of miners + # produce up-version blocks plus a grace period + + # At this point the chain is 200 blocks long + # alternating between version=0x20000000 and version=0x30000000 + # blocks. + + # Nodes will vote for 2MB until the vote expiration date; votes + # for 2MB in blocks with times past the exipration date are + # ignored. + + # NOTE: the order of these test is important! + # set_node_times must advance time. Local time moving + # backwards causes problems. + + # Time starts a little before fork activation time: + set_node_times(self.nodes, FORK_TIME - 100) + + # No supermajority yet + self.TestMineBig(expect_big=False, expect_version=True) + + # Create a block after the expiration date. This will be rejected + # by the other nodes for being more than 2 hours in the future, + # and will have FORK_BLOCK_BIT cleared. + + set_node_times(self.nodes[0:1], FORK_DEADLINE + 100) + + b1hash = self.nodes[0].generate(1)[0] + b1 = self.nodes[0].getblock(b1hash, True) + assert(not (b1['version'] & FORK_BLOCK_BIT)) + self.nodes[0].invalidateblock(b1hash) + set_node_times(self.nodes[0:1], FORK_TIME - 100) + assert(self.sync_blocks(self.nodes)) + + + # node2 creates empty up-version blocks; creating + # 50 in a row makes 75 of previous 100 up-version + # (which is the -regtest activation condition) + t_delta = FORK_GRACE_PERIOD/50 + blocks = [] + for i in range(50): + set_node_times(self.nodes, FORK_TIME + t_delta*i - 1) + blocks.append(self.nodes[2].generate(1)[0]) + assert(self.sync_blocks(self.nodes)) + + # Earliest time for a big block is the timestamp of the + # supermajority block plus grace period: + lastblock = self.nodes[0].getblock(blocks[-1], True) + t_fork = lastblock["time"] + FORK_GRACE_PERIOD + + self.TestMineBig(expect_big=False, expect_version=True) # Supermajority... but before grace period end + + # Test right around the switchover time. + set_node_times(self.nodes, t_fork-1) + self.TestMineBig(expect_big=False, expect_version=True) + + # Note that node's local times are irrelevant, block timestamps + # are all that count-- so node0 will mine a big block with timestamp in the + # future from the perspective of the other nodes, but as long as + # it's timestamp is not too far in the future (2 hours) it will be + # accepted. + self.nodes[0].setmocktime(t_fork) + self.TestMineBig(expect_big=True, expect_version=True) + + # Shutdown then restart node[0], it should + # remember supermajority state and produce a big block. + stop_node(self.nodes[0], 0) + self.nodes[0] = start_node(0, self.options.tmpdir, ["-blockmaxsize=2000000", "-debug=net", + "-mocktime=%d"%(t_fork,)]) + self.copy_mempool(self.nodes[1], self.nodes[0]) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 0, 3) + self.TestMineBig(expect_big=True, expect_version=True) + + # Test re-orgs past the activation block (blocks[-1]) + # + # Shutdown node[0] again: + stop_node(self.nodes[0], 0) + + # Mine a longer chain with two version=4 blocks: + self.nodes[3].invalidateblock(blocks[-1]) + v4blocks = self.nodes[3].generate(2) + assert(self.sync_blocks(self.nodes[1:])) + + # Restart node0, it should re-org onto longer chain, reset + # activation time, and refuse to mine a big block: + self.nodes[0] = start_node(0, self.options.tmpdir, ["-blockmaxsize=2000000", "-debug=net", + "-mocktime=%d"%(t_fork,)]) + self.copy_mempool(self.nodes[1], self.nodes[0]) + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 0, 3) + assert(self.sync_blocks(self.nodes)) + self.TestMineBig(expect_big=False, expect_version=True) + + # Mine 4 FORK_BLOCK_BIT blocks and set the time past the + # grace period: bigger block OK: + self.nodes[2].generate(4) + assert(self.sync_blocks(self.nodes)) + set_node_times(self.nodes, t_fork + FORK_GRACE_PERIOD) + self.TestMineBig(expect_big=True, expect_version=True) + + # Finally, mine blocks well after the expiration time and make sure + # bigger blocks are still OK: + set_node_times(self.nodes, FORK_DEADLINE+FORK_GRACE_PERIOD*11) + self.nodes[2].generate(4) + assert(self.sync_blocks(self.nodes)) + self.TestMineBig(expect_big=True, expect_version=False) + +class BigBlockTest2(BigBlockTest): + + def run_test(self): + print("Testing around deadline time") + + # 49 blocks just before expiration time: + t_delta = FORK_GRACE_PERIOD/50 + pre_expire_blocks = [] + for i in range(49): + set_node_times(self.nodes, FORK_DEADLINE - (t_delta*(50-i))) + pre_expire_blocks.append(self.nodes[2].generate(1)[0]) + assert(self.sync_blocks(self.nodes)) + self.TestMineBig(expect_big=False, expect_version=True) + + # Gee, darn: JUST missed the deadline! + set_node_times(self.nodes, FORK_DEADLINE+1) + block_past_expiration = self.nodes[0].generate(1)[0] + + # Stuck with small blocks + set_node_times(self.nodes, FORK_DEADLINE+FORK_GRACE_PERIOD*11) + self.nodes[2].generate(4) + assert(self.sync_blocks(self.nodes)) + self.TestMineBig(expect_big=False, expect_version=False) + + # If vote fails, should be warned about running obsolete code: + info = self.nodes[0].getmininginfo() + assert("obsolete" in info['errors']) + +if __name__ == '__main__': + print("Be patient, these tests can take 2 or more minutes to run.") + + BigBlockTest().main() + BigBlockTest2().main() + + print("Cached test chain and transactions left in %s"%(CACHE_DIR)) + print(" (remove that directory if you will not run this test again)") diff --git a/qa/rpc-tests/bipdersig-p2p.py b/qa/rpc-tests/bipdersig-p2p.py index 9118b8facfd..07f53000eb9 100755 --- a/qa/rpc-tests/bipdersig-p2p.py +++ b/qa/rpc-tests/bipdersig-p2p.py @@ -93,9 +93,9 @@ class BIP66Test(ComparisonTestFramework): height += 1 yield TestInstance(test_blocks, sync_every_block=False) - ''' Mine 749 version 3 blocks ''' + ''' Mine 74 version 3 blocks ''' test_blocks = [] - for i in xrange(749): + for i in xrange(74): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 3 block.rehash() @@ -107,7 +107,7 @@ class BIP66Test(ComparisonTestFramework): yield TestInstance(test_blocks, sync_every_block=False) ''' - Check that the new DERSIG rules are not enforced in the 750th + Check that the new DERSIG rules are not enforced in the 75th version 3 block. ''' spendtx = self.create_transaction(self.nodes[0], @@ -128,7 +128,7 @@ class BIP66Test(ComparisonTestFramework): yield TestInstance([[block, True]]) ''' - Check that the new DERSIG rules are enforced in the 751st version 3 + Check that the new DERSIG rules are enforced in the 76th version 3 block. ''' spendtx = self.create_transaction(self.nodes[0], @@ -145,9 +145,9 @@ class BIP66Test(ComparisonTestFramework): self.last_block_time += 1 yield TestInstance([[block, False]]) - ''' Mine 199 new version blocks on last valid tip ''' + ''' Mine 19 new version blocks on last valid tip ''' test_blocks = [] - for i in xrange(199): + for i in xrange(19): block = create_block(self.tip, create_coinbase(height), self.last_block_time + 1) block.nVersion = 3 block.rehash() diff --git a/qa/rpc-tests/prioritise_transaction.py b/qa/rpc-tests/prioritise_transaction.py index 4a79d38da0e..a724875b9aa 100755 --- a/qa/rpc-tests/prioritise_transaction.py +++ b/qa/rpc-tests/prioritise_transaction.py @@ -25,7 +25,7 @@ class PrioritiseTransactionTest(BitcoinTestFramework): self.nodes = [] self.is_network_split = False - self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-printpriority=1"])) + self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-printpriority=1", "-blockmaxsize=750000"])) self.relayfee = self.nodes[0].getnetworkinfo()['relayfee'] def run_test(self): diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py index 584f318d0bb..52596aa2e9b 100755 --- a/qa/rpc-tests/test_framework/test_framework.py +++ b/qa/rpc-tests/test_framework/test_framework.py @@ -159,7 +159,6 @@ class BitcoinTestFramework(object): if success: print("Tests successful") - sys.exit(0) else: print("Failed") sys.exit(1) diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d89132f8066..456c534bcd4 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -43,6 +43,7 @@ BITCOIN_TESTS =\ test/base58_tests.cpp \ test/base64_tests.cpp \ test/bip32_tests.cpp \ + test/block_size_tests.cpp \ test/bloom_tests.cpp \ test/checkblock_tests.cpp \ test/Checkpoints_tests.cpp \ diff --git a/src/chainparams.cpp b/src/chainparams.cpp index 9cf99492c91..c16748c8db9 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -97,6 +97,11 @@ public: genesis = CreateGenesisBlock(1231006505, 2083236893, 0x1d00ffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); + // Timestamps for forking consensus rule changes: + // Allow bigger blocks if: + consensus.nActivateSizeForkMajority = 750; // 75% of hashpower to activate fork + consensus.nSizeForkGracePeriod = 60*60*24*28; // four week grace period after activation + consensus.nSizeForkExpiration = 1514764800; // 2018-01-01 00:00:00 GMT assert(consensus.hashGenesisBlock == uint256S("0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")); assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); @@ -172,8 +177,13 @@ public: nMaxTipAge = 0x7fffffff; nPruneAfterHeight = 1000; + //! Modify the testnet genesis block so the timestamp is valid for a later start. genesis = CreateGenesisBlock(1296688602, 414098458, 0x1d00ffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); + + consensus.nActivateSizeForkMajority = 75; // 75 of 100 to activate fork + consensus.nSizeForkGracePeriod = 60*60*24; // 1-day grace period + consensus.nSizeForkExpiration = 1514764800; // 2018-01-01 00:00:00 GMT assert(consensus.hashGenesisBlock == uint256S("0x000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943")); assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); @@ -217,9 +227,9 @@ public: CRegTestParams() { strNetworkID = "regtest"; consensus.nSubsidyHalvingInterval = 150; - consensus.nMajorityEnforceBlockUpgrade = 750; - consensus.nMajorityRejectBlockOutdated = 950; - consensus.nMajorityWindow = 1000; + consensus.nMajorityEnforceBlockUpgrade = 75; + consensus.nMajorityRejectBlockOutdated = 95; + consensus.nMajorityWindow = 100; consensus.BIP34Height = -1; // BIP34 has not necessarily activated on regtest consensus.BIP34Hash = uint256(); consensus.powLimit = uint256S("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); @@ -238,6 +248,9 @@ public: genesis = CreateGenesisBlock(1296688602, 2, 0x207fffff, 1, 50 * COIN); consensus.hashGenesisBlock = genesis.GetHash(); + consensus.nActivateSizeForkMajority = 75; // 75 of 100 to activate fork + consensus.nSizeForkGracePeriod = 60*60*24; // 1-day grace period + consensus.nSizeForkExpiration = 1514764800; // 2018-01-01 00:00:00 GMT assert(consensus.hashGenesisBlock == uint256S("0x0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206")); assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); diff --git a/src/consensus/params.h b/src/consensus/params.h index 335750fe807..fd14ad59c53 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -29,6 +29,15 @@ struct Params { int64_t nPowTargetSpacing; int64_t nPowTargetTimespan; int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; } + + /** 2MB fork activation parameters */ + int nActivateSizeForkMajority; + int64_t nSizeForkGracePeriod; + int64_t nSizeForkExpiration; + + int ActivateSizeForkMajority() const { return nActivateSizeForkMajority; } + int64_t SizeForkGracePeriod() const { return nSizeForkGracePeriod; } + int64_t SizeForkExpiration() const { return nSizeForkExpiration; } }; } // namespace Consensus diff --git a/src/main.cpp b/src/main.cpp index 9514c7ef9fb..45f93305dd5 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ CCriticalSection cs_main; BlockMap mapBlockIndex; CChain chainActive; CBlockIndex *pindexBestHeader = NULL; +boost::atomic sizeForkTime(std::numeric_limits::max()); int64_t nTimeBestReceived = 0; CWaitableCriticalSection csBestBlock; CConditionVariable cvBlockChange; @@ -91,10 +93,16 @@ map > mapOrphanTransactionsByPrev GUARDED_BY(cs_main);; void EraseOrphansFor(NodeId peer) EXCLUSIVE_LOCKS_REQUIRED(cs_main); /** - * Returns true if there are nRequired or more blocks of minVersion or above - * in the last Consensus::Params::nMajorityWindow blocks, starting at pstart and going backwards. + * Returns true if there are nRequired or more blocks with a version that matches + * versionOrBitmask in the last Consensus::Params::nMajorityWindow blocks, + * starting at pstart and going backwards. + * + * A bitmask is used to be compatible with BIP009, + * so it is possible for multiple forks to be in-progress + * at the same time. A simple >= version field is used for forks that + * predate this proposal. */ -static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned nRequired, const Consensus::Params& consensusParams); +static bool IsSuperMajority(int versionOrBitmask, const CBlockIndex* pstart, unsigned nRequired, const Consensus::Params& consensusParams, bool useBitMask=false); static void CheckBlockIndex(const Consensus::Params& consensusParams); /** Constant stuff for coinbase transactions we create: */ @@ -1961,6 +1969,21 @@ static int64_t nTimeIndex = 0; static int64_t nTimeCallbacks = 0; static int64_t nTimeTotal = 0; +static bool DidBlockTriggerSizeFork(const CBlock &block, const CBlockIndex *pindex, const CChainParams &chainparams) +{ + if (pblocktree->ForkBitActivated(FORK_BIT_2MB) != uint256()) + return false; // Already active + if (block.nTime > chainparams.GetConsensus().SizeForkExpiration()) { + // 2MB vote failed: this code is obsolete + strMiscWarning = _("Warning: This version is obsolete; upgrade required!"); + CAlert::Notify(strMiscWarning, true); + return false; + } + if ((block.nVersion & FORK_BIT_2MB) != FORK_BIT_2MB) + return false; + return IsSuperMajority(FORK_BIT_2MB, pindex, chainparams.GetConsensus().ActivateSizeForkMajority(), chainparams.GetConsensus(), true /* use bitmask */); +} + bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pindex, CCoinsViewCache& view, bool fJustCheck) { const CChainParams& chainparams = Params(); @@ -2164,6 +2187,14 @@ bool ConnectBlock(const CBlock& block, CValidationState& state, CBlockIndex* pin int64_t nTime6 = GetTimeMicros(); nTimeCallbacks += nTime6 - nTime5; LogPrint("bench", " - Callbacks: %.2fms [%.2fs]\n", 0.001 * (nTime6 - nTime5), nTimeCallbacks * 0.000001); + if (DidBlockTriggerSizeFork(block, pindex, chainparams)) { + uint32_t tAllowBigger = block.nTime + chainparams.GetConsensus().SizeForkGracePeriod(); + LogPrintf("%s: Max block size fork activating at time %d, bigger blocks allowed at time %d\n", + __func__, block.nTime, tAllowBigger); + pblocktree->ActivateForkBit(FORK_BIT_2MB, pindex->GetBlockHash()); + sizeForkTime.store(tAllowBigger); + } + return true; } @@ -2310,14 +2341,16 @@ void static UpdateTip(CBlockIndex *pindexNew) { { int nUpgraded = 0; const CBlockIndex* pindex = chainActive.Tip(); + int32_t voteBits = FORK_BIT_2MB; + for (int i = 0; i < 100 && pindex != NULL; i++) { - if (pindex->nVersion > CBlock::CURRENT_VERSION) + if (!CBlock::VersionKnown(pindex->nVersion, voteBits)) ++nUpgraded; pindex = pindex->pprev; } if (nUpgraded > 0) - LogPrintf("%s: %d of last 100 blocks above version %d\n", __func__, nUpgraded, (int)CBlock::CURRENT_VERSION); + LogPrintf("%s: %d of last 100 blocks unknown version\n", __func__, nUpgraded); if (nUpgraded > 100/2) { // strMiscWarning is read by GetWarnings(), called by Qt and the JSON-RPC code to warn the user: @@ -2367,6 +2400,14 @@ bool static DisconnectTip(CValidationState& state, const Consensus::Params& cons // UpdateTransactionsFromBlock finds descendants of any transactions in this // block that were added back and cleans up the mempool state. mempool.UpdateTransactionsFromBlock(vHashUpdate); + + // Re-org past the size fork, reset activation condition: + if (pblocktree->ForkBitActivated(FORK_BIT_2MB) == pindexDelete->GetBlockHash()) { + LogPrintf("%s: re-org past size fork\n", __func__); + pblocktree->ActivateForkBit(FORK_BIT_2MB, uint256()); + sizeForkTime.store(std::numeric_limits::max()); + } + // Update chainActive and related variables. UpdateTip(pindexDelete->pprev); // Let wallets know transactions went from 1-confirmed to @@ -3182,12 +3223,13 @@ static bool AcceptBlock(const CBlock& block, CValidationState& state, const CCha return true; } -static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned nRequired, const Consensus::Params& consensusParams) +static bool IsSuperMajority(int versionOrBitmask, const CBlockIndex* pstart, unsigned nRequired, const Consensus::Params& consensusParams, bool useBitMask) { unsigned int nFound = 0; for (int i = 0; i < consensusParams.nMajorityWindow && nFound < nRequired && pstart != NULL; i++) { - if (pstart->nVersion >= minVersion) + if ((useBitMask && ((pstart->nVersion & versionOrBitmask) == versionOrBitmask)) || + (!useBitMask && (pstart->nVersion >= versionOrBitmask))) ++nFound; pstart = pstart->pprev; } @@ -3429,6 +3471,15 @@ bool static LoadBlockIndexDB() if (!pblocktree->LoadBlockIndexGuts()) return false; + // If the max-block-size fork threshold was reached, update + // chainparams so big blocks are allowed: + uint256 sizeForkHash = pblocktree->ForkBitActivated(FORK_BIT_2MB); + if (sizeForkHash != uint256()) { + BlockMap::iterator it = mapBlockIndex.find(sizeForkHash); + assert(it != mapBlockIndex.end()); + sizeForkTime.store(it->second->GetBlockTime() + chainparams.GetConsensus().SizeForkGracePeriod()); + } + boost::this_thread::interruption_point(); // Calculate nChainWork @@ -5668,11 +5719,11 @@ bool SendMessages(CNode* pto) } /** Maximum size of a block */ -unsigned int MaxBlockSize(uint32_t nBLockTime) +unsigned int MaxBlockSize(uint32_t nBlockTime) { - if (true) // TODO: activation condition - return MAX_BLOCK_SIZE; - return OLD_MAX_BLOCK_SIZE; + if (nBlockTime < sizeForkTime.load()) + return OLD_MAX_BLOCK_SIZE; + return MAX_BLOCK_SIZE; } diff --git a/src/miner.cpp b/src/miner.cpp index af839fa671e..59a5211d637 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -71,6 +71,7 @@ int64_t UpdateTime(CBlockHeader* pblock, const Consensus::Params& consensusParam return nNewTime - nOldTime; } + CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& scriptPubKeyIn) { // Create new block @@ -79,11 +80,6 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s return NULL; CBlock *pblock = &pblocktemplate->block; // pointer for convenience - // -regtest only: allow overriding block.nVersion with - // -blockversion=N to test forking scenarios - if (chainparams.MineBlocksOnDemand()) - pblock->nVersion = GetArg("-blockversion", pblock->nVersion); - // Create coinbase tx CMutableTransaction txNew; txNew.vin.resize(1); @@ -122,12 +118,26 @@ CBlockTemplate* CreateNewBlock(const CChainParams& chainparams, const CScript& s pblock->nTime = GetAdjustedTime(); const int64_t nMedianTimePast = pindexPrev->GetMedianTimePast(); - unsigned int nSizeLimit = MaxBlockSize(nMedianTimePast); + pblock->nVersion = BASE_VERSION; + // Vote for 2 MB until the vote expiration time + if (pblock->nTime <= chainparams.GetConsensus().SizeForkExpiration()) + pblock->nVersion |= FORK_BIT_2MB; - // Largest block you're willing to create: - unsigned int nBlockMaxSize = GetArg("-blockmaxsize", DEFAULT_BLOCK_MAX_SIZE); - // Limit to between 1K and MAX_BLOCK_SIZE-1K for sanity: - nBlockMaxSize = std::max((unsigned int)1000, std::min((unsigned int)(MAX_BLOCK_SIZE-1000), nBlockMaxSize)); + // -regtest only: allow overriding block.nVersion with + // -blockversion=N to test forking scenarios + if (Params().MineBlocksOnDemand()) + pblock->nVersion = GetArg("-blockversion", pblock->nVersion); + + UpdateTime(pblock, Params().GetConsensus(), pindexPrev); + + uint32_t nConsensusMaxSize = MaxBlockSize(pblock->nTime); + // Largest block you're willing to create, defaults to being the biggest possible. + // Miners can adjust downwards if they wish to throttle their blocks, for instance, to work around + // high orphan rates or other scaling problems. + uint32_t nBlockMaxSize = (uint32_t) GetArg("-blockmaxsize", nConsensusMaxSize); + // Limit to betweeen 1K and MAX_BLOCK_SIZE-1K for sanity: + nBlockMaxSize = std::max((uint32_t)1000, + std::min(nConsensusMaxSize-1000, nBlockMaxSize)); // How much of the block should be dedicated to high-priority transactions, // included regardless of the fees they pay diff --git a/src/primitives/block.h b/src/primitives/block.h index 0e93399c08e..c3473b29891 100644 --- a/src/primitives/block.h +++ b/src/primitives/block.h @@ -10,6 +10,11 @@ #include "serialize.h" #include "uint256.h" +const uint32_t BIP_009_MASK = 0x20000000; +const uint32_t BASE_VERSION = 0x20000000; // Will accept 2MB blocks +const uint32_t FORK_BIT_2MB = 0x10000000; // Vote for 2MB fork +const bool DEFAULT_2MB_VOTE = false; + /** Nodes collect new transactions into a block, hash them into a hash tree, * and scan through nonce values to make the block's hash satisfy proof-of-work * requirements. When they solve the proof-of-work, they broadcast the block @@ -20,8 +25,9 @@ class CBlockHeader { public: + static const int32_t CURRENT_VERSION = BASE_VERSION; + // header - static const int32_t CURRENT_VERSION=4; int32_t nVersion; uint256 hashPrevBlock; uint256 hashMerkleRoot; @@ -91,6 +97,20 @@ public: *((CBlockHeader*)this) = header; } + static bool VersionKnown(int32_t nVersion, int32_t voteBits) + { + if (nVersion >= 1 && nVersion <= 4) + return true; + // BIP009 / versionbits: + if (nVersion & BIP_009_MASK) + { + uint32_t v = nVersion & ~BIP_009_MASK; + if ((v & ~voteBits) == 0) + return true; + } + return false; + } + ADD_SERIALIZE_METHODS; template diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 1048344e009..ed804702e42 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -582,7 +582,7 @@ UniValue getblocktemplate(const UniValue& params, bool fHelp) result.push_back(Pair("mutable", aMutable)); result.push_back(Pair("noncerange", "00000000ffffffff")); result.push_back(Pair("sigoplimit", (int64_t)MAX_BLOCK_SIGOPS)); - result.push_back(Pair("sizelimit", (int64_t)MAX_BLOCK_SIZE)); + result.push_back(Pair("sizelimit", (int64_t)MaxBlockSize(pblock->nTime))); result.push_back(Pair("curtime", pblock->GetBlockTime())); result.push_back(Pair("bits", strprintf("%08x", pblock->nBits))); result.push_back(Pair("height", (int64_t)(pindexPrev->nHeight+1))); diff --git a/src/test/block_size_tests.cpp b/src/test/block_size_tests.cpp new file mode 100644 index 00000000000..65e0a104b3c --- /dev/null +++ b/src/test/block_size_tests.cpp @@ -0,0 +1,169 @@ +// Copyright (c) 2016 The Bitcoin developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "chainparams.h" +#include "consensus/validation.h" +#include "consensus/consensus.h" +#include "main.h" +#include "miner.h" +#include "pubkey.h" +#include "random.h" +#include "uint256.h" +#include "util.h" + +#include "test/test_bitcoin.h" + +#include +#include + +extern boost::atomic sizeForkTime; + +BOOST_FIXTURE_TEST_SUITE(block_size_tests, TestingSetup) + +// Fill block with dummy transactions until it's serialized size is exactly nSize +static void +FillBlock(CBlock& block, unsigned int nSize) +{ + assert(block.vtx.size() > 0); // Start with at least a coinbase + + unsigned int nBlockSize = ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); + if (nBlockSize > nSize) { + block.vtx.resize(1); // passed in block is too big, start with just coinbase + nBlockSize = ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); + } + + CMutableTransaction tx; + tx.vin.resize(1); + tx.vin[0].scriptSig = CScript() << OP_11; + tx.vin[0].prevout.hash = block.vtx[0].GetHash(); // passes CheckBlock, would fail if we checked inputs. + tx.vin[0].prevout.n = 0; + tx.vout.resize(1); + tx.vout[0].nValue = 1LL; + tx.vout[0].scriptPubKey = block.vtx[0].vout[0].scriptPubKey; + + unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); + block.vtx.reserve(1+nSize/nTxSize); + + // ... add copies of tx to the block to get close to nSize: + while (nBlockSize+nTxSize < nSize) { + block.vtx.push_back(tx); + nBlockSize += nTxSize; + tx.vin[0].prevout.hash = GetRandHash(); // Just to make each tx unique + } + // Make the last transaction exactly the right size by making the scriptSig bigger. + block.vtx.pop_back(); + nBlockSize = ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); + unsigned int nFill = nSize - nBlockSize - nTxSize; + for (unsigned int i = 0; i < nFill; i++) + tx.vin[0].scriptSig << OP_11; + block.vtx.push_back(tx); + nBlockSize = ::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); + assert(nBlockSize == nSize); +} + +static bool TestCheckBlock(CBlock& block, uint64_t nTime, unsigned int nSize) +{ + SetMockTime(nTime); + block.nTime = nTime; + FillBlock(block, nSize); + CValidationState validationState; + bool fResult = CheckBlock(block, validationState, false, false) && validationState.IsValid(); + SetMockTime(0); + return fResult; +} + +// +// Unit test CheckBlock() for conditions around the block size hard fork +// +BOOST_AUTO_TEST_CASE(BigBlockFork_Time1) +{ + CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + const CChainParams& chainparams = Params(CBaseChainParams::MAIN); + CBlockTemplate *pblocktemplate; + + uint64_t t = GetTime(); + uint64_t preforkSize = OLD_MAX_BLOCK_SIZE; + uint64_t postforkSize = MAX_BLOCK_SIZE; + uint64_t tActivate = t; + + sizeForkTime.store(tActivate); + + LOCK(cs_main); + + BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); + CBlock *pblock = &pblocktemplate->block; + + // Before fork time... + BOOST_CHECK(TestCheckBlock(*pblock, t-1LL, preforkSize)); // 1MB : valid + BOOST_CHECK(!TestCheckBlock(*pblock, t-1LL, preforkSize+1)); // >1MB : invalid + BOOST_CHECK(!TestCheckBlock(*pblock, t-1LL, postforkSize)); // big : invalid + + // Exactly at fork time... + BOOST_CHECK(TestCheckBlock(*pblock, t, preforkSize)); // 1MB : valid + BOOST_CHECK(TestCheckBlock(*pblock, t, postforkSize)); // big : valid + BOOST_CHECK(!TestCheckBlock(*pblock, t, postforkSize+1)); // big+1 : invalid + + // After fork time... + BOOST_CHECK(TestCheckBlock(*pblock, t+11000, preforkSize)); // 1MB : valid + BOOST_CHECK(TestCheckBlock(*pblock, t+11000, postforkSize)); // big : valid + BOOST_CHECK(!TestCheckBlock(*pblock, t+11000, postforkSize+1)); // big+1 : invalid + + sizeForkTime.store(std::numeric_limits::max()); +} + +// Test activation time 30 days after earliest possible: +BOOST_AUTO_TEST_CASE(BigBlockFork_Time2) +{ + CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + CBlockTemplate *pblocktemplate; + const CChainParams& chainparams = Params(CBaseChainParams::MAIN); + + uint64_t t = GetTime(); + uint64_t preforkSize = OLD_MAX_BLOCK_SIZE; + uint64_t postforkSize = MAX_BLOCK_SIZE; + + uint64_t tActivate = t+60*60*24*30; + sizeForkTime.store(tActivate); + + LOCK(cs_main); + + BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); + CBlock *pblock = &pblocktemplate->block; + + // Exactly at fork time... + BOOST_CHECK(TestCheckBlock(*pblock, t, preforkSize)); // 1MB : valid + BOOST_CHECK(!TestCheckBlock(*pblock, t, postforkSize)); // big : invalid + + // Exactly at activation time.... + BOOST_CHECK(TestCheckBlock(*pblock, tActivate, preforkSize)); // 1MB : valid + BOOST_CHECK(TestCheckBlock(*pblock, tActivate, postforkSize)); // big : valid + + sizeForkTime.store(std::numeric_limits::max()); +} + +// Test: no miner consensus, no big blocks: +BOOST_AUTO_TEST_CASE(BigBlockFork_NoActivation) +{ + CScript scriptPubKey = CScript() << ParseHex("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f") << OP_CHECKSIG; + CBlockTemplate *pblocktemplate; + const CChainParams& chainparams = Params(CBaseChainParams::MAIN); + + uint64_t t = GetTime(); + uint64_t preforkSize = OLD_MAX_BLOCK_SIZE; + uint64_t postforkSize = MAX_BLOCK_SIZE; + + LOCK(cs_main); + + BOOST_CHECK(pblocktemplate = CreateNewBlock(chainparams, scriptPubKey)); + CBlock *pblock = &pblocktemplate->block; + + // Exactly at fork time... + BOOST_CHECK(TestCheckBlock(*pblock, t, preforkSize)); // 1MB : valid + BOOST_CHECK(!TestCheckBlock(*pblock, t, postforkSize)); // big : invalid + + uint64_t tAfter = t+11000; + BOOST_CHECK(!TestCheckBlock(*pblock, tAfter, postforkSize)); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/txdb.cpp b/src/txdb.cpp index f99e11f26e3..12a5fdbff03 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -28,6 +28,7 @@ static const char DB_FLAG = 'F'; static const char DB_REINDEX_FLAG = 'R'; static const char DB_LAST_BLOCK = 'l'; +static const char DB_FORK_ACTIVATION = 'a'; CCoinsViewDB::CCoinsViewDB(size_t nCacheSize, bool fMemory, bool fWipe) : db(GetDataDir() / "chainstate", nCacheSize, fMemory, fWipe, true) { @@ -215,5 +216,55 @@ bool CBlockTreeDB::LoadBlockIndexGuts() } } + // Load fork activation info + pcursor->Seek(make_pair(DB_FORK_ACTIVATION, 0)); + while (pcursor->Valid()) { + try { + std::pair key; + if (pcursor->GetKey(key) && key.first == DB_FORK_ACTIVATION) { + uint256 blockHash; + if (pcursor->GetValue(blockHash)) { + forkActivationMap[key.second] = blockHash; + } + pcursor->Next(); + } else { + break; // finished loading block index + } + } + catch (std::exception &e) { + return error("%s : Deserialize or I/O error - %s", __func__, e.what()); + } + } + return true; } + +uint256 CBlockTreeDB::ForkBitActivated(int32_t nForkVersionBit) const +{ + // Returns block at which a supermajority was reached for given + // fork version bit. + // NOTE! The max blocksize fork adds a grace period + // during which no bigger blocks are allowed; this routine + // just keeps track of the hash of the block that + // triggers the fork condition + + std::map::const_iterator it = forkActivationMap.find(nForkVersionBit); + if (it != forkActivationMap.end()) + return it->second; + + return uint256(); +} + +bool CBlockTreeDB::ActivateForkBit(int32_t nForkVersionBit, const uint256& blockHash) +{ + // Called when a supermajority of blocks (ending with blockHash) + // support a rule change + // OR if a chain re-org happens around the activation block, + // called with uint256(0) to reset the flag in the database. + + forkActivationMap[nForkVersionBit] = blockHash; + if (blockHash == uint256()) + return Erase(make_pair(DB_FORK_ACTIVATION, nForkVersionBit)); + else + return Write(make_pair(DB_FORK_ACTIVATION, nForkVersionBit), blockHash); +} diff --git a/src/txdb.h b/src/txdb.h index 22e0c5704cb..09ca1fd6bcc 100644 --- a/src/txdb.h +++ b/src/txdb.h @@ -49,6 +49,8 @@ public: private: CBlockTreeDB(const CBlockTreeDB&); void operator=(const CBlockTreeDB&); + std::map forkActivationMap; + public: bool WriteBatchSync(const std::vector >& fileInfo, int nLastFile, const std::vector& blockinfo); bool ReadBlockFileInfo(int nFile, CBlockFileInfo &fileinfo); @@ -60,6 +62,8 @@ public: bool WriteFlag(const std::string &name, bool fValue); bool ReadFlag(const std::string &name, bool &fValue); bool LoadBlockIndexGuts(); + uint256 ForkBitActivated(int32_t nForkVersionBit) const; + bool ActivateForkBit(int32_t nForkVersionBit, const uint256& blockHash); }; #endif // BITCOIN_TXDB_H