Repository: bitcoin/bitcoin
File: test/functional/rpc_psbt.py (Python)

Code before:
        assert_greater_than_or_equal(max_tx_weight_sufficient, weight)

        # If inputs are specified, do not automatically add more:
        utxo1 = self.nodes[0].listunspent()[0]
        assert_raises_rpc_error(-4, "The preselected coins total amount does not cover the transaction target. "
                                    "Please allow other inputs to be automatically selected or include more coins manually",
                                self.nodes[0].walletcreatefundedpsbt, [{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90})

        psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():90}, 0, {"add_inputs": True})['psbt']
        assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['tx']['vin']), 2)

        # Inputs argument can be null
        self.nodes[0].walletcreatefundedpsbt(None, {self.nodes[2].getnewaddress():10})

        # Node 1 should not be able to add anything to it but still return the psbtx same as before
        psbtx = self.nodes[1].walletprocesspsbt(psbtx1)['psbt']
        assert_equal(psbtx1, psbtx)

        # Node 0 should not be able to sign the transaction with the wallet is locked
        self.nodes[0].encryptwallet("password")
        assert_raises_rpc_error(-13, "Please enter the wallet passphrase with walletpassphrase first", self.nodes[0].walletprocesspsbt, psbtx)

        # Node 0 should be able to process without signing though
        unsigned_tx = self.nodes[0].walletprocesspsbt(psbtx, False)
        assert_equal(unsigned_tx['complete'], False)

        self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000)

        # Sign the transaction but don't finalize
        processed_psbt = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=False)
        assert "hex" not in processed_psbt
        signed_psbt = processed_psbt['psbt']

        # Finalize and send
        finalized_hex = self.nodes[0].finalizepsbt(signed_psbt)['hex']
        self.nodes[0].sendrawtransaction(finalized_hex)

        # Alternative method: sign AND finalize in one command
        processed_finalized_psbt = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=True)
        finalized_psbt = processed_finalized_psbt['psbt']
        finalized_psbt_hex = processed_finalized_psbt['hex']
        assert_not_equal(signed_psbt, finalized_psbt)
        assert finalized_psbt_hex == finalized_hex

        # Manually selected inputs can be locked:
        assert_equal(len(self.nodes[0].listlockunspent()), 0)
        utxo1 = self.nodes[0].listunspent()[0]
        psbtx1 = self.nodes[0].walletcreatefundedpsbt([{"txid": utxo1['txid'], "vout": utxo1['vout']}], {self.nodes[2].getnewaddress():1}, 0,{"lockUnspents": True})["psbt"]
        assert_equal(len(self.nodes[0].listlockunspent()), 1)

        # Locks are ignored for manually selected inputs

Review comment (bug):
Relieved to see this now, it was bugging me earlier (but could not prove to myself until I found it in the spec) that how it can finalise to the same hex with a different sighash when underneath `PSBTInputSignedAndVerified` uses the same `interpreter.cpp::VerifyScript()` that is used in other script validation flows as well.

Code after:
        def_wallet.send(outputs)
        self.generate(self.nodes[0], 6)
        utxos = wallet.listunspent()

        # Make a PSBT
        psbt = wallet.walletcreatefundedpsbt(utxos, [{def_wallet.getnewaddress(): 0.5}])["psbt"]

        # Process the PSBT with the wallet
        wallet_psbt = wallet.walletprocesspsbt(psbt=psbt, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]

        # Separately process the PSBT with descriptors
        desc_psbt = self.nodes[0].descriptorprocesspsbt(psbt=psbt, descriptors=descs, sighashtype="ALL|ANYONECANPAY", finalize=False)["psbt"]

        for psbt in [wallet_psbt, desc_psbt]:
            # Check that the PSBT has a sighash field on all inputs
            dec_psbt = self.nodes[0].decodepsbt(psbt)
            for input in dec_psbt["inputs"]:
                assert_equal(input["sighash"], "ALL|ANYONECANPAY")

            # Make sure we can still finalize the transaction
            fin_res = self.nodes[0].finalizepsbt(psbt)
            assert_equal(fin_res["complete"], True)
            fin_hex = fin_res["hex"]
            assert_equal(self.nodes[0].testmempoolaccept([fin_hex])[0]["allowed"], True)

            # Change the sighash field to a different value and make sure we can no longer finalize
            mod_psbt = PSBT.from_base64(psbt)
            mod_psbt.i[0].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
            mod_psbt.i[1].map[PSBT_IN_SIGHASH_TYPE] = (SIGHASH_ALL).to_bytes(4, byteorder="little")
            psbt = mod_psbt.to_base64()
            fin_res = self.nodes[0].finalizepsbt(psbt)
            assert_equal(fin_res["complete"], False)

        self.nodes[0].sendrawtransaction(fin_hex)
        self.generate(self.nodes[0], 1)

        wallet.unloadwallet()

    def assert_change_type(self, psbtx, expected_type):
        """Assert that the given PSBT has a change output with the given type."""

        # The decodepsbt RPC is stateless and independent of any settings, we can always just call it on the first node
        decoded_psbt = self.nodes[0].decodepsbt(psbtx["psbt"])
        changepos = psbtx["changepos"]
        assert_equal(decoded_psbt["tx"]["vout"][changepos]["scriptPubKey"]["type"], expected_type)

    def run_test(self):
        # Create and fund a raw tx for sending 10 BTC
        psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']

        self.log.info("Test for invalid maximum transaction weights")