Merge #17853: [0.19] psbt: handle unspendable psbts

ca5f8deefd Mark PSBTs spending unspendable outputs as invalid in analysis (Andrew Chow)
551583398b Have a PSBTAnalysis state that indicates invalid PSBT (Andrew Chow)

Pull request description:

  Backport of #17524

ACKs for top commit:
  achow101:
    ACK ca5f8deefd

Tree-SHA512: b5f2b951beb9477ac3176a0aade845654d2108ca3a9fbc72097ba4b4797df5419053d6b489bbaa03be08cb8cfdc37a83db8b7642ffa52d42b7aa8ea14aff39cc
This commit is contained in:
fanquake 2020-01-04 17:43:11 +08:00
commit febf04841f
No known key found for this signature in database
GPG key ID: 2EEB9F5CC09526C1
6 changed files with 29 additions and 1 deletions

View file

@ -7,6 +7,7 @@
#include <node/psbt.h> #include <node/psbt.h>
#include <policy/policy.h> #include <policy/policy.h>
#include <policy/settings.h> #include <policy/settings.h>
#include <tinyformat.h>
#include <numeric> #include <numeric>
@ -39,6 +40,11 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
calc_fee = false; 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 // Check if it is final
if (!utxo.IsNull() && !PSBTInputSigned(input)) { if (!utxo.IsNull() && !PSBTInputSigned(input)) {
input_analysis.is_final = false; input_analysis.is_final = false;

View file

@ -30,6 +30,17 @@ struct PSBTAnalysis {
Optional<CAmount> fee; //!< Amount of fee being paid by the transaction Optional<CAmount> fee; //!< Amount of fee being paid by the transaction
std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction
PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next
std::string error; //!< Error message
void SetInvalid(std::string err_msg)
{
estimated_vsize = nullopt;
estimated_feerate = nullopt;
fee = nullopt;
inputs.clear();
next = PSBTRole::CREATOR;
error = err_msg;
}
}; };
/** /**

View file

@ -349,6 +349,7 @@ TransactionError CombinePSBTs(PartiallySignedTransaction& out, const std::vector
std::string PSBTRoleName(PSBTRole role) { std::string PSBTRoleName(PSBTRole role) {
switch (role) { switch (role) {
case PSBTRole::CREATOR: return "creator";
case PSBTRole::UPDATER: return "updater"; case PSBTRole::UPDATER: return "updater";
case PSBTRole::SIGNER: return "signer"; case PSBTRole::SIGNER: return "signer";
case PSBTRole::FINALIZER: return "finalizer"; case PSBTRole::FINALIZER: return "finalizer";

View file

@ -552,6 +552,7 @@ struct PartiallySignedTransaction
}; };
enum class PSBTRole { enum class PSBTRole {
CREATOR,
UPDATER, UPDATER,
SIGNER, SIGNER,
FINALIZER, FINALIZER,

View file

@ -1671,6 +1671,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request)
" \"estimated_feerate\" : feerate (numeric, optional) Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kB. Shown only if all UTXO slots in the PSBT have been filled.\n" " \"estimated_feerate\" : feerate (numeric, optional) Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kB. Shown only if all UTXO slots in the PSBT have been filled.\n"
" \"fee\" : fee (numeric, optional) The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled.\n" " \"fee\" : fee (numeric, optional) The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled.\n"
" \"next\" : \"role\" (string) Role of the next person that this psbt needs to go to\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" "}\n"
}, },
RPCExamples { RPCExamples {
@ -1723,7 +1724,7 @@ UniValue analyzepsbt(const JSONRPCRequest& request)
} }
inputs_result.push_back(input_univ); 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) { if (psbta.estimated_vsize != nullopt) {
result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize); result.pushKV("estimated_vsize", (int)*psbta.estimated_vsize);
@ -1735,6 +1736,9 @@ UniValue analyzepsbt(const JSONRPCRequest& request)
result.pushKV("fee", ValueFromAmount(*psbta.fee)); result.pushKV("fee", ValueFromAmount(*psbta.fee));
} }
result.pushKV("next", PSBTRoleName(psbta.next)); result.pushKV("next", PSBTRoleName(psbta.next));
if (!psbta.error.empty()) {
result.pushKV("error", psbta.error);
}
return result; return result;
} }

View file

@ -416,5 +416,10 @@ class PSBTTest(BitcoinTestFramework):
analyzed = self.nodes[0].analyzepsbt(signed) analyzed = self.nodes[0].analyzepsbt(signed)
assert analyzed['inputs'][0]['has_utxo'] and analyzed['inputs'][0]['is_final'] and analyzed['next'] == 'extractor' 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//////8JxOh0LR2HAI8AAAAAAAEBIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHEAABAACAAAEBIADC6wsAAAAAF2oUt/X69ELjeX2nTof+fZ10l+OyAokDAQcJAwEHENkMak8AAAAA')
assert_equal(analysis['next'], 'creator')
assert_equal(analysis['error'], 'PSBT is not valid. Input 0 spends unspendable output')
if __name__ == '__main__': if __name__ == '__main__':
PSBTTest().main() PSBTTest().main()