3
3
# Distributed under the MIT software license, see the accompanying
4
4
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5
5
"""Test fee estimation code."""
6
+ from decimal import Decimal
7
+ import random
6
8
7
- from test_framework .test_framework import BitcoinTestFramework
8
- from test_framework .util import *
9
- from test_framework .script import CScript , OP_1 , OP_DROP , OP_2 , OP_HASH160 , OP_EQUAL , hash160 , OP_TRUE
10
9
from test_framework .mininode import CTransaction , CTxIn , CTxOut , COutPoint , ToHex , COIN
10
+ from test_framework .script import CScript , OP_1 , OP_DROP , OP_2 , OP_HASH160 , OP_EQUAL , hash160 , OP_TRUE
11
+ from test_framework .test_framework import BitcoinTestFramework
12
+ from test_framework .util import satoshi_round , sync_mempools , sync_blocks , connect_nodes , assert_greater_than
11
13
12
14
# Construct 2 trivial P2SH's and the ScriptSigs that spend them
13
15
# So we can create many transactions without needing to spend
14
16
# time signing.
15
- redeem_script_1 = CScript ([OP_1 , OP_DROP ])
16
- redeem_script_2 = CScript ([OP_2 , OP_DROP ])
17
- P2SH_1 = CScript ([OP_HASH160 , hash160 (redeem_script_1 ), OP_EQUAL ])
18
- P2SH_2 = CScript ([OP_HASH160 , hash160 (redeem_script_2 ), OP_EQUAL ])
17
+ REDEEM_SCRIPT_1 = CScript ([OP_1 , OP_DROP ])
18
+ REDEEM_SCRIPT_2 = CScript ([OP_2 , OP_DROP ])
19
+ P2SH_1 = CScript ([OP_HASH160 , hash160 (REDEEM_SCRIPT_1 ), OP_EQUAL ])
20
+ P2SH_2 = CScript ([OP_HASH160 , hash160 (REDEEM_SCRIPT_2 ), OP_EQUAL ])
19
21
20
22
# Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
21
- SCRIPT_SIG = [CScript ([OP_TRUE , redeem_script_1 ]), CScript ([OP_TRUE , redeem_script_2 ])]
23
+ SCRIPT_SIG = [CScript ([OP_TRUE , REDEEM_SCRIPT_1 ]), CScript ([OP_TRUE , REDEEM_SCRIPT_2 ])]
22
24
23
25
global log
24
26
25
27
def small_txpuzzle_randfee (from_node , conflist , unconflist , amount , min_fee , fee_increment ):
26
- """
27
- Create and send a transaction with a random fee.
28
+ """Create and send a transaction with a random fee.
29
+
28
30
The transaction pays to a trivial P2SH script, and assumes that its inputs
29
31
are of the same form.
30
32
The function takes a list of confirmed outputs and unconfirmed outputs
31
33
and attempts to use the confirmed list first for its inputs.
32
34
It adds the newly created outputs to the unconfirmed list.
33
- Returns (raw transaction, fee)
34
- """
35
+ Returns (raw transaction, fee)."""
36
+
35
37
# It's best to exponentially distribute our random fees
36
38
# because the buckets are exponentially spaced.
37
39
# Exponentially distributed from 1-128 * fee_increment
38
- rand_fee = float (fee_increment )* (1.1892 ** random .randint (0 ,28 ))
40
+ rand_fee = float (fee_increment ) * (1.1892 ** random .randint (0 , 28 ))
39
41
# Total fee ranges from min_fee to min_fee + 127*fee_increment
40
42
fee = min_fee - fee_increment + satoshi_round (rand_fee )
41
43
tx = CTransaction ()
@@ -50,83 +52,81 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
50
52
total_in += t ["amount" ]
51
53
tx .vin .append (CTxIn (COutPoint (int (t ["txid" ], 16 ), t ["vout" ]), b"" ))
52
54
if total_in <= amount + fee :
53
- raise RuntimeError ("Insufficient funds: need %d, have %d" % (amount + fee , total_in ))
54
- tx .vout .append (CTxOut (int ((total_in - amount - fee )* COIN ), P2SH_1 ))
55
- tx .vout .append (CTxOut (int (amount * COIN ), P2SH_2 ))
55
+ raise RuntimeError ("Insufficient funds: need %d, have %d" % (amount + fee , total_in ))
56
+ tx .vout .append (CTxOut (int ((total_in - amount - fee ) * COIN ), P2SH_1 ))
57
+ tx .vout .append (CTxOut (int (amount * COIN ), P2SH_2 ))
56
58
# These transactions don't need to be signed, but we still have to insert
57
59
# the ScriptSig that will satisfy the ScriptPubKey.
58
60
for inp in tx .vin :
59
61
inp .scriptSig = SCRIPT_SIG [inp .prevout .n ]
60
62
txid = from_node .sendrawtransaction (ToHex (tx ), True )
61
- unconflist .append ({ "txid" : txid , "vout" : 0 , "amount" : total_in - amount - fee })
62
- unconflist .append ({ "txid" : txid , "vout" : 1 , "amount" : amount })
63
+ unconflist .append ({"txid" : txid , "vout" : 0 , "amount" : total_in - amount - fee })
64
+ unconflist .append ({"txid" : txid , "vout" : 1 , "amount" : amount })
63
65
64
66
return (ToHex (tx ), fee )
65
67
66
- def split_inputs (from_node , txins , txouts , initial_split = False ):
67
- """
68
- We need to generate a lot of inputs so we can generate a ton of transactions.
68
+ def split_inputs (from_node , txins , txouts , initial_split = False ):
69
+ """Generate a lot of inputs so we can generate a ton of transactions.
70
+
69
71
This function takes an input from txins, and creates and sends a transaction
70
72
which splits the value into 2 outputs which are appended to txouts.
71
73
Previously this was designed to be small inputs so they wouldn't have
72
- a high coin age when the notion of priority still existed.
73
- """
74
+ a high coin age when the notion of priority still existed."""
75
+
74
76
prevtxout = txins .pop ()
75
77
tx = CTransaction ()
76
78
tx .vin .append (CTxIn (COutPoint (int (prevtxout ["txid" ], 16 ), prevtxout ["vout" ]), b"" ))
77
79
78
- half_change = satoshi_round (prevtxout ["amount" ]/ 2 )
79
- rem_change = prevtxout ["amount" ] - half_change - Decimal ("0.00001000" )
80
- tx .vout .append (CTxOut (int (half_change * COIN ), P2SH_1 ))
81
- tx .vout .append (CTxOut (int (rem_change * COIN ), P2SH_2 ))
80
+ half_change = satoshi_round (prevtxout ["amount" ] / 2 )
81
+ rem_change = prevtxout ["amount" ] - half_change - Decimal ("0.00001000" )
82
+ tx .vout .append (CTxOut (int (half_change * COIN ), P2SH_1 ))
83
+ tx .vout .append (CTxOut (int (rem_change * COIN ), P2SH_2 ))
82
84
83
85
# If this is the initial split we actually need to sign the transaction
84
86
# Otherwise we just need to insert the proper ScriptSig
85
- if (initial_split ) :
87
+ if (initial_split ):
86
88
completetx = from_node .signrawtransaction (ToHex (tx ))["hex" ]
87
- else :
89
+ else :
88
90
tx .vin [0 ].scriptSig = SCRIPT_SIG [prevtxout ["vout" ]]
89
91
completetx = ToHex (tx )
90
92
txid = from_node .sendrawtransaction (completetx , True )
91
- txouts .append ({ "txid" : txid , "vout" : 0 , "amount" : half_change })
92
- txouts .append ({ "txid" : txid , "vout" : 1 , "amount" : rem_change })
93
-
94
- def check_estimates (node , fees_seen , max_invalid , print_estimates = True ):
95
- """
96
- This function calls estimatefee and verifies that the estimates
97
- meet certain invariants.
98
- """
99
- all_estimates = [ node .estimatefee (i ) for i in range (1 ,26 ) ]
93
+ txouts .append ({"txid" : txid , "vout" : 0 , "amount" : half_change })
94
+ txouts .append ({"txid" : txid , "vout" : 1 , "amount" : rem_change })
95
+
96
+ def check_estimates (node , fees_seen , max_invalid , print_estimates = True ):
97
+ """Call estimatefee and verify that the estimates meet certain invariants."""
98
+
99
+ all_estimates = [node .estimatefee (i ) for i in range (1 , 26 )]
100
100
if print_estimates :
101
- log .info ([str (all_estimates [e - 1 ]) for e in [1 ,2 , 3 , 6 , 15 ,25 ]])
102
- delta = 1.0e-6 # account for rounding error
101
+ log .info ([str (all_estimates [e - 1 ]) for e in [1 , 2 , 3 , 6 , 15 , 25 ]])
102
+ delta = 1.0e-6 # account for rounding error
103
103
last_e = max (fees_seen )
104
104
for e in [x for x in all_estimates if x >= 0 ]:
105
105
# Estimates should be within the bounds of what transactions fees actually were:
106
- if float (e )+ delta < min (fees_seen ) or float (e )- delta > max (fees_seen ):
106
+ if float (e ) + delta < min (fees_seen ) or float (e ) - delta > max (fees_seen ):
107
107
raise AssertionError ("Estimated fee (%f) out of range (%f,%f)"
108
- % (float (e ), min (fees_seen ), max (fees_seen )))
108
+ % (float (e ), min (fees_seen ), max (fees_seen )))
109
109
# Estimates should be monotonically decreasing
110
- if float (e )- delta > last_e :
110
+ if float (e ) - delta > last_e :
111
111
raise AssertionError ("Estimated fee (%f) larger than last fee (%f) for lower number of confirms"
112
- % (float (e ),float (last_e )))
112
+ % (float (e ), float (last_e )))
113
113
last_e = e
114
114
valid_estimate = False
115
115
invalid_estimates = 0
116
- for i ,e in enumerate (all_estimates ): # estimate is for i+1
116
+ for i , e in enumerate (all_estimates ): # estimate is for i+1
117
117
if e >= 0 :
118
118
valid_estimate = True
119
119
if i >= 13 : # for n>=14 estimatesmartfee(n/2) should be at least as high as estimatefee(n)
120
- assert (node .estimatesmartfee ((i + 1 ) // 2 )["feerate" ] > float (e ) - delta )
120
+ assert_greater_than (node .estimatesmartfee ((i + 1 ) // 2 )["feerate" ], float (e ) - delta )
121
121
122
122
else :
123
123
invalid_estimates += 1
124
124
125
125
# estimatesmartfee should still be valid
126
- approx_estimate = node .estimatesmartfee (i + 1 )["feerate" ]
127
- answer_found = node .estimatesmartfee (i + 1 )["blocks" ]
128
- assert (approx_estimate > 0 )
129
- assert (answer_found > i + 1 )
126
+ approx_estimate = node .estimatesmartfee (i + 1 )["feerate" ]
127
+ answer_found = node .estimatesmartfee (i + 1 )["blocks" ]
128
+ assert_greater_than (approx_estimate , 0 )
129
+ assert_greater_than (answer_found , i + 1 )
130
130
131
131
# Once we're at a high enough confirmation count that we can give an estimate
132
132
# We should have estimates for all higher confirmation counts
@@ -136,7 +136,7 @@ def check_estimates(node, fees_seen, max_invalid, print_estimates = True):
136
136
# Check on the expected number of different confirmation counts
137
137
# that we might not have valid estimates for
138
138
if invalid_estimates > max_invalid :
139
- raise AssertionError ("More than (%d) invalid estimates" % (max_invalid ))
139
+ raise AssertionError ("More than (%d) invalid estimates" % (max_invalid ))
140
140
return all_estimates
141
141
142
142
@@ -160,7 +160,6 @@ def setup_network(self):
160
160
# Node2 is a stingy miner, that
161
161
# produces too small blocks (room for only 55 or so transactions)
162
162
163
-
164
163
def transact_and_mine (self , numblocks , mining_node ):
165
164
min_fee = Decimal ("0.00001" )
166
165
# We will now mine numblocks blocks generating on average 100 transactions between each block
@@ -169,14 +168,14 @@ def transact_and_mine(self, numblocks, mining_node):
169
168
# resorting to tx's that depend on the mempool when those run out
170
169
for i in range (numblocks ):
171
170
random .shuffle (self .confutxo )
172
- for j in range (random .randrange (100 - 50 ,100 + 50 )):
173
- from_index = random .randint (1 ,2 )
171
+ for j in range (random .randrange (100 - 50 , 100 + 50 )):
172
+ from_index = random .randint (1 , 2 )
174
173
(txhex , fee ) = small_txpuzzle_randfee (self .nodes [from_index ], self .confutxo ,
175
174
self .memutxo , Decimal ("0.005" ), min_fee , min_fee )
176
175
tx_kbytes = (len (txhex ) // 2 ) / 1000.0
177
- self .fees_per_kb .append (float (fee )/ tx_kbytes )
176
+ self .fees_per_kb .append (float (fee ) / tx_kbytes )
178
177
sync_mempools (self .nodes [0 :3 ], wait = .1 )
179
- mined = mining_node .getblock (mining_node .generate (1 )[0 ],True )["tx" ]
178
+ mined = mining_node .getblock (mining_node .generate (1 )[0 ], True )["tx" ]
180
179
sync_blocks (self .nodes [0 :3 ], wait = .1 )
181
180
# update which txouts are confirmed
182
181
newmem = []
@@ -210,13 +209,13 @@ def run_test(self):
210
209
# Use txouts to monitor the available utxo, since these won't be tracked in wallet
211
210
reps = 0
212
211
while (reps < 5 ):
213
- #Double txouts to txouts2
214
- while (len (self .txouts )> 0 ):
212
+ # Double txouts to txouts2
213
+ while (len (self .txouts ) > 0 ):
215
214
split_inputs (self .nodes [0 ], self .txouts , self .txouts2 )
216
215
while (len (self .nodes [0 ].getrawmempool ()) > 0 ):
217
216
self .nodes [0 ].generate (1 )
218
- #Double txouts2 to txouts
219
- while (len (self .txouts2 )> 0 ):
217
+ # Double txouts2 to txouts
218
+ while (len (self .txouts2 ) > 0 ):
220
219
split_inputs (self .nodes [0 ], self .txouts2 , self .txouts )
221
220
while (len (self .nodes [0 ].getrawmempool ()) > 0 ):
222
221
self .nodes [0 ].generate (1 )
@@ -235,7 +234,7 @@ def run_test(self):
235
234
236
235
self .fees_per_kb = []
237
236
self .memutxo = []
238
- self .confutxo = self .txouts # Start with the set of confirmed txouts after splitting
237
+ self .confutxo = self .txouts # Start with the set of confirmed txouts after splitting
239
238
self .log .info ("Will output estimates for 1/2/3/6/15/25 blocks" )
240
239
241
240
for i in range (2 ):
0 commit comments