Dash Core  0.12.2.1
P2P Digital Currency
smartfees.py
Go to the documentation of this file.
1 #!/usr/bin/env python2
2 # Copyright (c) 2014-2015 The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #
7 # Test fee estimation code
8 #
9 
10 from collections import OrderedDict
11 from test_framework.test_framework import BitcoinTestFramework
12 from test_framework.util import *
13 
14 # Construct 2 trivial P2SH's and the ScriptSigs that spend them
15 # So we can create many many transactions without needing to spend
16 # time signing.
17 P2SH_1 = "8kctg1WWKdoLveifyNnDYtRAqBPpqgL8z2" # P2SH of "OP_1 OP_DROP"
18 P2SH_2 = "8xp4fcNB8rz9UbZC47tv6eui1ZSPMd3iYT" # P2SH of "OP_2 OP_DROP"
19 # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
20 # 4 bytes of OP_TRUE and push 2-byte redeem script of "OP_1 OP_DROP" or "OP_2 OP_DROP"
21 SCRIPT_SIG = ["0451025175", "0451025275"]
22 
23 class DecimalEncoder(json.JSONEncoder):
24  def default(self, o):
25  if isinstance(o, Decimal):
26  return float(o)
27  return super(DecimalEncoder, self).default(o)
28 
29 def swap_outputs_in_rawtx(rawtx, outputs, inputnum):
30  '''
31  Since dictionaries in python are unsorted make sure that our outputs are correctly ordered.
32  Note: comparing strings to get "correct order" is based on the fact that
33  P2SH_1 string is < P2SH_2 string in this particular case.
34  '''
35  outputs_unordered = json.dumps(outputs, cls=DecimalEncoder)
36  outputs_ordered = json.dumps(outputs, sort_keys=True, cls=DecimalEncoder)
37  if outputs_ordered != outputs_unordered: # nope, we need to do some work here
38  first_rawoutput = rawtx[12+82*inputnum:12+82*inputnum+64]
39  second_rawoutput = rawtx[12+82*inputnum+64:12+82*inputnum+64+64]
40  rawtx = rawtx[0:12+82*inputnum] + second_rawoutput + first_rawoutput + rawtx[12+82*inputnum+64+64:]
41  return rawtx
42 
43 def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment):
44  '''
45  Create and send a transaction with a random fee.
46  The transaction pays to a trivial P2SH script, and assumes that its inputs
47  are of the same form.
48  The function takes a list of confirmed outputs and unconfirmed outputs
49  and attempts to use the confirmed list first for its inputs.
50  It adds the newly created outputs to the unconfirmed list.
51  Returns (raw transaction, fee)
52  '''
53  # It's best to exponentially distribute our random fees
54  # because the buckets are exponentially spaced.
55  # Exponentially distributed from 1-128 * fee_increment
56  rand_fee = float(fee_increment)*(1.1892**random.randint(0,28))
57  # Total fee ranges from min_fee to min_fee + 127*fee_increment
58  fee = min_fee - fee_increment + satoshi_round(rand_fee)
59  inputs = []
60  total_in = Decimal("0.00000000")
61  while total_in <= (amount + fee) and len(conflist) > 0:
62  t = conflist.pop(0)
63  total_in += t["amount"]
64  inputs.append({ "txid" : t["txid"], "vout" : t["vout"]} )
65  if total_in <= amount + fee:
66  while total_in <= (amount + fee) and len(unconflist) > 0:
67  t = unconflist.pop(0)
68  total_in += t["amount"]
69  inputs.append({ "txid" : t["txid"], "vout" : t["vout"]} )
70  if total_in <= amount + fee:
71  raise RuntimeError("Insufficient funds: need %d, have %d"%(amount+fee, total_in))
72  outputs = {}
73  outputs = OrderedDict([(P2SH_1, total_in - amount - fee),
74  (P2SH_2, amount)])
75  rawtx = from_node.createrawtransaction(inputs, outputs)
76  rawtx = swap_outputs_in_rawtx(rawtx, outputs, len(inputs))
77  # createrawtransaction constructs a transaction that is ready to be signed.
78  # These transactions don't need to be signed, but we still have to insert the ScriptSig
79  # that will satisfy the ScriptPubKey.
80  completetx = rawtx[0:10]
81  inputnum = 0
82  for inp in inputs:
83  completetx += rawtx[10+82*inputnum:82+82*inputnum]
84  completetx += SCRIPT_SIG[inp["vout"]]
85  completetx += rawtx[84+82*inputnum:92+82*inputnum]
86  inputnum += 1
87  completetx += rawtx[10+82*inputnum:]
88  txid = from_node.sendrawtransaction(completetx, True)
89  unconflist.append({ "txid" : txid, "vout" : 0 , "amount" : total_in - amount - fee})
90  unconflist.append({ "txid" : txid, "vout" : 1 , "amount" : amount})
91 
92  return (completetx, fee)
93 
94 def split_inputs(from_node, txins, txouts, initial_split = False):
95  '''
96  We need to generate a lot of very small inputs so we can generate a ton of transactions
97  and they will have low priority.
98  This function takes an input from txins, and creates and sends a transaction
99  which splits the value into 2 outputs which are appended to txouts.
100  '''
101  prevtxout = txins.pop()
102  inputs = []
103  inputs.append({ "txid" : prevtxout["txid"], "vout" : prevtxout["vout"] })
104  half_change = satoshi_round(prevtxout["amount"]/2)
105  rem_change = prevtxout["amount"] - half_change - Decimal("0.00010000")
106  outputs = OrderedDict([(P2SH_1, half_change), (P2SH_2, rem_change)])
107  rawtx = from_node.createrawtransaction(inputs, outputs)
108  rawtx = swap_outputs_in_rawtx(rawtx, outputs, len(inputs))
109  # If this is the initial split we actually need to sign the transaction
110  # Otherwise we just need to insert the property ScriptSig
111  if (initial_split) :
112  completetx = from_node.signrawtransaction(rawtx)["hex"]
113  else :
114  completetx = rawtx[0:82] + SCRIPT_SIG[prevtxout["vout"]] + rawtx[84:]
115  txid = from_node.sendrawtransaction(completetx, True)
116  txouts.append({ "txid" : txid, "vout" : 0 , "amount" : half_change})
117  txouts.append({ "txid" : txid, "vout" : 1 , "amount" : rem_change})
118 
119 def check_estimates(node, fees_seen, max_invalid, print_estimates = True):
120  '''
121  This function calls estimatefee and verifies that the estimates
122  meet certain invariants.
123  '''
124  all_estimates = [ node.estimatefee(i) for i in range(1,26) ]
125  if print_estimates:
126  print([str(all_estimates[e-1]) for e in [1,2,3,6,15,25]])
127  delta = 1.0e-6 # account for rounding error
128  last_e = max(fees_seen)
129  for e in [x for x in all_estimates if x >= 0]:
130  # Estimates should be within the bounds of what transactions fees actually were:
131  if float(e)+delta < min(fees_seen) or float(e)-delta > max(fees_seen):
132  raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
133  %(float(e), min(fees_seen), max(fees_seen)))
134  # Estimates should be monotonically decreasing
135  if float(e)-delta > last_e:
136  raise AssertionError("Estimated fee (%f) larger than last fee (%f) for lower number of confirms"
137  %(float(e),float(last_e)))
138  last_e = e
139  valid_estimate = False
140  invalid_estimates = 0
141  for i,e in enumerate(all_estimates): # estimate is for i+1
142  if e >= 0:
143  valid_estimate = True
144  # estimatesmartfee should return the same result
145  assert_equal(node.estimatesmartfee(i+1)["feerate"], e)
146 
147  else:
148  invalid_estimates += 1
149 
150  # estimatesmartfee should still be valid
151  approx_estimate = node.estimatesmartfee(i+1)["feerate"]
152  answer_found = node.estimatesmartfee(i+1)["blocks"]
153  assert(approx_estimate > 0)
154  assert(answer_found > i+1)
155 
156  # Once we're at a high enough confirmation count that we can give an estimate
157  # We should have estimates for all higher confirmation counts
158  if valid_estimate:
159  raise AssertionError("Invalid estimate appears at higher confirm count than valid estimate")
160 
161  # Check on the expected number of different confirmation counts
162  # that we might not have valid estimates for
163  if invalid_estimates > max_invalid:
164  raise AssertionError("More than (%d) invalid estimates"%(max_invalid))
165  return all_estimates
166 
167 
169 
170  def setup_network(self):
171  '''
172  We'll setup the network to have 3 nodes that all mine with different parameters.
173  But first we need to use one node to create a lot of small low priority outputs
174  which we will use to generate our transactions.
175  '''
176  self.nodes = []
177  # Use node0 to mine blocks for input splitting
178  self.nodes.append(start_node(0, self.options.tmpdir, ["-maxorphantx=1000",
179  "-relaypriority=0", "-whitelist=127.0.0.1"]))
180 
181  print("This test is time consuming, please be patient")
182  print("Splitting inputs to small size so we can generate low priority tx's")
183  self.txouts = []
184  self.txouts2 = []
185  # Split a coinbase into two transaction puzzle outputs
186  split_inputs(self.nodes[0], self.nodes[0].listunspent(0), self.txouts, True)
187 
188  # Mine
189  while (len(self.nodes[0].getrawmempool()) > 0):
190  self.nodes[0].generate(1)
191 
192  # Repeatedly split those 2 outputs, doubling twice for each rep
193  # Use txouts to monitor the available utxo, since these won't be tracked in wallet
194  reps = 0
195  while (reps < 5):
196  #Double txouts to txouts2
197  while (len(self.txouts)>0):
198  split_inputs(self.nodes[0], self.txouts, self.txouts2)
199  while (len(self.nodes[0].getrawmempool()) > 0):
200  self.nodes[0].generate(1)
201  #Double txouts2 to txouts
202  while (len(self.txouts2)>0):
203  split_inputs(self.nodes[0], self.txouts2, self.txouts)
204  while (len(self.nodes[0].getrawmempool()) > 0):
205  self.nodes[0].generate(1)
206  reps += 1
207  print("Finished splitting")
208 
209  # Now we can connect the other nodes, didn't want to connect them earlier
210  # so the estimates would not be affected by the splitting transactions
211  # Node1 mines small blocks but that are bigger than the expected transaction rate,
212  # and allows free transactions.
213  # NOTE: the CreateNewBlock code starts counting block size at 1,000 bytes,
214  # (17k is room enough for 110 or so transactions)
215  self.nodes.append(start_node(1, self.options.tmpdir,
216  ["-blockprioritysize=1500", "-blockmaxsize=17000",
217  "-maxorphantx=1000", "-relaypriority=0", "-debug=estimatefee"]))
218  connect_nodes(self.nodes[1], 0)
219 
220  # Node2 is a stingy miner, that
221  # produces too small blocks (room for only 55 or so transactions)
222  node2args = ["-blockprioritysize=0", "-blockmaxsize=8000", "-maxorphantx=1000", "-relaypriority=0"]
223 
224  self.nodes.append(start_node(2, self.options.tmpdir, node2args))
225  connect_nodes(self.nodes[0], 2)
226  connect_nodes(self.nodes[2], 1)
227 
228  self.is_network_split = False
229  self.sync_all()
230 
231  def transact_and_mine(self, numblocks, mining_node):
232  min_fee = Decimal("0.0001")
233  # We will now mine numblocks blocks generating on average 100 transactions between each block
234  # We shuffle our confirmed txout set before each set of transactions
235  # small_txpuzzle_randfee will use the transactions that have inputs already in the chain when possible
236  # resorting to tx's that depend on the mempool when those run out
237  for i in range(numblocks):
238  random.shuffle(self.confutxo)
239  for j in range(random.randrange(100-50,100+50)):
240  from_index = random.randint(1,2)
241  (txhex, fee) = small_txpuzzle_randfee(self.nodes[from_index], self.confutxo,
242  self.memutxo, Decimal("0.005"), min_fee, min_fee)
243  tx_kbytes = (len(txhex) // 2) / 1000.0
244  self.fees_per_kb.append(float(fee)/tx_kbytes)
245  sync_mempools(self.nodes[0:3],.1)
246  mined = mining_node.getblock(mining_node.generate(1)[0],True)["tx"]
247  sync_blocks(self.nodes[0:3],.1)
248  # update which txouts are confirmed
249  newmem = []
250  for utx in self.memutxo:
251  if utx["txid"] in mined:
252  self.confutxo.append(utx)
253  else:
254  newmem.append(utx)
255  self.memutxo = newmem
256 
257  def run_test(self):
258  self.fees_per_kb = []
259  self.memutxo = []
260  self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting
261  print("Will output estimates for 1/2/3/6/15/25 blocks")
262 
263  for i in xrange(2):
264  print("Creating transactions and mining them with a block size that can't keep up")
265  # Create transactions and mine 10 small blocks with node 2, but create txs faster than we can mine
266  self.transact_and_mine(10, self.nodes[2])
267  check_estimates(self.nodes[1], self.fees_per_kb, 14)
268 
269  print("Creating transactions and mining them at a block size that is just big enough")
270  # Generate transactions while mining 10 more blocks, this time with node1
271  # which mines blocks with capacity just above the rate that transactions are being created
272  self.transact_and_mine(10, self.nodes[1])
273  check_estimates(self.nodes[1], self.fees_per_kb, 2)
274 
275  # Finish by mining a normal-sized block:
276  while len(self.nodes[1].getrawmempool()) > 0:
277  self.nodes[1].generate(1)
278 
279  sync_blocks(self.nodes[0:3],.1)
280  print("Final estimates after emptying mempools")
281  check_estimates(self.nodes[1], self.fees_per_kb, 2)
282 
283 if __name__ == '__main__':
def split_inputs(from_node, txins, txouts, initial_split=False)
Definition: smartfees.py:94
UniValue listunspent(const UniValue &params, bool fHelp)
Definition: rpcwallet.cpp:2533
def swap_outputs_in_rawtx(rawtx, outputs, inputnum)
Definition: smartfees.py:29
def connect_nodes(from_connection, node_num)
Definition: util.py:343
def sync_mempools(rpc_connections, wait=1)
Definition: util.py:127
def transact_and_mine(self, numblocks, mining_node)
Definition: smartfees.py:231
def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment)
Definition: smartfees.py:43
def check_estimates(node, fees_seen, max_invalid, print_estimates=True)
Definition: smartfees.py:119
def satoshi_round(amount)
Definition: util.py:525
def default(self, o)
Definition: smartfees.py:24
def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None)
Definition: util.py:281
UniValue generate(const UniValue &params, bool fHelp)
Definition: mining.cpp:122
def sync_blocks(rpc_connections, wait=1)
Definition: util.py:117
UniValue getrawmempool(const UniValue &params, bool fHelp)
Definition: blockchain.cpp:234
def assert_equal(thing1, thing2)
Definition: util.py:461