Merge #14780: PSBT backports to 0.17
7bee41452b
Add test for conversion from non-witness to witness UTXO (Pieter Wuille)ff56bb9b44
Add regression test for PSBT signing bug #14473 (Glenn Willen)db445d4e5a
Refactor PSBTInput signing to enforce invariant (Glenn Willen)ad94165db9
Simplify arguments to SignPSBTInput (Glenn Willen)39ece4fc28
Add bool PSBTInputSigned (Glenn Willen)70ee1f8709
New PartiallySignedTransaction constructor from CTransction (Glenn Willen)a9eab081d5
Remove redundant txConst parameter to FillPSBT (Glenn Willen)cfdd6b2f6c
More concise conversion of CDataStream to string (Glenn Willen)a3fe125490
check that a separator is found for psbt inputs, outputs, and global map (Andrew Chow) Pull request description: This is a backport of #14588, #14377, and #14197's test to 0.17. Tree-SHA512: 07535ec69a878a63b549e5e463345e233f34662dff805202614cf2ffc896c6d1981363e6d06d02db2e02d815075ad8ebdc5f93f637052cff8c8cbe6c8dfa096a
This commit is contained in:
commit
5d12143c73
8 changed files with 161 additions and 73 deletions
|
@ -1643,13 +1643,14 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all of the previous transactions
|
// Finalize input signatures -- in case we have partial signatures that add up to a complete
|
||||||
|
// signature, but have not combined them yet (e.g. because the combiner that created this
|
||||||
|
// PartiallySignedTransaction did not understand them), this will combine them into a final
|
||||||
|
// script.
|
||||||
bool complete = true;
|
bool complete = true;
|
||||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||||
PSBTInput& input = psbtx.inputs.at(i);
|
|
||||||
|
|
||||||
SignatureData sigdata;
|
SignatureData sigdata;
|
||||||
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, sigdata, i, 1);
|
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, sigdata, i, SIGHASH_ALL);
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
|
@ -1662,10 +1663,10 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
|
||||||
mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
|
mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
|
||||||
}
|
}
|
||||||
ssTx << mtx;
|
ssTx << mtx;
|
||||||
result.pushKV("hex", HexStr(ssTx.begin(), ssTx.end()));
|
result.pushKV("hex", HexStr(ssTx.str()));
|
||||||
} else {
|
} else {
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
|
result.pushKV("psbt", EncodeBase64(ssTx.str()));
|
||||||
}
|
}
|
||||||
result.pushKV("complete", complete);
|
result.pushKV("complete", complete);
|
||||||
|
|
||||||
|
|
|
@ -233,10 +233,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
|
||||||
return sigdata.complete;
|
return sigdata.complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, SignatureData& sigdata, int index, int sighash)
|
bool PSBTInputSigned(PSBTInput& input)
|
||||||
{
|
{
|
||||||
// if this input has a final scriptsig or scriptwitness, don't do anything with it
|
return !input.final_script_sig.empty() || !input.final_script_witness.IsNull();
|
||||||
if (!input.final_script_sig.empty() || !input.final_script_witness.IsNull()) {
|
}
|
||||||
|
|
||||||
|
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, SignatureData& sigdata, int index, int sighash)
|
||||||
|
{
|
||||||
|
PSBTInput& input = psbt.inputs.at(index);
|
||||||
|
const CMutableTransaction& tx = *psbt.tx;
|
||||||
|
|
||||||
|
if (PSBTInputSigned(input)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,15 +253,19 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t
|
||||||
// Get UTXO
|
// Get UTXO
|
||||||
bool require_witness_sig = false;
|
bool require_witness_sig = false;
|
||||||
CTxOut utxo;
|
CTxOut utxo;
|
||||||
|
|
||||||
|
// Verify input sanity, which checks that at most one of witness or non-witness utxos is provided.
|
||||||
|
if (!input.IsSane()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (input.non_witness_utxo) {
|
if (input.non_witness_utxo) {
|
||||||
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
|
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
|
||||||
if (input.non_witness_utxo->GetHash() != tx.vin[index].prevout.hash) return false;
|
COutPoint prevout = tx.vin[index].prevout;
|
||||||
// If both witness and non-witness UTXO are provided, verify that they match. This check shouldn't
|
if (input.non_witness_utxo->GetHash() != prevout.hash) {
|
||||||
// matter, as the PSBT deserializer enforces only one of both is provided, and the only way both
|
return false;
|
||||||
// can be present is when they're added simultaneously by FillPSBT (in which case they always match).
|
}
|
||||||
// Still, check in order to not rely on callers to enforce this.
|
utxo = input.non_witness_utxo->vout[prevout.n];
|
||||||
if (!input.witness_utxo.IsNull() && input.non_witness_utxo->vout[tx.vin[index].prevout.n] != input.witness_utxo) return false;
|
|
||||||
utxo = input.non_witness_utxo->vout[tx.vin[index].prevout.n];
|
|
||||||
} else if (!input.witness_utxo.IsNull()) {
|
} else if (!input.witness_utxo.IsNull()) {
|
||||||
utxo = input.witness_utxo;
|
utxo = input.witness_utxo;
|
||||||
// When we're taking our information from a witness UTXO, we can't verify it is actually data from
|
// When we're taking our information from a witness UTXO, we can't verify it is actually data from
|
||||||
|
@ -272,6 +283,13 @@ bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& t
|
||||||
// Verify that a witness signature was produced in case one was required.
|
// Verify that a witness signature was produced in case one was required.
|
||||||
if (require_witness_sig && !sigdata.witness) return false;
|
if (require_witness_sig && !sigdata.witness) return false;
|
||||||
input.FromSignatureData(sigdata);
|
input.FromSignatureData(sigdata);
|
||||||
|
|
||||||
|
// If we have a witness signature, use the smaller witness UTXO.
|
||||||
|
if (sigdata.witness) {
|
||||||
|
input.witness_utxo = utxo;
|
||||||
|
input.non_witness_utxo = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
return sig_complete;
|
return sig_complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -491,6 +509,12 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PartiallySignedTransaction::PartiallySignedTransaction(const CTransaction& tx) : tx(tx)
|
||||||
|
{
|
||||||
|
inputs.resize(tx.vin.size());
|
||||||
|
outputs.resize(tx.vout.size());
|
||||||
|
}
|
||||||
|
|
||||||
bool PartiallySignedTransaction::IsNull() const
|
bool PartiallySignedTransaction::IsNull() const
|
||||||
{
|
{
|
||||||
return !tx && inputs.empty() && outputs.empty() && unknown.empty();
|
return !tx && inputs.empty() && outputs.empty() && unknown.empty();
|
||||||
|
|
|
@ -286,6 +286,7 @@ struct PSBTInput
|
||||||
template <typename Stream>
|
template <typename Stream>
|
||||||
inline void Unserialize(Stream& s) {
|
inline void Unserialize(Stream& s) {
|
||||||
// Read loop
|
// Read loop
|
||||||
|
bool found_sep = false;
|
||||||
while(!s.empty()) {
|
while(!s.empty()) {
|
||||||
// Read
|
// Read
|
||||||
std::vector<unsigned char> key;
|
std::vector<unsigned char> key;
|
||||||
|
@ -293,7 +294,10 @@ struct PSBTInput
|
||||||
|
|
||||||
// the key is empty if that was actually a separator byte
|
// the key is empty if that was actually a separator byte
|
||||||
// This is a special case for key lengths 0 as those are not allowed (except for separator)
|
// This is a special case for key lengths 0 as those are not allowed (except for separator)
|
||||||
if (key.empty()) return;
|
if (key.empty()) {
|
||||||
|
found_sep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// First byte of key is the type
|
// First byte of key is the type
|
||||||
unsigned char type = key[0];
|
unsigned char type = key[0];
|
||||||
|
@ -408,6 +412,10 @@ struct PSBTInput
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!found_sep) {
|
||||||
|
throw std::ios_base::failure("Separator is missing at the end of an input map");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Stream>
|
template <typename Stream>
|
||||||
|
@ -461,6 +469,7 @@ struct PSBTOutput
|
||||||
template <typename Stream>
|
template <typename Stream>
|
||||||
inline void Unserialize(Stream& s) {
|
inline void Unserialize(Stream& s) {
|
||||||
// Read loop
|
// Read loop
|
||||||
|
bool found_sep = false;
|
||||||
while(!s.empty()) {
|
while(!s.empty()) {
|
||||||
// Read
|
// Read
|
||||||
std::vector<unsigned char> key;
|
std::vector<unsigned char> key;
|
||||||
|
@ -468,7 +477,10 @@ struct PSBTOutput
|
||||||
|
|
||||||
// the key is empty if that was actually a separator byte
|
// the key is empty if that was actually a separator byte
|
||||||
// This is a special case for key lengths 0 as those are not allowed (except for separator)
|
// This is a special case for key lengths 0 as those are not allowed (except for separator)
|
||||||
if (key.empty()) return;
|
if (key.empty()) {
|
||||||
|
found_sep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// First byte of key is the type
|
// First byte of key is the type
|
||||||
unsigned char type = key[0];
|
unsigned char type = key[0];
|
||||||
|
@ -513,6 +525,10 @@ struct PSBTOutput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!found_sep) {
|
||||||
|
throw std::ios_base::failure("Separator is missing at the end of an output map");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Stream>
|
template <typename Stream>
|
||||||
|
@ -534,6 +550,7 @@ struct PartiallySignedTransaction
|
||||||
bool IsSane() const;
|
bool IsSane() const;
|
||||||
PartiallySignedTransaction() {}
|
PartiallySignedTransaction() {}
|
||||||
PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {}
|
PartiallySignedTransaction(const PartiallySignedTransaction& psbt_in) : tx(psbt_in.tx), inputs(psbt_in.inputs), outputs(psbt_in.outputs), unknown(psbt_in.unknown) {}
|
||||||
|
explicit PartiallySignedTransaction(const CTransaction& tx);
|
||||||
|
|
||||||
// Only checks if they refer to the same transaction
|
// Only checks if they refer to the same transaction
|
||||||
friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b)
|
friend bool operator==(const PartiallySignedTransaction& a, const PartiallySignedTransaction &b)
|
||||||
|
@ -588,6 +605,7 @@ struct PartiallySignedTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read global data
|
// Read global data
|
||||||
|
bool found_sep = false;
|
||||||
while(!s.empty()) {
|
while(!s.empty()) {
|
||||||
// Read
|
// Read
|
||||||
std::vector<unsigned char> key;
|
std::vector<unsigned char> key;
|
||||||
|
@ -595,7 +613,10 @@ struct PartiallySignedTransaction
|
||||||
|
|
||||||
// the key is empty if that was actually a separator byte
|
// the key is empty if that was actually a separator byte
|
||||||
// This is a special case for key lengths 0 as those are not allowed (except for separator)
|
// This is a special case for key lengths 0 as those are not allowed (except for separator)
|
||||||
if (key.empty()) break;
|
if (key.empty()) {
|
||||||
|
found_sep = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// First byte of key is the type
|
// First byte of key is the type
|
||||||
unsigned char type = key[0];
|
unsigned char type = key[0];
|
||||||
|
@ -635,6 +656,10 @@ struct PartiallySignedTransaction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!found_sep) {
|
||||||
|
throw std::ios_base::failure("Separator is missing at the end of the global map");
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure that we got an unsigned tx
|
// Make sure that we got an unsigned tx
|
||||||
if (!tx) {
|
if (!tx) {
|
||||||
throw std::ios_base::failure("No unsigned transcation was provided");
|
throw std::ios_base::failure("No unsigned transcation was provided");
|
||||||
|
@ -689,8 +714,11 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
|
||||||
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType);
|
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType);
|
||||||
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType);
|
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType);
|
||||||
|
|
||||||
|
/** Checks whether a PSBTInput is already signed. */
|
||||||
|
bool PSBTInputSigned(PSBTInput& input);
|
||||||
|
|
||||||
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
|
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
|
||||||
bool SignPSBTInput(const SigningProvider& provider, const CMutableTransaction& tx, PSBTInput& input, SignatureData& sigdata, int index, int sighash = 1);
|
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, SignatureData& sigdata, int index, int sighash = SIGHASH_ALL);
|
||||||
|
|
||||||
/** Extract signature data from a transaction input, and insert it. */
|
/** Extract signature data from a transaction input, and insert it. */
|
||||||
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout);
|
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout);
|
||||||
|
|
|
@ -4502,24 +4502,34 @@ void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubK
|
||||||
hd_keypaths.emplace(vchPubKey, keypath);
|
hd_keypaths.emplace(vchPubKey, keypath);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs)
|
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type, bool sign, bool bip32derivs)
|
||||||
{
|
{
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
// Get all of the previous transactions
|
// Get all of the previous transactions
|
||||||
bool complete = true;
|
bool complete = true;
|
||||||
for (unsigned int i = 0; i < txConst->vin.size(); ++i) {
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||||
const CTxIn& txin = txConst->vin[i];
|
const CTxIn& txin = psbtx.tx->vin[i];
|
||||||
PSBTInput& input = psbtx.inputs.at(i);
|
PSBTInput& input = psbtx.inputs.at(i);
|
||||||
|
|
||||||
// If we don't know about this input, skip it and let someone else deal with it
|
if (PSBTInputSigned(input)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify input looks sane. This will check that we have at most one uxto, witness or non-witness.
|
||||||
|
if (!input.IsSane()) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "PSBT input is not sane.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have no utxo, grab it from the wallet.
|
||||||
|
if (!input.non_witness_utxo && input.witness_utxo.IsNull()) {
|
||||||
const uint256& txhash = txin.prevout.hash;
|
const uint256& txhash = txin.prevout.hash;
|
||||||
const auto it = pwallet->mapWallet.find(txhash);
|
const auto it = pwallet->mapWallet.find(txhash);
|
||||||
if (it != pwallet->mapWallet.end()) {
|
if (it != pwallet->mapWallet.end()) {
|
||||||
const CWalletTx& wtx = it->second;
|
const CWalletTx& wtx = it->second;
|
||||||
CTxOut utxo = wtx.tx->vout[txin.prevout.n];
|
// We only need the non_witness_utxo, which is a superset of the witness_utxo.
|
||||||
// Update both UTXOs from the wallet.
|
// The signing code will switch to the smaller witness_utxo if this is ok.
|
||||||
input.non_witness_utxo = wtx.tx;
|
input.non_witness_utxo = wtx.tx;
|
||||||
input.witness_utxo = utxo;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the Sighash type
|
// Get the Sighash type
|
||||||
|
@ -4529,9 +4539,9 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const C
|
||||||
|
|
||||||
SignatureData sigdata;
|
SignatureData sigdata;
|
||||||
if (sign) {
|
if (sign) {
|
||||||
complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type);
|
complete &= SignPSBTInput(*pwallet, psbtx, sigdata, i, sighash_type);
|
||||||
} else {
|
} else {
|
||||||
complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type);
|
complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), psbtx, sigdata, i, sighash_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sigdata.witness) {
|
if (sigdata.witness) {
|
||||||
|
@ -4541,15 +4551,6 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If both UTXO types are present, drop the unnecessary one.
|
|
||||||
if (input.non_witness_utxo && !input.witness_utxo.IsNull()) {
|
|
||||||
if (sigdata.witness) {
|
|
||||||
input.non_witness_utxo = nullptr;
|
|
||||||
} else {
|
|
||||||
input.witness_utxo.SetNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get public key paths
|
// Get public key paths
|
||||||
if (bip32derivs) {
|
if (bip32derivs) {
|
||||||
for (const auto& pubkey_it : sigdata.misc_pubkeys) {
|
for (const auto& pubkey_it : sigdata.misc_pubkeys) {
|
||||||
|
@ -4559,8 +4560,8 @@ bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const C
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
|
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
|
||||||
for (unsigned int i = 0; i < txConst->vout.size(); ++i) {
|
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
||||||
const CTxOut& out = txConst->vout.at(i);
|
const CTxOut& out = psbtx.tx->vout.at(i);
|
||||||
PSBTOutput& psbt_out = psbtx.outputs.at(i);
|
PSBTOutput& psbt_out = psbtx.outputs.at(i);
|
||||||
|
|
||||||
// Dummy tx so we can use ProduceSignature to get stuff out
|
// Dummy tx so we can use ProduceSignature to get stuff out
|
||||||
|
@ -4637,19 +4638,15 @@ UniValue walletprocesspsbt(const JSONRPCRequest& request)
|
||||||
// Get the sighash type
|
// Get the sighash type
|
||||||
int nHashType = ParseSighashString(request.params[2]);
|
int nHashType = ParseSighashString(request.params[2]);
|
||||||
|
|
||||||
// Use CTransaction for the constant parts of the
|
|
||||||
// transaction to avoid rehashing.
|
|
||||||
const CTransaction txConst(*psbtx.tx);
|
|
||||||
|
|
||||||
// Fill transaction with our data and also sign
|
// Fill transaction with our data and also sign
|
||||||
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
|
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
|
||||||
bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool();
|
bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool();
|
||||||
bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs);
|
bool complete = FillPSBT(pwallet, psbtx, nHashType, sign, bip32derivs);
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
|
result.pushKV("psbt", EncodeBase64(ssTx.str()));
|
||||||
result.pushKV("complete", complete);
|
result.pushKV("complete", complete);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -4741,29 +4738,18 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
||||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
|
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
|
||||||
|
|
||||||
// Make a blank psbt
|
// Make a blank psbt
|
||||||
PartiallySignedTransaction psbtx;
|
PartiallySignedTransaction psbtx(rawTx);
|
||||||
psbtx.tx = rawTx;
|
|
||||||
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
|
|
||||||
psbtx.inputs.push_back(PSBTInput());
|
|
||||||
}
|
|
||||||
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
|
|
||||||
psbtx.outputs.push_back(PSBTOutput());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use CTransaction for the constant parts of the
|
|
||||||
// transaction to avoid rehashing.
|
|
||||||
const CTransaction txConst(*psbtx.tx);
|
|
||||||
|
|
||||||
// Fill transaction with out data but don't sign
|
// Fill transaction with out data but don't sign
|
||||||
bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool();
|
bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool();
|
||||||
FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs);
|
FillPSBT(pwallet, psbtx, 1, false, bip32derivs);
|
||||||
|
|
||||||
// Serialize the PSBT
|
// Serialize the PSBT
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
|
result.pushKV("psbt", EncodeBase64(ssTx.str()));
|
||||||
result.pushKV("fee", ValueFromAmount(fee));
|
result.pushKV("fee", ValueFromAmount(fee));
|
||||||
result.pushKV("changepos", change_position);
|
result.pushKV("changepos", change_position);
|
||||||
return result;
|
return result;
|
||||||
|
|
|
@ -30,5 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException);
|
||||||
|
|
||||||
UniValue getaddressinfo(const JSONRPCRequest& request);
|
UniValue getaddressinfo(const JSONRPCRequest& request);
|
||||||
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
|
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
|
||||||
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false);
|
bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false);
|
||||||
#endif //BITCOIN_WALLET_RPCWALLET_H
|
#endif //BITCOIN_WALLET_RPCWALLET_H
|
||||||
|
|
|
@ -59,12 +59,8 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
|
||||||
CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssData(ParseHex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000000000000000000"), SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssData >> psbtx;
|
ssData >> psbtx;
|
||||||
|
|
||||||
// Use CTransaction for the constant parts of the
|
|
||||||
// transaction to avoid rehashing.
|
|
||||||
const CTransaction txConst(*psbtx.tx);
|
|
||||||
|
|
||||||
// Fill transaction with our data
|
// Fill transaction with our data
|
||||||
FillPSBT(&m_wallet, psbtx, &txConst, 1, false, true);
|
FillPSBT(&m_wallet, psbtx, SIGHASH_ALL, false, true);
|
||||||
|
|
||||||
// Get the final tx
|
// Get the final tx
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
|
|
|
@ -17,7 +17,8 @@
|
||||||
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
|
"cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAABB9oARzBEAiB0AYrUGACXuHMyPAAVcgs2hMyBI4kQSOfbzZtVrWecmQIgc9Npt0Dj61Pc76M4I8gHBRTKVafdlUTxV8FnkTJhEYwBSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAUdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSrgABASAAwusLAAAAABepFLf1+vQOPUClpFmx2zU18rcvqSHohwEHIyIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQjaBABHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwFHMEQCIGX0W6WZi1mif/4ae+0BavHx+Q1Us6qPdFCqX1aiUQO9AiB/ckcDrR7blmgLKEtW1P/LiPf7dZ6rvgiqMPKbhROD0gFHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4AIQIDqaTDf1mW06ol26xrVwrwZQOUSSlCRgs1R1PtnuylhxDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA",
|
||||||
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
|
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wCAwABAAAAAAEAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
|
||||||
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
|
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAgAAFgAUYunpgv/zTdgjlhAxawkM0qO3R8sAAQAiACCHa62DLx0WgBXtQSMqnqZaGBXZ7xPA74dZ9ktbKyeKZQEBJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
|
||||||
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A"
|
"cHNidP8BAHMCAAAAATAa6YblFqHsisW0vGVz0y+DtGXiOtdhZ9aLOOcwtNvbAAAAAAD/////AnR7AQAAAAAAF6kUA6oXrogrXQ1Usl1jEE5P/s57nqKHYEOZOwAAAAAXqRS5IbG6b3IuS/qDtlV6MTmYakLsg4cAAAAAAAEBHwDKmjsAAAAAFgAU0tlLZK4IWH7vyO6xh8YB6Tn5A3wAAQAWABRi6emC//NN2COWEDFrCQzSo7dHywABACIAIIdrrYMvHRaAFe1BIyqeploYFdnvE8Dvh1n2S1srJ4plIQEAJVEhA7fOI6AcW0vwCmQlN836uzFbZoMyhnR471EwnSvVf4qHUa4A",
|
||||||
|
"cHNidP8BAHMCAAAAAbiWoY6pOQepFsEGhUPXaulX9rvye2NH+NrdlAHg+WgpAQAAAAD/////AkBLTAAAAAAAF6kUqWwXCcLM5BN2zoNqMNT5qMlIi7+HQEtMAAAAAAAXqRSVF/in2XNxAlN1OSxkyp0z+Wtg2YcAAAAAAAEBIBNssgAAAAAAF6kUamsvautR8hRlMRY6OKNTx03DK96HAQcXFgAUo8u1LWpHprjt/uENAwBpGZD0UH0BCGsCRzBEAiAONfH3DYiw67ZbylrsxCF/XXpVwyWBRgofyRbPslzvwgIgIKCsWw5sHSIPh1icNvcVLZLHWj6NA7Dk+4Os2pOnMbQBIQPGStfYHPtyhpV7zIWtn0Q4GXv5gK1zy/tnJ+cBXu4iiwABABYAFMwmJQEz+HDpBEEabxJ5PogPsqZRAAEAFgAUyCrGc3h3FYCmiIspbv2pSTKZ5jU"
|
||||||
],
|
],
|
||||||
"valid" : [
|
"valid" : [
|
||||||
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
|
"cHNidP8BAHUCAAAAASaBcTce3/KF6Tet7qSze3gADAVmy7OtZGQXE8pCFxv2AAAAAAD+////AtPf9QUAAAAAGXapFNDFmQPFusKGh2DpD9UhpGZap2UgiKwA4fUFAAAAABepFDVF5uM7gyxHBQ8k0+65PJwDlIvHh7MuEwAAAQD9pQEBAAAAAAECiaPHHqtNIOA3G7ukzGmPopXJRjr6Ljl/hTPMti+VZ+UBAAAAFxYAFL4Y0VKpsBIDna89p95PUzSe7LmF/////4b4qkOnHf8USIk6UwpyN+9rRgi7st0tAXHmOuxqSJC0AQAAABcWABT+Pp7xp0XpdNkCxDVZQ6vLNL1TU/////8CAMLrCwAAAAAZdqkUhc/xCX/Z4Ai7NK9wnGIZeziXikiIrHL++E4sAAAAF6kUM5cluiHv1irHU6m80GfWx6ajnQWHAkcwRAIgJxK+IuAnDzlPVoMR3HyppolwuAJf3TskAinwf4pfOiQCIAGLONfc0xTnNMkna9b7QPZzMlvEuqFEyADS8vAtsnZcASED0uFWdJQbrUqZY3LLh+GFbTZSYG2YVi/jnF6efkE/IQUCSDBFAiEA0SuFLYXc2WHS9fSrZgZU327tzHlMDDPOXMMJ/7X85Y0CIGczio4OFyXBl/saiK9Z9R5E5CVbIBZ8hoQDHAXR8lkqASECI7cr7vCWXRC+B3jv7NYfysb3mk6haTkzgHNEZPhPKrMAAAAAAAAA",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from test_framework.test_framework import BitcoinTestFramework
|
from test_framework.test_framework import BitcoinTestFramework
|
||||||
from test_framework.util import assert_equal, assert_raises_rpc_error, find_output
|
from test_framework.util import assert_equal, assert_raises_rpc_error, find_output, disconnect_nodes, connect_nodes_bi, sync_blocks
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -23,6 +23,45 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
def skip_test_if_missing_module(self):
|
def skip_test_if_missing_module(self):
|
||||||
self.skip_if_no_wallet()
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def test_utxo_conversion(self):
|
||||||
|
mining_node = self.nodes[2]
|
||||||
|
offline_node = self.nodes[0]
|
||||||
|
online_node = self.nodes[1]
|
||||||
|
|
||||||
|
# Disconnect offline node from others
|
||||||
|
disconnect_nodes(offline_node, 1)
|
||||||
|
disconnect_nodes(online_node, 0)
|
||||||
|
disconnect_nodes(offline_node, 2)
|
||||||
|
disconnect_nodes(mining_node, 0)
|
||||||
|
|
||||||
|
# Mine a transaction that credits the offline address
|
||||||
|
offline_addr = offline_node.getnewaddress(address_type="p2sh-segwit")
|
||||||
|
online_addr = online_node.getnewaddress(address_type="p2sh-segwit")
|
||||||
|
online_node.importaddress(offline_addr, "", False)
|
||||||
|
mining_node.sendtoaddress(address=offline_addr, amount=1.0)
|
||||||
|
mining_node.generate(nblocks=1)
|
||||||
|
sync_blocks([mining_node, online_node])
|
||||||
|
|
||||||
|
# Construct an unsigned PSBT on the online node (who doesn't know the output is Segwit, so will include a non-witness UTXO)
|
||||||
|
utxos = online_node.listunspent(addresses=[offline_addr])
|
||||||
|
raw = online_node.createrawtransaction([{"txid":utxos[0]["txid"], "vout":utxos[0]["vout"]}],[{online_addr:0.9999}])
|
||||||
|
psbt = online_node.walletprocesspsbt(online_node.converttopsbt(raw))["psbt"]
|
||||||
|
assert("non_witness_utxo" in mining_node.decodepsbt(psbt)["inputs"][0])
|
||||||
|
|
||||||
|
# Have the offline node sign the PSBT (which will update the UTXO to segwit)
|
||||||
|
signed_psbt = offline_node.walletprocesspsbt(psbt)["psbt"]
|
||||||
|
assert("witness_utxo" in mining_node.decodepsbt(signed_psbt)["inputs"][0])
|
||||||
|
|
||||||
|
# Make sure we can mine the resulting transaction
|
||||||
|
txid = mining_node.sendrawtransaction(mining_node.finalizepsbt(signed_psbt)["hex"])
|
||||||
|
mining_node.generate(1)
|
||||||
|
sync_blocks([mining_node, online_node])
|
||||||
|
assert_equal(online_node.gettxout(txid,0)["confirmations"], 1)
|
||||||
|
|
||||||
|
# Reconnect
|
||||||
|
connect_nodes_bi(self.nodes, 0, 1)
|
||||||
|
connect_nodes_bi(self.nodes, 0, 2)
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# Create and fund a raw tx for sending 10 BTC
|
# Create and fund a raw tx for sending 10 BTC
|
||||||
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
|
psbtx1 = self.nodes[0].walletcreatefundedpsbt([], {self.nodes[2].getnewaddress():10})['psbt']
|
||||||
|
@ -168,6 +207,13 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
|
assert tx_in["sequence"] > MAX_BIP125_RBF_SEQUENCE
|
||||||
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
||||||
|
|
||||||
|
# Regression test for 14473 (mishandling of already-signed witness transaction):
|
||||||
|
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
|
||||||
|
complete_psbt = self.nodes[0].walletprocesspsbt(psbtx_info["psbt"])
|
||||||
|
double_processed_psbt = self.nodes[0].walletprocesspsbt(complete_psbt["psbt"])
|
||||||
|
assert_equal(complete_psbt, double_processed_psbt)
|
||||||
|
# We don't care about the decode result, but decoding must succeed.
|
||||||
|
self.nodes[0].decodepsbt(double_processed_psbt["psbt"])
|
||||||
|
|
||||||
# Make sure change address wallet does not have P2SH innerscript access to results in success
|
# Make sure change address wallet does not have P2SH innerscript access to results in success
|
||||||
# when attempting BnB coin selection
|
# when attempting BnB coin selection
|
||||||
|
@ -228,6 +274,12 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
extracted = self.nodes[2].finalizepsbt(extractor['extract'], True)['hex']
|
extracted = self.nodes[2].finalizepsbt(extractor['extract'], True)['hex']
|
||||||
assert_equal(extracted, extractor['result'])
|
assert_equal(extracted, extractor['result'])
|
||||||
|
|
||||||
|
# Unload extra wallets
|
||||||
|
for i, signer in enumerate(signers):
|
||||||
|
self.nodes[2].unloadwallet("wallet{}".format(i))
|
||||||
|
|
||||||
|
self.test_utxo_conversion()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
PSBTTest().main()
|
PSBTTest().main()
|
||||||
|
|
Loading…
Reference in a new issue