mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-10 03:47:29 -03:00
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: tACK494455f8
. The test runs successfully with the wallet disabled. vincenzopalazzo: tACK494455f8a5
Tree-SHA512: 89789fc34a4374c79c4b90acd926ac69153aad655dab50450ed796f03c770bd675ad872e906f516f90e8d4cb40b83b55f3c78a94b13bfb8fe8f5e27624937748
This commit is contained in:
commit
cd110cdd0e
1 changed files with 35 additions and 70 deletions
|
@ -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…
Reference in a new issue