2017-09-06 18:49:43 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# Copyright (c) 2017 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 logic for setting nMinimumChainWork on command line.
|
|
|
|
|
|
|
|
Nodes don't consider themselves out of "initial block download" until
|
|
|
|
their active chain has more work than nMinimumChainWork.
|
|
|
|
|
|
|
|
Nodes don't download blocks from a peer unless the peer's best known block
|
|
|
|
has more work than nMinimumChainWork.
|
|
|
|
|
|
|
|
While in initial block download, nodes won't relay blocks to their peers, so
|
|
|
|
test that this parameter functions as intended by verifying that block relay
|
|
|
|
only succeeds past a given node once its nMinimumChainWork has been exceeded.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
|
|
from test_framework.util import sync_blocks, connect_nodes, assert_equal
|
|
|
|
|
|
|
|
# 2 hashes required per regtest block (with no difficulty adjustment)
|
|
|
|
REGTEST_WORK_PER_BLOCK = 2
|
|
|
|
|
|
|
|
class MinimumChainWorkTest(BitcoinTestFramework):
|
|
|
|
def set_test_params(self):
|
|
|
|
self.setup_clean_chain = True
|
|
|
|
self.num_nodes = 3
|
Merge #11490: Disconnect from outbound peers with bad headers chains
e065249 Add unit test for outbound peer eviction (Suhas Daftuar)
5a6d00c Permit disconnection of outbound peers on bad/slow chains (Suhas Daftuar)
c60fd71 Disconnecting from bad outbound peers in IBD (Suhas Daftuar)
Pull request description:
The first commit will disconnect an outbound peer that serves us a headers chain with insufficient work while we're in IBD.
The second commit introduces a way to disconnect outbound peers whose chains fall out of sync with ours:
For a given outbound peer, we check whether their best known block (which is known from the blocks they announce to us) has at least as much work as our tip. If it doesn't, we set a 20 minute timeout, and if we still haven't heard about a block with as much work as our tip had when we set the timeout, then we send a single getheaders message, and wait 2 more minutes. If after two minutes their best known block has insufficient work, we disconnect that peer.
We protect 4 of our outbound peers (who provide some "good" headers chains, ie a chain with at least as much work as our tip at some point) from being subject to this logic, to prevent excessive network topology changes as a result of this algorithm, while still ensuring that we have a reasonable number of nodes not known to be on bogus chains.
We also don't require our peers to be on the same chain as us, to prevent accidental partitioning of the network in the event of a chain split. Note that if our peers are ever on a more work chain than our tip, then we will download and validate it, and then either reorg to it, or learn of a consensus incompatibility with that peer and disconnect. This PR is designed to protect against peers that are on a less work chain which we may never try to download and validate.
Tree-SHA512: 2e0169a1dd8a7fb95980573ac4a201924bffdd724c19afcab5efcef076fdbe1f2cec7dc5f5d7e0a6327216f56d3828884f73642e00c8534b56ec2bb4c854a656
2017-10-26 21:53:19 +02:00
|
|
|
|
2017-09-06 18:49:43 +02:00
|
|
|
self.extra_args = [[], ["-minimumchainwork=0x65"], ["-minimumchainwork=0x65"]]
|
|
|
|
self.node_min_work = [0, 101, 101]
|
|
|
|
|
|
|
|
def setup_network(self):
|
|
|
|
# This test relies on the chain setup being:
|
|
|
|
# node0 <- node1 <- node2
|
|
|
|
# Before leaving IBD, nodes prefer to download blocks from outbound
|
|
|
|
# peers, so ensure that we're mining on an outbound peer and testing
|
|
|
|
# block relay to inbound peers.
|
|
|
|
self.setup_nodes()
|
|
|
|
for i in range(self.num_nodes-1):
|
|
|
|
connect_nodes(self.nodes[i+1], i)
|
|
|
|
|
|
|
|
def run_test(self):
|
|
|
|
# Start building a chain on node0. node2 shouldn't be able to sync until node1's
|
|
|
|
# minchainwork is exceeded
|
|
|
|
starting_chain_work = REGTEST_WORK_PER_BLOCK # Genesis block's work
|
|
|
|
self.log.info("Testing relay across node %d (minChainWork = %d)", 1, self.node_min_work[1])
|
|
|
|
|
|
|
|
starting_blockcount = self.nodes[2].getblockcount()
|
|
|
|
|
|
|
|
num_blocks_to_generate = int((self.node_min_work[1] - starting_chain_work) / REGTEST_WORK_PER_BLOCK)
|
|
|
|
self.log.info("Generating %d blocks on node0", num_blocks_to_generate)
|
|
|
|
hashes = self.nodes[0].generate(num_blocks_to_generate)
|
|
|
|
|
|
|
|
self.log.info("Node0 current chain work: %s", self.nodes[0].getblockheader(hashes[-1])['chainwork'])
|
|
|
|
|
|
|
|
# Sleep a few seconds and verify that node2 didn't get any new blocks
|
|
|
|
# or headers. We sleep, rather than sync_blocks(node0, node1) because
|
|
|
|
# it's reasonable either way for node1 to get the blocks, or not get
|
|
|
|
# them (since they're below node1's minchainwork).
|
|
|
|
time.sleep(3)
|
|
|
|
|
|
|
|
self.log.info("Verifying node 2 has no more blocks than before")
|
|
|
|
self.log.info("Blockcounts: %s", [n.getblockcount() for n in self.nodes])
|
|
|
|
# Node2 shouldn't have any new headers yet, because node1 should not
|
|
|
|
# have relayed anything.
|
|
|
|
assert_equal(len(self.nodes[2].getchaintips()), 1)
|
|
|
|
assert_equal(self.nodes[2].getchaintips()[0]['height'], 0)
|
|
|
|
|
|
|
|
assert self.nodes[1].getbestblockhash() != self.nodes[0].getbestblockhash()
|
|
|
|
assert_equal(self.nodes[2].getblockcount(), starting_blockcount)
|
|
|
|
|
|
|
|
self.log.info("Generating one more block")
|
|
|
|
self.nodes[0].generate(1)
|
|
|
|
|
|
|
|
self.log.info("Verifying nodes are all synced")
|
Merge #11490: Disconnect from outbound peers with bad headers chains
e065249 Add unit test for outbound peer eviction (Suhas Daftuar)
5a6d00c Permit disconnection of outbound peers on bad/slow chains (Suhas Daftuar)
c60fd71 Disconnecting from bad outbound peers in IBD (Suhas Daftuar)
Pull request description:
The first commit will disconnect an outbound peer that serves us a headers chain with insufficient work while we're in IBD.
The second commit introduces a way to disconnect outbound peers whose chains fall out of sync with ours:
For a given outbound peer, we check whether their best known block (which is known from the blocks they announce to us) has at least as much work as our tip. If it doesn't, we set a 20 minute timeout, and if we still haven't heard about a block with as much work as our tip had when we set the timeout, then we send a single getheaders message, and wait 2 more minutes. If after two minutes their best known block has insufficient work, we disconnect that peer.
We protect 4 of our outbound peers (who provide some "good" headers chains, ie a chain with at least as much work as our tip at some point) from being subject to this logic, to prevent excessive network topology changes as a result of this algorithm, while still ensuring that we have a reasonable number of nodes not known to be on bogus chains.
We also don't require our peers to be on the same chain as us, to prevent accidental partitioning of the network in the event of a chain split. Note that if our peers are ever on a more work chain than our tip, then we will download and validate it, and then either reorg to it, or learn of a consensus incompatibility with that peer and disconnect. This PR is designed to protect against peers that are on a less work chain which we may never try to download and validate.
Tree-SHA512: 2e0169a1dd8a7fb95980573ac4a201924bffdd724c19afcab5efcef076fdbe1f2cec7dc5f5d7e0a6327216f56d3828884f73642e00c8534b56ec2bb4c854a656
2017-10-26 21:53:19 +02:00
|
|
|
|
|
|
|
# Because nodes in regtest are all manual connections (eg using
|
|
|
|
# addnode), node1 should not have disconnected node0. If not for that,
|
|
|
|
# we'd expect node1 to have disconnected node0 for serving an
|
|
|
|
# insufficient work chain, in which case we'd need to reconnect them to
|
|
|
|
# continue the test.
|
|
|
|
|
2017-09-06 18:49:43 +02:00
|
|
|
self.sync_all()
|
|
|
|
self.log.info("Blockcounts: %s", [n.getblockcount() for n in self.nodes])
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
MinimumChainWorkTest().main()
|