Skip to content

Commit

Permalink
test: refactor: support sending funds with outpoint result
Browse files Browse the repository at this point in the history
This commit introduces a helper `create_outpoints` to execute the
`send` RPC and immediately return the target address outpoints as UTXO
dictionary in the common format, making the tests more readable and
avoiding unnecessary duplication.
  • Loading branch information
theStack committed Oct 24, 2023
1 parent d724bb5 commit 73a339a
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 107 deletions.
47 changes: 19 additions & 28 deletions test/functional/rpc_psbt.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
assert_greater_than,
assert_greater_than_or_equal,
assert_raises_rpc_error,
find_output,
find_vout_for_address,
random_bytes,
)
Expand Down Expand Up @@ -417,16 +416,17 @@ def run_test(self):
self.nodes[0].converttopsbt(hexstring=signedtx['hex'], permitsigdata=True)

# Create outputs to nodes 1 and 2
# (note that we intentionally create two different txs here, as we want
# to check that each node is missing prevout data for one of the two
# utxos, see "should only have data for one input" test below)
node1_addr = self.nodes[1].getnewaddress()
node2_addr = self.nodes[2].getnewaddress()
txid1 = self.nodes[0].sendtoaddress(node1_addr, 13)
txid2 = self.nodes[0].sendtoaddress(node2_addr, 13)
blockhash = self.generate(self.nodes[0], 6)[0]
vout1 = find_output(self.nodes[1], txid1, 13, blockhash=blockhash)
vout2 = find_output(self.nodes[2], txid2, 13, blockhash=blockhash)
utxo1 = self.create_outpoints(self.nodes[0], outputs=[{node1_addr: 13}])[0]
utxo2 = self.create_outpoints(self.nodes[0], outputs=[{node2_addr: 13}])[0]
self.generate(self.nodes[0], 6)[0]

# Create a psbt spending outputs from nodes 1 and 2
psbt_orig = self.nodes[0].createpsbt([{"txid":txid1, "vout":vout1}, {"txid":txid2, "vout":vout2}], {self.nodes[0].getnewaddress():25.999})
psbt_orig = self.nodes[0].createpsbt([utxo1, utxo2], {self.nodes[0].getnewaddress():25.999})

# Update psbts, should only have data for one input and not the other
psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig, False, "ALL")['psbt']
Expand Down Expand Up @@ -603,22 +603,17 @@ def run_test(self):

# Send to all types of addresses
addr1 = self.nodes[1].getnewaddress("", "bech32")
txid1 = self.nodes[0].sendtoaddress(addr1, 11)
vout1 = find_output(self.nodes[0], txid1, 11)
addr2 = self.nodes[1].getnewaddress("", "legacy")
txid2 = self.nodes[0].sendtoaddress(addr2, 11)
vout2 = find_output(self.nodes[0], txid2, 11)
addr3 = self.nodes[1].getnewaddress("", "p2sh-segwit")
txid3 = self.nodes[0].sendtoaddress(addr3, 11)
vout3 = find_output(self.nodes[0], txid3, 11)
utxo1, utxo2, utxo3 = self.create_outpoints(self.nodes[1], outputs=[{addr1: 11}, {addr2: 11}, {addr3: 11}])
self.sync_all()

def test_psbt_input_keys(psbt_input, keys):
"""Check that the psbt input has only the expected keys."""
assert_equal(set(keys), set(psbt_input.keys()))

# Create a PSBT. None of the inputs are filled initially
psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999})
psbt = self.nodes[1].createpsbt([utxo1, utxo2, utxo3], {self.nodes[0].getnewaddress():32.999})
decoded = self.nodes[1].decodepsbt(psbt)
test_psbt_input_keys(decoded['inputs'][0], [])
test_psbt_input_keys(decoded['inputs'][1], [])
Expand All @@ -641,15 +636,14 @@ def test_psbt_input_keys(psbt_input, keys):
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script'])

# Two PSBTs with a common input should not be joinable
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})
psbt1 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')})
assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, updated])

# Join two distinct PSBTs
addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit")
txid4 = self.nodes[0].sendtoaddress(addr4, 5)
vout4 = find_output(self.nodes[0], txid4, 5)
utxo4 = self.create_outpoints(self.nodes[0], outputs=[{addr4: 5}])[0]
self.generate(self.nodes[0], 6)
psbt2 = self.nodes[1].createpsbt([{"txid":txid4, "vout":vout4}], {self.nodes[0].getnewaddress():Decimal('4.999')})
psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')})
psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt']
psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0]
Expand All @@ -669,11 +663,10 @@ def test_psbt_input_keys(psbt_input, keys):

# Newly created PSBT needs UTXOs and updating
addr = self.nodes[1].getnewaddress("", "p2sh-segwit")
txid = self.nodes[0].sendtoaddress(addr, 7)
utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 7}])[0]
addrinfo = self.nodes[1].getaddressinfo(addr)
blockhash = self.generate(self.nodes[0], 6)[0]
vout = find_output(self.nodes[0], txid, 7, blockhash=blockhash)
psbt = self.nodes[1].createpsbt([{"txid":txid, "vout":vout}], {self.nodes[0].getnewaddress("", "p2sh-segwit"):Decimal('6.999')})
self.generate(self.nodes[0], 6)[0]
psbt = self.nodes[1].createpsbt([utxo], {self.nodes[0].getnewaddress("", "p2sh-segwit"):Decimal('6.999')})
analyzed = self.nodes[0].analyzepsbt(psbt)
assert not analyzed['inputs'][0]['has_utxo'] and not analyzed['inputs'][0]['is_final'] and analyzed['inputs'][0]['next'] == 'updater' and analyzed['next'] == 'updater'

Expand Down Expand Up @@ -872,9 +865,8 @@ def test_psbt_input_keys(psbt_input, keys):

self.log.info("Test that walletprocesspsbt both updates and signs a non-updated psbt containing Taproot inputs")
addr = self.nodes[0].getnewaddress("", "bech32m")
txid = self.nodes[0].sendtoaddress(addr, 1)
vout = find_vout_for_address(self.nodes[0], txid, addr)
psbt = self.nodes[0].createpsbt([{"txid": txid, "vout": vout}], [{self.nodes[0].getnewaddress(): 0.9999}])
utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 1}])[0]
psbt = self.nodes[0].createpsbt([utxo], [{self.nodes[0].getnewaddress(): 0.9999}])
signed = self.nodes[0].walletprocesspsbt(psbt)
rawtx = signed["hex"]
self.nodes[0].sendrawtransaction(rawtx)
Expand Down Expand Up @@ -962,11 +954,10 @@ def test_psbt_input_keys(psbt_input, keys):

descriptor = descsum_create(f"wpkh({key})")

txid = self.nodes[0].sendtoaddress(address, 1)
utxo = self.create_outpoints(self.nodes[0], outputs=[{address: 1}])[0]
self.sync_all()
vout = find_output(self.nodes[0], txid, 1)

psbt = self.nodes[2].createpsbt([{"txid": txid, "vout": vout}], {self.nodes[0].getnewaddress(): 0.99999})
psbt = self.nodes[2].createpsbt([utxo], {self.nodes[0].getnewaddress(): 0.99999})
decoded = self.nodes[2].decodepsbt(psbt)
test_psbt_input_keys(decoded['inputs'][0], [])

Expand Down
17 changes: 17 additions & 0 deletions test/functional/test_framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
PortSeed,
assert_equal,
check_json_precision,
find_vout_for_address,
get_datadir_path,
initialize_datadir,
p2p_port,
Expand Down Expand Up @@ -697,6 +698,22 @@ def generatetodescriptor(self, generator, *args, sync_fun=None, **kwargs):
sync_fun() if sync_fun else self.sync_all()
return blocks

def create_outpoints(self, node, *, outputs):
"""Send funds to a given list of `{address: amount}` targets using the bitcoind
wallet and return the corresponding outpoints as a list of dictionaries
`[{"txid": txid, "vout": vout1}, {"txid": txid, "vout": vout2}, ...]`.
The result can be used to specify inputs for RPCs like `createrawtransaction`,
`createpsbt`, `lockunspent` etc."""
assert all(len(output.keys()) == 1 for output in outputs)
send_res = node.send(outputs)
assert send_res["complete"]
utxos = []
for output in outputs:
address = list(output.keys())[0]
vout = find_vout_for_address(node, send_res["txid"], address)
utxos.append({"txid": send_res["txid"], "vout": vout})
return utxos

def sync_blocks(self, nodes=None, wait=1, timeout=60):
"""
Wait until everybody has the same tip.
Expand Down
6 changes: 2 additions & 4 deletions test/functional/wallet_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
assert_equal,
assert_fee_amount,
assert_raises_rpc_error,
find_vout_for_address,
)
from test_framework.wallet_util import test_address
from test_framework.wallet import MiniWallet
Expand Down Expand Up @@ -471,10 +470,9 @@ def run_test(self):
# Import address and private key to check correct behavior of spendable unspents
# 1. Send some coins to generate new UTXO
address_to_import = self.nodes[2].getnewaddress()
txid = self.nodes[0].sendtoaddress(address_to_import, 1)
utxo = self.create_outpoints(self.nodes[0], outputs=[{address_to_import: 1}])[0]
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.nodes[2].lockunspent(False, [utxo])
self.generate(self.nodes[0], 1, sync_fun=lambda: self.sync_all(self.nodes[0:3]))

self.log.info("Test sendtoaddress with fee_rate param (explicit fee rate in sat/vB)")
Expand Down
37 changes: 15 additions & 22 deletions test/functional/wallet_fundrawtransaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
assert_greater_than_or_equal,
assert_raises_rpc_error,
count_bytes,
find_vout_for_address,
get_fee,
)
from test_framework.wallet_util import generate_keypair, WalletUnlock
Expand Down Expand Up @@ -85,14 +84,13 @@ 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})
if self.watchonly_utxo is not None:
to_keep.append(self.watchonly_utxo)
wallet.lockunspent(True)
wallet.lockunspent(False, to_keep)

def run_test(self):
self.watchonly_txid = None
self.watchonly_vout = None
self.watchonly_utxo = 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
Expand Down Expand Up @@ -163,11 +161,10 @@ def test_change_position(self):
watchonly_pubkey = self.nodes[0].getaddressinfo(watchonly_address)["pubkey"]
self.watchonly_amount = Decimal(200)
wwatch.importpubkey(watchonly_pubkey, "", True)
self.watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, self.watchonly_amount)
self.watchonly_utxo = self.create_outpoints(self.nodes[0], outputs=[{watchonly_address: self.watchonly_amount}])[0]

# Lock UTXO so nodes[0] doesn't accidentally spend it
self.watchonly_vout = find_vout_for_address(self.nodes[0], self.watchonly_txid, watchonly_address)
self.nodes[0].lockunspent(False, [{"txid": self.watchonly_txid, "vout": self.watchonly_vout}])
self.nodes[0].lockunspent(False, [self.watchonly_utxo])

self.nodes[0].sendtoaddress(self.nodes[3].get_wallet_rpc(self.default_wallet_name).getnewaddress(), self.watchonly_amount / 10)

Expand Down Expand Up @@ -738,7 +735,7 @@ def test_watchonly(self):
result = wwatch.fundrawtransaction(rawtx, True)
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
assert_equal(len(res_dec["vin"]), 1)
assert_equal(res_dec["vin"][0]["txid"], self.watchonly_txid)
assert_equal(res_dec["vin"][0]["txid"], self.watchonly_utxo['txid'])

assert "fee" in result.keys()
assert_greater_than(result["changepos"], -1)
Expand All @@ -758,7 +755,7 @@ def test_all_watched_funds(self):
result = wwatch.fundrawtransaction(rawtx, includeWatching=True, changeAddress=w3.getrawchangeaddress(), subtractFeeFromOutputs=[0])
res_dec = self.nodes[0].decoderawtransaction(result["hex"])
assert_equal(len(res_dec["vin"]), 1)
assert res_dec["vin"][0]["txid"] == self.watchonly_txid
assert res_dec["vin"][0]["txid"] == self.watchonly_utxo['txid']

assert_greater_than(result["fee"], 0)
assert_equal(result["changepos"], -1)
Expand Down Expand Up @@ -970,10 +967,9 @@ def test_subtract_fee_with_presets(self):
self.log.info("Test fundrawtxn subtract fee from outputs with preset inputs that are sufficient")

addr = self.nodes[0].getnewaddress()
txid = self.nodes[0].sendtoaddress(addr, 10)
vout = find_vout_for_address(self.nodes[0], txid, addr)
utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 10}])[0]

rawtx = self.nodes[0].createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(): 5}])
rawtx = self.nodes[0].createrawtransaction([utxo], [{self.nodes[0].getnewaddress(): 5}])
fundedtx = self.nodes[0].fundrawtransaction(rawtx, subtractFeeFromOutputs=[0])
signedtx = self.nodes[0].signrawtransactionwithwallet(fundedtx['hex'])
self.nodes[0].sendrawtransaction(signedtx['hex'])
Expand Down Expand Up @@ -1264,22 +1260,20 @@ def test_weight_calculation(self):

addr = wallet.getnewaddress(address_type="bech32")
ext_addr = self.nodes[0].getnewaddress(address_type="bech32")
txid = self.nodes[0].send([{addr: 5}, {ext_addr: 5}])["txid"]
vout = find_vout_for_address(self.nodes[0], txid, addr)
ext_vout = find_vout_for_address(self.nodes[0], txid, ext_addr)
utxo, ext_utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 5}, {ext_addr: 5}])

self.nodes[0].sendtoaddress(wallet.getnewaddress(address_type="bech32"), 5)
self.generate(self.nodes[0], 1)

rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': vout}], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}])
rawtx = wallet.createrawtransaction([utxo], [{self.nodes[0].getnewaddress(address_type="bech32"): 8}])
fundedtx = wallet.fundrawtransaction(rawtx, fee_rate=10, change_type="bech32")
# with 71-byte signatures we should expect following tx size
# tx overhead (10) + 2 inputs (41 each) + 2 p2wpkh (31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 byte sig witnesses (107 each)) / witness scaling factor (4)
tx_size = ceil(10 + 41*2 + 31*2 + (2 + 107*2)/4)
assert_equal(fundedtx['fee'] * COIN, tx_size * 10)

# Using the other output should have 72 byte sigs
rawtx = wallet.createrawtransaction([{'txid': txid, 'vout': ext_vout}], [{self.nodes[0].getnewaddress(): 13}])
rawtx = wallet.createrawtransaction([ext_utxo], [{self.nodes[0].getnewaddress(): 13}])
ext_desc = self.nodes[0].getaddressinfo(ext_addr)["desc"]
fundedtx = wallet.fundrawtransaction(rawtx, fee_rate=10, change_type="bech32", solving_data={"descriptors": [ext_desc]})
# tx overhead (10) + 3 inputs (41 each) + 2 p2wpkh(31 each) + (segwit marker and flag (2) + 2 p2wpkh 71 bytes sig witnesses (107 each) + p2wpkh 72 byte sig witness (108)) / witness scaling factor (4)
Expand All @@ -1298,10 +1292,9 @@ def test_include_unsafe(self):
addr = wallet.getnewaddress()
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))
utxo = self.create_outpoints(self.nodes[2], outputs=[{addr: 5}])[0]
inputs.append((utxo['txid'], utxo['vout']))
self.sync_mempools()

# Unsafe inputs are ignored by default.
rawtx = wallet.createrawtransaction([], [{self.nodes[2].getnewaddress(): 7.5}])
Expand Down
11 changes: 4 additions & 7 deletions test/functional/wallet_importdescriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
find_vout_for_address,
)
from test_framework.wallet_util import (
get_generate_key,
Expand Down Expand Up @@ -493,12 +492,10 @@ def run_test(self):
assert_equal(wmulti_pub.getwalletinfo()['keypoolsize'], 999)

# generate some utxos for next tests
txid = w0.sendtoaddress(addr, 10)
vout = find_vout_for_address(self.nodes[0], txid, addr)
utxo = self.create_outpoints(w0, outputs=[{addr: 10}])[0]

addr2 = wmulti_pub.getnewaddress('', 'bech32')
txid2 = w0.sendtoaddress(addr2, 10)
vout2 = find_vout_for_address(self.nodes[0], txid2, addr2)
utxo2 = self.create_outpoints(w0, outputs=[{addr2: 10}])[0]

self.generate(self.nodes[0], 6)
assert_equal(wmulti_pub.getbalance(), wmulti_priv.getbalance())
Expand Down Expand Up @@ -554,7 +551,7 @@ def run_test(self):
assert_equal(res[1]['success'], True)
assert_equal(res[1]['warnings'][0], 'Not all private keys provided. Some wallet functionality may return unexpected errors')

rawtx = self.nodes[1].createrawtransaction([{'txid': txid, 'vout': vout}], {w0.getnewaddress(): 9.999})
rawtx = self.nodes[1].createrawtransaction([utxo], {w0.getnewaddress(): 9.999})
tx_signed_1 = wmulti_priv1.signrawtransactionwithwallet(rawtx)
assert_equal(tx_signed_1['complete'], False)
tx_signed_2 = wmulti_priv2.signrawtransactionwithwallet(tx_signed_1['hex'])
Expand Down Expand Up @@ -648,7 +645,7 @@ def run_test(self):
}])
assert_equal(res[0]['success'], True)

rawtx = self.nodes[1].createrawtransaction([{'txid': txid2, 'vout': vout2}], {w0.getnewaddress(): 9.999})
rawtx = self.nodes[1].createrawtransaction([utxo2], {w0.getnewaddress(): 9.999})
tx = wmulti_priv3.signrawtransactionwithwallet(rawtx)
assert_equal(tx['complete'], True)
self.nodes[1].sendrawtransaction(tx['hex'])
Expand Down
Loading

0 comments on commit 73a339a

Please sign in to comment.