1616from test_framework .script import (
1717 CScript ,
1818 OP_1 ,
19- OP_2 ,
2019 OP_DROP ,
2120 OP_TRUE ,
2221)
3534# Construct 2 trivial P2SH's and the ScriptSigs that spend them
3635# So we can create many transactions without needing to spend
3736# time signing.
38- REDEEM_SCRIPT_1 = CScript ([OP_1 , OP_DROP ])
39- REDEEM_SCRIPT_2 = CScript ([OP_2 , OP_DROP ])
40- P2SH_1 = script_to_p2sh_script (REDEEM_SCRIPT_1 )
41- P2SH_2 = script_to_p2sh_script (REDEEM_SCRIPT_2 )
37+ SCRIPT = CScript ([OP_1 , OP_DROP ])
38+ P2SH = script_to_p2sh_script (SCRIPT )
39+ REDEEM_SCRIPT = CScript ([OP_TRUE , SCRIPT ])
4240
43- # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
44- SCRIPT_SIG = [CScript ([OP_TRUE , REDEEM_SCRIPT_1 ]), CScript ([OP_TRUE , REDEEM_SCRIPT_2 ])]
4541
46-
47- def small_txpuzzle_randfee (from_node , conflist , unconflist , amount , min_fee , fee_increment ):
42+ def small_txpuzzle_randfee (
43+ from_node , conflist , unconflist , amount , min_fee , fee_increment
44+ ):
4845 """Create and send a transaction with a random fee.
4946
5047 The transaction pays to a trivial P2SH script, and assumes that its inputs
@@ -65,56 +62,22 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
6562 while total_in <= (amount + fee ) and len (conflist ) > 0 :
6663 t = conflist .pop (0 )
6764 total_in += t ["amount" ]
68- tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), b"" ))
65+ tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), REDEEM_SCRIPT ))
66+ while total_in <= (amount + fee ) and len (unconflist ) > 0 :
67+ t = unconflist .pop (0 )
68+ total_in += t ["amount" ]
69+ tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), REDEEM_SCRIPT ))
6970 if total_in <= amount + fee :
70- while total_in <= (amount + fee ) and len (unconflist ) > 0 :
71- t = unconflist .pop (0 )
72- total_in += t ["amount" ]
73- tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), b"" ))
74- if total_in <= amount + fee :
75- raise RuntimeError (f"Insufficient funds: need { amount + fee } , have { total_in } " )
76- tx .vout .append (CTxOut (int ((total_in - amount - fee ) * COIN ), P2SH_1 ))
77- tx .vout .append (CTxOut (int (amount * COIN ), P2SH_2 ))
78- # These transactions don't need to be signed, but we still have to insert
79- # the ScriptSig that will satisfy the ScriptPubKey.
80- for inp in tx .vin :
81- inp .scriptSig = SCRIPT_SIG [inp .prevout .n ]
71+ raise RuntimeError (f"Insufficient funds: need { amount + fee } , have { total_in } " )
72+ tx .vout .append (CTxOut (int ((total_in - amount - fee ) * COIN ), P2SH ))
73+ tx .vout .append (CTxOut (int (amount * COIN ), P2SH ))
8274 txid = from_node .sendrawtransaction (hexstring = tx .serialize ().hex (), maxfeerate = 0 )
8375 unconflist .append ({"txid" : txid , "vout" : 0 , "amount" : total_in - amount - fee })
8476 unconflist .append ({"txid" : txid , "vout" : 1 , "amount" : amount })
8577
8678 return (tx .serialize ().hex (), fee )
8779
8880
89- def split_inputs (from_node , txins , txouts , initial_split = False ):
90- """Generate a lot of inputs so we can generate a ton of transactions.
91-
92- This function takes an input from txins, and creates and sends a transaction
93- which splits the value into 2 outputs which are appended to txouts.
94- Previously this was designed to be small inputs so they wouldn't have
95- a high coin age when the notion of priority still existed."""
96-
97- prevtxout = txins .pop ()
98- tx = CTransaction ()
99- tx .vin .append (CTxIn (COutPoint (int (prevtxout ["txid" ], 16 ), prevtxout ["vout" ]), b"" ))
100-
101- half_change = satoshi_round (prevtxout ["amount" ] / 2 )
102- rem_change = prevtxout ["amount" ] - half_change - Decimal ("0.00001000" )
103- tx .vout .append (CTxOut (int (half_change * COIN ), P2SH_1 ))
104- tx .vout .append (CTxOut (int (rem_change * COIN ), P2SH_2 ))
105-
106- # If this is the initial split we actually need to sign the transaction
107- # Otherwise we just need to insert the proper ScriptSig
108- if (initial_split ):
109- completetx = from_node .signrawtransactionwithwallet (tx .serialize ().hex ())["hex" ]
110- else :
111- tx .vin [0 ].scriptSig = SCRIPT_SIG [prevtxout ["vout" ]]
112- completetx = tx .serialize ().hex ()
113- txid = from_node .sendrawtransaction (hexstring = completetx , maxfeerate = 0 )
114- txouts .append ({"txid" : txid , "vout" : 0 , "amount" : half_change })
115- txouts .append ({"txid" : txid , "vout" : 1 , "amount" : rem_change })
116-
117-
11881def check_raw_estimates (node , fees_seen ):
11982 """Call estimaterawfee and verify that the estimates meet certain invariants."""
12083
@@ -125,7 +88,10 @@ def check_raw_estimates(node, fees_seen):
12588 assert_greater_than (feerate , 0 )
12689
12790 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 )} )" )
91+ raise AssertionError (
92+ f"Estimated fee ({ feerate } ) out of range ({ min (fees_seen )} ,{ max (fees_seen )} )"
93+ )
94+
12995
13096
13197def check_smart_estimates (node , fees_seen ):
@@ -134,18 +100,22 @@ def check_smart_estimates(node, fees_seen):
134100 delta = 1.0e-6 # account for rounding error
135101 last_feerate = float (max (fees_seen ))
136102 all_smart_estimates = [node .estimatesmartfee (i ) for i in range (1 , 26 )]
137- mempoolMinFee = node .getmempoolinfo ()[' mempoolminfee' ]
138- minRelaytxFee = node .getmempoolinfo ()[' minrelaytxfee' ]
103+ mempoolMinFee = node .getmempoolinfo ()[" mempoolminfee" ]
104+ minRelaytxFee = node .getmempoolinfo ()[" minrelaytxfee" ]
139105 for i , e in enumerate (all_smart_estimates ): # estimate is for i+1
140106 feerate = float (e ["feerate" ])
141107 assert_greater_than (feerate , 0 )
142108 assert_greater_than_or_equal (feerate , float (mempoolMinFee ))
143109 assert_greater_than_or_equal (feerate , float (minRelaytxFee ))
144110
145111 if feerate + delta < min (fees_seen ) or feerate - delta > max (fees_seen ):
146- 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+ )
147115 if feerate - delta > last_feerate :
148- 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+ )
149119 last_feerate = feerate
150120
151121 if i == 0 :
@@ -161,12 +131,11 @@ def check_estimates(node, fees_seen):
161131class EstimateFeeTest (BitcoinTestFramework ):
162132 def set_test_params (self ):
163133 self .num_nodes = 3
164- # mine non-standard txs (e.g. txs with "dust" outputs)
165134 # Force fSendTrickle to true (via whitelist)
166135 self .extra_args = [
167- [
"-acceptnonstdtxn=1" , "- [email protected] "],
168- [
"-acceptnonstdtxn=1" , "- [email protected] ",
"-blockmaxsize=17000" ],
169- [
"-acceptnonstdtxn=1" , "- [email protected] ",
"-blockmaxsize=8000" ]
136+ 137+ [
"[email protected] " ,
"-blockmaxsize=17000" ],
138+ [
"[email protected] " ,
"-blockmaxsize=8000" ]
170139 ]
171140
172141 def skip_test_if_missing_module (self ):
@@ -199,11 +168,17 @@ def transact_and_mine(self, numblocks, mining_node):
199168 random .shuffle (self .confutxo )
200169 for _ in range (random .randrange (100 - 50 , 100 + 50 )):
201170 from_index = random .randint (1 , 2 )
202- (txhex , fee ) = small_txpuzzle_randfee (self .nodes [from_index ], self .confutxo ,
203- self .memutxo , Decimal ("0.005" ), min_fee , min_fee )
171+ (txhex , fee ) = small_txpuzzle_randfee (
172+ self .nodes [from_index ],
173+ self .confutxo ,
174+ self .memutxo ,
175+ Decimal ("0.005" ),
176+ min_fee ,
177+ min_fee ,
178+ )
204179 tx_kbytes = (len (txhex ) // 2 ) / 1000.0
205180 self .fees_per_kb .append (float (fee ) / tx_kbytes )
206- self .sync_mempools (wait = .1 )
181+ self .sync_mempools (wait = 0 .1 )
207182 mined = mining_node .getblock (self .generate (mining_node , 1 )[0 ], True )["tx" ]
208183 # update which txouts are confirmed
209184 newmem = []
@@ -216,46 +191,49 @@ def transact_and_mine(self, numblocks, mining_node):
216191
217192 def initial_split (self , node ):
218193 """Split two coinbase UTxOs into many small coins"""
219- self .txouts = []
220- self .txouts2 = []
221- # Split a coinbase into two transaction puzzle outputs
222- split_inputs (node , node .listunspent (0 ), self .txouts , True )
223-
224- # Mine
194+ utxo_count = 2048
195+ self .confutxo = []
196+ splitted_amount = Decimal ("0.04" )
197+ fee = Decimal ("0.0007" )
198+ # Calculate change from UTXOs instead of relying on hardcoded amounts
199+ coinbase_utxos = node .listunspent ()[:2 ]
200+ total_input = sum (Decimal (str (cb ["amount" ])) for cb in coinbase_utxos )
201+ change = total_input - splitted_amount * utxo_count - fee
202+
203+ tx = CTransaction ()
204+ tx .vin = [
205+ CTxIn (COutPoint (int (cb ["txid" ], 16 ), cb ["vout" ]))
206+ for cb in coinbase_utxos
207+ ]
208+ tx .vout = [CTxOut (int (splitted_amount * COIN ), P2SH ) for _ in range (utxo_count )]
209+ tx .vout .append (CTxOut (int (change * COIN ), P2SH ))
210+ txhex = node .signrawtransactionwithwallet (tx .serialize ().hex ())["hex" ]
211+ txid = node .sendrawtransaction (txhex )
212+ self .confutxo = [
213+ {"txid" : txid , "vout" : i , "amount" : splitted_amount }
214+ for i in range (utxo_count )
215+ ]
225216 while len (node .getrawmempool ()) > 0 :
226217 self .generate (node , 1 , sync_fun = self .no_op )
227218
228- # Repeatedly split those 2 outputs, doubling twice for each rep
229- # Use txouts to monitor the available utxo, since these won't be tracked in wallet
230- reps = 0
231- while reps < 5 :
232- # Double txouts to txouts2
233- while len (self .txouts ) > 0 :
234- split_inputs (node , self .txouts , self .txouts2 )
235- while len (node .getrawmempool ()) > 0 :
236- self .generate (node , 1 , sync_fun = self .no_op )
237- # Double txouts2 to txouts
238- while len (self .txouts2 ) > 0 :
239- split_inputs (node , self .txouts2 , self .txouts )
240- while len (node .getrawmempool ()) > 0 :
241- self .generate (node , 1 , sync_fun = self .no_op )
242- reps += 1
243-
244219 def sanity_check_estimates_range (self ):
245220 """Populate estimation buckets, assert estimates are in a sane range and
246221 are strictly increasing as the target decreases."""
247222 self .fees_per_kb = []
248223 self .memutxo = []
249- self .confutxo = self .txouts # Start with the set of confirmed txouts after splitting
250224 self .log .info ("Will output estimates for 1/2/3/6/15/25 blocks" )
251225
252226 for _ in range (2 ):
253- self .log .info ("Creating transactions and mining them with a block size that can't keep up" )
227+ self .log .info (
228+ "Creating transactions and mining them with a block size that can't keep up"
229+ )
254230 # Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
255231 self .transact_and_mine (10 , self .nodes [2 ])
256232 check_estimates (self .nodes [1 ], self .fees_per_kb )
257233
258- self .log .info ("Creating transactions and mining them at a block size that is just big enough" )
234+ self .log .info (
235+ "Creating transactions and mining them at a block size that is just big enough"
236+ )
259237 # Generate transactions while mining 10 more blocks, this time with node1
260238 # which mines blocks with capacity just above the rate that transactions are being created
261239 self .transact_and_mine (10 , self .nodes [1 ])
@@ -264,12 +242,13 @@ def sanity_check_estimates_range(self):
264242 # Finish by mining a normal-sized block:
265243 while len (self .nodes [1 ].getrawmempool ()) > 0 :
266244 self .generate (self .nodes [1 ], 1 )
245+
267246 self .log .info ("Final estimates after emptying mempools" )
268247 check_estimates (self .nodes [1 ], self .fees_per_kb )
269248
270249 def test_feerate_mempoolminfee (self ):
271- high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )[' feerate' ]
272- self .restart_node (1 , extra_args = [f' -minrelaytxfee={ high_val } ' ])
250+ high_val = 3 * self .nodes [1 ].estimatesmartfee (1 )[" feerate" ]
251+ self .restart_node (1 , extra_args = [f" -minrelaytxfee={ high_val } " ])
273252 check_estimates (self .nodes [1 ], self .fees_per_kb )
274253 self .stop_node (1 , expected_stderr = "Warning: -minrelaytxfee is set very high! The wallet will avoid paying less than the minimum relay fee." )
275254
@@ -295,14 +274,17 @@ def run_test(self):
295274 self .sanity_check_estimates_range ()
296275
297276 # check that the effective feerate is greater than or equal to the mempoolminfee even for high mempoolminfee
298- self .log .info ("Test fee rate estimation after restarting node with high MempoolMinFee" )
277+ self .log .info (
278+ "Test fee rate estimation after restarting node with high MempoolMinFee"
279+ )
299280 self .test_feerate_mempoolminfee ()
300281
301282 self .log .info ("Testing that fee estimation is disabled in blocksonly." )
302283 self .restart_node (0 , ["-blocksonly" ])
303- assert_raises_rpc_error (- 32603 , "Fee estimation disabled" ,
304- self .nodes [0 ].estimatesmartfee , 2 )
284+ assert_raises_rpc_error (
285+ - 32603 , "Fee estimation disabled" , self .nodes [0 ].estimatesmartfee , 2
286+ )
305287
306288
307- if __name__ == ' __main__' :
289+ if __name__ == " __main__" :
308290 EstimateFeeTest ().main ()
0 commit comments