mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-08-24 19:51:29 +02:00
test: add truc wallet tests
This commit is contained in:
@@ -34,6 +34,9 @@ DEFAULT_MIN_RELAY_TX_FEE = 100
|
|||||||
# Default for -incrementalrelayfee in sat/kvB
|
# Default for -incrementalrelayfee in sat/kvB
|
||||||
DEFAULT_INCREMENTAL_RELAY_FEE = 100
|
DEFAULT_INCREMENTAL_RELAY_FEE = 100
|
||||||
|
|
||||||
|
TRUC_MAX_VSIZE = 10000
|
||||||
|
TRUC_CHILD_MAX_VSIZE = 1000
|
||||||
|
|
||||||
def assert_mempool_contents(test_framework, node, expected=None, sync=True):
|
def assert_mempool_contents(test_framework, node, expected=None, sync=True):
|
||||||
"""Assert that all transactions in expected are in the mempool,
|
"""Assert that all transactions in expected are in the mempool,
|
||||||
and no additional ones exist. 'expected' is an array of
|
and no additional ones exist. 'expected' is an array of
|
||||||
|
@@ -109,6 +109,7 @@ BASE_SCRIPTS = [
|
|||||||
'rpc_psbt.py',
|
'rpc_psbt.py',
|
||||||
'wallet_fundrawtransaction.py',
|
'wallet_fundrawtransaction.py',
|
||||||
'wallet_bumpfee.py',
|
'wallet_bumpfee.py',
|
||||||
|
'wallet_v3_txs.py',
|
||||||
'wallet_backup.py',
|
'wallet_backup.py',
|
||||||
'feature_segwit.py --v2transport',
|
'feature_segwit.py --v2transport',
|
||||||
'feature_segwit.py --v1transport',
|
'feature_segwit.py --v1transport',
|
||||||
|
589
test/functional/wallet_v3_txs.py
Executable file
589
test/functional/wallet_v3_txs.py
Executable file
@@ -0,0 +1,589 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 2025 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 how the wallet deals with TRUC transactions"""
|
||||||
|
|
||||||
|
from decimal import Decimal, getcontext
|
||||||
|
|
||||||
|
from test_framework.authproxy import JSONRPCException
|
||||||
|
from test_framework.messages import (
|
||||||
|
COIN,
|
||||||
|
CTransaction,
|
||||||
|
CTxOut,
|
||||||
|
)
|
||||||
|
from test_framework.script import (
|
||||||
|
CScript,
|
||||||
|
OP_RETURN
|
||||||
|
)
|
||||||
|
|
||||||
|
from test_framework.script_util import bulk_vout
|
||||||
|
|
||||||
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
|
from test_framework.util import (
|
||||||
|
assert_equal,
|
||||||
|
assert_greater_than,
|
||||||
|
assert_raises_rpc_error,
|
||||||
|
)
|
||||||
|
|
||||||
|
from test_framework.mempool_util import (
|
||||||
|
TRUC_MAX_VSIZE,
|
||||||
|
TRUC_CHILD_MAX_VSIZE,
|
||||||
|
)
|
||||||
|
|
||||||
|
# sweep alice and bob's wallets and clear the mempool
|
||||||
|
def cleanup(func):
|
||||||
|
def wrapper(self, *args):
|
||||||
|
try:
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
func(self, *args)
|
||||||
|
finally:
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
try:
|
||||||
|
self.alice.sendall([self.charlie.getnewaddress()])
|
||||||
|
except JSONRPCException as e:
|
||||||
|
assert "Total value of UTXO pool too low to pay for transaction" in e.error['message']
|
||||||
|
try:
|
||||||
|
self.bob.sendall([self.charlie.getnewaddress()])
|
||||||
|
except JSONRPCException as e:
|
||||||
|
assert "Total value of UTXO pool too low to pay for transaction" in e.error['message']
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
|
||||||
|
for wallet in [self.alice, self.bob]:
|
||||||
|
balance = wallet.getbalances()["mine"]
|
||||||
|
for balance_type in ["untrusted_pending", "trusted", "immature"]:
|
||||||
|
assert_equal(balance[balance_type], 0)
|
||||||
|
|
||||||
|
assert_equal(self.alice.getrawmempool(), [])
|
||||||
|
assert_equal(self.bob.getrawmempool(), [])
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
class WalletV3Test(BitcoinTestFramework):
|
||||||
|
def skip_test_if_missing_module(self):
|
||||||
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def set_test_params(self):
|
||||||
|
getcontext().prec=10
|
||||||
|
self.num_nodes = 1
|
||||||
|
self.setup_clean_chain = True
|
||||||
|
|
||||||
|
def send_tx(self, from_wallet, inputs, outputs, version):
|
||||||
|
raw_tx = from_wallet.createrawtransaction(inputs=inputs, outputs=outputs, version=version)
|
||||||
|
if inputs == []:
|
||||||
|
raw_tx = from_wallet.fundrawtransaction(raw_tx, {'include_unsafe' : True})["hex"]
|
||||||
|
raw_tx = from_wallet.signrawtransactionwithwallet(raw_tx)["hex"]
|
||||||
|
txid = from_wallet.sendrawtransaction(raw_tx)
|
||||||
|
return txid
|
||||||
|
|
||||||
|
def bulk_tx(self, tx, amount, target_vsize):
|
||||||
|
tx.vout.append(CTxOut(nValue=(amount * COIN), scriptPubKey=CScript([OP_RETURN])))
|
||||||
|
bulk_vout(tx, target_vsize)
|
||||||
|
|
||||||
|
def run_test_with_swapped_versions(self, test_func):
|
||||||
|
test_func(2, 3)
|
||||||
|
test_func(3, 2)
|
||||||
|
|
||||||
|
def run_test(self):
|
||||||
|
self.nodes[0].createwallet("alice")
|
||||||
|
self.alice = self.nodes[0].get_wallet_rpc("alice")
|
||||||
|
|
||||||
|
self.nodes[0].createwallet("bob")
|
||||||
|
self.bob = self.nodes[0].get_wallet_rpc("bob")
|
||||||
|
|
||||||
|
self.nodes[0].createwallet("charlie")
|
||||||
|
self.charlie = self.nodes[0].get_wallet_rpc("charlie")
|
||||||
|
|
||||||
|
self.generatetoaddress(self.nodes[0], 100, self.charlie.getnewaddress())
|
||||||
|
|
||||||
|
self.run_test_with_swapped_versions(self.tx_spends_unconfirmed_tx_with_wrong_version)
|
||||||
|
self.run_test_with_swapped_versions(self.va_tx_spends_confirmed_vb_tx)
|
||||||
|
self.run_test_with_swapped_versions(self.spend_inputs_with_different_versions)
|
||||||
|
self.spend_inputs_with_different_versions_default_version()
|
||||||
|
self.v3_utxos_appear_in_listunspent()
|
||||||
|
self.truc_tx_with_conflicting_sibling()
|
||||||
|
self.truc_tx_with_conflicting_sibling_change()
|
||||||
|
self.v3_tx_evicted_from_mempool_by_sibling()
|
||||||
|
self.v3_conflict_removed_from_mempool()
|
||||||
|
self.mempool_conflicts_removed_when_v3_conflict_removed()
|
||||||
|
self.max_tx_weight()
|
||||||
|
self.max_tx_child_weight()
|
||||||
|
self.user_input_weight_not_overwritten()
|
||||||
|
self.user_input_weight_not_overwritten_v3_child()
|
||||||
|
self.createpsbt_v3()
|
||||||
|
self.send_v3()
|
||||||
|
self.sendall_v3()
|
||||||
|
self.sendall_with_unconfirmed_v3()
|
||||||
|
self.walletcreatefundedpsbt_v3()
|
||||||
|
self.sendall_truc_weight_limit()
|
||||||
|
self.sendall_truc_child_weight_limit()
|
||||||
|
self.mix_non_truc_versions()
|
||||||
|
self.cant_spend_multiple_unconfirmed_truc_outputs()
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def tx_spends_unconfirmed_tx_with_wrong_version(self, version_a, version_b):
|
||||||
|
self.log.info(f"Test unavailable funds when v{version_b} tx spends unconfirmed v{version_a} tx")
|
||||||
|
|
||||||
|
outputs = {self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, version_a)
|
||||||
|
|
||||||
|
assert_equal(self.bob.getbalances()["mine"]["trusted"], 0)
|
||||||
|
assert_greater_than(self.bob.getbalances()["mine"]["untrusted_pending"], 0)
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 1.0}
|
||||||
|
|
||||||
|
raw_tx_v2 = self.bob.createrawtransaction(inputs=[], outputs=outputs, version=version_b)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Insufficient funds",
|
||||||
|
self.bob.fundrawtransaction,
|
||||||
|
raw_tx_v2, {'include_unsafe': True}
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def va_tx_spends_confirmed_vb_tx(self, version_a, version_b):
|
||||||
|
self.log.info(f"Test available funds when v{version_b} tx spends confirmed v{version_a} tx")
|
||||||
|
|
||||||
|
outputs = {self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, version_a)
|
||||||
|
|
||||||
|
assert_equal(self.bob.getbalances()["mine"]["trusted"], 0)
|
||||||
|
assert_greater_than(self.bob.getbalances()["mine"]["untrusted_pending"], 0)
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 1.0}
|
||||||
|
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
|
||||||
|
self.send_tx(self.bob, [], outputs, version_b)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def v3_utxos_appear_in_listunspent(self):
|
||||||
|
self.log.info("Test that unconfirmed v3 utxos still appear in listunspent")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
parent_txid = self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
assert_equal(self.alice.listunspent(minconf=0)[0]["txid"], parent_txid)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def truc_tx_with_conflicting_sibling(self):
|
||||||
|
self.log.info("Test v3 transaction with conflicting sibling")
|
||||||
|
|
||||||
|
# unconfirmed v3 tx to alice & bob
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
# alice spends her output with a v3 transaction
|
||||||
|
alice_unspent = self.alice.listunspent(minconf=0)[0]
|
||||||
|
outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - Decimal(0.00000120)}
|
||||||
|
self.send_tx(self.alice, [alice_unspent], outputs, 3)
|
||||||
|
|
||||||
|
# bob tries to spend money
|
||||||
|
outputs = {self.bob.getnewaddress() : 1.999}
|
||||||
|
bob_tx = self.bob.createrawtransaction(inputs=[], outputs=outputs, version=3)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Insufficient funds",
|
||||||
|
self.bob.fundrawtransaction,
|
||||||
|
bob_tx, {'include_unsafe': True}
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def truc_tx_with_conflicting_sibling_change(self):
|
||||||
|
self.log.info("Test v3 transaction with conflicting sibling change")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 8.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
|
||||||
|
# unconfirmed v3 tx to alice & bob
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.alice, [], outputs, 3)
|
||||||
|
|
||||||
|
# bob spends his output with a v3 transaction
|
||||||
|
bob_unspent = self.bob.listunspent(minconf=0)[0]
|
||||||
|
outputs = {self.bob.getnewaddress() : bob_unspent['amount'] - Decimal(0.00000120)}
|
||||||
|
self.send_tx(self.bob, [bob_unspent], outputs, 3)
|
||||||
|
|
||||||
|
# alice tries to spend money
|
||||||
|
outputs = {self.alice.getnewaddress() : 1.999}
|
||||||
|
alice_tx = self.alice.createrawtransaction(inputs=[], outputs=outputs, version=3)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Insufficient funds",
|
||||||
|
self.alice.fundrawtransaction,
|
||||||
|
alice_tx, {'include_unsafe': True}
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def spend_inputs_with_different_versions(self, version_a, version_b):
|
||||||
|
self.log.info(f"Test spending a pre-selected v{version_a} input with a v{version_b} transaction")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, version_a)
|
||||||
|
|
||||||
|
# alice spends her output
|
||||||
|
alice_unspent = self.alice.listunspent(minconf=0)[0]
|
||||||
|
outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - Decimal(0.00000120)}
|
||||||
|
alice_tx = self.alice.createrawtransaction(inputs=[alice_unspent], outputs=outputs, version=version_b)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
f"Can't spend unconfirmed version {version_a} pre-selected input with a version {version_b} tx",
|
||||||
|
self.alice.fundrawtransaction,
|
||||||
|
alice_tx
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def spend_inputs_with_different_versions_default_version(self):
|
||||||
|
self.log.info("Test spending a pre-selected v3 input with the default version of transaction")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
# alice spends her output
|
||||||
|
alice_unspent = self.alice.listunspent(minconf=0)[0]
|
||||||
|
outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - Decimal(0.00000120)}
|
||||||
|
alice_tx = self.alice.createrawtransaction(inputs=[alice_unspent], outputs=outputs) # don't set the version here
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Can't spend unconfirmed version 3 pre-selected input with a version 2 tx",
|
||||||
|
self.alice.fundrawtransaction,
|
||||||
|
alice_tx
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def v3_tx_evicted_from_mempool_by_sibling(self):
|
||||||
|
self.log.info("Test v3 transaction evicted because of conflicting sibling")
|
||||||
|
|
||||||
|
# unconfirmed v3 tx to alice & bob
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
# alice spends her output with a v3 transaction
|
||||||
|
alice_unspent = self.alice.listunspent(minconf=0)[0]
|
||||||
|
alice_fee = Decimal(0.00000120)
|
||||||
|
outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - alice_fee}
|
||||||
|
alice_txid = self.send_tx(self.alice, [alice_unspent], outputs, 3)
|
||||||
|
|
||||||
|
# bob tries to spend money
|
||||||
|
bob_unspent = self.bob.listunspent(minconf=0)[0]
|
||||||
|
outputs = {self.bob.getnewaddress() : bob_unspent['amount'] - Decimal(0.00010120)}
|
||||||
|
bob_txid = self.send_tx(self.bob, [bob_unspent], outputs, 3)
|
||||||
|
|
||||||
|
assert_equal(self.alice.gettransaction(alice_txid)['mempoolconflicts'], [bob_txid])
|
||||||
|
|
||||||
|
self.log.info("Test that re-submitting Alice's transaction with a higher fee removes bob's tx as a mempool conflict")
|
||||||
|
fee_delta = Decimal(0.00030120)
|
||||||
|
outputs = {self.alice.getnewaddress() : alice_unspent['amount'] - fee_delta}
|
||||||
|
alice_txid = self.send_tx(self.alice, [alice_unspent], outputs, 3)
|
||||||
|
assert_equal(self.alice.gettransaction(alice_txid)['mempoolconflicts'], [])
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def v3_conflict_removed_from_mempool(self):
|
||||||
|
self.log.info("Test a v3 conflict being removed")
|
||||||
|
# send a v2 output to alice and confirm it
|
||||||
|
txid = self.charlie.sendall([self.alice.getnewaddress()])["txid"]
|
||||||
|
assert_equal(self.charlie.gettransaction(txid, verbose=True)["decoded"]["version"], 2)
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
# create a v3 tx to alice and bob
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
alice_v2_unspent = self.alice.listunspent(minconf=1)[0]
|
||||||
|
alice_unspent = self.alice.listunspent(minconf=0, maxconf=0)[0]
|
||||||
|
|
||||||
|
# alice spends both of her outputs
|
||||||
|
outputs = {self.charlie.getnewaddress() : alice_v2_unspent['amount'] + alice_unspent['amount'] - Decimal(0.00005120)}
|
||||||
|
self.send_tx(self.alice, [alice_v2_unspent, alice_unspent], outputs, 3)
|
||||||
|
# bob can't create a transaction
|
||||||
|
outputs = {self.bob.getnewaddress() : 1.999}
|
||||||
|
bob_tx = self.bob.createrawtransaction(inputs=[], outputs=outputs, version=3)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Insufficient funds",
|
||||||
|
self.bob.fundrawtransaction,
|
||||||
|
bob_tx, {'include_unsafe': True}
|
||||||
|
)
|
||||||
|
# alice fee-bumps her tx so it only spends the v2 utxo
|
||||||
|
outputs = {self.charlie.getnewaddress() : alice_v2_unspent['amount'] - Decimal(0.00015120)}
|
||||||
|
self.send_tx(self.alice, [alice_v2_unspent], outputs, 2)
|
||||||
|
# bob can now create a transaction
|
||||||
|
outputs = {self.bob.getnewaddress() : 1.999}
|
||||||
|
self.send_tx(self.bob, [], outputs, 3)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def mempool_conflicts_removed_when_v3_conflict_removed(self):
|
||||||
|
self.log.info("Test that we remove v3 txs from mempool_conflicts correctly")
|
||||||
|
# send a v2 output to alice and confirm it
|
||||||
|
txid = self.charlie.sendall([self.alice.getnewaddress()])["txid"]
|
||||||
|
assert_equal(self.charlie.gettransaction(txid, verbose=True)["decoded"]["version"], 2)
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
# create a v3 tx to alice and bob
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0, self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
alice_v2_unspent = self.alice.listunspent(minconf=1)[0]
|
||||||
|
alice_unspent = self.alice.listunspent(minconf=0, maxconf=0)[0]
|
||||||
|
# bob spends his utxo
|
||||||
|
inputs=[]
|
||||||
|
outputs = {self.bob.getnewaddress() : 1.999}
|
||||||
|
bob_txid = self.send_tx(self.bob, inputs, outputs, 3)
|
||||||
|
# alice spends both of her utxos, replacing bob's tx
|
||||||
|
outputs = {self.charlie.getnewaddress() : alice_v2_unspent['amount'] + alice_unspent['amount'] - Decimal(0.00005120)}
|
||||||
|
alice_txid = self.send_tx(self.alice, [alice_v2_unspent, alice_unspent], outputs, 3)
|
||||||
|
# bob's tx now has a mempool conflict
|
||||||
|
assert_equal(self.bob.gettransaction(bob_txid)['mempoolconflicts'], [alice_txid])
|
||||||
|
# alice fee-bumps her tx so it only spends the v2 utxo
|
||||||
|
outputs = {self.charlie.getnewaddress() : alice_v2_unspent['amount'] - Decimal(0.00015120)}
|
||||||
|
self.send_tx(self.alice, [alice_v2_unspent], outputs, 2)
|
||||||
|
# bob's tx now has non conflicts and can be rebroadcast
|
||||||
|
bob_tx = self.bob.gettransaction(bob_txid)
|
||||||
|
assert_equal(bob_tx['mempoolconflicts'], [])
|
||||||
|
self.bob.sendrawtransaction(bob_tx['hex'])
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def max_tx_weight(self):
|
||||||
|
self.log.info("Test max v3 transaction weight.")
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.version = 3 # make this a truc tx
|
||||||
|
# increase tx weight almost to the max truc size
|
||||||
|
self.bulk_tx(tx, 5, TRUC_MAX_VSIZE - 100)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
|
||||||
|
self.charlie.fundrawtransaction,
|
||||||
|
tx.serialize_with_witness().hex(),
|
||||||
|
{'include_unsafe' : True}
|
||||||
|
)
|
||||||
|
|
||||||
|
tx.version = 2
|
||||||
|
self.charlie.fundrawtransaction(tx.serialize_with_witness().hex())
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def max_tx_child_weight(self):
|
||||||
|
self.log.info("Test max v3 transaction child weight.")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 10}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.version = 3
|
||||||
|
|
||||||
|
self.bulk_tx(tx, 5, TRUC_CHILD_MAX_VSIZE - 100)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"The inputs size exceeds the maximum weight. Please try sending a smaller amount or manually consolidating your wallet's UTXOs",
|
||||||
|
self.alice.fundrawtransaction,
|
||||||
|
tx.serialize_with_witness().hex(),
|
||||||
|
{'include_unsafe' : True}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
self.alice.fundrawtransaction(tx.serialize_with_witness().hex())
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def user_input_weight_not_overwritten(self):
|
||||||
|
self.log.info("Test that the user-input tx weight is not overwritten by the truc maximum")
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.version = 3
|
||||||
|
|
||||||
|
self.bulk_tx(tx, 5, int(TRUC_MAX_VSIZE/2))
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Maximum transaction weight is less than transaction weight without inputs",
|
||||||
|
self.charlie.fundrawtransaction,
|
||||||
|
tx.serialize_with_witness().hex(),
|
||||||
|
{'include_unsafe' : True, 'max_tx_weight' : int(TRUC_MAX_VSIZE/2)}
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def user_input_weight_not_overwritten_v3_child(self):
|
||||||
|
self.log.info("Test that the user-input tx weight is not overwritten by the truc child maximum")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 10}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
tx = CTransaction()
|
||||||
|
tx.version = 3
|
||||||
|
|
||||||
|
self.bulk_tx(tx, 5, int(TRUC_CHILD_MAX_VSIZE/2))
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Maximum transaction weight is less than transaction weight without inputs",
|
||||||
|
self.alice.fundrawtransaction,
|
||||||
|
tx.serialize_with_witness().hex(),
|
||||||
|
{'include_unsafe' : True, 'max_tx_weight' : int(TRUC_CHILD_MAX_VSIZE/2)}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
self.alice.fundrawtransaction(tx.serialize_with_witness().hex())
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def createpsbt_v3(self):
|
||||||
|
self.log.info("Test setting version to 3 with createpsbt")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 10}
|
||||||
|
psbt = self.charlie.createpsbt(inputs=[], outputs=outputs, version=3)
|
||||||
|
assert_equal(self.charlie.decodepsbt(psbt)["tx"]["version"], 3)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def send_v3(self):
|
||||||
|
self.log.info("Test setting version to 3 with send")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 10}
|
||||||
|
tx_hex = self.charlie.send(outputs=outputs, add_to_wallet=False, version=3)["hex"]
|
||||||
|
assert_equal(self.charlie.decoderawtransaction(tx_hex)["version"], 3)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def sendall_v3(self):
|
||||||
|
self.log.info("Test setting version to 3 with sendall")
|
||||||
|
|
||||||
|
tx_hex = self.charlie.sendall(recipients=[self.alice.getnewaddress()], version=3, add_to_wallet=False)["hex"]
|
||||||
|
assert_equal(self.charlie.decoderawtransaction(tx_hex)["version"], 3)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def sendall_with_unconfirmed_v3(self):
|
||||||
|
self.log.info("Test setting version to 3 with sendall + unconfirmed inputs")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress(): 2.00001 for _ in range(4)}
|
||||||
|
|
||||||
|
self.send_tx(self.charlie, [], outputs, 2)
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
|
||||||
|
unspents = self.alice.listunspent()
|
||||||
|
|
||||||
|
# confirmed v2 utxos
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
confirmed_v2 = self.send_tx(self.alice, [unspents[0]], outputs, 2)
|
||||||
|
|
||||||
|
# confirmed v3 utxos
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
confirmed_v3 = self.send_tx(self.alice, [unspents[1]], outputs, 3)
|
||||||
|
|
||||||
|
self.generate(self.nodes[0], 1)
|
||||||
|
|
||||||
|
# unconfirmed v2 utxos
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
unconfirmed_v2 = self.send_tx(self.alice, [unspents[2]], outputs, 2)
|
||||||
|
|
||||||
|
# unconfirmed v3 utxos
|
||||||
|
outputs = {self.alice.getnewaddress() : 2.0}
|
||||||
|
unconfirmed_v3 = self.send_tx(self.alice, [unspents[3]], outputs, 3)
|
||||||
|
|
||||||
|
# Test that the only unconfirmed inputs this v3 tx spends are v3
|
||||||
|
tx_hex = self.alice.sendall([self.bob.getnewaddress()], version=3, add_to_wallet=False, minconf=0)["hex"]
|
||||||
|
|
||||||
|
decoded_tx = self.alice.decoderawtransaction(tx_hex)
|
||||||
|
decoded_vin_txids = [txin["txid"] for txin in decoded_tx["vin"]]
|
||||||
|
|
||||||
|
assert_equal(decoded_tx["version"], 3)
|
||||||
|
|
||||||
|
assert confirmed_v3 in decoded_vin_txids
|
||||||
|
assert confirmed_v2 in decoded_vin_txids
|
||||||
|
assert unconfirmed_v3 in decoded_vin_txids
|
||||||
|
assert unconfirmed_v2 not in decoded_vin_txids
|
||||||
|
|
||||||
|
# Test that the only unconfirmed inputs this v2 tx spends are v2
|
||||||
|
tx_hex = self.alice.sendall([self.bob.getnewaddress()], version=2, add_to_wallet=False, minconf=0)["hex"]
|
||||||
|
|
||||||
|
decoded_tx = self.alice.decoderawtransaction(tx_hex)
|
||||||
|
decoded_vin_txids = [txin["txid"] for txin in decoded_tx["vin"]]
|
||||||
|
|
||||||
|
assert_equal(decoded_tx["version"], 2)
|
||||||
|
|
||||||
|
assert confirmed_v3 in decoded_vin_txids
|
||||||
|
assert confirmed_v2 in decoded_vin_txids
|
||||||
|
assert unconfirmed_v2 in decoded_vin_txids
|
||||||
|
assert unconfirmed_v3 not in decoded_vin_txids
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def walletcreatefundedpsbt_v3(self):
|
||||||
|
self.log.info("Test setting version to 3 with walletcreatefundedpsbt")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 10}
|
||||||
|
psbt = self.charlie.walletcreatefundedpsbt(inputs=[], outputs=outputs, version=3)["psbt"]
|
||||||
|
assert_equal(self.charlie.decodepsbt(psbt)["tx"]["version"], 3)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def sendall_truc_weight_limit(self):
|
||||||
|
self.log.info("Test that sendall follows truc tx weight limit")
|
||||||
|
self.charlie.sendall([self.alice.getnewaddress() for _ in range(300)], add_to_wallet=False, version=2)
|
||||||
|
|
||||||
|
# check that error is only raised if version is 3
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Transaction too large" ,
|
||||||
|
self.charlie.sendall,
|
||||||
|
[self.alice.getnewaddress() for _ in range(300)],
|
||||||
|
version=3
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def sendall_truc_child_weight_limit(self):
|
||||||
|
self.log.info("Test that sendall follows spending unconfirmed truc tx weight limit")
|
||||||
|
outputs = {self.charlie.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
self.charlie.sendall([self.alice.getnewaddress() for _ in range(50)], add_to_wallet=False)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Transaction too large" ,
|
||||||
|
self.charlie.sendall,
|
||||||
|
[self.alice.getnewaddress() for _ in range(50)],
|
||||||
|
version=3
|
||||||
|
)
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def mix_non_truc_versions(self):
|
||||||
|
self.log.info("Test that we can mix non-truc versions when spending an unconfirmed output")
|
||||||
|
|
||||||
|
outputs = {self.bob.getnewaddress() : 2.0}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 1)
|
||||||
|
|
||||||
|
assert_equal(self.bob.getbalances()["mine"]["trusted"], 0)
|
||||||
|
assert_greater_than(self.bob.getbalances()["mine"]["untrusted_pending"], 0)
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress() : 1.0}
|
||||||
|
|
||||||
|
raw_tx_v2 = self.bob.createrawtransaction(inputs=[], outputs=outputs, version=2)
|
||||||
|
|
||||||
|
# does not throw an error
|
||||||
|
self.bob.fundrawtransaction(raw_tx_v2, {'include_unsafe': True})["hex"]
|
||||||
|
|
||||||
|
@cleanup
|
||||||
|
def cant_spend_multiple_unconfirmed_truc_outputs(self):
|
||||||
|
self.log.info("Test that we can't spend multiple unconfirmed truc outputs")
|
||||||
|
|
||||||
|
outputs = {self.alice.getnewaddress(): 2.00001}
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
self.send_tx(self.charlie, [], outputs, 3)
|
||||||
|
|
||||||
|
assert_equal(len(self.alice.listunspent(minconf=0)), 2)
|
||||||
|
|
||||||
|
outputs = {self.bob.getnewaddress() : 3.0}
|
||||||
|
|
||||||
|
raw_tx = self.alice.createrawtransaction(inputs=[], outputs=outputs, version=3)
|
||||||
|
|
||||||
|
assert_raises_rpc_error(
|
||||||
|
-4,
|
||||||
|
"Insufficient funds",
|
||||||
|
self.alice.fundrawtransaction,
|
||||||
|
raw_tx,
|
||||||
|
{'include_unsafe' : True}
|
||||||
|
)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
WalletV3Test(__file__).main()
|
Reference in New Issue
Block a user