0010eedd12
Fixes a race condition in p2p-policy tests by waiting for a reject message rather than assuming it was received before a pong message
210 lines
7.9 KiB
Python
210 lines
7.9 KiB
Python
#!/usr/bin/env python3
|
|
# Copyright (c) 2021 The Dogecoin Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
"""P2P Policies QA test
|
|
|
|
# Tests relay and mempool acceptance policies from p2p perspective
|
|
"""
|
|
|
|
from test_framework.mininode import *
|
|
from test_framework.test_framework import BitcoinTestFramework
|
|
from test_framework.util import *
|
|
|
|
class TestNode(NodeConnCB):
|
|
def __init__(self):
|
|
NodeConnCB.__init__(self)
|
|
self.connection = None
|
|
self.ping_counter = 1
|
|
self.last_pong = msg_pong()
|
|
self.txinvs = {}
|
|
self.rejects = []
|
|
|
|
def add_connection(self, conn):
|
|
self.connection = conn
|
|
|
|
# Track transaction invs for wait_for_tx_inv
|
|
def on_inv(self, conn, message):
|
|
for i in message.inv:
|
|
if (i.type == 1):
|
|
self.txinvs[format(i.hash, '064x')] = True
|
|
|
|
# Track pongs for sync_with_ping
|
|
def on_pong(self, conn, message):
|
|
self.last_pong = message
|
|
|
|
# Track reject messages
|
|
def on_reject(self, conn, message):
|
|
self.rejects.append(message)
|
|
|
|
# wait for a rejection message
|
|
def wait_for_reject(self, num_rejects=None):
|
|
if num_rejects is None:
|
|
num_rejects = len(self.rejects)
|
|
def reject_received():
|
|
return len(self.rejects) > num_rejects
|
|
return wait_until(reject_received, timeout=10)
|
|
|
|
# wait for verack to make sure the node accepts our connection attempt
|
|
def wait_for_verack(self):
|
|
def veracked():
|
|
return self.verack_received
|
|
return wait_until(veracked, timeout=10)
|
|
|
|
# Wait until we have received an inv of a specific tx
|
|
def wait_for_tx_inv(self, hash, timeout=30):
|
|
def have_received_tx_inv():
|
|
try:
|
|
return self.txinvs[hash]
|
|
except KeyError as e:
|
|
return False
|
|
return wait_until(have_received_tx_inv, timeout=timeout)
|
|
|
|
# Send a ping message and wait until we get the pong message back
|
|
def sync_with_ping(self, timeout=30):
|
|
def received_pong():
|
|
return (self.last_pong.nonce == self.ping_counter)
|
|
self.connection.send_message(msg_ping(nonce=self.ping_counter))
|
|
success = wait_until(received_pong, timeout=timeout)
|
|
self.ping_counter += 1
|
|
return success
|
|
|
|
class P2PPolicyTests(BitcoinTestFramework):
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setup_clean_chain = True
|
|
self.num_nodes = 1
|
|
self.utxo = []
|
|
|
|
# a private key and corresponding address and p2pkh output script
|
|
self.srcPrivKey = "cRhVU6TU1qHfRg3ee59yqg7ifhREKPLPPk8eccrrAEEY74bY1dCY"
|
|
self.srcAddr = "mmMP9oKFdADezYzduwJFcLNmmi8JHUKdx9"
|
|
self.srcOutScript = "76a91440015860f45d48eeeb2224dce3ad94ba91763e1e88ac"
|
|
|
|
# valid regtest address that no one has the key to
|
|
self.tgtAddr = "mkwDHkWXF8x6aFtdGVm5E9PVC7yPY8cb4r"
|
|
|
|
def create_testnode(self, node_idx=0):
|
|
node = TestNode()
|
|
conn = NodeConn('127.0.0.1', p2p_port(node_idx), self.nodes[node_idx], node)
|
|
node.add_connection(conn)
|
|
return node
|
|
|
|
def setup_network(self):
|
|
self.nodes = []
|
|
|
|
# a Dogecoin Core node that behaves similar to mainnet policies
|
|
self.nodes.append(start_node(0, self.options.tmpdir, ["-debug", "-acceptnonstdtxn=0"]))
|
|
|
|
# custom testnodes
|
|
self.sendNode = self.create_testnode() # to send tx from
|
|
self.recvNode = self.create_testnode() # to check relay from
|
|
|
|
# start networking and handshake the mininodes
|
|
NetworkThread().start()
|
|
self.sendNode.wait_for_verack()
|
|
self.recvNode.wait_for_verack()
|
|
|
|
def run_test(self):
|
|
self.nodes[0].generate(101)
|
|
|
|
### test constants ###
|
|
koinu = Decimal("0.00000001") # 1 Koinu expressed in DOGE
|
|
ten = Decimal("10.0") # uniform 10 DOGE seed moneys
|
|
|
|
### parameters from fee policy ###
|
|
relay_fee = Decimal("0.001") # DEFAULT_MIN_RELAY_TX_FEE
|
|
soft_dust_limit = Decimal("0.01") # DEFAULT_DUST_LIMIT
|
|
|
|
relay_fee_per_byte = relay_fee / 1000
|
|
|
|
# create a bunch of UTXO with seed money from the Dogecoin Core wallet
|
|
for i in range(10):
|
|
inputs = [self.nodes[0].listunspent()[0]]
|
|
outputs = { self.srcAddr : ten }
|
|
tx = self.nodes[0].createrawtransaction(inputs, outputs)
|
|
signed = self.nodes[0].signrawtransaction(tx)
|
|
txid = self.nodes[0].sendrawtransaction(signed['hex'], True)
|
|
self.utxo.append(txid)
|
|
self.nodes[0].generate(1)
|
|
|
|
# test legacy output of 1 DOGE output and 1 DOGE fee
|
|
output = { self.tgtAddr : 1, self.srcAddr: 8 }
|
|
self.run_relay_test(output, None)
|
|
|
|
# test exact relay fee rate
|
|
output = { self.tgtAddr: ten - relay_fee_per_byte * 192}
|
|
tx = self.run_relay_test(output, None)
|
|
|
|
# test too low relay fee rate
|
|
output = { self.tgtAddr: ten - relay_fee_per_byte * 191 + koinu }
|
|
tx = self.run_relay_test(output, 66) # 66 = too low fee
|
|
|
|
# test exact dust limit
|
|
change = ten - soft_dust_limit - relay_fee_per_byte * 226
|
|
output = { self.tgtAddr : soft_dust_limit, self.srcAddr: change}
|
|
self.run_relay_test(output, None)
|
|
|
|
# test soft dust limit with sufficient fee
|
|
amount = soft_dust_limit - koinu
|
|
change = ten - amount - relay_fee_per_byte * 226 - soft_dust_limit
|
|
output = { self.tgtAddr : amount, self.srcAddr: change }
|
|
self.run_relay_test(output, None)
|
|
|
|
# test soft dust limit with insufficient fee
|
|
amount = soft_dust_limit - koinu
|
|
change = ten - amount - relay_fee_per_byte * 225 - soft_dust_limit + koinu
|
|
output = { self.tgtAddr : amount, self.srcAddr: change }
|
|
self.run_relay_test(output, 66)
|
|
|
|
# test a 1 koinu output with sufficient fee
|
|
amount = koinu
|
|
change = ten - amount - relay_fee_per_byte * 226 - soft_dust_limit
|
|
output = { self.tgtAddr : amount, self.srcAddr: change }
|
|
self.run_relay_test(output, 64) # 64 = dust
|
|
|
|
# test a 1 koinu output with insufficient fee
|
|
amount = koinu
|
|
change = ten - amount - relay_fee_per_byte * 225 - soft_dust_limit + koinu
|
|
output = { self.tgtAddr : amount, self.srcAddr: change }
|
|
self.run_relay_test(output, 64)
|
|
|
|
|
|
# test mempool acceptance and relay outcomes
|
|
def run_relay_test(self, output, expected_reject_code):
|
|
num_rejects = len(self.sendNode.rejects)
|
|
|
|
tx = self.spend_utxo(output)
|
|
self.sendNode.sync_with_ping(timeout=10)
|
|
|
|
if (expected_reject_code is None):
|
|
# test that the tx got relayed
|
|
assert_equal(self.recvNode.wait_for_tx_inv(tx.hash), True)
|
|
assert_equal(len(self.sendNode.rejects), num_rejects)
|
|
else:
|
|
# wait until there was a rejection received with the correct code
|
|
assert_equal(self.sendNode.wait_for_reject(num_rejects), True)
|
|
assert_equal(self.sendNode.rejects[-1].code, expected_reject_code)
|
|
|
|
return tx
|
|
|
|
# spend seed money with a key not in the Dogecoin Core wallet.
|
|
def spend_utxo(self, output):
|
|
# construct the transaction using Dogecoin Core raw tx APIs
|
|
input = [{ "txid": self.utxo.pop(), "vout": 0, "scriptPubKey": self.srcOutScript }]
|
|
rawtx = self.nodes[0].createrawtransaction(input, output)
|
|
signed_tx = self.nodes[0].signrawtransaction(rawtx, input, [self.srcPrivKey])
|
|
|
|
# import the signed tx into a format the mininode client understands
|
|
# and send the tx from there rather than from Dogecoin Core, to test
|
|
# mempool acceptance as it would happen on mainnet: through relay
|
|
tx = FromHex(CTransaction(), signed_tx['hex'])
|
|
tx.rehash()
|
|
self.sendNode.connection.send_message(msg_tx(tx))
|
|
|
|
return tx
|
|
|
|
if __name__ == '__main__':
|
|
P2PPolicyTests().main()
|