[tests] Fix style warnings in feature_fee_estimation.py

This commit is contained in:
John Newbery 2018-02-02 10:08:37 -05:00
parent 4cad91663d
commit d119f2ec1a

View file

@ -3,39 +3,41 @@
# Distributed under the MIT software license, see the accompanying # Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php. # file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code.""" """Test fee estimation code."""
from decimal import Decimal
import random
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
from test_framework.script import CScript, OP_1, OP_DROP, OP_2, OP_HASH160, OP_EQUAL, hash160, OP_TRUE
from test_framework.mininode import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, COIN from test_framework.mininode import CTransaction, CTxIn, CTxOut, COutPoint, ToHex, COIN
from test_framework.script import CScript, OP_1, OP_DROP, OP_2, OP_HASH160, OP_EQUAL, hash160, OP_TRUE
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import satoshi_round, sync_mempools, sync_blocks, connect_nodes, assert_greater_than
# Construct 2 trivial P2SH's and the ScriptSigs that spend them # Construct 2 trivial P2SH's and the ScriptSigs that spend them
# So we can create many transactions without needing to spend # So we can create many transactions without needing to spend
# time signing. # time signing.
redeem_script_1 = CScript([OP_1, OP_DROP]) REDEEM_SCRIPT_1 = CScript([OP_1, OP_DROP])
redeem_script_2 = CScript([OP_2, OP_DROP]) REDEEM_SCRIPT_2 = CScript([OP_2, OP_DROP])
P2SH_1 = CScript([OP_HASH160, hash160(redeem_script_1), OP_EQUAL]) P2SH_1 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_1), OP_EQUAL])
P2SH_2 = CScript([OP_HASH160, hash160(redeem_script_2), OP_EQUAL]) P2SH_2 = CScript([OP_HASH160, hash160(REDEEM_SCRIPT_2), OP_EQUAL])
# Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2 # Associated ScriptSig's to spend satisfy P2SH_1 and P2SH_2
SCRIPT_SIG = [CScript([OP_TRUE, redeem_script_1]), CScript([OP_TRUE, redeem_script_2])] SCRIPT_SIG = [CScript([OP_TRUE, REDEEM_SCRIPT_1]), CScript([OP_TRUE, REDEEM_SCRIPT_2])]
global log global log
def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment): def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee_increment):
""" """Create and send a transaction with a random fee.
Create and send a transaction with a random fee.
The transaction pays to a trivial P2SH script, and assumes that its inputs The transaction pays to a trivial P2SH script, and assumes that its inputs
are of the same form. are of the same form.
The function takes a list of confirmed outputs and unconfirmed outputs The function takes a list of confirmed outputs and unconfirmed outputs
and attempts to use the confirmed list first for its inputs. and attempts to use the confirmed list first for its inputs.
It adds the newly created outputs to the unconfirmed list. It adds the newly created outputs to the unconfirmed list.
Returns (raw transaction, fee) Returns (raw transaction, fee)."""
"""
# It's best to exponentially distribute our random fees # It's best to exponentially distribute our random fees
# because the buckets are exponentially spaced. # because the buckets are exponentially spaced.
# Exponentially distributed from 1-128 * fee_increment # Exponentially distributed from 1-128 * fee_increment
rand_fee = float(fee_increment)*(1.1892**random.randint(0,28)) rand_fee = float(fee_increment) * (1.1892 ** random.randint(0, 28))
# Total fee ranges from min_fee to min_fee + 127*fee_increment # Total fee ranges from min_fee to min_fee + 127*fee_increment
fee = min_fee - fee_increment + satoshi_round(rand_fee) fee = min_fee - fee_increment + satoshi_round(rand_fee)
tx = CTransaction() tx = CTransaction()
@ -50,83 +52,81 @@ def small_txpuzzle_randfee(from_node, conflist, unconflist, amount, min_fee, fee
total_in += t["amount"] total_in += t["amount"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b"")) tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), b""))
if total_in <= amount + fee: if total_in <= amount + fee:
raise RuntimeError("Insufficient funds: need %d, have %d"%(amount+fee, total_in)) raise RuntimeError("Insufficient funds: need %d, have %d" % (amount + fee, total_in))
tx.vout.append(CTxOut(int((total_in - amount - fee)*COIN), P2SH_1)) tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH_1))
tx.vout.append(CTxOut(int(amount*COIN), P2SH_2)) tx.vout.append(CTxOut(int(amount * COIN), P2SH_2))
# These transactions don't need to be signed, but we still have to insert # These transactions don't need to be signed, but we still have to insert
# the ScriptSig that will satisfy the ScriptPubKey. # the ScriptSig that will satisfy the ScriptPubKey.
for inp in tx.vin: for inp in tx.vin:
inp.scriptSig = SCRIPT_SIG[inp.prevout.n] inp.scriptSig = SCRIPT_SIG[inp.prevout.n]
txid = from_node.sendrawtransaction(ToHex(tx), True) txid = from_node.sendrawtransaction(ToHex(tx), True)
unconflist.append({ "txid" : txid, "vout" : 0 , "amount" : total_in - amount - fee}) unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee})
unconflist.append({ "txid" : txid, "vout" : 1 , "amount" : amount}) unconflist.append({"txid": txid, "vout": 1, "amount": amount})
return (ToHex(tx), fee) return (ToHex(tx), fee)
def split_inputs(from_node, txins, txouts, initial_split = False): def split_inputs(from_node, txins, txouts, initial_split=False):
""" """Generate a lot of inputs so we can generate a ton of transactions.
We need to generate a lot of inputs so we can generate a ton of transactions.
This function takes an input from txins, and creates and sends a transaction This function takes an input from txins, and creates and sends a transaction
which splits the value into 2 outputs which are appended to txouts. which splits the value into 2 outputs which are appended to txouts.
Previously this was designed to be small inputs so they wouldn't have Previously this was designed to be small inputs so they wouldn't have
a high coin age when the notion of priority still existed. a high coin age when the notion of priority still existed."""
"""
prevtxout = txins.pop() prevtxout = txins.pop()
tx = CTransaction() tx = CTransaction()
tx.vin.append(CTxIn(COutPoint(int(prevtxout["txid"], 16), prevtxout["vout"]), b"")) tx.vin.append(CTxIn(COutPoint(int(prevtxout["txid"], 16), prevtxout["vout"]), b""))
half_change = satoshi_round(prevtxout["amount"]/2) half_change = satoshi_round(prevtxout["amount"] / 2)
rem_change = prevtxout["amount"] - half_change - Decimal("0.00001000") rem_change = prevtxout["amount"] - half_change - Decimal("0.00001000")
tx.vout.append(CTxOut(int(half_change*COIN), P2SH_1)) tx.vout.append(CTxOut(int(half_change * COIN), P2SH_1))
tx.vout.append(CTxOut(int(rem_change*COIN), P2SH_2)) tx.vout.append(CTxOut(int(rem_change * COIN), P2SH_2))
# If this is the initial split we actually need to sign the transaction # If this is the initial split we actually need to sign the transaction
# Otherwise we just need to insert the proper ScriptSig # Otherwise we just need to insert the proper ScriptSig
if (initial_split) : if (initial_split):
completetx = from_node.signrawtransaction(ToHex(tx))["hex"] completetx = from_node.signrawtransaction(ToHex(tx))["hex"]
else : else:
tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]] tx.vin[0].scriptSig = SCRIPT_SIG[prevtxout["vout"]]
completetx = ToHex(tx) completetx = ToHex(tx)
txid = from_node.sendrawtransaction(completetx, True) txid = from_node.sendrawtransaction(completetx, True)
txouts.append({ "txid" : txid, "vout" : 0 , "amount" : half_change}) txouts.append({"txid": txid, "vout": 0, "amount": half_change})
txouts.append({ "txid" : txid, "vout" : 1 , "amount" : rem_change}) txouts.append({"txid": txid, "vout": 1, "amount": rem_change})
def check_estimates(node, fees_seen, max_invalid, print_estimates = True): def check_estimates(node, fees_seen, max_invalid, print_estimates=True):
""" """Call estimatefee and verify that the estimates meet certain invariants."""
This function calls estimatefee and verifies that the estimates
meet certain invariants. all_estimates = [node.estimatefee(i) for i in range(1, 26)]
"""
all_estimates = [ node.estimatefee(i) for i in range(1,26) ]
if print_estimates: if print_estimates:
log.info([str(all_estimates[e-1]) for e in [1,2,3,6,15,25]]) log.info([str(all_estimates[e - 1]) for e in [1, 2, 3, 6, 15, 25]])
delta = 1.0e-6 # account for rounding error delta = 1.0e-6 # account for rounding error
last_e = max(fees_seen) last_e = max(fees_seen)
for e in [x for x in all_estimates if x >= 0]: for e in [x for x in all_estimates if x >= 0]:
# Estimates should be within the bounds of what transactions fees actually were: # Estimates should be within the bounds of what transactions fees actually were:
if float(e)+delta < min(fees_seen) or float(e)-delta > max(fees_seen): if float(e) + delta < min(fees_seen) or float(e) - delta > max(fees_seen):
raise AssertionError("Estimated fee (%f) out of range (%f,%f)" raise AssertionError("Estimated fee (%f) out of range (%f,%f)"
%(float(e), min(fees_seen), max(fees_seen))) % (float(e), min(fees_seen), max(fees_seen)))
# Estimates should be monotonically decreasing # Estimates should be monotonically decreasing
if float(e)-delta > last_e: if float(e) - delta > last_e:
raise AssertionError("Estimated fee (%f) larger than last fee (%f) for lower number of confirms" raise AssertionError("Estimated fee (%f) larger than last fee (%f) for lower number of confirms"
%(float(e),float(last_e))) % (float(e), float(last_e)))
last_e = e last_e = e
valid_estimate = False valid_estimate = False
invalid_estimates = 0 invalid_estimates = 0
for i,e in enumerate(all_estimates): # estimate is for i+1 for i, e in enumerate(all_estimates): # estimate is for i+1
if e >= 0: if e >= 0:
valid_estimate = True valid_estimate = True
if i >= 13: # for n>=14 estimatesmartfee(n/2) should be at least as high as estimatefee(n) if i >= 13: # for n>=14 estimatesmartfee(n/2) should be at least as high as estimatefee(n)
assert(node.estimatesmartfee((i+1)//2)["feerate"] > float(e) - delta) assert_greater_than(node.estimatesmartfee((i + 1) // 2)["feerate"], float(e) - delta)
else: else:
invalid_estimates += 1 invalid_estimates += 1
# estimatesmartfee should still be valid # estimatesmartfee should still be valid
approx_estimate = node.estimatesmartfee(i+1)["feerate"] approx_estimate = node.estimatesmartfee(i + 1)["feerate"]
answer_found = node.estimatesmartfee(i+1)["blocks"] answer_found = node.estimatesmartfee(i + 1)["blocks"]
assert(approx_estimate > 0) assert_greater_than(approx_estimate, 0)
assert(answer_found > i+1) assert_greater_than(answer_found, i + 1)
# Once we're at a high enough confirmation count that we can give an estimate # Once we're at a high enough confirmation count that we can give an estimate
# We should have estimates for all higher confirmation counts # We should have estimates for all higher confirmation counts
@ -136,7 +136,7 @@ def check_estimates(node, fees_seen, max_invalid, print_estimates = True):
# Check on the expected number of different confirmation counts # Check on the expected number of different confirmation counts
# that we might not have valid estimates for # that we might not have valid estimates for
if invalid_estimates > max_invalid: if invalid_estimates > max_invalid:
raise AssertionError("More than (%d) invalid estimates"%(max_invalid)) raise AssertionError("More than (%d) invalid estimates" % (max_invalid))
return all_estimates return all_estimates
@ -160,7 +160,6 @@ class EstimateFeeTest(BitcoinTestFramework):
# Node2 is a stingy miner, that # Node2 is a stingy miner, that
# produces too small blocks (room for only 55 or so transactions) # produces too small blocks (room for only 55 or so transactions)
def transact_and_mine(self, numblocks, mining_node): def transact_and_mine(self, numblocks, mining_node):
min_fee = Decimal("0.00001") min_fee = Decimal("0.00001")
# We will now mine numblocks blocks generating on average 100 transactions between each block # We will now mine numblocks blocks generating on average 100 transactions between each block
@ -169,14 +168,14 @@ class EstimateFeeTest(BitcoinTestFramework):
# resorting to tx's that depend on the mempool when those run out # resorting to tx's that depend on the mempool when those run out
for i in range(numblocks): for i in range(numblocks):
random.shuffle(self.confutxo) random.shuffle(self.confutxo)
for j in range(random.randrange(100-50,100+50)): for j in range(random.randrange(100 - 50, 100 + 50)):
from_index = random.randint(1,2) from_index = random.randint(1, 2)
(txhex, fee) = small_txpuzzle_randfee(self.nodes[from_index], self.confutxo, (txhex, fee) = small_txpuzzle_randfee(self.nodes[from_index], self.confutxo,
self.memutxo, Decimal("0.005"), min_fee, min_fee) self.memutxo, Decimal("0.005"), min_fee, min_fee)
tx_kbytes = (len(txhex) // 2) / 1000.0 tx_kbytes = (len(txhex) // 2) / 1000.0
self.fees_per_kb.append(float(fee)/tx_kbytes) self.fees_per_kb.append(float(fee) / tx_kbytes)
sync_mempools(self.nodes[0:3], wait=.1) sync_mempools(self.nodes[0:3], wait=.1)
mined = mining_node.getblock(mining_node.generate(1)[0],True)["tx"] mined = mining_node.getblock(mining_node.generate(1)[0], True)["tx"]
sync_blocks(self.nodes[0:3], wait=.1) sync_blocks(self.nodes[0:3], wait=.1)
# update which txouts are confirmed # update which txouts are confirmed
newmem = [] newmem = []
@ -210,13 +209,13 @@ class EstimateFeeTest(BitcoinTestFramework):
# Use txouts to monitor the available utxo, since these won't be tracked in wallet # Use txouts to monitor the available utxo, since these won't be tracked in wallet
reps = 0 reps = 0
while (reps < 5): while (reps < 5):
#Double txouts to txouts2 # Double txouts to txouts2
while (len(self.txouts)>0): while (len(self.txouts) > 0):
split_inputs(self.nodes[0], self.txouts, self.txouts2) split_inputs(self.nodes[0], self.txouts, self.txouts2)
while (len(self.nodes[0].getrawmempool()) > 0): while (len(self.nodes[0].getrawmempool()) > 0):
self.nodes[0].generate(1) self.nodes[0].generate(1)
#Double txouts2 to txouts # Double txouts2 to txouts
while (len(self.txouts2)>0): while (len(self.txouts2) > 0):
split_inputs(self.nodes[0], self.txouts2, self.txouts) split_inputs(self.nodes[0], self.txouts2, self.txouts)
while (len(self.nodes[0].getrawmempool()) > 0): while (len(self.nodes[0].getrawmempool()) > 0):
self.nodes[0].generate(1) self.nodes[0].generate(1)
@ -235,7 +234,7 @@ class EstimateFeeTest(BitcoinTestFramework):
self.fees_per_kb = [] self.fees_per_kb = []
self.memutxo = [] self.memutxo = []
self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting self.confutxo = self.txouts # Start with the set of confirmed txouts after splitting
self.log.info("Will output estimates for 1/2/3/6/15/25 blocks") self.log.info("Will output estimates for 1/2/3/6/15/25 blocks")
for i in range(2): for i in range(2):