aboutsummaryrefslogtreecommitdiff
path: root/test/functional
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2021-09-03 21:27:47 +1200
committerSamuel Dobson <dobsonsa68@gmail.com>2021-09-03 22:45:48 +1200
commitd5d0a5c6045d6ff92d32c9773a5854f92970e2a7 (patch)
treeb9960afe95c0d8a9e5e9def669eac9564408a6c6 /test/functional
parentf036c35e51fea8cf2a0dc41c10c47e37fc01e999 (diff)
parent3633b667ffca5a715d9fb27e977515c1e24f600a (diff)
downloadbitcoin-d5d0a5c6045d6ff92d32c9773a5854f92970e2a7.tar.xz
Merge bitcoin/bitcoin#17526: Add Single Random Draw as an additional coin selection algorithm
3633b667ffca5a715d9fb27e977515c1e24f600a Use SelectCoinsSRD if it has less waste (Andrew Chow) 8bf789b4b4b26082aea1d91c4d7aa8b01aedfdcf Add SelectCoinsSRD function (Andrew Chow) 2ad3b5d2ad03f781f564ee697ef11e18b2edcea3 tests: wallet_basic lock needed unspents (Andrew Chow) b77885f13e480304085c654f1b948b20bba63452 tests: wallet_txn explicilty specify inputs (Andrew Chow) 59ba7d2861359f19fa740da9daad1a199409583f tests: rpc_fundrawtx better test for UTXO inclusion with include_unsafe (Andrew Chow) a165bfbe44b4db3a40b8d1ba8095035367c932a7 tests: rpc_fundrawtx use specific inputs for unavailable change test (Andrew Chow) df765a484d84702f066e127813fa45a7d440a957 tests: rpc_fundrawtx lock to UTXO types (Andrew Chow) Pull request description: To ease in the use of SRD as our fallback mechanism, this PR adds it as a secondary fallback algorithm in addition to the knapsack solver. Since #22009, the solution with the least waste will be chosen. This pattern is continued with SRD simply being another solution whose waste is compared. ACKs for top commit: glozow: reACK 3633b66 via `git range-diff 981b9d1...3633b66`, thanks for taking the suggestions laanwj: Concept and code review ACK 3633b667ffca5a715d9fb27e977515c1e24f600a Tree-SHA512: 895659f553fea2230990136565bdf18b1328de8b0ce47f06b64bb4d69301f6dd68cb38debe5c24fb6de1317b735fc020a987c541f00bbea65229de47e53adf92
Diffstat (limited to 'test/functional')
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py78
-rwxr-xr-xtest/functional/wallet_basic.py4
-rwxr-xr-xtest/functional/wallet_txn_clone.py13
-rwxr-xr-xtest/functional/wallet_txn_doublespend.py13
4 files changed, 89 insertions, 19 deletions
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 56312dc6e5..cda0ae0eeb 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -47,7 +47,40 @@ class RawTransactionsTest(BitcoinTestFramework):
self.connect_nodes(0, 2)
self.connect_nodes(0, 3)
+ def lock_outputs_type(self, wallet, outputtype):
+ """
+ Only allow UTXOs of the given type
+ """
+ if outputtype in ["legacy", "p2pkh", "pkh"]:
+ prefixes = ["pkh(", "sh(multi("]
+ elif outputtype in ["p2sh-segwit", "sh_wpkh"]:
+ prefixes = ["sh(wpkh(", "sh(wsh("]
+ elif outputtype in ["bech32", "wpkh"]:
+ prefixes = ["wpkh(", "wsh("]
+ else:
+ assert False, f"Unknown output type {outputtype}"
+
+ to_lock = []
+ for utxo in wallet.listunspent():
+ if "desc" in utxo:
+ for prefix in prefixes:
+ if utxo["desc"].startswith(prefix):
+ to_lock.append({"txid": utxo["txid"], "vout": utxo["vout"]})
+ wallet.lockunspent(False, to_lock)
+
+ def unlock_utxos(self, wallet):
+ """
+ Unlock all UTXOs except the watchonly one
+ """
+ to_keep = []
+ if self.watchonly_txid is not None and self.watchonly_vout is not None:
+ to_keep.append({"txid": self.watchonly_txid, "vout": self.watchonly_vout})
+ wallet.lockunspent(True)
+ wallet.lockunspent(False, to_keep)
+
def run_test(self):
+ self.watchonly_txid = None
+ self.watchonly_vout = None
self.log.info("Connect nodes, set fees, generate blocks, and sync")
self.min_relay_tx_fee = self.nodes[0].getnetworkinfo()['relayfee']
# This test is not meant to test fee estimation and we'd like
@@ -373,6 +406,7 @@ class RawTransactionsTest(BitcoinTestFramework):
def test_fee_p2pkh(self):
"""Compare fee of a standard pubkeyhash transaction."""
self.log.info("Test fundrawtxn p2pkh fee")
+ self.lock_outputs_type(self.nodes[0], "p2pkh")
inputs = []
outputs = {self.nodes[1].getnewaddress():1.1}
rawtx = self.nodes[0].createrawtransaction(inputs, outputs)
@@ -386,9 +420,12 @@ class RawTransactionsTest(BitcoinTestFramework):
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
+ self.unlock_utxos(self.nodes[0])
+
def test_fee_p2pkh_multi_out(self):
"""Compare fee of a standard pubkeyhash transaction with multiple outputs."""
self.log.info("Test fundrawtxn p2pkh fee with multiple outputs")
+ self.lock_outputs_type(self.nodes[0], "p2pkh")
inputs = []
outputs = {
self.nodes[1].getnewaddress():1.1,
@@ -409,8 +446,11 @@ class RawTransactionsTest(BitcoinTestFramework):
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
+ self.unlock_utxos(self.nodes[0])
+
def test_fee_p2sh(self):
"""Compare fee of a 2-of-2 multisig p2sh transaction."""
+ self.lock_outputs_type(self.nodes[0], "p2pkh")
# Create 2-of-2 addr.
addr1 = self.nodes[1].getnewaddress()
addr2 = self.nodes[1].getnewaddress()
@@ -433,9 +473,12 @@ class RawTransactionsTest(BitcoinTestFramework):
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
+ self.unlock_utxos(self.nodes[0])
+
def test_fee_4of5(self):
"""Compare fee of a standard pubkeyhash transaction."""
self.log.info("Test fundrawtxn fee with 4-of-5 addresses")
+ self.lock_outputs_type(self.nodes[0], "p2pkh")
# Create 4-of-5 addr.
addr1 = self.nodes[1].getnewaddress()
@@ -474,6 +517,8 @@ class RawTransactionsTest(BitcoinTestFramework):
feeDelta = Decimal(fundedTx['fee']) - Decimal(signedFee)
assert feeDelta >= 0 and feeDelta <= self.fee_tolerance
+ self.unlock_utxos(self.nodes[0])
+
def test_spend_2of2(self):
"""Spend a 2-of-2 multisig transaction over fundraw."""
self.log.info("Test fundpsbt spending 2-of-2 multisig")
@@ -542,15 +587,18 @@ class RawTransactionsTest(BitcoinTestFramework):
# Drain the keypool.
self.nodes[1].getnewaddress()
self.nodes[1].getrawchangeaddress()
- inputs = []
- outputs = {self.nodes[0].getnewaddress():1.19999500}
+
+ # Choose 2 inputs
+ inputs = self.nodes[1].listunspent()[0:2]
+ value = sum(inp["amount"] for inp in inputs) - Decimal("0.00000500") # Pay a 500 sat fee
+ outputs = {self.nodes[0].getnewaddress():value}
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
# fund a transaction that does not require a new key for the change output
self.nodes[1].fundrawtransaction(rawtx)
# fund a transaction that requires a new key for the change output
# creating the key must be impossible because the wallet is locked
- outputs = {self.nodes[0].getnewaddress():1.1}
+ outputs = {self.nodes[0].getnewaddress():value - Decimal("0.1")}
rawtx = self.nodes[1].createrawtransaction(inputs, outputs)
assert_raises_rpc_error(-4, "Transaction needs a change address, but we can't generate it.", self.nodes[1].fundrawtransaction, rawtx)
@@ -944,31 +992,31 @@ class RawTransactionsTest(BitcoinTestFramework):
# We receive unconfirmed funds from external keys (unsafe outputs).
addr = wallet.getnewaddress()
- txid1 = self.nodes[2].sendtoaddress(addr, 6)
- txid2 = self.nodes[2].sendtoaddress(addr, 4)
- self.sync_all()
- vout1 = find_vout_for_address(wallet, txid1, addr)
- vout2 = find_vout_for_address(wallet, txid2, addr)
+ inputs = []
+ for i in range(0, 2):
+ txid = self.nodes[2].sendtoaddress(addr, 5)
+ self.sync_mempools()
+ vout = find_vout_for_address(wallet, txid, addr)
+ inputs.append((txid, vout))
# Unsafe inputs are ignored by default.
- rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 5}])
+ rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 7.5}])
assert_raises_rpc_error(-4, "Insufficient funds", wallet.fundrawtransaction, rawtx)
# But we can opt-in to use them for funding.
fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
- assert any([txin['txid'] == txid1 and txin['vout'] == vout1 for txin in tx_dec['vin']])
+ assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
- wallet.sendrawtransaction(signedtx['hex'])
+ assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
# And we can also use them once they're confirmed.
self.generate(self.nodes[0], 1)
- rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 3}])
- fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": True})
+ fundedtx = wallet.fundrawtransaction(rawtx, {"include_unsafe": False})
tx_dec = wallet.decoderawtransaction(fundedtx['hex'])
- assert any([txin['txid'] == txid2 and txin['vout'] == vout2 for txin in tx_dec['vin']])
+ assert all((txin["txid"], txin["vout"]) in inputs for txin in tx_dec["vin"])
signedtx = wallet.signrawtransactionwithwallet(fundedtx['hex'])
- wallet.sendrawtransaction(signedtx['hex'])
+ assert wallet.testmempoolaccept([signedtx['hex']])[0]["allowed"]
def test_22670(self):
# In issue #22670, it was observed that ApproximateBestSubset may
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index 5c464ae142..f57f2a44bd 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -13,6 +13,7 @@ from test_framework.util import (
assert_equal,
assert_fee_amount,
assert_raises_rpc_error,
+ find_vout_for_address,
)
from test_framework.wallet_util import test_address
@@ -463,6 +464,9 @@ class WalletTest(BitcoinTestFramework):
# 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
+ self.sync_mempools(self.nodes[0:3])
+ vout = find_vout_for_address(self.nodes[2], txid, address_to_import)
+ self.nodes[2].lockunspent(False, [{"txid": txid, "vout": vout}])
self.generate(self.nodes[0], 1)
self.sync_all(self.nodes[0:3])
diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py
index 3eb525a9bc..7f178d7d46 100755
--- a/test/functional/wallet_txn_clone.py
+++ b/test/functional/wallet_txn_clone.py
@@ -7,6 +7,7 @@
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
+ find_vout_for_address
)
from test_framework.messages import (
COIN,
@@ -33,6 +34,13 @@ class TxnMallTest(BitcoinTestFramework):
super().setup_network()
self.disconnect_nodes(1, 2)
+ def spend_txid(self, txid, vout, outputs):
+ inputs = [{"txid": txid, "vout": vout}]
+ tx = self.nodes[0].createrawtransaction(inputs, outputs)
+ tx = self.nodes[0].fundrawtransaction(tx)
+ tx = self.nodes[0].signrawtransactionwithwallet(tx['hex'])
+ return self.nodes[0].sendrawtransaction(tx['hex'])
+
def run_test(self):
if self.options.segwit:
output_type = "p2sh-segwit"
@@ -49,6 +57,7 @@ class TxnMallTest(BitcoinTestFramework):
node0_address1 = self.nodes[0].getnewaddress(address_type=output_type)
node0_txid1 = self.nodes[0].sendtoaddress(node0_address1, 1219)
node0_tx1 = self.nodes[0].gettransaction(node0_txid1)
+ self.nodes[0].lockunspent(False, [{"txid":node0_txid1, "vout": find_vout_for_address(self.nodes[0], node0_txid1, node0_address1)}])
node0_address2 = self.nodes[0].getnewaddress(address_type=output_type)
node0_txid2 = self.nodes[0].sendtoaddress(node0_address2, 29)
@@ -61,8 +70,8 @@ class TxnMallTest(BitcoinTestFramework):
node1_address = self.nodes[1].getnewaddress()
# Send tx1, and another transaction tx2 that won't be cloned
- txid1 = self.nodes[0].sendtoaddress(node1_address, 40)
- txid2 = self.nodes[0].sendtoaddress(node1_address, 20)
+ txid1 = self.spend_txid(node0_txid1, find_vout_for_address(self.nodes[0], node0_txid1, node0_address1), {node1_address: 40})
+ txid2 = self.spend_txid(node0_txid2, find_vout_for_address(self.nodes[0], node0_txid2, node0_address2), {node1_address: 20})
# Construct a clone of tx1, to be malleated
rawtx1 = self.nodes[0].getrawtransaction(txid1, 1)
diff --git a/test/functional/wallet_txn_doublespend.py b/test/functional/wallet_txn_doublespend.py
index bfa171d913..150e4083b9 100755
--- a/test/functional/wallet_txn_doublespend.py
+++ b/test/functional/wallet_txn_doublespend.py
@@ -9,6 +9,7 @@ from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
find_output,
+ find_vout_for_address
)
@@ -29,6 +30,13 @@ class TxnMallTest(BitcoinTestFramework):
super().setup_network()
self.disconnect_nodes(1, 2)
+ def spend_txid(self, txid, vout, outputs):
+ inputs = [{"txid": txid, "vout": vout}]
+ tx = self.nodes[0].createrawtransaction(inputs, outputs)
+ tx = self.nodes[0].fundrawtransaction(tx)
+ tx = self.nodes[0].signrawtransactionwithwallet(tx['hex'])
+ return self.nodes[0].sendrawtransaction(tx['hex'])
+
def run_test(self):
# All nodes should start with 1,250 BTC:
starting_balance = 1250
@@ -47,6 +55,7 @@ class TxnMallTest(BitcoinTestFramework):
node0_address_foo = self.nodes[0].getnewaddress()
fund_foo_txid = self.nodes[0].sendtoaddress(node0_address_foo, 1219)
fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid)
+ self.nodes[0].lockunspent(False, [{"txid":fund_foo_txid, "vout": find_vout_for_address(self.nodes[0], fund_foo_txid, node0_address_foo)}])
node0_address_bar = self.nodes[0].getnewaddress()
fund_bar_txid = self.nodes[0].sendtoaddress(node0_address_bar, 29)
@@ -77,8 +86,8 @@ class TxnMallTest(BitcoinTestFramework):
assert_equal(doublespend["complete"], True)
# Create two spends using 1 50 BTC coin each
- txid1 = self.nodes[0].sendtoaddress(node1_address, 40)
- txid2 = self.nodes[0].sendtoaddress(node1_address, 20)
+ txid1 = self.spend_txid(fund_foo_txid, find_vout_for_address(self.nodes[0], fund_foo_txid, node0_address_foo), {node1_address: 40})
+ txid2 = self.spend_txid(fund_bar_txid, find_vout_for_address(self.nodes[0], fund_bar_txid, node0_address_bar), {node1_address: 20})
# Have node0 mine a block:
if (self.options.mine_block):