Changeset View
Changeset View
Standalone View
Standalone View
test/functional/sendheaders-selfish-mining.py
- This file was added.
Property | Old Value | New Value |
---|---|---|
File Mode | null | 100755 |
#!/usr/bin/env python3 | |||||
# Copyright (c) 2018 The Bitcoin ABC developers | |||||
# Distributed under the MIT software license, see the accompanying | |||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | |||||
"""Discourage selfish mining tests | |||||
This test ensures that ABC gives priority to "honest" nodes when the selfish | |||||
mining behavior is obvious. The "obviousness" is determined by most accurate | |||||
timestamp at the time of receiving the block header. This selfish mining | |||||
discouragement should work regardless of if the selfish miner is broadcasting | |||||
before or after the honest miner. | |||||
""" | |||||
import time | |||||
from test_framework.blocktools import (create_block, create_coinbase) | |||||
from test_framework.mininode import ( | |||||
NetworkThread, | |||||
NodeConn, | |||||
NodeConnCB, | |||||
msg_block, | |||||
) | |||||
from test_framework.test_framework import BitcoinTestFramework | |||||
from test_framework.util import ( | |||||
assert_equal, | |||||
p2p_port, | |||||
) | |||||
class DiscourageSelfishMiningTests(BitcoinTestFramework): | |||||
def set_test_params(self): | |||||
self.setup_clean_chain = True | |||||
self.num_nodes = 2 | |||||
def get_chaintip_hashes(self, node): | |||||
hashes = [] | |||||
for tip in node.getchaintips(): | |||||
hashes.append(tip['hash']) | |||||
return hashes | |||||
def setmocktime(self, time): | |||||
for node in self.nodes: | |||||
node.setmocktime(time) | |||||
def hash_to_int(self, hash_str): | |||||
return int(hash_str, 16) | |||||
def get_tip(self, node): | |||||
return self.hash_to_int(node.getbestblockhash()) | |||||
def run_test(self): | |||||
node_helpers = NodeConnCB() | |||||
connections = [] | |||||
connections.append( | |||||
NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node_helpers)) | |||||
node_helpers.add_connection(connections[0]) | |||||
# Start up network handling in another thread. This needs to be called | |||||
# after the P2P connections have been created. | |||||
NetworkThread().start() | |||||
# wait_for_verack ensures that the P2P connection is fully up. | |||||
node_helpers.wait_for_verack() | |||||
self.nodes[0].generate(nblocks=1) | |||||
self.sync_all([self.nodes[0:1]]) | |||||
block_time = self.nodes[0].getblock( | |||||
self.nodes[0].getbestblockhash())['time'] + 1 | |||||
self.setmocktime(block_time) | |||||
common_block = self.get_tip(self.nodes[0]) | |||||
height = self.nodes[0].getblockcount() + 1 | |||||
# Test 1.a: More accurate timestamped block is retained when less | |||||
# accurate block is withheld for some time, but both blocks are | |||||
# received at the same time. | |||||
selfish_block1 = create_block( | |||||
common_block, create_coinbase(height), block_time) | |||||
selfish_block1.solve() | |||||
block_time += 3 | |||||
honest_block1 = create_block( | |||||
common_block, create_coinbase(height), block_time) | |||||
honest_block1.solve() | |||||
# Wait for enough time to pass so that the selfish block is identified | |||||
self.setmocktime(block_time) | |||||
# Selfishly mined block gets broadcasted in response to the honest block | |||||
node_helpers.send_and_ping(msg_block(honest_block1)) | |||||
node_helpers.send_and_ping(msg_block(selfish_block1)) | |||||
# node[0] should have both blocks as chaintips, with the honest block as the active tip | |||||
assert(honest_block1.hash in self.get_chaintip_hashes(self.nodes[0])) | |||||
assert(selfish_block1.hash in self.get_chaintip_hashes(self.nodes[0])) | |||||
actual_tip = self.get_tip(self.nodes[0]) | |||||
assert_equal(actual_tip, honest_block1.sha256) | |||||
block_time += 1 | |||||
height += 1 | |||||
# Test 1.b: Less accurate block's chain is extended. The chaintip | |||||
# must switch now that that chain is longest PoW. | |||||
longest_pow_block1 = create_block( | |||||
self.hash_to_int(selfish_block1.hash), create_coinbase(height), block_time) | |||||
longest_pow_block1.solve() | |||||
node_helpers.send_and_ping(msg_block(longest_pow_block1)) | |||||
print(self.hash_to_int(honest_block1.hash)) | |||||
print(self.hash_to_int(selfish_block1.hash)) | |||||
print(self.hash_to_int(self.nodes[0].getbestblockhash())) | |||||
print(self.hash_to_int(longest_pow_block1.hash)) | |||||
actual_tip = self.get_tip(self.nodes[0]) | |||||
assert_equal(actual_tip, longest_pow_block1.sha256) | |||||
# Test 2: More accurately timestamped block replaces less accurate | |||||
# block despite receiving the less accurate block first. | |||||
common_block = actual_tip | |||||
selfish_block2 = create_block( | |||||
common_block, create_coinbase(height), block_time) | |||||
selfish_block2.solve() | |||||
# Selfish miner holds onto the block for a while before broadcasting | |||||
block_time += 5 | |||||
self.setmocktime(block_time) | |||||
node_helpers.send_and_ping(msg_block(selfish_block2)) | |||||
# Honest miner finds a block shortly after the selfish miner | |||||
# (without switching chaintips) and broadcasts immediately. | |||||
block_time += 1 | |||||
self.setmocktime(block_time) | |||||
honest_block2 = create_block( | |||||
common_block, create_coinbase(height), block_time) | |||||
honest_block2.solve() | |||||
node_helpers.send_and_ping(msg_block(honest_block2)) | |||||
# Selfishly-mined block is no longer the active tip and synced to other | |||||
# node(s) as expected. | |||||
assert_equal(self.get_tip(self.nodes[1]), honest_block2.sha256) | |||||
if __name__ == '__main__': | |||||
DiscourageSelfishMiningTests().main() |