mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-15 12:51:00 +02:00
Merge e6e4c18a9b into merged_master (Bitcoin PR bitcoin/bitcoin#30162)
This commit is contained in:
commit
07fc40d829
4 changed files with 86 additions and 17 deletions
49
test/functional/feature_framework_miniwallet.py
Executable file
49
test/functional/feature_framework_miniwallet.py
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2024 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test MiniWallet."""
|
||||
from test_framework.blocktools import COINBASE_MATURITY
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import (
|
||||
assert_greater_than_or_equal,
|
||||
)
|
||||
from test_framework.wallet import (
|
||||
MiniWallet,
|
||||
MiniWalletMode,
|
||||
)
|
||||
|
||||
|
||||
class FeatureFrameworkMiniWalletTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
|
||||
def test_tx_padding(self):
|
||||
"""Verify that MiniWallet's transaction padding (`target_weight` parameter)
|
||||
works accurately enough (i.e. at most 3 + 8 WUs higher) with all modes.""" # ELEMENTS add 8 WUs
|
||||
for mode_name, wallet in self.wallets:
|
||||
self.log.info(f"Test tx padding with MiniWallet mode {mode_name}...")
|
||||
utxo = wallet.get_utxo(mark_as_spent=False)
|
||||
for target_weight in [1300, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 4000000,
|
||||
1301, 2001, 4337, 13371, 23219, 49153, 102035, 223419, 3999989]: # ELEMENTS: min raised to ~1300 (P2PK base ~1168 WU)
|
||||
tx = wallet.create_self_transfer(utxo_to_spend=utxo, target_weight=target_weight)["tx"]
|
||||
self.log.debug(f"-> target weight: {target_weight}, actual weight: {tx.get_weight()}")
|
||||
assert_greater_than_or_equal(tx.get_weight(), target_weight)
|
||||
assert_greater_than_or_equal(target_weight + 3 + 8, tx.get_weight()) # ELEMENTS add 8 WUs
|
||||
|
||||
def run_test(self):
|
||||
node = self.nodes[0]
|
||||
self.wallets = [
|
||||
("ADDRESS_OP_TRUE", MiniWallet(node, mode=MiniWalletMode.ADDRESS_OP_TRUE)),
|
||||
("RAW_OP_TRUE", MiniWallet(node, mode=MiniWalletMode.RAW_OP_TRUE)),
|
||||
("RAW_P2PK", MiniWallet(node, mode=MiniWalletMode.RAW_P2PK)),
|
||||
]
|
||||
for _, wallet in self.wallets:
|
||||
self.generate(wallet, 10)
|
||||
self.generate(wallet, COINBASE_MATURITY)
|
||||
|
||||
self.test_tx_padding()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
FeatureFrameworkMiniWalletTest().main()
|
||||
|
|
@ -59,7 +59,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
|||
mempoolmin_feerate = node.getmempoolinfo()["mempoolminfee"]
|
||||
tx_A = self.wallet.send_self_transfer(
|
||||
from_node=node,
|
||||
fee=(mempoolmin_feerate / 1000) * (A_weight // 4) + Decimal('0.000001'),
|
||||
fee_rate=mempoolmin_feerate,
|
||||
target_weight=A_weight,
|
||||
utxo_to_spend=rbf_utxo,
|
||||
confirmed_only=True
|
||||
|
|
@ -77,7 +77,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
|||
non_cpfp_carveout_weight = 40001 # EXTRA_DESCENDANT_TX_SIZE_LIMIT + 1
|
||||
tx_C = self.wallet.create_self_transfer(
|
||||
target_weight=non_cpfp_carveout_weight,
|
||||
fee = (mempoolmin_feerate / 1000) * (non_cpfp_carveout_weight // 4) + Decimal('0.000001'),
|
||||
fee_rate=mempoolmin_feerate,
|
||||
utxo_to_spend=tx_B["new_utxo"],
|
||||
confirmed_only=True
|
||||
)
|
||||
|
|
@ -109,7 +109,7 @@ class MempoolLimitTest(BitcoinTestFramework):
|
|||
# happen in the middle of package evaluation, as it can invalidate the coins cache.
|
||||
mempool_evicted_tx = self.wallet.send_self_transfer(
|
||||
from_node=node,
|
||||
fee=(mempoolmin_feerate / 1000) * (evicted_weight // 4) + Decimal('0.000001'),
|
||||
fee_rate=mempoolmin_feerate,
|
||||
target_weight=evicted_weight,
|
||||
confirmed_only=True
|
||||
)
|
||||
|
|
@ -135,11 +135,11 @@ class MempoolLimitTest(BitcoinTestFramework):
|
|||
parent_weight = 100000
|
||||
num_big_parents = 3
|
||||
assert_greater_than(parent_weight * num_big_parents, current_info["maxmempool"] - current_info["bytes"])
|
||||
parent_fee = (100 * mempoolmin_feerate / 1000) * (parent_weight // 4)
|
||||
parent_feerate = 100 * mempoolmin_feerate
|
||||
|
||||
big_parent_txids = []
|
||||
for i in range(num_big_parents):
|
||||
parent = self.wallet.create_self_transfer(fee=parent_fee, target_weight=parent_weight, confirmed_only=True)
|
||||
parent = self.wallet.create_self_transfer(fee_rate=parent_feerate, target_weight=parent_weight, confirmed_only=True)
|
||||
parent_utxos.append(parent["new_utxo"])
|
||||
package_hex.append(parent["hex"])
|
||||
big_parent_txids.append(parent["txid"])
|
||||
|
|
@ -314,18 +314,22 @@ class MempoolLimitTest(BitcoinTestFramework):
|
|||
target_weight_each = 200000
|
||||
assert_greater_than(target_weight_each * 2, node.getmempoolinfo()["maxmempool"] - node.getmempoolinfo()["bytes"])
|
||||
# Should be a true CPFP: parent's feerate is just below mempool min feerate
|
||||
parent_fee = (mempoolmin_feerate / 1000) * (target_weight_each // 4) - Decimal("0.00001")
|
||||
parent_feerate = mempoolmin_feerate - Decimal("0.000001") # 0.1 sats/vbyte below min feerate
|
||||
# Parent + child is above mempool minimum feerate
|
||||
child_fee = (worst_feerate_btcvb) * (target_weight_each // 4) - Decimal("0.00001")
|
||||
child_feerate = (worst_feerate_btcvb * 1000) - Decimal("0.000001") # 0.1 sats/vbyte below worst feerate
|
||||
# However, when eviction is triggered, these transactions should be at the bottom.
|
||||
# This assertion assumes parent and child are the same size.
|
||||
miniwallet.rescan_utxos()
|
||||
tx_parent_just_below = miniwallet.create_self_transfer(fee=parent_fee, target_weight=target_weight_each)
|
||||
tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee=child_fee, target_weight=target_weight_each)
|
||||
tx_parent_just_below = miniwallet.create_self_transfer(fee_rate=parent_feerate, target_weight=target_weight_each)
|
||||
tx_child_just_above = miniwallet.create_self_transfer(utxo_to_spend=tx_parent_just_below["new_utxo"], fee_rate=child_feerate, target_weight=target_weight_each)
|
||||
# This package ranks below the lowest descendant package in the mempool
|
||||
assert_greater_than(worst_feerate_btcvb, (parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()))
|
||||
assert_greater_than(mempoolmin_feerate, (parent_fee) / (tx_parent_just_below["tx"].get_vsize()))
|
||||
assert_greater_than((parent_fee + child_fee) / (tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()), mempoolmin_feerate / 1000)
|
||||
package_fee = tx_parent_just_below["fee"] + tx_child_just_above["fee"]
|
||||
package_vsize = tx_parent_just_below["tx"].get_vsize() + tx_child_just_above["tx"].get_vsize()
|
||||
# ELEMENTS: fee is in satoshis
|
||||
package_fee_btc = Decimal(package_fee) / COIN
|
||||
assert_greater_than(worst_feerate_btcvb, package_fee_btc / package_vsize)
|
||||
assert_greater_than(mempoolmin_feerate, Decimal(tx_parent_just_below["fee"]) / COIN * 1000 / (tx_parent_just_below["tx"].get_vsize()))
|
||||
assert_greater_than(package_fee_btc / package_vsize, mempoolmin_feerate / 1000)
|
||||
res = node.submitpackage([tx_parent_just_below["hex"], tx_child_just_above["hex"]])
|
||||
for wtxid in [tx_parent_just_below["wtxid"], tx_child_just_above["wtxid"]]:
|
||||
assert_equal(res["tx-results"][wtxid]["error"], "mempool full")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
from copy import deepcopy
|
||||
from decimal import Decimal
|
||||
from enum import Enum
|
||||
import math
|
||||
from typing import (
|
||||
Any,
|
||||
Optional,
|
||||
|
|
@ -34,10 +35,13 @@ from test_framework.messages import (
|
|||
CTxOut,
|
||||
CTxOutValue,
|
||||
hash256,
|
||||
ser_compact_size,
|
||||
WITNESS_SCALE_FACTOR,
|
||||
)
|
||||
from test_framework.script import (
|
||||
CScript,
|
||||
LEAF_VERSION_TAPSCRIPT,
|
||||
OP_1,
|
||||
OP_NOP,
|
||||
OP_RETURN,
|
||||
OP_TRUE,
|
||||
|
|
@ -54,6 +58,7 @@ from test_framework.util import (
|
|||
assert_approx,
|
||||
assert_equal,
|
||||
assert_greater_than_or_equal,
|
||||
get_fee,
|
||||
)
|
||||
from test_framework.wallet_util import generate_keypair
|
||||
|
||||
|
|
@ -121,14 +126,17 @@ class MiniWallet:
|
|||
"""Pad a transaction with extra outputs until it reaches a target weight (or higher).
|
||||
returns the tx
|
||||
"""
|
||||
tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN, b'a'])))
|
||||
tx.vout.append(CTxOut(nValue=0, scriptPubKey=CScript([OP_RETURN])))
|
||||
# determine number of needed padding bytes by converting weight difference to vbytes
|
||||
dummy_vbytes = (target_weight - tx.get_weight() + 3) // 4
|
||||
dummy_vbytes += 2 # ELEMENTS
|
||||
tx.vout[-1].scriptPubKey = CScript([OP_RETURN, b'a' * dummy_vbytes])
|
||||
# Lower bound should always be off by at most 3
|
||||
# compensate for the increase of the compact-size encoded script length
|
||||
# (note that the length encoding of the unpadded output script needs one byte)
|
||||
dummy_vbytes -= len(ser_compact_size(dummy_vbytes)) - 1
|
||||
tx.vout[-1].scriptPubKey = CScript([OP_RETURN] + [OP_1] * dummy_vbytes)
|
||||
# Actual weight should be at most 3 higher than target weight
|
||||
assert_greater_than_or_equal(tx.get_weight(), target_weight)
|
||||
# Higher bound should always be off by at most 3 + 12 weight (for encoding the length)
|
||||
assert_greater_than_or_equal(target_weight + 15 + 7, tx.get_weight()) # ELEMENTS
|
||||
assert_greater_than_or_equal(target_weight + 3 + 8, tx.get_weight()) # ELEMENTS: add 8 WUs
|
||||
|
||||
def get_balance(self):
|
||||
return sum(u['value'] for u in self._utxos)
|
||||
|
|
@ -385,9 +393,16 @@ class MiniWallet:
|
|||
vsize = Decimal(248) # P2PK (73 bytes scriptSig + 35 bytes scriptPubKey + 60 bytes other)
|
||||
else:
|
||||
assert False
|
||||
|
||||
if target_weight and not fee: # respect fee_rate if target weight is passed
|
||||
# the actual weight might be off by 3 WUs, so calculate based on that (see self._bulk_tx)
|
||||
max_actual_weight = target_weight + 3 + 8 # ELEMENTS add 8 WUs
|
||||
fee = get_fee(math.ceil(max_actual_weight / WITNESS_SCALE_FACTOR), fee_rate)
|
||||
|
||||
# ELEMENTS: no fee output if fee == 0
|
||||
if fee == 0 and fee_rate == 0:
|
||||
vsize = vsize - 44
|
||||
|
||||
send_value = utxo_to_spend["value"] - (fee or (fee_rate * vsize / 1000))
|
||||
total_input_value_sats = int(COIN * utxo_to_spend["value"])
|
||||
send_value_sats = int(COIN * send_value)
|
||||
|
|
|
|||
|
|
@ -409,6 +409,7 @@ BASE_SCRIPTS = [
|
|||
'feature_addrman.py',
|
||||
'feature_asmap.py',
|
||||
'feature_fastprune.py',
|
||||
'feature_framework_miniwallet.py',
|
||||
'mempool_unbroadcast.py',
|
||||
# ELEMENTS: need to point past versions at our own release server
|
||||
#'mempool_compatibility.py',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue