Merge bitcoin/bitcoin#24817: test: use MiniWallet for feature_fee_estimation.py

494455f8a5 test: use MiniWallet for feature_fee_estimation.py (Sebastian Falbesoner)

Pull request description:

  This PR enables one more of the non-wallet functional tests (feature_fee_estimation.py) to be run even with the Bitcoin Core wallet disabled by using the MiniWallet instead, as proposed in https://github.com/bitcoin/bitcoin/issues/20078. It takes use of the recently introduced methods `{create,send}_self_transfer_multi` (#24637) which allows to specify multiple UTXOs to be spent rather than only one. Very likely the test can still be simplified (e.g. coin selection in `small_txpuzzle_randfee`), but this is a first step.

ACKs for top commit:
  ayush933:
    tACK 494455f8 . The test runs successfully with the wallet disabled.
  vincenzopalazzo:
    tACK 494455f8a5

Tree-SHA512: 89789fc34a4374c79c4b90acd926ac69153aad655dab50450ed796f03c770bd675ad872e906f516f90e8d4cb40b83b55f3c78a94b13bfb8fe8f5e27624937748
24.x
MarcoFalke 3 years ago
commit cd110cdd0e
No known key found for this signature in database
GPG Key ID: CE2B75697E69A548

@ -3,25 +3,13 @@
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test fee estimation code."""
from copy import deepcopy
from decimal import Decimal
import os
import random
from test_framework.messages import (
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.util import (
@ -31,22 +19,14 @@ from test_framework.util import (
assert_raises_rpc_error,
satoshi_round,
)
# 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])
from test_framework.wallet import MiniWallet
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
and attempts to use the confirmed list first for its inputs.
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))
# Total fee ranges from min_fee to min_fee + 127*fee_increment
fee = min_fee - fee_increment + satoshi_round(rand_fee)
tx = CTransaction()
utxos_to_spend = []
total_in = Decimal("0.00000000")
while total_in <= (amount + fee) and len(conflist) > 0:
t = conflist.pop(0)
total_in += t["amount"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT))
total_in += t["value"]
utxos_to_spend.append(t)
while total_in <= (amount + fee) and len(unconflist) > 0:
t = unconflist.pop(0)
total_in += t["amount"]
tx.vin.append(CTxIn(COutPoint(int(t["txid"], 16), t["vout"]), REDEEM_SCRIPT))
total_in += t["value"]
utxos_to_spend.append(t)
if total_in <= amount + fee:
raise RuntimeError(f"Insufficient funds: need {amount + fee}, have {total_in}")
tx.vout.append(CTxOut(int((total_in - amount - fee) * COIN), P2SH))
tx.vout.append(CTxOut(int(amount * COIN), P2SH))
tx = wallet.create_self_transfer_multi(
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)
unconflist.append({"txid": txid, "vout": 0, "amount": total_in - amount - fee})
unconflist.append({"txid": txid, "vout": 1, "amount": amount})
unconflist.append({"txid": txid, "vout": 0, "value": total_in - amount - fee})
unconflist.append({"txid": txid, "vout": 1, "value": amount})
return (tx.serialize().hex(), fee)
@ -129,17 +115,13 @@ def check_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)."""
tx = CTransaction()
tx.vin = [CTxIn(COutPoint(int(utxo["txid"], 16), utxo["vout"]), REDEEM_SCRIPT)]
tx.vout = [CTxOut(int(utxo["amount"] * COIN), P2SH)]
# vbytes == bytes as we are using legacy transactions
fee = tx.get_vsize() * feerate
tx.vout[0].nValue -= fee
return node.sendrawtransaction(tx.serialize().hex())
return wallet.send_self_transfer(
from_node=node,
utxo_to_spend=utxo,
fee_rate=Decimal(feerate * 1000) / COIN,
)['txid']
class EstimateFeeTest(BitcoinTestFramework):
@ -152,9 +134,6 @@ class EstimateFeeTest(BitcoinTestFramework):
["-whitelist=noban@127.0.0.1", "-blockmaxweight=32000"],
]
def skip_test_if_missing_module(self):
self.skip_if_no_wallet()
def setup_network(self):
"""
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)
# Node2 is a stingy miner, that
# 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):
min_fee = Decimal("0.00001")
@ -183,6 +159,7 @@ class EstimateFeeTest(BitcoinTestFramework):
for _ in range(random.randrange(100 - 50, 100 + 50)):
from_index = random.randint(1, 2)
(txhex, fee) = small_txpuzzle_randfee(
self.wallet,
self.nodes[from_index],
self.confutxo,
self.memutxo,
@ -205,24 +182,10 @@ class EstimateFeeTest(BitcoinTestFramework):
def initial_split(self, node):
"""Split two coinbase UTxOs into many small coins"""
utxo_count = 2048
self.confutxo = []
splitted_amount = Decimal("0.04")
fee = Decimal("0.1")
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)
]
self.confutxo = self.wallet.send_self_transfer_multi(
from_node=node,
utxos_to_spend=[self.wallet.get_utxo() for _ in range(2)],
num_outputs=2048)['new_utxos']
while len(node.getrawmempool()) > 0:
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
for _ in range(45):
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)
txids_to_replace.append(txid)
# Broadcast 5 low fee transaction which don't need to
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
self.sync_mempools(wait=0.1, nodes=[node, miner])
for txid in txids_to_replace:
@ -298,7 +261,7 @@ class EstimateFeeTest(BitcoinTestFramework):
# RBF the low-fee transactions
while len(utxos_to_respend) > 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
self.sync_mempools(wait=0.1, nodes=[node, miner])
@ -316,6 +279,8 @@ class EstimateFeeTest(BitcoinTestFramework):
# Split two coinbases into many small utxos
self.start_node(0)
self.wallet = MiniWallet(self.nodes[0])
self.wallet.rescan_utxos()
self.initial_split(self.nodes[0])
self.log.info("Finished splitting")

Loading…
Cancel
Save