Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1251psbt2 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=0)1252assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, psbt2])1254# Join two distinct PSBTs1255psbt1 = self.nodes[1].createpsbt(inputs=[utxo1, utxo2, utxo3], outputs={self.nodes[0].getnewaddress():32.999}, psbt_version=0)1256addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit")1257utxo4 = self.create_outpoints(self.nodes[0], outputs=[{addr4: 5}])[0]1258self.generate(self.nodes[0], 6)1259psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')}, psbt_version=0)1260psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt']1261psbt2_decoded = self.nodes[0].decodepsbt(psbt2)1262assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0]1263joined = self.nodes[0].joinpsbts([psbt1, psbt2])1264joined_decoded = self.nodes[0].decodepsbt(joined)1265assert_equal(len(joined_decoded['inputs']), 4)1266assert_equal(len(joined_decoded['outputs']), 2)1267assert "final_scriptwitness" not in joined_decoded['inputs'][3]1268assert "final_scriptSig" not in joined_decoded['inputs'][3]1270# Check that joining shuffles the inputs and outputs1271# 10 attempts should be enough to get a shuffled join1272shuffled = False1273for _ in range(10):1274shuffled_joined = self.nodes[0].joinpsbts([psbt1, psbt2])1275shuffled |= joined != shuffled_joined1276if shuffled:1277break1278assert shuffled1280# Check that joining preserves global xpub and proprietary records1281def global_xpub_key(extended_pubkey):1282xpub_data, xpub_version = base58_to_byte(extended_pubkey)1283return bytes([PSBT_GLOBAL_XPUB]) + bytes([xpub_version]) + xpub_data1285xpub1 = "tpubD6NzVbkrYhZ4XgiXtGrdW5XDAPFCL9h7we1vwNCpn8tGbBcgfVYjXyhWo4E1xkh56hjod1RhGjxbaTLV3X4FyWuejifB9jusQ46QzG87VKp"1286xpub_key1 = global_xpub_key(xpub1)1287xpub_key2 = global_xpub_key("tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B")1288xpub_value = b"\x00\x00\x00\x00" # master key fingerprint with an empty derivation path1289global_prop_key = bytes([PSBT_GLOBAL_PROPRIETARY]) + b"\x02\x01\x02\x00" # identifier "0102", subtype 01290global_prop_value = b"\xde\xad\xbe\xef"1292psbt1_obj = PSBT.from_base64(psbt1)1293psbt1_obj.g.map[xpub_key1] = xpub_value1294psbt1_obj.g.map[global_prop_key] = global_prop_value1295psbt2_obj = PSBT.from_base64(psbt2)1296psbt2_obj.g.map[xpub_key2] = xpub_value1297joined_globals = PSBT.from_base64(self.nodes[0].joinpsbts([psbt1_obj.to_base64(), psbt2_obj.to_base64()]))1298assert_equal(joined_globals.g.map[xpub_key1], xpub_value)1299assert_equal(joined_globals.g.map[xpub_key2], xpub_value)1300assert_equal(joined_globals.g.map[global_prop_key], global_prop_value)1302# Same proprietary key in both PSBTs with different values: the first PSBT's value wins1303collide_key = bytes([PSBT_GLOBAL_PROPRIETARY]) + b"\x02\x03\x04\x00"1304psbt_first_obj = PSBT.from_base64(psbt1)1305psbt_first_obj.g.map[collide_key] = b"\x11\x11\x11\x11"1306psbt_second_obj = PSBT.from_base64(psbt2)1307psbt_second_obj.g.map[collide_key] = b"\x22\x22\x22\x22"1308joined_collision = PSBT.from_base64(self.nodes[0].joinpsbts([psbt_first_obj.to_base64(), psbt_second_obj.to_base64()]))1309assert_equal(joined_collision.g.map[collide_key], b"\x11\x11\x11\x11")1311# Same xpub with conflicting origins: the first PSBT's origin is kept, avoiding duplicate keys1312conflict_first_obj = PSBT.from_base64(psbt1)1313conflict_first_obj.g.map[xpub_key1] = xpub_value1314conflict_second_obj = PSBT.from_base64(psbt2)1315conflict_second_obj.g.map[xpub_key1] = b"\x11\x11\x11\x11"1316joined_conflict = self.nodes[0].joinpsbts([conflict_first_obj.to_base64(), conflict_second_obj.to_base64()])1317assert_equal(self.nodes[0].decodepsbt(joined_conflict)["global_xpubs"], [{"xpub": xpub1, "master_fingerprint": "00000000", "path": "m"}])1319# Newly created PSBT needs UTXOs and updating1320addr = self.nodes[1].getnewaddress("", "p2sh-segwit")1321utxo = self.create_outpoints(self.nodes[0], outputs=[{addr: 7}])[0]1322addrinfo = self.nodes[1].getaddressinfo(addr)1323self.generate(self.nodes[0], 6)[0]1324psbt = self.nodes[1].createpsbt([utxo], {self.nodes[0].getnewaddress("", "p2sh-segwit"):Decimal('6.999')})1325analyzed = self.nodes[0].analyzepsbt(psbt)1326assert_equal(analyzed['inputs'][0]['has_utxo'], False)1327assert_equal(analyzed['inputs'][0]['is_final'], False)1328assert_equal(analyzed['inputs'][0]['next'], 'updater')1329assert_equal(analyzed['next'], 'updater')1331# After update with wallet, only needs signing1332updated = self.nodes[1].walletprocesspsbt(psbt, False, 'ALL', True)['psbt']1333analyzed = self.nodes[0].analyzepsbt(updated)1334assert_equal(analyzed['inputs'][0]['has_utxo'], True)1335assert_equal(analyzed['inputs'][0]['is_final'], False)1336assert_equal(analyzed['inputs'][0]['next'], 'signer')1337assert_equal(analyzed['next'], 'signer')1338assert_equal(analyzed['inputs'][0]['missing']['signatures'][0], addrinfo['embedded']['witness_program'])1340# Check fee and size things1341assert_equal(analyzed['fee'], Decimal('0.001'))1342assert_equal(analyzed['estimated_vsize'], 134)1343assert_equal(analyzed['estimated_feerate'], Decimal('0.00746268'))1345# After signing and finalizing, needs extracting1346signed = self.nodes[1].walletprocesspsbt(updated)['psbt']1347analyzed = self.nodes[0].analyzepsbt(signed)1348assert_equal(analyzed['inputs'][0]['has_utxo'], True)1349assert_equal(analyzed['inputs'][0]['is_final'], True)1350assert_equal(analyzed['next'], 'extractor')1352self.log.info("PSBT spending unspendable outputs should have error message and Creator as next")1353analysis = self.nodes[0].analyzepsbt('cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWAEHYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFv8/wADXYP/7//////8JxOh0LR2HAI8AAAAAAAEBIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHEAABAACAAAEBIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHENkMak8AAAAA')1354assert_equal(analysis['next'], 'creator')1355assert_equal(analysis['error'], 'PSBT is not valid. Input 0 spends unspendable output')1357self.log.info("PSBT with invalid values should have error message and Creator as next")1358analysis = self.nodes[0].analyzepsbt('cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAR8AgIFq49AHABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA')1359assert_equal(analysis['next'], 'creator')1360assert_equal(analysis['error'], 'PSBT is not valid. Input 0 has invalid value')1362self.log.info("PSBT with signed, but not finalized, inputs should have Finalizer as next")1363analysis = self.nodes[0].analyzepsbt('cHNidP8BAHECAAAAAZYezcxdnbXoQCmrD79t/LzDgtUo9ERqixk8wgioAobrAAAAAAD9////AlDDAAAAAAAAFgAUy/UxxZuzZswcmFnN/E9DGSiHLUsuGPUFAAAAABYAFLsH5o0R38wXx+X2cCosTMCZnQ4baAAAAAABAR8A4fUFAAAAABYAFOBI2h5thf3+Lflb2LGCsVSZwsltIgIC/i4dtVARCRWtROG0HHoGcaVklzJUcwo5homgGkSNAnJHMEQCIGx7zKcMIGr7cEES9BR4Kdt/pzPTK3fKWcGyCJXb7MVnAiALOBgqlMH4GbC1HDh/HmylmO54fyEy4lKde7/BT/PWxwEBAwQBAAAAIgYC/i4dtVARCRWtROG0HHoGcaVklzJUcwo5homgGkSNAnIYDwVpQ1QAAIABAACAAAAAgAAAAAAAAAAAAAAiAgL+CIiB59NSCssOJRGiMYQK1chahgAaaJpIXE41Cyir+xgPBWlDVAAAgAEAAIAAAACAAQAAAAAAAAAA')1364assert_equal(analysis['next'], 'finalizer')1366analysis = self.nodes[0].analyzepsbt('cHNidP8BAHECAAAAAfA00BFgAm6tp86RowwH6BMImQNL5zXUcTT97XoLGz0BAAAAAAD/////AgCAgWrj0AcAFgAUKNw0x8HRctAgmvoevm4u1SbN7XL87QKVAAAAABYAFPck4gF7iL4NL4wtfRAKgQbghiTUAAAAAAABAR8A8gUqAQAAABYAFJUDtxf2PHo641HEOBOAIvFMNTr2AAAA')1367assert_equal(analysis['next'], 'creator')1368assert_equal(analysis['error'], 'PSBT is not valid. Output amount invalid')1370assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].analyzepsbt, "cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==")1372assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].walletprocesspsbt, "cHNidP8BAJoCAAAAAkvEW8NnDtdNtDpsmze+Ht2LH35IJcKv00jKAlUs21RrAwAAAAD/////S8Rbw2cO1020OmybN74e3Ysffkglwq/TSMoCVSzbVGsBAAAAAP7///8CwLYClQAAAAAWABSNJKzjaUb3uOxixsvh1GGE3fW7zQD5ApUAAAAAFgAUKNw0x8HRctAgmvoevm4u1SbN7XIAAAAAAAEAnQIAAAACczMa321tVHuN4GKWKRncycI22aX3uXgwSFUKM2orjRsBAAAAAP7///9zMxrfbW1Ue43gYpYpGdzJwjbZpfe5eDBIVQozaiuNGwAAAAAA/v///wIA+QKVAAAAABl2qRT9zXUVA8Ls5iVqynLHe5/vSe1XyYisQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAAAAAQEfQM0ClQAAAAAWABRmWQUcjSjghQ8/uH4Bn/zkakwLtAAAAA==")1374self.log.info("Test that we can fund psbts with external inputs specified")1376privkey, _ = generate_keypair(wif=True)1378self.nodes[1].createwallet("extfund")1379wallet = self.nodes[1].get_wallet_rpc("extfund")1381# Make a weird but signable script. sh(wsh(pkh())) descriptor accomplishes this1382desc = descsum_create("sh(wsh(pkh({})))".format(privkey))1383res = self.nodes[0].importdescriptors([{"desc": desc, "timestamp": "now"}])1384assert_equal(res[0]["success"], True)1385addr = self.nodes[0].deriveaddresses(desc)[0]1386addr_info = self.nodes[0].getaddressinfo(addr)1388self.nodes[0].sendtoaddress(addr, 10)1389self.nodes[0].sendtoaddress(wallet.getnewaddress(), 10)1390self.generate(self.nodes[0], 6)1391ext_utxo = self.nodes[0].listunspent(addresses=[addr])[0]1393# An external input without solving data should result in an error1394assert_raises_rpc_error(-4, "Not solvable pre-selected input COutPoint(%s, %s)" % (ext_utxo["txid"][0:10], ext_utxo["vout"]), wallet.walletcreatefundedpsbt, [ext_utxo], {self.nodes[0].getnewaddress(): 15})1396# But funding should work when the solving data is provided1397psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data": {"pubkeys": [addr_info['pubkey']], "scripts": [addr_info["embedded"]["scriptPubKey"], addr_info["embedded"]["embedded"]["scriptPubKey"]]}})1398signed = wallet.walletprocesspsbt(psbt['psbt'])1399assert_equal(signed['complete'], False)1400signed = self.nodes[0].walletprocesspsbt(signed['psbt'])1401assert_equal(signed['complete'], True)1403psbt = wallet.walletcreatefundedpsbt([ext_utxo], {self.nodes[0].getnewaddress(): 15}, 0, {"add_inputs": True, "solving_data":{"descriptors": [desc]}})1404signed = wallet.walletprocesspsbt(psbt['psbt'])1405assert_equal(signed['complete'], False)1406signed = self.nodes[0].walletprocesspsbt(signed['psbt'])1407assert_equal(signed['complete'], True)1408final = signed['hex']1410dec = self.nodes[0].decodepsbt(signed["psbt"])1411for psbt_in in dec["inputs"]:1412if psbt_in["previous_txid"] == ext_utxo["txid"] and psbt_in["previous_vout"] == ext_utxo["vout"]:1413break1414scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""1415witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None1416input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)1417low_input_weight = input_weight // 21418high_input_weight = input_weight * 21420# Input weight error conditions1421assert_raises_rpc_error(1422-8,1423"Input weights should be specified in inputs rather than in options.",1424wallet.walletcreatefundedpsbt,1425inputs=[ext_utxo],1426outputs={self.nodes[0].getnewaddress(): 15},1427options={"input_weights": [{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": 1000}]}1428)1430# Funding should also work if the input weight is provided1431psbt = wallet.walletcreatefundedpsbt(1432inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": input_weight}],1433outputs={self.nodes[0].getnewaddress(): 15},1434add_inputs=True,1435)1436signed = wallet.walletprocesspsbt(psbt["psbt"])1437signed = self.nodes[0].walletprocesspsbt(signed["psbt"])1438final = signed["hex"]1439assert_equal(self.nodes[0].testmempoolaccept([final])[0]["allowed"], True)1440# Reducing the weight should have a lower fee1441psbt2 = wallet.walletcreatefundedpsbt(1442inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": low_input_weight}],1443outputs={self.nodes[0].getnewaddress(): 15},1444add_inputs=True,1445)1446assert_greater_than(psbt["fee"], psbt2["fee"])1447# Increasing the weight should have a higher fee1448psbt2 = wallet.walletcreatefundedpsbt(1449inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}],1450outputs={self.nodes[0].getnewaddress(): 15},1451add_inputs=True,1452)1453assert_greater_than(psbt2["fee"], psbt["fee"])1454# The provided weight should override the calculated weight when solving data is provided1455psbt3 = wallet.walletcreatefundedpsbt(1456inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}],1457outputs={self.nodes[0].getnewaddress(): 15},1458add_inputs=True, solving_data={"descriptors": [desc]},1459)1460assert_equal(psbt2["fee"], psbt3["fee"])1462# Import the external utxo descriptor so that we can sign for it from the test wallet1463res = wallet.importdescriptors([{"desc": desc, "timestamp": "now"}])1464assert_equal(res[0]["success"], True)1465# The provided weight should override the calculated weight for a wallet input1466psbt3 = wallet.walletcreatefundedpsbt(1467inputs=[{"txid": ext_utxo["txid"], "vout": ext_utxo["vout"], "weight": high_input_weight}],1468outputs={self.nodes[0].getnewaddress(): 15},1469add_inputs=True,1470)1471assert_equal(psbt2["fee"], psbt3["fee"])1473self.log.info("Test signing inputs that the wallet has keys for but is not watching the scripts")1474self.nodes[1].createwallet(wallet_name="scriptwatchonly", disable_private_keys=True)1475watchonly = self.nodes[1].get_wallet_rpc("scriptwatchonly")1477privkey, pubkey = generate_keypair(wif=True)1479desc = descsum_create("wsh(pkh({}))".format(pubkey.hex()))1480res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}])1481assert_equal(res[0]["success"], True)1482addr = self.nodes[0].deriveaddresses(desc)[0]1483self.nodes[0].sendtoaddress(addr, 10)1484self.generate(self.nodes[0], 1)1485wallet_importprivkey(self.nodes[0], privkey, "now")1487psbt = watchonly.sendall([wallet.getnewaddress()])["psbt"]1488signed_tx = self.nodes[0].walletprocesspsbt(psbt)1489self.nodes[0].sendrawtransaction(signed_tx["hex"])1491# Same test but for taproot1492privkey, pubkey = generate_keypair(wif=True)1494desc = descsum_create("tr({},pk({}))".format(H_POINT, pubkey.hex()))1495res = watchonly.importdescriptors([{"desc": desc, "timestamp": "now"}])1496assert_equal(res[0]["success"], True)1497addr = self.nodes[0].deriveaddresses(desc)[0]1498self.nodes[0].sendtoaddress(addr, 10)1499self.generate(self.nodes[0], 1)1500self.nodes[0].importdescriptors([{"desc": descsum_create("tr({})".format(privkey)), "timestamp":"now"}])
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.