test: use MiniWallet for feature_fee_estimation.py

This test can now be run even with the Bitcoin Core wallet disabled.
24.x
Sebastian Falbesoner 3 years ago
parent 747cdf1d65
commit 494455f8a5

@ -3,25 +3,13 @@
# 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 copy import deepcopy
from decimal import Decimal from decimal import Decimal
import os import os
import random import random
from test_framework.messages import ( from test_framework.messages import (
COIN, COIN,
COutPoint,
CTransaction,
CTxIn,
CTxOut,
)
from test_framework.script import (
CScript,
OP_1,
OP_DROP,
OP_TRUE,
)
from test_framework.script_util import (
script_to_p2sh_script,
) )
from test_framework.test_framework import BitcoinTestFramework from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import ( from test_framework.util import (
@ -31,22 +19,14 @@ from test_framework.util import (
assert_raises_rpc_error, assert_raises_rpc_error,
satoshi_round, satoshi_round,
) )
from test_framework.wallet import MiniWallet
# Construct 2 trivial P2SH's and the ScriptSigs that spend them
# So we can create many transactions without needing to spend
# time signing.
SCRIPT = CScript([OP_1, OP_DROP])
P2SH = script_to_p2sh_script(SCRIPT)
REDEEM_SCRIPT = CScript([OP_TRUE, SCRIPT])
def small_txpuzzle_randfee( def small_txpuzzle_randfee(
from_node, conflist, unconflist, amount, min_fee, fee_increment wallet, 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 using MiniWallet.
The transaction pays to a trivial P2SH script, and assumes that its inputs
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.
@ -58,23 +38,29 @@ def small_txpuzzle_randfee(
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() utxos_to_spend = []
total_in = Decimal("0.00000000") total_in = Decimal("0.00000000")
while total_in <= (amount + fee) and len(conflist) > 0: while total_in <= (amount + fee) and len(conflist) > 0:
t = conflist.pop(0) t = conflist.pop(0)
total_in += t["amount"] total_in += t["value"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT)) utxos_to_spend.append(t)
while total_in <= (amount + fee) and len(unconflist) > 0: while total_in <= (amount + fee) and len(unconflist) > 0:
t = unconflist.pop(0) t = unconflist.pop(0)
total_in += t["amount"] total_in += t["value"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT)) utxos_to_spend.append(t)
if total_in <= amount + fee: if total_in <= amount + fee:
raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}") raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH)) tx = wallet.create_self_transfer_multi(
tx.vout.append(CTxOut(int(amount * COIN), P2SH)) from_node=from_node,
utxos_to_spend=utxos_to_spend,
fee_per_output=0)
tx.vout[0].nValue = int((total_in - amount - fee) * COIN)
tx.vout.append(deepcopy(tx.vout[0]))
tx.vout[1].nValue = int(amount * COIN)
txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0) txid = from_node.sendrawtransaction(hexstring=tx.serialize().hex(), maxfeerate=0)
unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee}) unconflist.append({"txid": txid, "vout": 0, "value": total_in - amount - fee})
unconflist.append({"txid": txid, "vout": 1, "amount": amount}) unconflist.append({"txid": txid, "vout": 1, "value": amount})
return (tx.serialize().hex(), fee) return (tx.serialize().hex(), fee)
@ -129,17 +115,13 @@ def check_estimates(node, fees_seen):
check_smart_estimates(node, fees_seen) check_smart_estimates(node, fees_seen)
def send_tx(node, utxo, feerate): def send_tx(wallet, node, utxo, feerate):
"""Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb).""" """Broadcast a 1in-1out transaction with a specific input and feerate (sat/vb)."""
tx = CTransaction() return wallet.send_self_transfer(
tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), REDEEM_SCRIPT)] from_node=node,
tx.vout = [CTxOut(int(utxo["amount"] * COIN), P2SH)] utxo_to_spend=utxo,
fee_rate=Decimal(feerate * 1000) / COIN,
# vbytes == bytes as we are using legacy transactions )['txid']
fee = tx.get_vsize() * feerate
tx.vout[0].nValue -= fee
return node.sendrawtransaction(tx.serialize().hex())
class EstimateFeeTest(BitcoinTestFramework): class EstimateFeeTest(BitcoinTestFramework):
@ -152,9 +134,6 @@ class EstimateFeeTest(BitcoinTestFramework):
["-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"], ["-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"],
] ]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self): def setup_network(self):
""" """
We'll setup the network to have 3 nodes that all mine with different parameters. We'll setup the network to have 3 nodes that all mine with different parameters.
@ -168,9 +147,6 @@ class EstimateFeeTest(BitcoinTestFramework):
# (68k weight is room enough for 120 or so transactions) # (68k weight is room enough for 120 or so transactions)
# 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)
self.start_nodes()
self.import_deterministic_coinbase_privkeys()
self.stop_nodes()
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")
@ -183,6 +159,7 @@ class EstimateFeeTest(BitcoinTestFramework):
for _ in range(random.randrange(100 - 50, 100 + 50)): for _ 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( (txhex, fee) = small_txpuzzle_randfee(
self.wallet,
self.nodes[from_index], self.nodes[from_index],
self.confutxo, self.confutxo,
self.memutxo, self.memutxo,
@ -205,24 +182,10 @@ class EstimateFeeTest(BitcoinTestFramework):
def initial_split(self, node): def initial_split(self, node):
"""Split two coinbase UTxOs into many small coins""" """Split two coinbase UTxOs into many small coins"""
utxo_count = 2048 self.confutxo = self.wallet.send_self_transfer_multi(
self.confutxo = [] from_node=node,
splitted_amount = Decimal("0.04") utxos_to_spend=[self.wallet.get_utxo() for _ in range(2)],
fee = Decimal("0.1") num_outputs=2048)['new_utxos']
change = Decimal("100") - splitted_amount * utxo_count - fee
tx = CTransaction()
tx.vin = [
CTxIn(COutPoint(int(cb["txid"], 16), cb["vout"]))
for cb in node.listunspent()[:2]
]
tx.vout = [CTxOut(int(splitted_amount * COIN), P2SH) for _ in range(utxo_count)]
tx.vout.append(CTxOut(int(change * COIN), P2SH))
txhex = node.signrawtransactionwithwallet(tx.serialize().hex())["hex"]
txid = node.sendrawtransaction(txhex)
self.confutxo = [
{"txid": txid, "vout": i, "amount": splitted_amount}
for i in range(utxo_count)
]
while len(node.getrawmempool()) > 0: while len(node.getrawmempool()) > 0:
self.generate(node, 1, sync_fun=self.no_op) self.generate(node, 1, sync_fun=self.no_op)
@ -284,12 +247,12 @@ class EstimateFeeTest(BitcoinTestFramework):
# Broadcast 45 low fee transactions that will need to be RBF'd # Broadcast 45 low fee transactions that will need to be RBF'd
for _ in range(45): for _ in range(45):
u = utxos.pop(0) u = utxos.pop(0)
txid = send_tx(node, u, low_feerate) txid = send_tx(self.wallet, node, u, low_feerate)
utxos_to_respend.append(u) utxos_to_respend.append(u)
txids_to_replace.append(txid) txids_to_replace.append(txid)
# Broadcast 5 low fee transaction which don't need to # Broadcast 5 low fee transaction which don't need to
for _ in range(5): for _ in range(5):
send_tx(node, utxos.pop(0), low_feerate) send_tx(self.wallet, node, utxos.pop(0), low_feerate)
# Mine the transactions on another node # Mine the transactions on another node
self.sync_mempools(wait=0.1, nodes=[node, miner]) self.sync_mempools(wait=0.1, nodes=[node, miner])
for txid in txids_to_replace: for txid in txids_to_replace:
@ -298,7 +261,7 @@ class EstimateFeeTest(BitcoinTestFramework):
# RBF the low-fee transactions # RBF the low-fee transactions
while len(utxos_to_respend) > 0: while len(utxos_to_respend) > 0:
u = utxos_to_respend.pop(0) u = utxos_to_respend.pop(0)
send_tx(node, u, high_feerate) send_tx(self.wallet, node, u, high_feerate)
# Mine the last replacement txs # Mine the last replacement txs
self.sync_mempools(wait=0.1, nodes=[node, miner]) self.sync_mempools(wait=0.1, nodes=[node, miner])
@ -316,6 +279,8 @@ class EstimateFeeTest(BitcoinTestFramework):
# Split two coinbases into many small utxos # Split two coinbases into many small utxos
self.start_node(0) self.start_node(0)
self.wallet = MiniWallet(self.nodes[0])
self.wallet.rescan_utxos()
self.initial_split(self.nodes[0]) self.initial_split(self.nodes[0])
self.log.info("Finished splitting") self.log.info("Finished splitting")

Loading…
Cancel
Save