diff --git a/src/node/psbt.h b/src/node/psbt.h --- a/src/node/psbt.h +++ b/src/node/psbt.h @@ -40,6 +40,17 @@ std::vector inputs; //! Which of the BIP 174 roles needs to handle the transaction next PSBTRole next; + //! Error message + std::string error; + + void SetInvalid(std::string err_msg) { + estimated_vsize = nullopt; + estimated_feerate = nullopt; + fee = nullopt; + inputs.clear(); + next = PSBTRole::CREATOR; + error = err_msg; + } }; /** diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include @@ -38,6 +39,12 @@ calc_fee = false; } + if (!utxo.IsNull() && utxo.scriptPubKey.IsUnspendable()) { + result.SetInvalid(strprintf( + "PSBT is not valid. Input %u spends unspendable output", i)); + return result; + } + // Check if it is final if (!utxo.IsNull() && !PSBTInputSigned(input)) { input_analysis.is_final = false; diff --git a/src/psbt.h b/src/psbt.h --- a/src/psbt.h +++ b/src/psbt.h @@ -510,6 +510,7 @@ }; enum class PSBTRole { + CREATOR, UPDATER, SIGNER, FINALIZER, diff --git a/src/psbt.cpp b/src/psbt.cpp --- a/src/psbt.cpp +++ b/src/psbt.cpp @@ -297,6 +297,8 @@ std::string PSBTRoleName(const PSBTRole role) { switch (role) { + case PSBTRole::CREATOR: + return "creator"; case PSBTRole::UPDATER: return "updater"; case PSBTRole::SIGNER: @@ -305,6 +307,7 @@ return "finalizer"; case PSBTRole::EXTRACTOR: return "extractor"; + // no default case, so the compiler can warn about missing cases } assert(false); } diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -1924,6 +1924,8 @@ "have been filled.\n" " \"next\" : \"role\" (string) Role of the next " "person that this psbt needs to go to\n" + " \"error\" : \"error\" (string) Error message if " + "there is one" "}\n"}, RPCExamples{HelpExampleCli("analyzepsbt", "\"psbt\"")}} .Check(request); @@ -1972,8 +1974,9 @@ } inputs_result.push_back(input_univ); } - result.pushKV("inputs", inputs_result); - + if (!inputs_result.empty()) { + result.pushKV("inputs", inputs_result); + } if (psbta.estimated_vsize != nullopt) { result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); } @@ -1985,6 +1988,9 @@ result.pushKV("fee", ValueFromAmount(*psbta.fee)); } result.pushKV("next", PSBTRoleName(psbta.next)); + if (!psbta.error.empty()) { + result.pushKV("error", psbta.error); + } return result; } diff --git a/test/functional/rpc_psbt.py b/test/functional/rpc_psbt.py --- a/test/functional/rpc_psbt.py +++ b/test/functional/rpc_psbt.py @@ -371,6 +371,15 @@ analyzed = self.nodes[0].analyzepsbt(signed) assert analyzed['inputs'][0]['has_utxo'] and analyzed['inputs'][0]['is_final'] and analyzed['next'] == 'extractor' + self.log.info( + "PSBT spending unspendable outputs should have error message and Creator as next") + analysis = self.nodes[0].analyzepsbt( + 'cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWAEHYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFv8/wADXYP/7//////8JxOh0LR2HAI8AAAAAAAEAIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHEAABAACAAAEAIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHENkMak8AAAAA') + assert_equal(analysis['next'], 'creator') + assert_equal( + analysis['error'], + 'PSBT is not valid. Input 0 spends unspendable output') + if __name__ == '__main__': PSBTTest().main()