40
40
REDEEM_SCRIPT = CScript ([OP_TRUE , SCRIPT ])
41
41
42
42
43
- 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
+ ):
44
46
"""Create and send a transaction with a random fee.
45
47
46
48
The transaction pays to a trivial P2SH script, and assumes that its inputs
@@ -81,34 +83,6 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
81
83
return (tx .serialize ().hex (), fee )
82
84
83
85
84
- def split_inputs (from_node , txins , txouts , initial_split = False ):
85
- """Generate a lot of inputs so we can generate a ton of transactions.
86
-
87
- This function takes an input from txins, and creates and sends a transaction
88
- which splits the value into 2 outputs which are appended to txouts.
89
- Previously this was designed to be small inputs so they wouldn't have
90
- a high coin age when the notion of priority still existed."""
91
-
92
- prevtxout = txins .pop ()
93
- tx = CTransaction ()
94
- tx .vin .append (CTxIn (COutPoint (int (prevtxout ["txid" ], 16 ), prevtxout ["vout" ]), b"" ))
95
-
96
- half_change = satoshi_round (prevtxout ["amount" ] / 2 )
97
- rem_change = prevtxout ["amount" ] - half_change - Decimal ("0.00001000" )
98
- tx .vout .append (CTxOut (int (half_change * COIN ), P2SH ))
99
- tx .vout .append (CTxOut (int (rem_change * COIN ), P2SH ))
100
-
101
- # If this is the initial split we actually need to sign the transaction
102
- # Otherwise we just need to insert the proper ScriptSig
103
- if (initial_split ):
104
- completetx = from_node .signrawtransactionwithwallet (tx .serialize ().hex ())["hex" ]
105
- else :
106
- tx .vin [0 ].scriptSig = REDEEM_SCRIPT
107
- completetx = tx .serialize ().hex ()
108
- txid = from_node .sendrawtransaction (hexstring = completetx , maxfeerate = 0 )
109
- txouts .append ({"txid" : txid , "vout" : 0 , "amount" : half_change })
110
- txouts .append ({"txid" : txid , "vout" : 1 , "amount" : rem_change })
111
-
112
86
def check_raw_estimates (node , fees_seen ):
113
87
"""Call estimaterawfee and verify that the estimates meet certain invariants."""
114
88
@@ -119,33 +93,41 @@ def check_raw_estimates(node, fees_seen):
119
93
assert_greater_than (feerate , 0 )
120
94
121
95
if feerate + delta < min (fees_seen ) or feerate - delta > max (fees_seen ):
122
- raise AssertionError (f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )" )
96
+ raise AssertionError (
97
+ f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )"
98
+ )
99
+
123
100
124
101
def check_smart_estimates (node , fees_seen ):
125
102
"""Call estimatesmartfee and verify that the estimates meet certain invariants."""
126
103
127
104
delta = 1.0e-6 # account for rounding error
128
105
last_feerate = float (max (fees_seen ))
129
106
all_smart_estimates = [node .estimatesmartfee (i ) for i in range (1 , 26 )]
130
- mempoolMinFee = node .getmempoolinfo ()[' mempoolminfee' ]
131
- minRelaytxFee = node .getmempoolinfo ()[' minrelaytxfee' ]
107
+ mempoolMinFee = node .getmempoolinfo ()[" mempoolminfee" ]
108
+ minRelaytxFee = node .getmempoolinfo ()[" minrelaytxfee" ]
132
109
for i , e in enumerate (all_smart_estimates ): # estimate is for i+1
133
110
feerate = float (e ["feerate" ])
134
111
assert_greater_than (feerate , 0 )
135
112
assert_greater_than_or_equal (feerate , float (mempoolMinFee ))
136
113
assert_greater_than_or_equal (feerate , float (minRelaytxFee ))
137
114
138
115
if feerate + delta < min (fees_seen ) or feerate - delta > max (fees_seen ):
139
- raise AssertionError (f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )" )
116
+ raise AssertionError (
117
+ f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )"
118
+ )
140
119
if feerate - delta > last_feerate :
141
- raise AssertionError (f"Estimated fee ({ feerate } ) larger than last fee ({ last_feerate } ) for lower number of confirms" )
120
+ raise AssertionError (
121
+ f"Estimated fee ({ feerate } ) larger than last fee ({ last_feerate } ) for lower number of confirms"
122
+ )
142
123
last_feerate = feerate
143
124
144
125
if i == 0 :
145
126
assert_equal (e ["blocks" ], 2 )
146
127
else :
147
128
assert_greater_than_or_equal (i + 1 , e ["blocks" ])
148
129
130
+
149
131
def check_estimates (node , fees_seen ):
150
132
check_raw_estimates (node , fees_seen )
151
133
check_smart_estimates (node , fees_seen )
@@ -206,11 +188,17 @@ def transact_and_mine(self, numblocks, mining_node):
206
188
random .shuffle (self .confutxo )
207
189
for _ in range (random .randrange (100 - 50 , 100 + 50 )):
208
190
from_index = random .randint (1 , 2 )
209
- (txhex , fee ) = small_txpuzzle_randfee (self .nodes [from_index ], self .confutxo ,
210
- self .memutxo , Decimal ("0.005" ), min_fee , min_fee )
191
+ (txhex , fee ) = small_txpuzzle_randfee (
192
+ self .nodes [from_index ],
193
+ self .confutxo ,
194
+ self .memutxo ,
195
+ Decimal ("0.005" ),
196
+ min_fee ,
197
+ min_fee ,
198
+ )
211
199
tx_kbytes = (len (txhex ) // 2 ) / 1000.0
212
200
self .fees_per_kb .append (float (fee ) / tx_kbytes )
213
- self .sync_mempools (wait = .1 )
201
+ self .sync_mempools (wait = 0 .1 )
214
202
mined = mining_node .getblock (self .generate (mining_node , 1 )[0 ], True )["tx" ]
215
203
# update which txouts are confirmed
216
204
newmem = []
@@ -223,46 +211,45 @@ def transact_and_mine(self, numblocks, mining_node):
223
211
224
212
def initial_split (self , node ):
225
213
"""Split two coinbase UTxOs into many small coins"""
226
- self .txouts = []
227
- self .txouts2 = []
228
- # Split a coinbase into two transaction puzzle outputs
229
- split_inputs (node , node .listunspent (0 ), self .txouts , True )
230
-
231
- # Mine
214
+ utxo_count = 2048
215
+ self .confutxo = []
216
+ splitted_amount = Decimal ("0.04" )
217
+ fee = Decimal ("0.1" )
218
+ change = Decimal ("100" ) - splitted_amount * utxo_count - fee
219
+ tx = CTransaction ()
220
+ tx .vin = [
221
+ CTxIn (COutPoint (int (cb ["txid" ], 16 ), cb ["vout" ]), b"" )
222
+ for cb in node .listunspent ()[:2 ]
223
+ ]
224
+ tx .vout = [CTxOut (int (splitted_amount * COIN ), P2SH ) for _ in range (utxo_count )]
225
+ tx .vout .append (CTxOut (int (change * COIN ), P2SH ))
226
+ txhex = node .signrawtransactionwithwallet (tx .serialize ().hex ())["hex" ]
227
+ txid = node .sendrawtransaction (txhex )
228
+ self .confutxo = [
229
+ {"txid" : txid , "vout" : i , "amount" : splitted_amount }
230
+ for i in range (utxo_count )
231
+ ]
232
232
while len (node .getrawmempool ()) > 0 :
233
233
self .generate (node , 1 , sync_fun = self .no_op )
234
234
235
- # Repeatedly split those 2 outputs, doubling twice for each rep
236
- # Use txouts to monitor the available utxo, since these won't be tracked in wallet
237
- reps = 0
238
- while reps < 5 :
239
- # Double txouts to txouts2
240
- while len (self .txouts ) > 0 :
241
- split_inputs (node , self .txouts , self .txouts2 )
242
- while len (node .getrawmempool ()) > 0 :
243
- self .generate (node , 1 , sync_fun = self .no_op )
244
- # Double txouts2 to txouts
245
- while len (self .txouts2 ) > 0 :
246
- split_inputs (node , self .txouts2 , self .txouts )
247
- while len (node .getrawmempool ()) > 0 :
248
- self .generate (node , 1 , sync_fun = self .no_op )
249
- reps += 1
250
-
251
235
def sanity_check_estimates_range (self ):
252
236
"""Populate estimation buckets, assert estimates are in a sane range and
253
237
are strictly increasing as the target decreases."""
254
238
self .fees_per_kb = []
255
239
self .memutxo = []
256
- self .confutxo = self .txouts # Start with the set of confirmed txouts after splitting
257
240
self .log .info ("Will output estimates for 1/2/3/6/15/25 blocks" )
258
241
259
242
for _ in range (2 ):
260
- self .log .info ("Creating transactions and mining them with a block size that can't keep up" )
243
+ self .log .info (
244
+ "Creating transactions and mining them with a block size that can't keep up"
245
+ )
261
246
# Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
262
247
self .transact_and_mine (10 , self .nodes [2 ])
263
248
check_estimates (self .nodes [1 ], self .fees_per_kb )
264
249
265
- self .log .info ("Creating transactions and mining them at a block size that is just big enough" )
250
+ self .log .info (
251
+ "Creating transactions and mining them at a block size that is just big enough"
252
+ )
266
253
# Generate transactions while mining 10 more blocks, this time with node1
267
254
# which mines blocks with capacity just above the rate that transactions are being created
268
255
self .transact_and_mine (10 , self .nodes [1 ])
@@ -271,12 +258,13 @@ def sanity_check_estimates_range(self):
271
258
# Finish by mining a normal-sized block:
272
259
while len (self .nodes [1 ].getrawmempool ()) > 0 :
273
260
self .generate (self .nodes [1 ], 1 )
261
+
274
262
self .log .info ("Final estimates after emptying mempools" )
275
263
check_estimates (self .nodes [1 ], self .fees_per_kb )
276
264
277
265
def test_feerate_mempoolminfee (self ):
278
- high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )[' feerate' ]
279
- self .restart_node (1 , extra_args = [f' -minrelaytxfee={ high_val } ' ])
266
+ high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )[" feerate" ]
267
+ self .restart_node (1 , extra_args = [f" -minrelaytxfee={ high_val } " ])
280
268
check_estimates (self .nodes [1 ], self .fees_per_kb )
281
269
self .restart_node (1 )
282
270
@@ -309,7 +297,7 @@ def sanity_check_rbf_estimates(self, utxos):
309
297
for _ in range (5 ):
310
298
send_tx (node , utxos .pop (0 ), low_feerate )
311
299
# Mine the transactions on another node
312
- self .sync_mempools (wait = .1 , nodes = [node , miner ])
300
+ self .sync_mempools (wait = 0 .1 , nodes = [node , miner ])
313
301
for txid in txids_to_replace :
314
302
miner .prioritisetransaction (txid = txid , fee_delta = - COIN )
315
303
self .generate (miner , 1 )
@@ -322,12 +310,12 @@ def sanity_check_rbf_estimates(self, utxos):
322
310
break
323
311
324
312
# Mine the last replacement txs
325
- self .sync_mempools (wait = .1 , nodes = [node , miner ])
313
+ self .sync_mempools (wait = 0 .1 , nodes = [node , miner ])
326
314
self .generate (miner , 1 )
327
315
328
316
# Only 10% of the transactions were really confirmed with a low feerate,
329
317
# the rest needed to be RBF'd. We must return the 90% conf rate feerate.
330
- high_feerate_kvb = Decimal (high_feerate ) / COIN * 10 ** 3
318
+ high_feerate_kvb = Decimal (high_feerate ) / COIN * 10 ** 3
331
319
est_feerate = node .estimatesmartfee (2 )["feerate" ]
332
320
assert est_feerate == high_feerate_kvb
333
321
@@ -353,7 +341,9 @@ def run_test(self):
353
341
self .sanity_check_estimates_range ()
354
342
355
343
# check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
356
- self .log .info ("Test fee rate estimation after restarting node with high MempoolMinFee" )
344
+ self .log .info (
345
+ "Test fee rate estimation after restarting node with high MempoolMinFee"
346
+ )
357
347
self .test_feerate_mempoolminfee ()
358
348
359
349
self .log .info ("Restarting node with fresh estimation" )
@@ -369,9 +359,10 @@ def run_test(self):
369
359
370
360
self .log .info ("Testing that fee estimation is disabled in blocksonly." )
371
361
self .restart_node (0 , ["-blocksonly" ])
372
- assert_raises_rpc_error (- 32603 , "Fee estimation disabled" ,
373
- self .nodes [0 ].estimatesmartfee , 2 )
362
+ assert_raises_rpc_error (
363
+ - 32603 , "Fee estimation disabled" , self .nodes [0 ].estimatesmartfee , 2
364
+ )
374
365
375
366
376
- if __name__ == ' __main__' :
367
+ if __name__ == " __main__" :
377
368
EstimateFeeTest ().main ()
0 commit comments