17
17
from test_framework .script import (
18
18
CScript ,
19
19
OP_1 ,
20
- OP_2 ,
21
20
OP_DROP ,
22
21
OP_TRUE ,
23
22
)
36
35
# Construct 2 trivial P2SH's and the ScriptSigs that spend them
37
36
# So we can create many transactions without needing to spend
38
37
# time signing.
39
- REDEEM_SCRIPT_1 = CScript ([OP_1 , OP_DROP ])
40
- REDEEM_SCRIPT_2 = CScript ([OP_2 , OP_DROP ])
41
- P2SH_1 = script_to_p2sh_script (REDEEM_SCRIPT_1 )
42
- P2SH_2 = script_to_p2sh_script (REDEEM_SCRIPT_2 )
38
+ SCRIPT = CScript ([OP_1 , OP_DROP ])
39
+ P2SH = script_to_p2sh_script (SCRIPT )
40
+ REDEEM_SCRIPT = CScript ([OP_TRUE , SCRIPT ])
43
41
44
- # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
45
- SCRIPT_SIG = [CScript ([OP_TRUE , REDEEM_SCRIPT_1 ]), CScript ([OP_TRUE , REDEEM_SCRIPT_2 ])]
46
42
47
-
48
- def small_txpuzzle_randfee (from_node , conflist , unconflist , amount , min_fee , fee_increment ):
43
+ def small_txpuzzle_randfee (
44
+ from_node , conflist , unconflist , amount , min_fee , fee_increment
45
+ ):
49
46
"""Create and send a transaction with a random fee.
50
47
51
48
The transaction pays to a trivial P2SH script, and assumes that its inputs
@@ -66,55 +63,22 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
66
63
while total_in <= (amount + fee ) and len (conflist ) > 0 :
67
64
t = conflist .pop (0 )
68
65
total_in += t ["amount" ]
69
- tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), b"" ))
66
+ tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), REDEEM_SCRIPT ))
67
+ while total_in <= (amount + fee ) and len (unconflist ) > 0 :
68
+ t = unconflist .pop (0 )
69
+ total_in += t ["amount" ]
70
+ tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), REDEEM_SCRIPT ))
70
71
if total_in <= amount + fee :
71
- while total_in <= (amount + fee ) and len (unconflist ) > 0 :
72
- t = unconflist .pop (0 )
73
- total_in += t ["amount" ]
74
- tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), b"" ))
75
- if total_in <= amount + fee :
76
- raise RuntimeError (f"Insufficient funds: need { amount + fee } , have { total_in } " )
77
- tx .vout .append (CTxOut (int ((total_in - amount - fee ) * COIN ), P2SH_1 ))
78
- tx .vout .append (CTxOut (int (amount * COIN ), P2SH_2 ))
79
- # These transactions don't need to be signed, but we still have to insert
80
- # the ScriptSig that will satisfy the ScriptPubKey.
81
- for inp in tx .vin :
82
- inp .scriptSig = SCRIPT_SIG [inp .prevout .n ]
72
+ raise RuntimeError (f"Insufficient funds: need { amount + fee } , have { total_in } " )
73
+ tx .vout .append (CTxOut (int ((total_in - amount - fee ) * COIN ), P2SH ))
74
+ tx .vout .append (CTxOut (int (amount * COIN ), P2SH ))
83
75
txid = from_node .sendrawtransaction (hexstring = tx .serialize ().hex (), maxfeerate = 0 )
84
76
unconflist .append ({"txid" : txid , "vout" : 0 , "amount" : total_in - amount - fee })
85
77
unconflist .append ({"txid" : txid , "vout" : 1 , "amount" : amount })
86
78
87
79
return (tx .serialize ().hex (), fee )
88
80
89
81
90
- def split_inputs (from_node , txins , txouts , initial_split = False ):
91
- """Generate a lot of inputs so we can generate a ton of transactions.
92
-
93
- This function takes an input from txins, and creates and sends a transaction
94
- which splits the value into 2 outputs which are appended to txouts.
95
- Previously this was designed to be small inputs so they wouldn't have
96
- a high coin age when the notion of priority still existed."""
97
-
98
- prevtxout = txins .pop ()
99
- tx = CTransaction ()
100
- tx .vin .append (CTxIn (COutPoint (int (prevtxout ["txid" ], 16 ), prevtxout ["vout" ]), b"" ))
101
-
102
- half_change = satoshi_round (prevtxout ["amount" ] / 2 )
103
- rem_change = prevtxout ["amount" ] - half_change - Decimal ("0.00001000" )
104
- tx .vout .append (CTxOut (int (half_change * COIN ), P2SH_1 ))
105
- tx .vout .append (CTxOut (int (rem_change * COIN ), P2SH_2 ))
106
-
107
- # If this is the initial split we actually need to sign the transaction
108
- # Otherwise we just need to insert the proper ScriptSig
109
- if (initial_split ):
110
- completetx = from_node .signrawtransactionwithwallet (tx .serialize ().hex ())["hex" ]
111
- else :
112
- tx .vin [0 ].scriptSig = SCRIPT_SIG [prevtxout ["vout" ]]
113
- completetx = tx .serialize ().hex ()
114
- txid = from_node .sendrawtransaction (hexstring = completetx , maxfeerate = 0 )
115
- txouts .append ({"txid" : txid , "vout" : 0 , "amount" : half_change })
116
- txouts .append ({"txid" : txid , "vout" : 1 , "amount" : rem_change })
117
-
118
82
def check_raw_estimates (node , fees_seen ):
119
83
"""Call estimaterawfee and verify that the estimates meet certain invariants."""
120
84
@@ -125,61 +89,67 @@ def check_raw_estimates(node, fees_seen):
125
89
assert_greater_than (feerate , 0 )
126
90
127
91
if feerate + delta < min (fees_seen ) or feerate - delta > max (fees_seen ):
128
- raise AssertionError (f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )" )
92
+ raise AssertionError (
93
+ f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )"
94
+ )
95
+
129
96
130
97
def check_smart_estimates (node , fees_seen ):
131
98
"""Call estimatesmartfee and verify that the estimates meet certain invariants."""
132
99
133
100
delta = 1.0e-6 # account for rounding error
134
101
last_feerate = float (max (fees_seen ))
135
102
all_smart_estimates = [node .estimatesmartfee (i ) for i in range (1 , 26 )]
136
- mempoolMinFee = node .getmempoolinfo ()[' mempoolminfee' ]
137
- minRelaytxFee = node .getmempoolinfo ()[' minrelaytxfee' ]
103
+ mempoolMinFee = node .getmempoolinfo ()[" mempoolminfee" ]
104
+ minRelaytxFee = node .getmempoolinfo ()[" minrelaytxfee" ]
138
105
for i , e in enumerate (all_smart_estimates ): # estimate is for i+1
139
106
feerate = float (e ["feerate" ])
140
107
assert_greater_than (feerate , 0 )
141
108
assert_greater_than_or_equal (feerate , float (mempoolMinFee ))
142
109
assert_greater_than_or_equal (feerate , float (minRelaytxFee ))
143
110
144
111
if feerate + delta < min (fees_seen ) or feerate - delta > max (fees_seen ):
145
- raise AssertionError (f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )" )
112
+ raise AssertionError (
113
+ f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )"
114
+ )
146
115
if feerate - delta > last_feerate :
147
- raise AssertionError (f"Estimated fee ({ feerate } ) larger than last fee ({ last_feerate } ) for lower number of confirms" )
116
+ raise AssertionError (
117
+ f"Estimated fee ({ feerate } ) larger than last fee ({ last_feerate } ) for lower number of confirms"
118
+ )
148
119
last_feerate = feerate
149
120
150
121
if i == 0 :
151
122
assert_equal (e ["blocks" ], 2 )
152
123
else :
153
124
assert_greater_than_or_equal (i + 1 , e ["blocks" ])
154
125
126
+
155
127
def check_estimates (node , fees_seen ):
156
128
check_raw_estimates (node , fees_seen )
157
129
check_smart_estimates (node , fees_seen )
158
130
159
131
160
132
def send_tx (node , utxo , feerate ):
161
133
"""Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
162
- overhead , op , scriptsig , nseq , value , spk = 10 , 36 , 5 , 4 , 8 , 24
163
- tx_size = overhead + op + scriptsig + nseq + value + spk
164
- fee = tx_size * feerate
165
-
166
134
tx = CTransaction ()
167
- tx .vin = [CTxIn (COutPoint (int (utxo ["txid" ], 16 ), utxo ["vout" ]), SCRIPT_SIG [utxo ["vout" ]])]
168
- tx .vout = [CTxOut (int (utxo ["amount" ] * COIN ) - fee , P2SH_1 )]
169
- txid = node .sendrawtransaction (tx .serialize ().hex ())
135
+ tx .vin = [CTxIn (COutPoint (int (utxo ["txid" ], 16 ), utxo ["vout" ]), REDEEM_SCRIPT )]
136
+ tx .vout = [CTxOut (int (utxo ["amount" ] * COIN ), P2SH )]
137
+
138
+ # vbytes == bytes as we are using legacy transactions
139
+ fee = tx .get_vsize () * feerate
140
+ tx .vout [0 ].nValue -= fee
170
141
171
- return txid
142
+ return node . sendrawtransaction ( tx . serialize (). hex ())
172
143
173
144
174
145
class EstimateFeeTest (BitcoinTestFramework ):
175
146
def set_test_params (self ):
176
147
self .num_nodes = 3
177
- # mine non-standard txs (e.g. txs with "dust" outputs)
178
148
# Force fSendTrickle to true (via whitelist.noban)
179
149
self .extra_args = [
180
- [
"-acceptnonstdtxn" , "- [email protected] "],
181
- [
"-acceptnonstdtxn" , "- [email protected] ",
"-blockmaxweight=68000" ],
182
- [
"-acceptnonstdtxn" , "- [email protected] ",
"-blockmaxweight=32000" ],
150
+
151
+ [
"[email protected] " ,
"-blockmaxweight=68000" ],
152
+ [
"[email protected] " ,
"-blockmaxweight=32000" ],
183
153
]
184
154
185
155
def skip_test_if_missing_module (self ):
@@ -212,11 +182,17 @@ def transact_and_mine(self, numblocks, mining_node):
212
182
random .shuffle (self .confutxo )
213
183
for _ in range (random .randrange (100 - 50 , 100 + 50 )):
214
184
from_index = random .randint (1 , 2 )
215
- (txhex , fee ) = small_txpuzzle_randfee (self .nodes [from_index ], self .confutxo ,
216
- self .memutxo , Decimal ("0.005" ), min_fee , min_fee )
185
+ (txhex , fee ) = small_txpuzzle_randfee (
186
+ self .nodes [from_index ],
187
+ self .confutxo ,
188
+ self .memutxo ,
189
+ Decimal ("0.005" ),
190
+ min_fee ,
191
+ min_fee ,
192
+ )
217
193
tx_kbytes = (len (txhex ) // 2 ) / 1000.0
218
194
self .fees_per_kb .append (float (fee ) / tx_kbytes )
219
- self .sync_mempools (wait = .1 )
195
+ self .sync_mempools (wait = 0 .1 )
220
196
mined = mining_node .getblock (self .generate (mining_node , 1 )[0 ], True )["tx" ]
221
197
# update which txouts are confirmed
222
198
newmem = []
@@ -229,46 +205,45 @@ def transact_and_mine(self, numblocks, mining_node):
229
205
230
206
def initial_split (self , node ):
231
207
"""Split two coinbase UTxOs into many small coins"""
232
- self .txouts = []
233
- self .txouts2 = []
234
- # Split a coinbase into two transaction puzzle outputs
235
- split_inputs (node , node .listunspent (0 ), self .txouts , True )
236
-
237
- # Mine
208
+ utxo_count = 2048
209
+ self .confutxo = []
210
+ splitted_amount = Decimal ("0.04" )
211
+ fee = Decimal ("0.1" )
212
+ change = Decimal ("100" ) - splitted_amount * utxo_count - fee
213
+ tx = CTransaction ()
214
+ tx .vin = [
215
+ CTxIn (COutPoint (int (cb ["txid" ], 16 ), cb ["vout" ]))
216
+ for cb in node .listunspent ()[:2 ]
217
+ ]
218
+ tx .vout = [CTxOut (int (splitted_amount * COIN ), P2SH ) for _ in range (utxo_count )]
219
+ tx .vout .append (CTxOut (int (change * COIN ), P2SH ))
220
+ txhex = node .signrawtransactionwithwallet (tx .serialize ().hex ())["hex" ]
221
+ txid = node .sendrawtransaction (txhex )
222
+ self .confutxo = [
223
+ {"txid" : txid , "vout" : i , "amount" : splitted_amount }
224
+ for i in range (utxo_count )
225
+ ]
238
226
while len (node .getrawmempool ()) > 0 :
239
227
self .generate (node , 1 , sync_fun = self .no_op )
240
228
241
- # Repeatedly split those 2 outputs, doubling twice for each rep
242
- # Use txouts to monitor the available utxo, since these won't be tracked in wallet
243
- reps = 0
244
- while reps < 5 :
245
- # Double txouts to txouts2
246
- while len (self .txouts ) > 0 :
247
- split_inputs (node , self .txouts , self .txouts2 )
248
- while len (node .getrawmempool ()) > 0 :
249
- self .generate (node , 1 , sync_fun = self .no_op )
250
- # Double txouts2 to txouts
251
- while len (self .txouts2 ) > 0 :
252
- split_inputs (node , self .txouts2 , self .txouts )
253
- while len (node .getrawmempool ()) > 0 :
254
- self .generate (node , 1 , sync_fun = self .no_op )
255
- reps += 1
256
-
257
229
def sanity_check_estimates_range (self ):
258
230
"""Populate estimation buckets, assert estimates are in a sane range and
259
231
are strictly increasing as the target decreases."""
260
232
self .fees_per_kb = []
261
233
self .memutxo = []
262
- self .confutxo = self .txouts # Start with the set of confirmed txouts after splitting
263
234
self .log .info ("Will output estimates for 1/2/3/6/15/25 blocks" )
264
235
265
236
for _ in range (2 ):
266
- self .log .info ("Creating transactions and mining them with a block size that can't keep up" )
237
+ self .log .info (
238
+ "Creating transactions and mining them with a block size that can't keep up"
239
+ )
267
240
# Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
268
241
self .transact_and_mine (10 , self .nodes [2 ])
269
242
check_estimates (self .nodes [1 ], self .fees_per_kb )
270
243
271
- self .log .info ("Creating transactions and mining them at a block size that is just big enough" )
244
+ self .log .info (
245
+ "Creating transactions and mining them at a block size that is just big enough"
246
+ )
272
247
# Generate transactions while mining 10 more blocks, this time with node1
273
248
# which mines blocks with capacity just above the rate that transactions are being created
274
249
self .transact_and_mine (10 , self .nodes [1 ])
@@ -277,12 +252,13 @@ def sanity_check_estimates_range(self):
277
252
# Finish by mining a normal-sized block:
278
253
while len (self .nodes [1 ].getrawmempool ()) > 0 :
279
254
self .generate (self .nodes [1 ], 1 )
255
+
280
256
self .log .info ("Final estimates after emptying mempools" )
281
257
check_estimates (self .nodes [1 ], self .fees_per_kb )
282
258
283
259
def test_feerate_mempoolminfee (self ):
284
- high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )[' feerate' ]
285
- self .restart_node (1 , extra_args = [f' -minrelaytxfee={ high_val } ' ])
260
+ high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )[" feerate" ]
261
+ self .restart_node (1 , extra_args = [f" -minrelaytxfee={ high_val } " ])
286
262
check_estimates (self .nodes [1 ], self .fees_per_kb )
287
263
self .restart_node (1 )
288
264
@@ -303,7 +279,7 @@ def sanity_check_rbf_estimates(self, utxos):
303
279
utxos_to_respend = []
304
280
txids_to_replace = []
305
281
306
- assert len (utxos ) >= 250
282
+ assert_greater_than_or_equal ( len (utxos ), 250 )
307
283
for _ in range (5 ):
308
284
# Broadcast 45 low fee transactions that will need to be RBF'd
309
285
for _ in range (45 ):
@@ -315,27 +291,24 @@ def sanity_check_rbf_estimates(self, utxos):
315
291
for _ in range (5 ):
316
292
send_tx (node , utxos .pop (0 ), low_feerate )
317
293
# Mine the transactions on another node
318
- self .sync_mempools (wait = .1 , nodes = [node , miner ])
294
+ self .sync_mempools (wait = 0 .1 , nodes = [node , miner ])
319
295
for txid in txids_to_replace :
320
296
miner .prioritisetransaction (txid = txid , fee_delta = - COIN )
321
297
self .generate (miner , 1 )
322
298
# RBF the low-fee transactions
323
- while True :
324
- try :
325
- u = utxos_to_respend .pop (0 )
326
- send_tx (node , u , high_feerate )
327
- except IndexError :
328
- break
299
+ while len (utxos_to_respend ) > 0 :
300
+ u = utxos_to_respend .pop (0 )
301
+ send_tx (node , u , high_feerate )
329
302
330
303
# Mine the last replacement txs
331
- self .sync_mempools (wait = .1 , nodes = [node , miner ])
304
+ self .sync_mempools (wait = 0 .1 , nodes = [node , miner ])
332
305
self .generate (miner , 1 )
333
306
334
307
# Only 10% of the transactions were really confirmed with a low feerate,
335
308
# the rest needed to be RBF'd. We must return the 90% conf rate feerate.
336
- high_feerate_kvb = Decimal (high_feerate ) / COIN * 10 ** 3
309
+ high_feerate_kvb = Decimal (high_feerate ) / COIN * 10 ** 3
337
310
est_feerate = node .estimatesmartfee (2 )["feerate" ]
338
- assert est_feerate == high_feerate_kvb
311
+ assert_equal ( est_feerate , high_feerate_kvb )
339
312
340
313
def run_test (self ):
341
314
self .log .info ("This test is time consuming, please be patient" )
@@ -359,7 +332,9 @@ def run_test(self):
359
332
self .sanity_check_estimates_range ()
360
333
361
334
# check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
362
- self .log .info ("Test fee rate estimation after restarting node with high MempoolMinFee" )
335
+ self .log .info (
336
+ "Test fee rate estimation after restarting node with high MempoolMinFee"
337
+ )
363
338
self .test_feerate_mempoolminfee ()
364
339
365
340
self .log .info ("Restarting node with fresh estimation" )
@@ -375,9 +350,10 @@ def run_test(self):
375
350
376
351
self .log .info ("Testing that fee estimation is disabled in blocksonly." )
377
352
self .restart_node (0 , ["-blocksonly" ])
378
- assert_raises_rpc_error (- 32603 , "Fee estimation disabled" ,
379
- self .nodes [0 ].estimatesmartfee , 2 )
353
+ assert_raises_rpc_error (
354
+ - 32603 , "Fee estimation disabled" , self .nodes [0 ].estimatesmartfee , 2
355
+ )
380
356
381
357
382
- if __name__ == ' __main__' :
358
+ if __name__ == " __main__" :
383
359
EstimateFeeTest ().main ()
0 commit comments