def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): """ Tests for the sign + send psbt RPCs """ amount = 1000000 total_outs = 12 coin_mvt_plugin = os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py') l1 = node_factory.get_node(options={'plugin': coin_mvt_plugin}, feerates=(7500, 7500, 7500, 7500)) l2 = node_factory.get_node() addr = chainparams['example_addr'] # Add a medley of funds to withdraw later, bech32 + p2sh-p2wpkh for i in range(total_outs // 2): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress( l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == total_outs) # Make a PSBT out of our inputs reserved = l1.rpc.reserveinputs( outputs=[{ addr: Millisatoshi(3 * amount * 1000) }]) assert len([x for x in l1.rpc.listfunds()['outputs'] if x['reserved']]) == 4 psbt = bitcoind.rpc.decodepsbt(reserved['psbt']) saved_input = psbt['tx']['vin'][0] # Go ahead and unreserve the UTXOs, we'll use a smaller # set of them to create a second PSBT that we'll attempt to sign # and broadcast (to disastrous results) l1.rpc.unreserveinputs(reserved['psbt']) # Re-reserve one of the utxos we just unreserved utxos = [] utxos.append(saved_input['txid'] + ":" + str(saved_input['vout'])) second_reservation = l1.rpc.reserveinputs( [{ addr: Millisatoshi(amount * 0.5 * 1000) }], feerate='253perkw', utxos=utxos) # We require the utxos be reserved before signing them with pytest.raises( RpcError, match=r"Aborting PSBT signing. UTXO .* is not reserved"): l1.rpc.signpsbt(reserved['psbt'])['signed_psbt'] # Now we unreserve the singleton, so we can reserve it again l1.rpc.unreserveinputs(second_reservation['psbt']) # We re-reserve the first set... utxos = [] for vin in psbt['tx']['vin']: utxos.append(vin['txid'] + ':' + str(vin['vout'])) reserved = l1.rpc.reserveinputs(outputs=[{ addr: Millisatoshi(3 * amount * 1000) }], utxos=utxos) # Sign + send the PSBT we've created signed_psbt = l1.rpc.signpsbt(reserved['psbt'])['signed_psbt'] broadcast_tx = l1.rpc.sendpsbt(signed_psbt) # Check that it was broadcast successfully l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format( broadcast_tx['tx'])) bitcoind.generate_block(1) # We expect a change output to be added to the wallet expected_outs = total_outs - 4 + 1 wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == expected_outs) # Let's try *sending* a PSBT that can't be finalized (it's unsigned) with pytest.raises(RpcError, match=r"PSBT not finalizeable"): l1.rpc.sendpsbt(second_reservation['psbt']) # Now we try signing a PSBT with an output that's already been spent with pytest.raises( RpcError, match=r"Aborting PSBT signing. UTXO {} is not reserved".format( utxos[0])): l1.rpc.signpsbt(second_reservation['psbt']) # Queue up another node, to make some PSBTs for us for i in range(total_outs // 2): bitcoind.rpc.sendtoaddress(l2.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress( l2.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) # Create a PSBT using L2 bitcoind.generate_block(1) wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == total_outs) l2_reserved = l2.rpc.reserveinputs( outputs=[{ addr: Millisatoshi(3 * amount * 1000) }]) # Try to get L1 to sign it with pytest.raises(RpcError, match=r"No wallet inputs to sign"): l1.rpc.signpsbt(l2_reserved['psbt']) # Add some of our own PSBT inputs to it l1_reserved = l1.rpc.reserveinputs( outputs=[{ addr: Millisatoshi(3 * amount * 1000) }]) joint_psbt = bitcoind.rpc.joinpsbts( [l1_reserved['psbt'], l2_reserved['psbt']]) half_signed_psbt = l1.rpc.signpsbt(joint_psbt)['signed_psbt'] totally_signed = l2.rpc.signpsbt(half_signed_psbt)['signed_psbt'] broadcast_tx = l1.rpc.sendpsbt(totally_signed) l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format( broadcast_tx['tx'])) # Send a PSBT that's not ours l2_reserved = l2.rpc.reserveinputs( outputs=[{ addr: Millisatoshi(3 * amount * 1000) }]) l2_signed_psbt = l2.rpc.signpsbt(l2_reserved['psbt'])['signed_psbt'] l1.rpc.sendpsbt(l2_signed_psbt) # Re-try sending the same tx? bitcoind.generate_block(1) sync_blockheight(bitcoind, [l1]) # Expect an error here with pytest.raises(JSONRPCError, match=r"Transaction already in block chain"): bitcoind.rpc.sendrawtransaction(broadcast_tx['tx']) # Try an empty PSBT with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.signpsbt('') with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.sendpsbt('') # Try a modified (invalid) PSBT string modded_psbt = l2_reserved['psbt'][:-3] + 'A' + l2_reserved['psbt'][-3:] with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.signpsbt(modded_psbt) wallet_coin_mvts = [ { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, # Nicely splits out withdrawals and chain fees, because it's all our tx { 'type': 'chain_mvt', 'credit': 0, 'debit': 988255000, 'tag': 'withdrawal' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 3000000000, 'tag': 'withdrawal' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 11745000, 'tag': 'chain_fees' }, { 'type': 'chain_mvt', 'credit': 988255000, 'debit': 0, 'tag': 'deposit' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track' }, # Note that this is technically wrong since we paid 11745sat in fees # but since it includes inputs / outputs from a second node, we can't # do proper acccounting for it. { 'type': 'chain_mvt', 'credit': 0, 'debit': 4000000000, 'tag': 'withdrawal' }, { 'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'chain_fees' }, { 'type': 'chain_mvt', 'credit': 988255000, 'debit': 0, 'tag': 'deposit' }, ] check_coin_moves(l1, 'wallet', wallet_coin_mvts)
def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): """ Tests for the sign + send psbt RPCs """ amount = 1000000 total_outs = 12 coin_mvt_plugin = os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py') l1 = node_factory.get_node(options={'plugin': coin_mvt_plugin}, feerates=(7500, 7500, 7500, 7500)) l2 = node_factory.get_node() addr = chainparams['example_addr'] out_total = Millisatoshi(amount * 3 * 1000) # Add a medley of funds to withdraw later, bech32 + p2sh-p2wpkh for i in range(total_outs // 2): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress(l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == total_outs) # Make a PSBT out of our inputs funding = l1.rpc.fundpsbt(satoshi=out_total, feerate=7500, startweight=42, reserve=True) assert len([x for x in l1.rpc.listfunds()['outputs'] if x['reserved']]) == 4 psbt = bitcoind.rpc.decodepsbt(funding['psbt']) saved_input = psbt['tx']['vin'][0] # Go ahead and unreserve the UTXOs, we'll use a smaller # set of them to create a second PSBT that we'll attempt to sign # and broadcast (to disastrous results) l1.rpc.unreserveinputs(funding['psbt']) # Re-reserve one of the utxos we just unreserved psbt = bitcoind.rpc.createpsbt([{'txid': saved_input['txid'], 'vout': saved_input['vout']}], []) l1.rpc.reserveinputs(psbt) # We require the utxos be reserved before signing them with pytest.raises(RpcError, match=r"Aborting PSBT signing. UTXO .* is not reserved"): l1.rpc.signpsbt(funding['psbt'])['signed_psbt'] # Now we unreserve the singleton, so we can reserve it again l1.rpc.unreserveinputs(psbt) # Now add an output. Note, we add the 'excess msat' to the output so # that our feerate is 'correct'. This is of particular importance to elementsd, # who requires that every satoshi be accounted for in a tx. out_1_ms = Millisatoshi(funding['excess_msat']) output_psbt = bitcoind.rpc.createpsbt([], [{addr: float((out_total + out_1_ms).to_btc())}]) fullpsbt = bitcoind.rpc.joinpsbts([funding['psbt'], output_psbt]) # We re-reserve the first set... l1.rpc.reserveinputs(fullpsbt) # Sign + send the PSBT we've created signed_psbt = l1.rpc.signpsbt(fullpsbt)['signed_psbt'] broadcast_tx = l1.rpc.sendpsbt(signed_psbt) # Check that it was broadcast successfully l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format(broadcast_tx['tx'])) bitcoind.generate_block(1) # We didn't add a change output. expected_outs = total_outs - 4 wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == expected_outs) # Let's try *sending* a PSBT that can't be finalized (it's unsigned) with pytest.raises(RpcError, match=r"PSBT not finalizeable"): l1.rpc.sendpsbt(fullpsbt) # Now we try signing a PSBT with an output that's already been spent with pytest.raises(RpcError, match=r"Aborting PSBT signing. UTXO .* is not reserved"): l1.rpc.signpsbt(fullpsbt) # Queue up another node, to make some PSBTs for us for i in range(total_outs // 2): bitcoind.rpc.sendtoaddress(l2.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress(l2.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) # Create a PSBT using L2 bitcoind.generate_block(1) wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == total_outs) l2_funding = l2.rpc.fundpsbt(satoshi=out_total, feerate=7500, startweight=42, reserve=True) # Try to get L1 to sign it with pytest.raises(RpcError, match=r"No wallet inputs to sign"): l1.rpc.signpsbt(l2_funding['psbt']) # With signonly it will fail if it can't sign it. with pytest.raises(RpcError, match=r"is unknown"): l1.rpc.signpsbt(l2_funding['psbt'], signonly=[0]) # Add some of our own PSBT inputs to it l1_funding = l1.rpc.fundpsbt(satoshi=out_total, feerate=7500, startweight=42, reserve=True) l1_num_inputs = len(bitcoind.rpc.decodepsbt(l1_funding['psbt'])['tx']['vin']) l2_num_inputs = len(bitcoind.rpc.decodepsbt(l2_funding['psbt'])['tx']['vin']) # Join and add an output (reorders!) out_2_ms = Millisatoshi(l1_funding['excess_msat']) out_amt = out_2_ms + Millisatoshi(l2_funding['excess_msat']) + out_total + out_total output_psbt = bitcoind.rpc.createpsbt([], [{addr: float(out_amt.to_btc())}]) joint_psbt = bitcoind.rpc.joinpsbts([l1_funding['psbt'], l2_funding['psbt'], output_psbt]) # Ask it to sign inputs it doesn't know, it will fail. with pytest.raises(RpcError, match=r"is unknown"): l1.rpc.signpsbt(joint_psbt, signonly=list(range(l1_num_inputs + 1))) # Similarly, it can't sign inputs it doesn't know. sign_success = [] for i in range(l1_num_inputs + l2_num_inputs): try: l1.rpc.signpsbt(joint_psbt, signonly=[i]) except RpcError: continue sign_success.append(i) assert len(sign_success) == l1_num_inputs # But it can sign all the valid ones at once. half_signed_psbt = l1.rpc.signpsbt(joint_psbt, signonly=sign_success)['signed_psbt'] for s in sign_success: assert bitcoind.rpc.decodepsbt(half_signed_psbt)['inputs'][s]['partial_signatures'] is not None totally_signed = l2.rpc.signpsbt(half_signed_psbt)['signed_psbt'] broadcast_tx = l1.rpc.sendpsbt(totally_signed) l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format(broadcast_tx['tx'])) # Send a PSBT that's not ours l2_funding = l2.rpc.fundpsbt(satoshi=out_total, feerate=7500, startweight=42, reserve=True) out_amt = Millisatoshi(l2_funding['excess_msat']) output_psbt = bitcoind.rpc.createpsbt([], [{addr: float((out_total + out_amt).to_btc())}]) psbt = bitcoind.rpc.joinpsbts([l2_funding['psbt'], output_psbt]) l2_signed_psbt = l2.rpc.signpsbt(psbt)['signed_psbt'] l1.rpc.sendpsbt(l2_signed_psbt) # Re-try sending the same tx? bitcoind.generate_block(1) sync_blockheight(bitcoind, [l1]) # Expect an error here with pytest.raises(JSONRPCError, match=r"Transaction already in block chain"): bitcoind.rpc.sendrawtransaction(broadcast_tx['tx']) # Try an empty PSBT with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.signpsbt('') with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.sendpsbt('') # Try an invalid PSBT string invalid_psbt = 'cHNidP8BAM0CAAAABJ9446mTRp/ml8OxSLC1hEvrcxG1L02AG7YZ4syHon2sAQAAAAD9////JFJH/NjKwjwrP9myuU68G7t8Q4VIChH0KUkZ5hSAyqcAAAAAAP3///8Uhrj0XDRhGRno8V7qEe4hHvZcmEjt3LQSIXWc+QU2tAEAAAAA/f///wstLikuBrgZJI83VPaY8aM7aPe5U6TMb06+jvGYzQLEAQAAAAD9////AcDGLQAAAAAAFgAUyQltQ/QI6lJgICYsza18hRa5KoEAAAAAAAEBH0BCDwAAAAAAFgAUqc1Qh7Q5kY1noDksmj7cJmHaIbQAAQEfQEIPAAAAAAAWABS3bdYeQbXvBSryHNoyYIiMBwu5rwABASBAQg8AAAAAABepFD1r0NuqAA+R7zDiXrlP7J+/PcNZhwEEFgAUKvGgVL/ThjWE/P1oORVXh/ObucYAAQEgQEIPAAAAAAAXqRRsrE5ugA1VJnAith5+msRMUTwl8ocBBBYAFMrfGCiLi0ZnOCY83ERKJ1sLYMY8A=' with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.signpsbt(invalid_psbt) wallet_coin_mvts = [ {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 3000000000 + int(out_1_ms), 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000 - int(out_1_ms), 'tag': 'chain_fees'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 4000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'chain_fees'}, ] check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams)
def test_sign_and_send_psbt(node_factory, bitcoind, chainparams): """ Tests for the sign + send psbt RPCs """ amount = 1000000 total_outs = 12 coin_mvt_plugin = os.path.join(os.getcwd(), 'tests/plugins/coin_movements.py') l1 = node_factory.get_node(options={'plugin': coin_mvt_plugin}, feerates=(7500, 7500, 7500, 7500)) l2 = node_factory.get_node() addr = chainparams['example_addr'] # Add a medley of funds to withdraw later, bech32 + p2sh-p2wpkh for i in range(total_outs // 2): bitcoind.rpc.sendtoaddress(l1.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress(l1.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) bitcoind.generate_block(1) wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == total_outs) # Make a PSBT out of our inputs funding = l1.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000), feerate=7500, startweight=42, reserve=True) assert len([x for x in l1.rpc.listfunds()['outputs'] if x['reserved']]) == 4 psbt = bitcoind.rpc.decodepsbt(funding['psbt']) saved_input = psbt['tx']['vin'][0] # Go ahead and unreserve the UTXOs, we'll use a smaller # set of them to create a second PSBT that we'll attempt to sign # and broadcast (to disastrous results) l1.rpc.unreserveinputs(funding['psbt']) # Re-reserve one of the utxos we just unreserved psbt = bitcoind.rpc.createpsbt([{'txid': saved_input['txid'], 'vout': saved_input['vout']}], []) l1.rpc.reserveinputs(psbt) # We require the utxos be reserved before signing them with pytest.raises(RpcError, match=r"Aborting PSBT signing. UTXO .* is not reserved"): l1.rpc.signpsbt(funding['psbt'])['signed_psbt'] # Now we unreserve the singleton, so we can reserve it again l1.rpc.unreserveinputs(psbt) # Now add an output. output_pbst = bitcoind.rpc.createpsbt([], [{addr: 3 * amount / 10**8}]) fullpsbt = bitcoind.rpc.joinpsbts([funding['psbt'], output_pbst]) # We re-reserve the first set... l1.rpc.reserveinputs(fullpsbt) # Sign + send the PSBT we've created signed_psbt = l1.rpc.signpsbt(fullpsbt)['signed_psbt'] broadcast_tx = l1.rpc.sendpsbt(signed_psbt) # Check that it was broadcast successfully l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format(broadcast_tx['tx'])) bitcoind.generate_block(1) # We didn't add a change output. expected_outs = total_outs - 4 wait_for(lambda: len(l1.rpc.listfunds()['outputs']) == expected_outs) # Let's try *sending* a PSBT that can't be finalized (it's unsigned) with pytest.raises(RpcError, match=r"PSBT not finalizeable"): l1.rpc.sendpsbt(fullpsbt) # Now we try signing a PSBT with an output that's already been spent with pytest.raises(RpcError, match=r"Aborting PSBT signing. UTXO .* is not reserved"): l1.rpc.signpsbt(fullpsbt) # Queue up another node, to make some PSBTs for us for i in range(total_outs // 2): bitcoind.rpc.sendtoaddress(l2.rpc.newaddr()['bech32'], amount / 10**8) bitcoind.rpc.sendtoaddress(l2.rpc.newaddr('p2sh-segwit')['p2sh-segwit'], amount / 10**8) # Create a PSBT using L2 bitcoind.generate_block(1) wait_for(lambda: len(l2.rpc.listfunds()['outputs']) == total_outs) l2_funding = l2.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000), feerate=7500, startweight=42, reserve=True) # Try to get L1 to sign it with pytest.raises(RpcError, match=r"No wallet inputs to sign"): l1.rpc.signpsbt(l2_funding['psbt']) # Add some of our own PSBT inputs to it l1_funding = l1.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000), feerate=7500, startweight=42, reserve=True) # Join and add an output joint_psbt = bitcoind.rpc.joinpsbts([l1_funding['psbt'], l2_funding['psbt'], output_pbst]) half_signed_psbt = l1.rpc.signpsbt(joint_psbt)['signed_psbt'] totally_signed = l2.rpc.signpsbt(half_signed_psbt)['signed_psbt'] broadcast_tx = l1.rpc.sendpsbt(totally_signed) l1.daemon.wait_for_log(r'sendrawtx exit 0 .* sendrawtransaction {}'.format(broadcast_tx['tx'])) # Send a PSBT that's not ours l2_funding = l2.rpc.fundpsbt(satoshi=Millisatoshi(3 * amount * 1000), feerate=7500, startweight=42, reserve=True) psbt = bitcoind.rpc.joinpsbts([l2_funding['psbt'], output_pbst]) l2_signed_psbt = l2.rpc.signpsbt(psbt)['signed_psbt'] l1.rpc.sendpsbt(l2_signed_psbt) # Re-try sending the same tx? bitcoind.generate_block(1) sync_blockheight(bitcoind, [l1]) # Expect an error here with pytest.raises(JSONRPCError, match=r"Transaction already in block chain"): bitcoind.rpc.sendrawtransaction(broadcast_tx['tx']) # Try an empty PSBT with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.signpsbt('') with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.sendpsbt('') # Try a modified (invalid) PSBT string modded_psbt = psbt[:-3] + 'A' + psbt[-3:] with pytest.raises(RpcError, match=r"should be a PSBT, not"): l1.rpc.signpsbt(modded_psbt) wallet_coin_mvts = [ {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 1000000000, 'debit': 0, 'tag': 'deposit'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 3000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'chain_fees'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 0, 'tag': 'spend_track'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 3000000000, 'tag': 'withdrawal'}, {'type': 'chain_mvt', 'credit': 0, 'debit': 1000000000, 'tag': 'chain_fees'}, ] check_coin_moves(l1, 'wallet', wallet_coin_mvts, chainparams)