forked from dogecoin/dogecoin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathp2p-policy.py
218 lines (175 loc) · 8.44 KB
/
p2p-policy.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python3
# Copyright (c) 2021-2022 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=120):
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(20):
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)
# test exact relay fee rate
output = { self.tgtAddr: ten - relay_fee_per_byte * 192}
tx = self.run_relay_test(output, None, 192)
# test too low relay fee rate
output = { self.tgtAddr: ten - relay_fee_per_byte * 192 + koinu }
tx = self.run_relay_test(output, 66, 192) # 66 = too low fee
# test exact dust limit
change = ten - soft_dust_limit - relay_fee_per_byte * 225
output = { self.tgtAddr : soft_dust_limit, self.srcAddr: change}
self.run_relay_test(output, None, 225)
# test soft dust limit with sufficient fee
amount = soft_dust_limit - koinu
change = ten - amount - relay_fee_per_byte * 225 - soft_dust_limit
output = { self.tgtAddr : amount, self.srcAddr: change }
self.run_relay_test(output, None, 225)
# 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, 225)
# test a 1 koinu output with sufficient fee
amount = koinu
change = ten - amount - relay_fee_per_byte * 225 - soft_dust_limit
output = { self.tgtAddr : amount, self.srcAddr: change }
self.run_relay_test(output, 64, 225) # 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, 225)
# test mempool acceptance and relay outcomes
def run_relay_test(self, output, expected_reject_code=None, expected_size=None):
num_rejects = len(self.sendNode.rejects)
tx = self.spend_utxo(output, expected_size)
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, expected_size, retries=0):
# 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])
# in case we have a smaller or larger tx due to unexpected R size,
# try again, with a limit of 20 times
if (expected_size is not None and len(signed_tx['hex']) != expected_size * 2):
self.utxo.insert(0, input[0]['txid'])
retries += 1
if retries >= 20:
raise AssertionError("Too many retries")
return self.spend_utxo(output, expected_size, retries)
# 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()