Skip to content

Commit

Permalink
Merge pull request bitcoin#2 from ajtowns/bip148
Browse files Browse the repository at this point in the history
Functional test draft
  • Loading branch information
earonesty committed May 30, 2017
2 parents 0f17665 + 22bf9c1 commit 7f92d07
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 1 deletion.
15 changes: 14 additions & 1 deletion src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ CTxMemPool mempool(&feeEstimator);

static void CheckBlockIndex(const Consensus::Params& consensusParams);
static bool IsWitnessLockedIn(const CBlockIndex* pindexPrev, const Consensus::Params& params);
static int DoS_UASF(int level);

/** Constant stuff for coinbase transactions we create: */
CScript COINBASE_FLAGS;
Expand Down Expand Up @@ -2859,6 +2860,18 @@ bool ContextualCheckBlock(const CBlock& block, CValidationState& state, const Co
return true;
}

// If enabling a UASF flag, peers passing on blocks that don't
// enforce the soft-fork are probably not trying to DoS us. To avoid
// disconnecting them, change the DoS level to 0.
static int DoS_UASF(int level)
{
if (gArgs.GetBoolArg("-bip148", DEFAULT_BIP148)) {
return 0;
} else {
return level;
}
}

static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex)
{
AssertLockHeld(cs_main);
Expand Down Expand Up @@ -2888,7 +2901,7 @@ static bool AcceptBlockHeader(const CBlockHeader& block, CValidationState& state
return state.DoS(10, error("%s: prev block not found", __func__), 0, "prev-blk-not-found");
pindexPrev = (*mi).second;
if (pindexPrev->nStatus & BLOCK_FAILED_MASK)
return state.DoS(100, error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");
return state.DoS(DoS_UASF(100), error("%s: prev block invalid", __func__), REJECT_INVALID, "bad-prevblk");

assert(pindexPrev);
if (fCheckpointsEnabled && !CheckIndexAgainstCheckpoint(pindexPrev, state, chainparams, hash))
Expand Down
173 changes: 173 additions & 0 deletions test/functional/bip148-segwit-uasf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
# Copyright (c) 2015-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.
"""Test the Mandatory Activation of Segregated Witness (BIP148) soft-fork logic."""

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *

## Scenario:
#
# Four miners, whose hashrate and strategies are:
#
# A ( 9%): mine longest chain, signalling segwit
# B (27%): mine longest chain, not signalling segwit
# C (37%): bip 148 enforcing, signalling segwit
# D (27%): switches from (A) to (C) during August
#
# (1, 3, 4 and 3 elevenths of total hashrate respectively; mining
# 11 blocks a day, gives 143 blocks in 13 days, just one block
# short of a retarget cycle)
#
# B, with 27% of hashrate is sufficient to block activation (which
# requires 25% on regtest)
# A+B+D has 65% of hashrate prior to D switching
# C+D has 55% of hashrate post D's switch, and since C's hashrate is larger
# than D's the original BIP-148 chain will eventually have more work
# than D's chain, forcing a reorg and eventual consensus

def bip9_blockver(*bits):
return "-blockversion=%d" % (0x20000000 + sum(1<<b for b in bits))

class BIP148Test(BitcoinTestFramework):
AUG_1 = 1501545600

def __init__(self):
super().__init__()
self.num_nodes = 8
self.setup_clean_chain = False
self.extra_args = [
[bip9_blockver(0)], # for initial CSV activation only
[bip9_blockver(1)],
[bip9_blockver()],
[bip9_blockver(1), "-bip148"],
[bip9_blockver(1)],
[], # non-mining user, restarts with -bip148 on Aug 5th
[], # well connected, non BIP148 peer
["-bip148"]] # well connected, BIP148 peer

self.day = -30 # 30 days before BIP168 activation
self.blockrate = [0,1,3,4,3,0,0]

def connect_all(self):
connect_nodes(self.nodes[6],7)
for i in range(6):
connect_nodes(self.nodes[i], 6)
connect_nodes(self.nodes[i], 7)
self.log.info("connecting %d and 6, 7" % (i))

def setup_network(self):
self.setup_nodes()
self.connect_all()
self.sync_all()

def mining(self):
time = self.AUG_1 + (self.day*24*60*60)
to_mine = self.blockrate[:]
groups = [ [self.nodes[i] for i in g] for g in self.sync_groups ]

while sum(to_mine) > 0:
for peer,blks in enumerate(to_mine):
if blks == 0: continue
set_node_times(self.nodes, time)
to_mine[peer] -= 1
self.nodes[peer].generate(1)
for g in groups:
if peer in g:
self.sync_all( [g] )
time += 10*60

self.day += 1

def bip148_restart(self, peer):
self.log.info("Restarting node %d with -bip148" % (peer,))
stop_node(self.nodes[peer], peer)
time = self.AUG_1 + (self.day*24*60*60)
self.nodes[peer] = start_node(peer, self.options.tmpdir, self.extra_args[4] + ["-bip148", "-mocktime=%d" % (time)])

n = self.nodes[peer]
blk = n.getblock(n.getbestblockhash())
worstblock = None
while blk["mediantime"] >= self.AUG_1:
if blk["version"] | 0x20000002 != blk["version"]:
worstblock = blk
blk = n.getblock(blk["previousblockhash"])
if worstblock is not None:
self.log.info("Invalidating block %d:%x:%s" % (worstblock["height"], worstblock["version"], worstblock["hash"]))
n.invalidateblock(worstblock["hash"])

self.log.info("Reconnecting nodes")
self.connect_all()
self.log.info("Removing %d from non-BIP148 sync group" % (peer,))
self.sync_groups[0].remove(peer)
self.log.info("Adding %d to BIP148 sync group" % (peer,))
self.sync_groups[1].add(peer)

def run_test(self):
cnt = self.nodes[0].getblockcount()
self.sync_groups = [set(range(self.num_nodes))]
# (if nodes aren't synced initially, the non-segwit blocks may get accidentally orphaned
# activating/locking in segwit...)

# Lock in CSV
self.nodes[0].generate(500)
if (self.nodes[0].getblockcount() != cnt + 500):
raise AssertionError("Failed to mine 500 bip9 bit 0 blocks")
cnt += 500

if get_bip9_status(self.nodes[0], 'csv')["status"] != "active":
raise AssertionError("Failed to activate OP_CSV")

self.sync_all()

for d in range(120):
swstatus = get_bip9_status(self.nodes[0], 'segwit')["status"]
if self.day == 0:
if swstatus != "started":
raise AssertionError("segwit soft-fork in state %s rather than started at day 0" % (swstatus))
self.log.info("Splitting sync groups")
self.sync_groups = [set((0,1,2,4,5,6)), set((3,7))]

self.mining()
tips = set(n.getbestblockhash() for n in self.nodes)
heights = [n.getblockcount() for n in self.nodes]
segwit = [get_bip9_status(n, 'segwit')["status"] for n in self.nodes]
connect = [len(n.getpeerinfo()) for n in self.nodes]
maxh = max(heights)
out = ["%d:" % (maxh)]
for c,h,s in zip(connect,heights,segwit):
a = "S" if s == "active" else "n"
a = "%d/%s" % (c,a)
if h < maxh:
out.append("%s%d" % (a, h-maxh))
else:
out.append(a)

self.log.info("Day %d:%s: status: %s:" % (self.day, swstatus, out))

if self.day == 7:
self.bip148_restart(5)

if self.day == 20:
self.bip148_restart(4)

for i in range(6):
if connect[i] == 0:
raise AssertionError("Peer %d has no connected peers after %d blocks" % (i,maxh))

if self.day > 5 and len(tips) == 1:
synccount += 1
else:
synccount = 0
if synccount > 5:
self.log.info("In sync for five days, consensus achieved")
break

if len(tips) > 1:
raise AssertionError("Chain split still exists at day %d" % (self.day))

return

if __name__ == '__main__':
BIP148Test().main()

0 comments on commit 7f92d07

Please sign in to comment.