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 | |||||
deadalnix: The whole test is very confusing because way too much focused on selfish mining and not ont he… | |||||
# 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. 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 run_test(self): | |||||
node = NodeConnCB() | |||||
deadalnixUnsubmitted Done Inline ActionsPlease give it a more descriptive name. You already boostrapped 2 nodes and this is the 3rd one, so surely, it's something more specific than just THE node. deadalnix: Please give it a more descriptive name. You already boostrapped 2 nodes and this is the 3rd one… | |||||
connections = [] | |||||
connections.append( | |||||
NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], node)) | |||||
node.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.wait_for_verack() | |||||
self.nodes[0].generate(nblocks=1) | |||||
self.sync_all([self.nodes[0:1]]) | |||||
# A selfish miner sending out headers in response to an honest block | |||||
# will not get the block relayed, as the honest block was seen first. | |||||
common_block = int(self.nodes[0].getbestblockhash(), 16) | |||||
block_time = self.nodes[0].getblock( | |||||
self.nodes[0].getbestblockhash())['time'] + 1 | |||||
height = self.nodes[0].getblockcount() + 1 | |||||
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 | |||||
time.sleep(3) | |||||
deadalnixUnsubmitted Done Inline ActionsYou cannot really rely on this. But you can setmocktime the node. You can also abuse the time difference (for instance 1h) to make the whole thing very reliable. deadalnix: You cannot really rely on this. But you can setmocktime the node. You can also abuse the time… | |||||
# Selfishly mined block gets broadcasted in response to the honest block | |||||
node.send_and_ping(msg_block(honest_block1)) | |||||
node.send_and_ping(msg_block(selfish_block1)) | |||||
actual_tip = int(self.nodes[0].getbestblockhash(), 16) | |||||
deadalnixUnsubmitted Done Inline ActionsPlease assign this before using it. It makes the code easier to follow. deadalnix: Please assign this before using it. It makes the code easier to follow. | |||||
# 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])) | |||||
assert_equal(actual_tip, honest_block1.sha256) | |||||
common_block = actual_tip | |||||
block_time += 1 | |||||
height += 1 | |||||
# A selfish miner withholding a block for too long will allow it to be replaced | |||||
# as the active chain tip by an honestly-mined block that was received at a more | |||||
# accurate time compared to the block timestamp. | |||||
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 | |||||
time.sleep(5) | |||||
node.send_and_ping(msg_block(selfish_block2)) | |||||
block_time += 5 | |||||
# Honest miner finds a block shortly after the selfish miner | |||||
# (without switching chaintips) and broadcasts immediately. | |||||
honest_block2 = create_block( | |||||
common_block, create_coinbase(height), block_time) | |||||
honest_block2.solve() | |||||
node.send_and_ping(msg_block(honest_block2)) | |||||
# Selfishly-mined block is no longer the active tip | |||||
actual_tip = int(self.nodes[1].getbestblockhash(), 16) | |||||
assert_equal(actual_tip, honest_block2.sha256) | |||||
if __name__ == '__main__': | |||||
DiscourageSelfishMiningTests().main() |
The whole test is very confusing because way too much focused on selfish mining and not ont he behavior is needs to test, namely that more accurate timestamp are preferred. You should rewrite it in term of block with accurate/inaccurate timestamp. Tests cases:
1/
2/
3/ Ensure that 1 and 2 also work when the fork is more than one level deep.
It relies on sleep which is generally a bad idea, especially since it's fairly easy to spoof timestamps and mock the timestamp of the node.