mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-03-13 11:35:20 +01:00
Merge 1137807299
into a50af6e4c4
This commit is contained in:
commit
bb81a46601
25 changed files with 1049 additions and 249 deletions
|
@ -69,8 +69,8 @@ def signet_txs(block, challenge):
|
|||
def decode_psbt(b64psbt):
|
||||
psbt = PSBT.from_base64(b64psbt)
|
||||
|
||||
assert len(psbt.tx.vin) == 1
|
||||
assert len(psbt.tx.vout) == 1
|
||||
assert len(psbt.i) == 1
|
||||
assert len(psbt.i) == 1
|
||||
assert PSBT_SIGNET_BLOCK in psbt.g.map
|
||||
|
||||
scriptSig = psbt.i[0].map.get(PSBT_IN_FINAL_SCRIPTSIG, b"")
|
||||
|
|
6
doc/release-notes-21283.md
Normal file
6
doc/release-notes-21283.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
Updated RPCs
|
||||
------------
|
||||
|
||||
- `createpsbt` and `walletcreatepsbt` will now default to creating
|
||||
version 2 PSBTs. An optional `psbt_version` argument is added to
|
||||
both which allows specifying the version of PSBT to create.
|
|
@ -22,11 +22,11 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
|||
|
||||
CAmount in_amt = 0;
|
||||
|
||||
result.inputs.resize(psbtx.tx->vin.size());
|
||||
result.inputs.resize(psbtx.inputs.size());
|
||||
|
||||
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
||||
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
PSBTInput& input = psbtx.inputs[i];
|
||||
PSBTInputAnalysis& input_analysis = result.inputs[i];
|
||||
|
||||
|
@ -35,7 +35,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
|||
|
||||
// Check for a UTXO
|
||||
CTxOut utxo;
|
||||
if (psbtx.GetInputUTXO(utxo, i)) {
|
||||
if (input.GetUTXO(utxo)) {
|
||||
if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) {
|
||||
result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i));
|
||||
return result;
|
||||
|
@ -43,7 +43,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
|||
in_amt += utxo.nValue;
|
||||
input_analysis.has_utxo = true;
|
||||
} else {
|
||||
if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
if (input.non_witness_utxo && input.prev_out >= input.non_witness_utxo->vout.size()) {
|
||||
result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i));
|
||||
return result;
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
|||
|
||||
// Calculate next role for PSBT by grabbing "minimum" PSBTInput next role
|
||||
result.next = PSBTRole::EXTRACTOR;
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
PSBTInputAnalysis& input_analysis = result.inputs[i];
|
||||
result.next = std::min(result.next, input_analysis.next);
|
||||
}
|
||||
|
@ -97,12 +97,12 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
|||
|
||||
if (calc_fee) {
|
||||
// Get the output amount
|
||||
CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0),
|
||||
[](CAmount a, const CTxOut& b) {
|
||||
if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) {
|
||||
CAmount out_amt = std::accumulate(psbtx.outputs.begin(), psbtx.outputs.end(), CAmount(0),
|
||||
[](CAmount a, const PSBTOutput& b) {
|
||||
if (!MoneyRange(a) || !MoneyRange(*b.amount) || !MoneyRange(a + *b.amount)) {
|
||||
return CAmount(-1);
|
||||
}
|
||||
return a += b.nValue;
|
||||
return a += *b.amount;
|
||||
}
|
||||
);
|
||||
if (!MoneyRange(out_amt)) {
|
||||
|
@ -115,23 +115,23 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
|
|||
result.fee = fee;
|
||||
|
||||
// Estimate the size
|
||||
CMutableTransaction mtx(*psbtx.tx);
|
||||
CMutableTransaction mtx(psbtx.GetUnsignedTx());
|
||||
CCoinsView view_dummy;
|
||||
CCoinsViewCache view(&view_dummy);
|
||||
bool success = true;
|
||||
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
PSBTInput& input = psbtx.inputs[i];
|
||||
Coin newcoin;
|
||||
|
||||
if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) {
|
||||
if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !input.GetUTXO(newcoin.out)) {
|
||||
success = false;
|
||||
break;
|
||||
} else {
|
||||
mtx.vin[i].scriptSig = input.final_script_sig;
|
||||
mtx.vin[i].scriptWitness = input.final_script_witness;
|
||||
newcoin.nHeight = 1;
|
||||
view.AddCoin(psbtx.tx->vin[i].prevout, std::move(newcoin), true);
|
||||
view.AddCoin(input.GetOutPoint(), std::move(newcoin), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
303
src/psbt.cpp
303
src/psbt.cpp
|
@ -6,14 +6,19 @@
|
|||
|
||||
#include <node/types.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/signingprovider.h>
|
||||
#include <util/check.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx) : tx(tx)
|
||||
PartiallySignedTransaction::PartiallySignedTransaction(const CMutableTransaction& tx, uint32_t version) : m_version(version)
|
||||
{
|
||||
inputs.resize(tx.vin.size());
|
||||
outputs.resize(tx.vout.size());
|
||||
if (version == 0) {
|
||||
this->tx = tx;
|
||||
}
|
||||
inputs.resize(tx.vin.size(), PSBTInput(GetVersion()));
|
||||
outputs.resize(tx.vout.size(), PSBTOutput(GetVersion()));
|
||||
SetupFromTx(tx);
|
||||
}
|
||||
|
||||
bool PartiallySignedTransaction::IsNull() const
|
||||
|
@ -24,10 +29,13 @@ bool PartiallySignedTransaction::IsNull() const
|
|||
bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
|
||||
{
|
||||
// Prohibited to merge two PSBTs over different transactions
|
||||
if (tx->GetHash() != psbt.tx->GetHash()) {
|
||||
if (GetUniqueID() != psbt.GetUniqueID()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Assume(*tx_version == psbt.tx_version)) {
|
||||
return false;
|
||||
}
|
||||
for (unsigned int i = 0; i < inputs.size(); ++i) {
|
||||
inputs[i].Merge(psbt.inputs[i]);
|
||||
}
|
||||
|
@ -41,51 +49,229 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
|
|||
m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
|
||||
}
|
||||
}
|
||||
if (fallback_locktime == std::nullopt && psbt.fallback_locktime != std::nullopt) fallback_locktime = psbt.fallback_locktime;
|
||||
if (m_tx_modifiable != std::nullopt && psbt.m_tx_modifiable != std::nullopt) *m_tx_modifiable |= *psbt.m_tx_modifiable;
|
||||
if (m_tx_modifiable == std::nullopt && psbt.m_tx_modifiable != std::nullopt) m_tx_modifiable = psbt.m_tx_modifiable;
|
||||
unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PartiallySignedTransaction::AddInput(const CTxIn& txin, PSBTInput& psbtin)
|
||||
bool PartiallySignedTransaction::ComputeTimeLock(uint32_t& locktime) const
|
||||
{
|
||||
if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) {
|
||||
std::optional<uint32_t> time_lock{0};
|
||||
std::optional<uint32_t> height_lock{0};
|
||||
for (const PSBTInput& input : inputs) {
|
||||
if (input.time_locktime != std::nullopt && input.height_locktime == std::nullopt) {
|
||||
height_lock.reset(); // Transaction can no longer have a height locktime
|
||||
if (time_lock == std::nullopt) {
|
||||
return false;
|
||||
}
|
||||
} else if (input.time_locktime == std::nullopt && input.height_locktime != std::nullopt) {
|
||||
time_lock.reset(); // Transaction can no longer have a time locktime
|
||||
if (height_lock == std::nullopt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (input.time_locktime && time_lock != std::nullopt) {
|
||||
time_lock = std::max(time_lock, input.time_locktime);
|
||||
}
|
||||
if (input.height_locktime && height_lock != std::nullopt) {
|
||||
height_lock = std::max(height_lock, input.height_locktime);
|
||||
}
|
||||
}
|
||||
if (height_lock != std::nullopt && *height_lock > 0) {
|
||||
locktime = *height_lock;
|
||||
return true;
|
||||
}
|
||||
if (time_lock != std::nullopt && *time_lock > 0) {
|
||||
locktime = *time_lock;
|
||||
return true;
|
||||
}
|
||||
locktime = fallback_locktime.value_or(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
CMutableTransaction PartiallySignedTransaction::GetUnsignedTx() const
|
||||
{
|
||||
if (tx != std::nullopt) {
|
||||
return *tx;
|
||||
}
|
||||
|
||||
CMutableTransaction mtx;
|
||||
mtx.version = *tx_version;
|
||||
bool locktime_success = ComputeTimeLock(mtx.nLockTime);
|
||||
assert(locktime_success);
|
||||
uint32_t max_sequence = CTxIn::SEQUENCE_FINAL;
|
||||
for (const PSBTInput& input : inputs) {
|
||||
CTxIn txin;
|
||||
txin.prevout.hash = input.prev_txid;
|
||||
txin.prevout.n = *input.prev_out;
|
||||
txin.nSequence = input.sequence.value_or(max_sequence);
|
||||
mtx.vin.push_back(txin);
|
||||
}
|
||||
for (const PSBTOutput& output : outputs) {
|
||||
CTxOut txout;
|
||||
txout.nValue = *output.amount;
|
||||
txout.scriptPubKey = *output.script;
|
||||
mtx.vout.push_back(txout);
|
||||
}
|
||||
return mtx;
|
||||
}
|
||||
|
||||
uint256 PartiallySignedTransaction::GetUniqueID() const
|
||||
{
|
||||
if (m_version == 0) {
|
||||
return tx->GetHash();
|
||||
}
|
||||
|
||||
// Get the unsigned transaction
|
||||
CMutableTransaction mtx = GetUnsignedTx();
|
||||
// Set the sequence numbers to 0
|
||||
for (CTxIn& txin : mtx.vin) {
|
||||
txin.nSequence = 0;
|
||||
}
|
||||
return mtx.GetHash();
|
||||
}
|
||||
|
||||
bool PartiallySignedTransaction::AddInput(PSBTInput& psbtin)
|
||||
{
|
||||
if (m_version == std::nullopt || m_version == 0) {
|
||||
// This is a v0 psbt, so do the v0 AddInput
|
||||
CTxIn txin(COutPoint(psbtin.prev_txid, *psbtin.prev_out));
|
||||
if (std::find(tx->vin.begin(), tx->vin.end(), txin) != tx->vin.end()) {
|
||||
// Prevent duplicate inputs
|
||||
return false;
|
||||
}
|
||||
tx->vin.push_back(txin);
|
||||
psbtin.partial_sigs.clear();
|
||||
psbtin.final_script_sig.clear();
|
||||
psbtin.final_script_witness.SetNull();
|
||||
inputs.push_back(psbtin);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_version >= 2) {
|
||||
if (psbtin.prev_txid.IsNull() || psbtin.prev_out == std::nullopt) {
|
||||
return false;
|
||||
}
|
||||
// Prevent duplicate inputs
|
||||
if (std::find_if(inputs.begin(), inputs.end(),
|
||||
[psbtin](const PSBTInput& psbt) {
|
||||
return psbt.prev_txid == psbtin.prev_txid && psbt.prev_out == psbtin.prev_out;
|
||||
}
|
||||
) != inputs.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check inputs modifiable flag
|
||||
if (m_tx_modifiable == std::nullopt || !m_tx_modifiable->test(0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Determine if we need to iterate the inputs.
|
||||
// For now, we only do this if the new input has a required time lock.
|
||||
// The BIP states that we should also do this if m_tx_modifiable's bit 2 is set
|
||||
// (Has SIGHASH_SINGLE flag) but since we are only adding inputs at the end of the vector,
|
||||
// we don't care about that.
|
||||
bool iterate_inputs = psbtin.time_locktime != std::nullopt || psbtin.height_locktime != std::nullopt;
|
||||
if (iterate_inputs) {
|
||||
uint32_t old_timelock;
|
||||
if (!ComputeTimeLock(old_timelock)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<uint32_t> time_lock = psbtin.time_locktime;
|
||||
std::optional<uint32_t> height_lock = psbtin.height_locktime;
|
||||
bool has_sigs = false;
|
||||
for (const PSBTInput& input : inputs) {
|
||||
if (input.time_locktime != std::nullopt && input.height_locktime == std::nullopt) {
|
||||
height_lock.reset(); // Transaction can no longer have a height locktime
|
||||
if (time_lock == std::nullopt) {
|
||||
return false;
|
||||
}
|
||||
} else if (input.time_locktime == std::nullopt && input.height_locktime != std::nullopt) {
|
||||
time_lock.reset(); // Transaction can no longer have a time locktime
|
||||
if (height_lock == std::nullopt) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (input.time_locktime && time_lock != std::nullopt) {
|
||||
time_lock = std::max(time_lock, input.time_locktime);
|
||||
}
|
||||
if (input.height_locktime && height_lock != std::nullopt) {
|
||||
height_lock = std::max(height_lock, input.height_locktime);
|
||||
}
|
||||
if (!input.partial_sigs.empty()) {
|
||||
has_sigs = true;
|
||||
}
|
||||
}
|
||||
uint32_t new_timelock = fallback_locktime.value_or(0);
|
||||
if (height_lock != std::nullopt && *height_lock > 0) {
|
||||
new_timelock = *height_lock;
|
||||
} else if (time_lock != std::nullopt && *time_lock > 0) {
|
||||
new_timelock = *time_lock;
|
||||
}
|
||||
if (has_sigs && old_timelock != new_timelock) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the input to the end
|
||||
inputs.push_back(psbtin);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PartiallySignedTransaction::AddOutput(const PSBTOutput& psbtout)
|
||||
{
|
||||
if (psbtout.amount == std::nullopt || !psbtout.script.has_value()) {
|
||||
return false;
|
||||
}
|
||||
tx->vin.push_back(txin);
|
||||
psbtin.partial_sigs.clear();
|
||||
psbtin.final_script_sig.clear();
|
||||
psbtin.final_script_witness.SetNull();
|
||||
inputs.push_back(psbtin);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput& psbtout)
|
||||
{
|
||||
tx->vout.push_back(txout);
|
||||
if (tx != std::nullopt) {
|
||||
// This is a v0 psbt, do the v0 AddOutput
|
||||
CTxOut txout(*psbtout.amount, *psbtout.script);
|
||||
tx->vout.push_back(txout);
|
||||
outputs.push_back(psbtout);
|
||||
return true;
|
||||
}
|
||||
|
||||
// No global tx, must be PSBTv2
|
||||
// Check outputs are modifiable
|
||||
if (m_tx_modifiable == std::nullopt || !m_tx_modifiable->test(1)) {
|
||||
return false;
|
||||
}
|
||||
outputs.push_back(psbtout);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
|
||||
bool PSBTInput::GetUTXO(CTxOut& utxo) const
|
||||
{
|
||||
const PSBTInput& input = inputs[input_index];
|
||||
uint32_t prevout_index = tx->vin[input_index].prevout.n;
|
||||
if (input.non_witness_utxo) {
|
||||
if (prevout_index >= input.non_witness_utxo->vout.size()) {
|
||||
if (non_witness_utxo) {
|
||||
if (prev_out >= non_witness_utxo->vout.size()) {
|
||||
return false;
|
||||
}
|
||||
if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) {
|
||||
if (non_witness_utxo->GetHash() != prev_txid) {
|
||||
return false;
|
||||
}
|
||||
utxo = input.non_witness_utxo->vout[prevout_index];
|
||||
} else if (!input.witness_utxo.IsNull()) {
|
||||
utxo = input.witness_utxo;
|
||||
utxo = non_witness_utxo->vout[*prev_out];
|
||||
} else if (!witness_utxo.IsNull()) {
|
||||
utxo = witness_utxo;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
COutPoint PSBTInput::GetOutPoint() const
|
||||
{
|
||||
return COutPoint(prev_txid, *prev_out);
|
||||
}
|
||||
|
||||
bool PSBTInput::IsNull() const
|
||||
{
|
||||
return !non_witness_utxo && witness_utxo.IsNull() && partial_sigs.empty() && unknown.empty() && hd_keypaths.empty() && redeem_script.empty() && witness_script.empty();
|
||||
|
@ -197,6 +383,9 @@ void PSBTInput::FromSignatureData(const SignatureData& sigdata)
|
|||
|
||||
void PSBTInput::Merge(const PSBTInput& input)
|
||||
{
|
||||
assert(prev_txid == input.prev_txid);
|
||||
assert(*prev_out == *input.prev_out);
|
||||
|
||||
if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo;
|
||||
if (witness_utxo.IsNull() && !input.witness_utxo.IsNull()) {
|
||||
witness_utxo = input.witness_utxo;
|
||||
|
@ -220,6 +409,9 @@ void PSBTInput::Merge(const PSBTInput& input)
|
|||
if (m_tap_key_sig.empty() && !input.m_tap_key_sig.empty()) m_tap_key_sig = input.m_tap_key_sig;
|
||||
if (m_tap_internal_key.IsNull() && !input.m_tap_internal_key.IsNull()) m_tap_internal_key = input.m_tap_internal_key;
|
||||
if (m_tap_merkle_root.IsNull() && !input.m_tap_merkle_root.IsNull()) m_tap_merkle_root = input.m_tap_merkle_root;
|
||||
if (sequence == std::nullopt && input.sequence != std::nullopt) sequence = input.sequence;
|
||||
if (time_locktime == std::nullopt && input.time_locktime != std::nullopt) time_locktime = input.time_locktime;
|
||||
if (height_locktime == std::nullopt && input.height_locktime != std::nullopt) height_locktime = input.height_locktime;
|
||||
}
|
||||
|
||||
void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
|
||||
|
@ -280,6 +472,9 @@ bool PSBTOutput::IsNull() const
|
|||
|
||||
void PSBTOutput::Merge(const PSBTOutput& output)
|
||||
{
|
||||
assert(*amount == *output.amount);
|
||||
assert(*script == *output.script);
|
||||
|
||||
hd_keypaths.insert(output.hd_keypaths.begin(), output.hd_keypaths.end());
|
||||
unknown.insert(output.unknown.begin(), output.unknown.end());
|
||||
m_tap_bip32_paths.insert(output.m_tap_bip32_paths.begin(), output.m_tap_bip32_paths.end());
|
||||
|
@ -303,7 +498,7 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
|
|||
|
||||
if (input.non_witness_utxo) {
|
||||
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
|
||||
COutPoint prevout = psbt.tx->vin[input_index].prevout;
|
||||
COutPoint prevout = input.GetOutPoint();
|
||||
if (prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -317,10 +512,11 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
|
|||
return false;
|
||||
}
|
||||
|
||||
const CMutableTransaction tx = psbt.GetUnsignedTx();
|
||||
if (txdata) {
|
||||
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL});
|
||||
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&tx, input_index, utxo.nValue, *txdata, MissingDataBehavior::FAIL});
|
||||
} else {
|
||||
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&(*psbt.tx), input_index, utxo.nValue, MissingDataBehavior::FAIL});
|
||||
return VerifyScript(input.final_script_sig, utxo.scriptPubKey, &input.final_script_witness, STANDARD_SCRIPT_VERIFY_FLAGS, MutableTransactionSignatureChecker{&tx, input_index, utxo.nValue, MissingDataBehavior::FAIL});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -337,7 +533,7 @@ size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt) {
|
|||
|
||||
void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index)
|
||||
{
|
||||
CMutableTransaction& tx = *Assert(psbt.tx);
|
||||
CMutableTransaction tx = psbt.GetUnsignedTx();
|
||||
const CTxOut& out = tx.vout.at(index);
|
||||
PSBTOutput& psbt_out = psbt.outputs.at(index);
|
||||
|
||||
|
@ -357,11 +553,11 @@ void UpdatePSBTOutput(const SigningProvider& provider, PartiallySignedTransactio
|
|||
|
||||
PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
|
||||
{
|
||||
const CMutableTransaction& tx = *psbt.tx;
|
||||
const CMutableTransaction& tx = psbt.GetUnsignedTx();
|
||||
bool have_all_spent_outputs = true;
|
||||
std::vector<CTxOut> utxos(tx.vin.size());
|
||||
for (size_t idx = 0; idx < tx.vin.size(); ++idx) {
|
||||
if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false;
|
||||
std::vector<CTxOut> utxos;
|
||||
for (const PSBTInput& input : psbt.inputs) {
|
||||
if (!input.GetUTXO(utxos.emplace_back())) have_all_spent_outputs = false;
|
||||
}
|
||||
PrecomputedTransactionData txdata;
|
||||
if (have_all_spent_outputs) {
|
||||
|
@ -375,7 +571,7 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
|
|||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize)
|
||||
{
|
||||
PSBTInput& input = psbt.inputs.at(index);
|
||||
const CMutableTransaction& tx = *psbt.tx;
|
||||
const CMutableTransaction& tx = psbt.GetUnsignedTx();
|
||||
|
||||
if (PSBTInputSignedAndVerified(psbt, index, txdata)) {
|
||||
return true;
|
||||
|
@ -391,7 +587,7 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
|
|||
|
||||
if (input.non_witness_utxo) {
|
||||
// If we're taking our information from a non-witness UTXO, verify that it matches the prevout.
|
||||
COutPoint prevout = tx.vin[index].prevout;
|
||||
COutPoint prevout = input.GetOutPoint();
|
||||
if (prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
return false;
|
||||
}
|
||||
|
@ -485,7 +681,7 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
|
|||
// script.
|
||||
bool complete = true;
|
||||
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
|
||||
}
|
||||
|
||||
|
@ -500,7 +696,7 @@ bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransacti
|
|||
return false;
|
||||
}
|
||||
|
||||
result = *psbtx.tx;
|
||||
result = psbtx.GetUnsignedTx();
|
||||
for (unsigned int i = 0; i < result.vin.size(); ++i) {
|
||||
result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
|
||||
result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
|
||||
|
@ -566,3 +762,36 @@ uint32_t PartiallySignedTransaction::GetVersion() const
|
|||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PartiallySignedTransaction::SetupFromTx(const CMutableTransaction& tx)
|
||||
{
|
||||
tx_version = tx.version;
|
||||
fallback_locktime = tx.nLockTime;
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < tx.vin.size(); ++i) {
|
||||
PSBTInput& input = inputs.at(i);
|
||||
const CTxIn& txin = tx.vin.at(i);
|
||||
|
||||
input.prev_txid = txin.prevout.hash;
|
||||
input.prev_out = txin.prevout.n;
|
||||
input.sequence = txin.nSequence;
|
||||
}
|
||||
|
||||
for (i = 0; i < tx.vout.size(); ++i) {
|
||||
PSBTOutput& output = outputs.at(i);
|
||||
const CTxOut& txout = tx.vout.at(i);
|
||||
|
||||
output.amount = txout.nValue;
|
||||
output.script = txout.scriptPubKey;
|
||||
}
|
||||
}
|
||||
|
||||
void PartiallySignedTransaction::CacheUnsignedTxPieces()
|
||||
{
|
||||
// To make things easier, we split up the global unsigned transaction
|
||||
// and use the PSBTv2 fields for PSBTv0.
|
||||
if (tx != std::nullopt) {
|
||||
SetupFromTx(*tx);
|
||||
}
|
||||
}
|
||||
|
|
400
src/psbt.h
400
src/psbt.h
|
@ -14,8 +14,10 @@
|
|||
#include <script/signingprovider.h>
|
||||
#include <span.h>
|
||||
#include <streams.h>
|
||||
#include <uint256.h>
|
||||
|
||||
#include <optional>
|
||||
#include <bitset>
|
||||
|
||||
namespace node {
|
||||
enum class TransactionError;
|
||||
|
@ -27,6 +29,11 @@ static constexpr uint8_t PSBT_MAGIC_BYTES[5] = {'p', 's', 'b', 't', 0xff};
|
|||
// Global types
|
||||
static constexpr uint8_t PSBT_GLOBAL_UNSIGNED_TX = 0x00;
|
||||
static constexpr uint8_t PSBT_GLOBAL_XPUB = 0x01;
|
||||
static constexpr uint8_t PSBT_GLOBAL_TX_VERSION = 0x02;
|
||||
static constexpr uint8_t PSBT_GLOBAL_FALLBACK_LOCKTIME = 0x03;
|
||||
static constexpr uint8_t PSBT_GLOBAL_INPUT_COUNT = 0x04;
|
||||
static constexpr uint8_t PSBT_GLOBAL_OUTPUT_COUNT = 0x05;
|
||||
static constexpr uint8_t PSBT_GLOBAL_TX_MODIFIABLE = 0x06;
|
||||
static constexpr uint8_t PSBT_GLOBAL_VERSION = 0xFB;
|
||||
static constexpr uint8_t PSBT_GLOBAL_PROPRIETARY = 0xFC;
|
||||
|
||||
|
@ -44,6 +51,11 @@ static constexpr uint8_t PSBT_IN_RIPEMD160 = 0x0A;
|
|||
static constexpr uint8_t PSBT_IN_SHA256 = 0x0B;
|
||||
static constexpr uint8_t PSBT_IN_HASH160 = 0x0C;
|
||||
static constexpr uint8_t PSBT_IN_HASH256 = 0x0D;
|
||||
static constexpr uint8_t PSBT_IN_PREVIOUS_TXID = 0x0e;
|
||||
static constexpr uint8_t PSBT_IN_OUTPUT_INDEX = 0x0f;
|
||||
static constexpr uint8_t PSBT_IN_SEQUENCE = 0x10;
|
||||
static constexpr uint8_t PSBT_IN_REQUIRED_TIME_LOCKTIME = 0x11;
|
||||
static constexpr uint8_t PSBT_IN_REQUIRED_HEIGHT_LOCKTIME = 0x12;
|
||||
static constexpr uint8_t PSBT_IN_TAP_KEY_SIG = 0x13;
|
||||
static constexpr uint8_t PSBT_IN_TAP_SCRIPT_SIG = 0x14;
|
||||
static constexpr uint8_t PSBT_IN_TAP_LEAF_SCRIPT = 0x15;
|
||||
|
@ -56,6 +68,8 @@ static constexpr uint8_t PSBT_IN_PROPRIETARY = 0xFC;
|
|||
static constexpr uint8_t PSBT_OUT_REDEEMSCRIPT = 0x00;
|
||||
static constexpr uint8_t PSBT_OUT_WITNESSSCRIPT = 0x01;
|
||||
static constexpr uint8_t PSBT_OUT_BIP32_DERIVATION = 0x02;
|
||||
static constexpr uint8_t PSBT_OUT_AMOUNT = 0x03;
|
||||
static constexpr uint8_t PSBT_OUT_SCRIPT = 0x04;
|
||||
static constexpr uint8_t PSBT_OUT_TAP_INTERNAL_KEY = 0x05;
|
||||
static constexpr uint8_t PSBT_OUT_TAP_TREE = 0x06;
|
||||
static constexpr uint8_t PSBT_OUT_TAP_BIP32_DERIVATION = 0x07;
|
||||
|
@ -70,7 +84,7 @@ static constexpr uint8_t PSBT_SEPARATOR = 0x00;
|
|||
const std::streamsize MAX_FILE_SIZE_PSBT = 100000000; // 100 MB
|
||||
|
||||
// PSBT version number
|
||||
static constexpr uint32_t PSBT_HIGHEST_VERSION = 0;
|
||||
static constexpr uint32_t PSBT_HIGHEST_VERSION = 2;
|
||||
|
||||
/** A structure for PSBT proprietary types */
|
||||
struct PSBTProprietary
|
||||
|
@ -209,6 +223,12 @@ struct PSBTInput
|
|||
std::map<uint160, std::vector<unsigned char>> hash160_preimages;
|
||||
std::map<uint256, std::vector<unsigned char>> hash256_preimages;
|
||||
|
||||
Txid prev_txid;
|
||||
std::optional<uint32_t> prev_out;
|
||||
std::optional<uint32_t> sequence;
|
||||
std::optional<uint32_t> time_locktime;
|
||||
std::optional<uint32_t> height_locktime;
|
||||
|
||||
// Taproot fields
|
||||
std::vector<unsigned char> m_tap_key_sig;
|
||||
std::map<std::pair<XOnlyPubKey, uint256>, std::vector<unsigned char>> m_tap_script_sigs;
|
||||
|
@ -221,11 +241,21 @@ struct PSBTInput
|
|||
std::set<PSBTProprietary> m_proprietary;
|
||||
std::optional<int> sighash_type;
|
||||
|
||||
uint32_t m_psbt_version;
|
||||
|
||||
bool IsNull() const;
|
||||
void FillSignatureData(SignatureData& sigdata) const;
|
||||
void FromSignatureData(const SignatureData& sigdata);
|
||||
void Merge(const PSBTInput& input);
|
||||
PSBTInput() = default;
|
||||
/**
|
||||
* Retrieves the UTXO for this input
|
||||
*
|
||||
* @param[out] utxo The UTXO of this input
|
||||
* @return Whether the UTXO could be retrieved
|
||||
*/
|
||||
bool GetUTXO(CTxOut& utxo) const;
|
||||
COutPoint GetOutPoint() const;
|
||||
PSBTInput(uint32_t psbt_version) : m_psbt_version(psbt_version) {}
|
||||
|
||||
template <typename Stream>
|
||||
inline void Serialize(Stream& s) const {
|
||||
|
@ -350,6 +380,31 @@ struct PSBTInput
|
|||
SerializeToVector(s, final_script_witness.stack);
|
||||
}
|
||||
|
||||
// Write PSBTv2 fields
|
||||
if (m_psbt_version >= 2) {
|
||||
// Write prev txid, vout, sequence, and lock times
|
||||
if (!prev_txid.IsNull()) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_IN_PREVIOUS_TXID));
|
||||
SerializeToVector(s, prev_txid);
|
||||
}
|
||||
if (prev_out != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_IN_OUTPUT_INDEX));
|
||||
SerializeToVector(s, *prev_out);
|
||||
}
|
||||
if (sequence != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_IN_SEQUENCE));
|
||||
SerializeToVector(s, *sequence);
|
||||
}
|
||||
if (time_locktime != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_IN_REQUIRED_TIME_LOCKTIME));
|
||||
SerializeToVector(s, *time_locktime);
|
||||
}
|
||||
if (height_locktime != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_IN_REQUIRED_HEIGHT_LOCKTIME));
|
||||
SerializeToVector(s, *height_locktime);
|
||||
}
|
||||
}
|
||||
|
||||
// Write proprietary things
|
||||
for (const auto& entry : m_proprietary) {
|
||||
s << entry.key;
|
||||
|
@ -572,6 +627,80 @@ struct PSBTInput
|
|||
hash256_preimages.emplace(hash, std::move(preimage));
|
||||
break;
|
||||
}
|
||||
case PSBT_IN_PREVIOUS_TXID:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, previous txid is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Previous txid key is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Previous txid is not allowed in PSBTv0");
|
||||
}
|
||||
UnserializeFromVector(s, prev_txid);
|
||||
break;
|
||||
}
|
||||
case PSBT_IN_OUTPUT_INDEX:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, previous output's index is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Previous output's index is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Previous output's index is not allowed in PSBTv0");
|
||||
}
|
||||
uint32_t v;
|
||||
UnserializeFromVector(s, v);
|
||||
prev_out = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_IN_SEQUENCE:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, sequence is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Sequence key is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Sequence is not allowed in PSBTv0");
|
||||
}
|
||||
uint32_t v;
|
||||
UnserializeFromVector(s, v);
|
||||
sequence = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_IN_REQUIRED_TIME_LOCKTIME:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, required time based locktime is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Required time based locktime is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Required time based locktime is not allowed in PSBTv0");
|
||||
}
|
||||
uint32_t v;
|
||||
UnserializeFromVector(s, v);
|
||||
if (v < LOCKTIME_THRESHOLD) {
|
||||
throw std::ios_base::failure("Required time based locktime is invalid (less than 500000000)");
|
||||
}
|
||||
time_locktime = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_IN_REQUIRED_HEIGHT_LOCKTIME:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, required height based locktime is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Required height based locktime is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Required height based locktime is not allowed in PSBTv0");
|
||||
}
|
||||
uint32_t v;
|
||||
UnserializeFromVector(s, v);
|
||||
if (v >= LOCKTIME_THRESHOLD) {
|
||||
throw std::ios_base::failure("Required time based locktime is invalid (greater than or equal to 500000000)");
|
||||
}
|
||||
height_locktime = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_IN_TAP_KEY_SIG:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
|
@ -702,6 +831,16 @@ struct PSBTInput
|
|||
if (!found_sep) {
|
||||
throw std::ios_base::failure("Separator is missing at the end of an input map");
|
||||
}
|
||||
|
||||
// Make sure required PSBTv2 fields are present
|
||||
if (m_psbt_version >= 2) {
|
||||
if (prev_txid.IsNull()) {
|
||||
throw std::ios_base::failure("Previous TXID is required in PSBTv2");
|
||||
}
|
||||
if (prev_out == std::nullopt) {
|
||||
throw std::ios_base::failure("Previous output's index is required in PSBTv2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Stream>
|
||||
|
@ -716,17 +855,24 @@ struct PSBTOutput
|
|||
CScript redeem_script;
|
||||
CScript witness_script;
|
||||
std::map<CPubKey, KeyOriginInfo> hd_keypaths;
|
||||
|
||||
std::optional<CAmount> amount;
|
||||
std::optional<CScript> script;
|
||||
|
||||
XOnlyPubKey m_tap_internal_key;
|
||||
std::vector<std::tuple<uint8_t, uint8_t, std::vector<unsigned char>>> m_tap_tree;
|
||||
std::map<XOnlyPubKey, std::pair<std::set<uint256>, KeyOriginInfo>> m_tap_bip32_paths;
|
||||
|
||||
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
|
||||
std::set<PSBTProprietary> m_proprietary;
|
||||
|
||||
uint32_t m_psbt_version;
|
||||
|
||||
bool IsNull() const;
|
||||
void FillSignatureData(SignatureData& sigdata) const;
|
||||
void FromSignatureData(const SignatureData& sigdata);
|
||||
void Merge(const PSBTOutput& output);
|
||||
PSBTOutput() = default;
|
||||
PSBTOutput(uint32_t psbt_version) : m_psbt_version(psbt_version) {}
|
||||
|
||||
template <typename Stream>
|
||||
inline void Serialize(Stream& s) const {
|
||||
|
@ -745,6 +891,18 @@ struct PSBTOutput
|
|||
// Write any hd keypaths
|
||||
SerializeHDKeypaths(s, hd_keypaths, CompactSizeWriter(PSBT_OUT_BIP32_DERIVATION));
|
||||
|
||||
if (m_psbt_version >= 2) {
|
||||
// Write amount and spk
|
||||
if (amount != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_AMOUNT));
|
||||
SerializeToVector(s, *amount);
|
||||
}
|
||||
if (script.has_value()) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_OUT_SCRIPT));
|
||||
s << *script;
|
||||
}
|
||||
}
|
||||
|
||||
// Write proprietary things
|
||||
for (const auto& entry : m_proprietary) {
|
||||
s << entry.key;
|
||||
|
@ -841,6 +999,34 @@ struct PSBTOutput
|
|||
DeserializeHDKeypaths(s, key, hd_keypaths);
|
||||
break;
|
||||
}
|
||||
case PSBT_OUT_AMOUNT:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, output amount is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Output amount key is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Output amount is not allowed in PSBTv0");
|
||||
}
|
||||
CAmount v;
|
||||
UnserializeFromVector(s, v);
|
||||
amount = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_OUT_SCRIPT:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, output script is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Output script key is more than one byte type");
|
||||
} else if (m_psbt_version == 0) {
|
||||
throw std::ios_base::failure("Output script is not allowed in PSBTv0");
|
||||
}
|
||||
CScript v;
|
||||
s >> v;
|
||||
script = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_OUT_TAP_INTERNAL_KEY:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
|
@ -938,6 +1124,16 @@ struct PSBTOutput
|
|||
if (!found_sep) {
|
||||
throw std::ios_base::failure("Separator is missing at the end of an output map");
|
||||
}
|
||||
|
||||
// Make sure required PSBTv2 fields are present
|
||||
if (m_psbt_version >= 2) {
|
||||
if (amount == std::nullopt) {
|
||||
throw std::ios_base::failure("Output amount is required in PSBTv2");
|
||||
}
|
||||
if (!script.has_value()) {
|
||||
throw std::ios_base::failure("Output script is required in PSBTv2");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Stream>
|
||||
|
@ -953,6 +1149,9 @@ struct PartiallySignedTransaction
|
|||
// We use a vector of CExtPubKey in the event that there happens to be the same KeyOriginInfos for different CExtPubKeys
|
||||
// Note that this map swaps the key and values from the serialization
|
||||
std::map<KeyOriginInfo, std::set<CExtPubKey>> m_xpubs;
|
||||
std::optional<uint32_t> tx_version;
|
||||
std::optional<uint32_t> fallback_locktime;
|
||||
std::optional<std::bitset<8>> m_tx_modifiable;
|
||||
std::vector<PSBTInput> inputs;
|
||||
std::vector<PSBTOutput> outputs;
|
||||
std::map<std::vector<unsigned char>, std::vector<unsigned char>> unknown;
|
||||
|
@ -965,18 +1164,15 @@ struct PartiallySignedTransaction
|
|||
/** Merge psbt into this. The two psbts must have the same underlying CTransaction (i.e. the
|
||||
* same actual Bitcoin transaction.) Returns true if the merge succeeded, false otherwise. */
|
||||
[[nodiscard]] bool Merge(const PartiallySignedTransaction& psbt);
|
||||
bool AddInput(const CTxIn& txin, PSBTInput& psbtin);
|
||||
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout);
|
||||
bool AddInput(PSBTInput& psbtin);
|
||||
bool AddOutput(const PSBTOutput& psbtout);
|
||||
void SetupFromTx(const CMutableTransaction& tx);
|
||||
void CacheUnsignedTxPieces();
|
||||
bool ComputeTimeLock(uint32_t& locktime) const;
|
||||
CMutableTransaction GetUnsignedTx() const;
|
||||
uint256 GetUniqueID() const;
|
||||
PartiallySignedTransaction() = default;
|
||||
explicit PartiallySignedTransaction(const CMutableTransaction& tx);
|
||||
/**
|
||||
* Finds the UTXO for a given input index
|
||||
*
|
||||
* @param[out] utxo The UTXO of the input if found
|
||||
* @param[in] input_index Index of the input to retrieve the UTXO of
|
||||
* @return Whether the UTXO for the specified input was found
|
||||
*/
|
||||
bool GetInputUTXO(CTxOut& utxo, int input_index) const;
|
||||
explicit PartiallySignedTransaction(const CMutableTransaction& tx, uint32_t version = 2);
|
||||
|
||||
template <typename Stream>
|
||||
inline void Serialize(Stream& s) const {
|
||||
|
@ -984,11 +1180,13 @@ struct PartiallySignedTransaction
|
|||
// magic bytes
|
||||
s << PSBT_MAGIC_BYTES;
|
||||
|
||||
// unsigned tx flag
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_UNSIGNED_TX));
|
||||
if (GetVersion() == 0) {
|
||||
// unsigned tx flag
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_UNSIGNED_TX));
|
||||
|
||||
// Write serialized tx to a stream
|
||||
SerializeToVector(s, TX_NO_WITNESS(*tx));
|
||||
// Write serialized tx to a stream
|
||||
SerializeToVector(s, TX_NO_WITNESS(GetUnsignedTx()));
|
||||
}
|
||||
|
||||
// Write xpubs
|
||||
for (const auto& xpub_pair : m_xpubs) {
|
||||
|
@ -1002,6 +1200,26 @@ struct PartiallySignedTransaction
|
|||
}
|
||||
}
|
||||
|
||||
if (GetVersion() >= 2) {
|
||||
// Write PSBTv2 tx version, locktime, counts, etc.
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_TX_VERSION));
|
||||
SerializeToVector(s, *tx_version);
|
||||
if (fallback_locktime != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_FALLBACK_LOCKTIME));
|
||||
SerializeToVector(s, *fallback_locktime);
|
||||
}
|
||||
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_INPUT_COUNT));
|
||||
SerializeToVector(s, CompactSizeWriter(inputs.size()));
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_OUTPUT_COUNT));
|
||||
SerializeToVector(s, CompactSizeWriter(outputs.size()));
|
||||
|
||||
if (m_tx_modifiable != std::nullopt) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_TX_MODIFIABLE));
|
||||
SerializeToVector(s, static_cast<uint8_t>(m_tx_modifiable->to_ulong()));
|
||||
}
|
||||
}
|
||||
|
||||
// PSBT version
|
||||
if (GetVersion() > 0) {
|
||||
SerializeToVector(s, CompactSizeWriter(PSBT_GLOBAL_VERSION));
|
||||
|
@ -1051,6 +1269,10 @@ struct PartiallySignedTransaction
|
|||
|
||||
// Read global data
|
||||
bool found_sep = false;
|
||||
uint64_t input_count = 0;
|
||||
uint64_t output_count = 0;
|
||||
bool found_input_count = false;
|
||||
bool found_output_count = false;
|
||||
while(!s.empty()) {
|
||||
// Read
|
||||
std::vector<unsigned char> key;
|
||||
|
@ -1086,6 +1308,69 @@ struct PartiallySignedTransaction
|
|||
throw std::ios_base::failure("Unsigned tx does not have empty scriptSigs and scriptWitnesses.");
|
||||
}
|
||||
}
|
||||
// Set the input and output counts
|
||||
input_count = tx->vin.size();
|
||||
output_count = tx->vout.size();
|
||||
break;
|
||||
}
|
||||
case PSBT_GLOBAL_TX_VERSION:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, global transaction version is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Global transaction version key is more than one byte type");
|
||||
}
|
||||
uint32_t v;
|
||||
UnserializeFromVector(s, v);
|
||||
tx_version = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_GLOBAL_FALLBACK_LOCKTIME:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, global fallback locktime is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Global fallback locktime key is more than one byte type");
|
||||
}
|
||||
uint32_t v;
|
||||
UnserializeFromVector(s, v);
|
||||
fallback_locktime = v;
|
||||
break;
|
||||
}
|
||||
case PSBT_GLOBAL_INPUT_COUNT:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, global input count is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Global input count key is more than one byte type");
|
||||
}
|
||||
CompactSizeReader reader(input_count);
|
||||
UnserializeFromVector(s, reader);
|
||||
found_input_count = true;
|
||||
break;
|
||||
}
|
||||
case PSBT_GLOBAL_OUTPUT_COUNT:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, global output count is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Global output count key is more than one byte type");
|
||||
}
|
||||
CompactSizeReader reader(output_count);
|
||||
UnserializeFromVector(s, reader);
|
||||
found_output_count = true;
|
||||
break;
|
||||
}
|
||||
case PSBT_GLOBAL_TX_MODIFIABLE:
|
||||
{
|
||||
if (!key_lookup.emplace(key).second) {
|
||||
throw std::ios_base::failure("Duplicate Key, tx modifiable flags is already provided");
|
||||
} else if (key.size() != 1) {
|
||||
throw std::ios_base::failure("Global tx modifiable flags key is more than one byte type");
|
||||
}
|
||||
uint8_t tx_mod;
|
||||
UnserializeFromVector(s, tx_mod);
|
||||
m_tx_modifiable.emplace(tx_mod);
|
||||
break;
|
||||
}
|
||||
case PSBT_GLOBAL_XPUB:
|
||||
|
@ -1164,46 +1449,99 @@ struct PartiallySignedTransaction
|
|||
throw std::ios_base::failure("Separator is missing at the end of the global map");
|
||||
}
|
||||
|
||||
// Make sure that we got an unsigned tx
|
||||
if (!tx) {
|
||||
throw std::ios_base::failure("No unsigned transaction was provided");
|
||||
const uint32_t psbt_ver = GetVersion();
|
||||
|
||||
// Check PSBT version constraints
|
||||
if (psbt_ver == 0) {
|
||||
// Make sure that we got an unsigned tx for PSBTv0
|
||||
if (!tx) {
|
||||
throw std::ios_base::failure("No unsigned transaction was provided");
|
||||
}
|
||||
// Make sure no PSBTv2 fields are present
|
||||
if (tx_version != std::nullopt) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_TX_VERSION is not allowed in PSBTv0");
|
||||
}
|
||||
if (fallback_locktime != std::nullopt) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_FALLBACK_LOCKTIME is not allowed in PSBTv0");
|
||||
}
|
||||
if (found_input_count) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_INPUT_COUNT is not allowed in PSBTv0");
|
||||
}
|
||||
if (found_output_count) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_OUTPUT_COUNT is not allowed in PSBTv0");
|
||||
}
|
||||
if (m_tx_modifiable != std::nullopt) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_TX_MODIFIABLE is not allowed in PSBTv0");
|
||||
}
|
||||
}
|
||||
// Disallow v1
|
||||
if (psbt_ver == 1) {
|
||||
throw std::ios_base::failure("There is no PSBT version 1");
|
||||
}
|
||||
if (psbt_ver >= 2) {
|
||||
// Tx version, input, and output counts are required
|
||||
if (tx_version == std::nullopt) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_TX_VERSION is required in PSBTv2");
|
||||
}
|
||||
if (!found_input_count) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_INPUT_COUNT is required in PSBTv2");
|
||||
}
|
||||
if (!found_output_count) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_OUTPUT_COUNT is required in PSBTv2");
|
||||
}
|
||||
// Unsigned tx is disallowed
|
||||
if (tx) {
|
||||
throw std::ios_base::failure("PSBT_GLOBAL_UNSIGNED_TX is not allowed in PSBTv2");
|
||||
}
|
||||
}
|
||||
|
||||
// Read input data
|
||||
unsigned int i = 0;
|
||||
while (!s.empty() && i < tx->vin.size()) {
|
||||
PSBTInput input;
|
||||
while (!s.empty() && i < input_count) {
|
||||
PSBTInput input(psbt_ver);
|
||||
s >> input;
|
||||
inputs.push_back(input);
|
||||
|
||||
// Make sure the non-witness utxo matches the outpoint
|
||||
if (input.non_witness_utxo) {
|
||||
if (input.non_witness_utxo->GetHash() != tx->vin[i].prevout.hash) {
|
||||
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
|
||||
if (psbt_ver == 0) {
|
||||
if (input.non_witness_utxo->GetHash() != tx->vin[i].prevout.hash) {
|
||||
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
|
||||
}
|
||||
if (tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
throw std::ios_base::failure("Input specifies output index that does not exist");
|
||||
}
|
||||
}
|
||||
if (tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
throw std::ios_base::failure("Input specifies output index that does not exist");
|
||||
if (psbt_ver >= 2) {
|
||||
if (input.non_witness_utxo->GetHash() != input.prev_txid) {
|
||||
throw std::ios_base::failure("Non-witness UTXO does not match outpoint hash");
|
||||
}
|
||||
if (input.prev_out.value() >= input.non_witness_utxo->vout.size()) {
|
||||
throw std::ios_base::failure("Input specifies output index that does not exist");
|
||||
}
|
||||
}
|
||||
}
|
||||
++i;
|
||||
}
|
||||
// Make sure that the number of inputs matches the number of inputs in the transaction
|
||||
if (inputs.size() != tx->vin.size()) {
|
||||
if (inputs.size() != input_count) {
|
||||
throw std::ios_base::failure("Inputs provided does not match the number of inputs in transaction.");
|
||||
}
|
||||
|
||||
// Read output data
|
||||
i = 0;
|
||||
while (!s.empty() && i < tx->vout.size()) {
|
||||
PSBTOutput output;
|
||||
while (!s.empty() && i < output_count) {
|
||||
PSBTOutput output(psbt_ver);
|
||||
s >> output;
|
||||
outputs.push_back(output);
|
||||
++i;
|
||||
}
|
||||
// Make sure that the number of outputs matches the number of outputs in the transaction
|
||||
if (outputs.size() != tx->vout.size()) {
|
||||
if (outputs.size() != output_count) {
|
||||
throw std::ios_base::failure("Outputs provided does not match the number of outputs in transaction.");
|
||||
}
|
||||
|
||||
CacheUnsignedTxPieces();
|
||||
}
|
||||
|
||||
template <typename Stream>
|
||||
|
|
|
@ -180,15 +180,15 @@ QString PSBTOperationsDialog::renderTransaction(const PartiallySignedTransaction
|
|||
QString tx_description;
|
||||
QLatin1String bullet_point(" * ");
|
||||
CAmount totalAmount = 0;
|
||||
for (const CTxOut& out : psbtx.tx->vout) {
|
||||
for (const PSBTOutput& out : psbtx.outputs) {
|
||||
CTxDestination address;
|
||||
ExtractDestination(out.scriptPubKey, address);
|
||||
totalAmount += out.nValue;
|
||||
ExtractDestination(*out.script, address);
|
||||
totalAmount += *out.amount;
|
||||
tx_description.append(bullet_point).append(tr("Sends %1 to %2")
|
||||
.arg(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, out.nValue))
|
||||
.arg(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, *out.amount))
|
||||
.arg(QString::fromStdString(EncodeDestination(address))));
|
||||
// Check if the address is one of ours
|
||||
if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(out)) tx_description.append(" (" + tr("own address") + ")");
|
||||
if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(CTxOut(*out.amount, *out.script))) tx_description.append(" (" + tr("own address") + ")");
|
||||
tx_description.append("<br>");
|
||||
}
|
||||
|
||||
|
|
|
@ -170,6 +170,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "walletcreatefundedpsbt", 3, "solving_data"},
|
||||
{ "walletcreatefundedpsbt", 3, "max_tx_weight"},
|
||||
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
|
||||
{ "walletcreatefundedpsbt", 5, "psbt_version" },
|
||||
{ "walletprocesspsbt", 1, "sign" },
|
||||
{ "walletprocesspsbt", 3, "bip32derivs" },
|
||||
{ "walletprocesspsbt", 4, "finalize" },
|
||||
|
@ -180,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "createpsbt", 1, "outputs" },
|
||||
{ "createpsbt", 2, "locktime" },
|
||||
{ "createpsbt", 3, "replaceable" },
|
||||
{ "createpsbt", 4, "psbt_version" },
|
||||
{ "combinepsbt", 0, "txs"},
|
||||
{ "joinpsbts", 0, "txs"},
|
||||
{ "finalizepsbt", 1, "extract"},
|
||||
|
|
|
@ -182,10 +182,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
|
|||
|
||||
// Fetch previous transactions:
|
||||
// First, look in the txindex and the mempool
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
PSBTInput& psbt_input = psbtx.inputs.at(i);
|
||||
const CTxIn& tx_in = psbtx.tx->vin.at(i);
|
||||
|
||||
for (PSBTInput& psbt_input : psbtx.inputs) {
|
||||
// The `non_witness_utxo` is the whole previous transaction
|
||||
if (psbt_input.non_witness_utxo) continue;
|
||||
|
||||
|
@ -194,29 +191,26 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
|
|||
// Look in the txindex
|
||||
if (g_txindex) {
|
||||
uint256 block_hash;
|
||||
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
|
||||
g_txindex->FindTx(psbt_input.prev_txid, block_hash, tx);
|
||||
}
|
||||
// If we still don't have it look in the mempool
|
||||
if (!tx) {
|
||||
tx = node.mempool->get(tx_in.prevout.hash);
|
||||
tx = node.mempool->get(psbt_input.prev_txid);
|
||||
}
|
||||
if (tx) {
|
||||
psbt_input.non_witness_utxo = tx;
|
||||
} else {
|
||||
coins[tx_in.prevout]; // Create empty map entry keyed by prevout
|
||||
coins[psbt_input.GetOutPoint()]; // Create empty map entry keyed by prevout
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't found all of the inputs, look for the missing ones in the utxo set
|
||||
if (!coins.empty()) {
|
||||
FindCoins(node, coins);
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
PSBTInput& input = psbtx.inputs.at(i);
|
||||
|
||||
for (PSBTInput& input : psbtx.inputs) {
|
||||
// If there are still missing utxos, add them if they were found in the utxo set
|
||||
if (!input.non_witness_utxo) {
|
||||
const CTxIn& tx_in = psbtx.tx->vin.at(i);
|
||||
const Coin& coin = coins.at(tx_in.prevout);
|
||||
const Coin& coin = coins.at(input.GetOutPoint());
|
||||
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
|
||||
input.witness_utxo = coin.out;
|
||||
}
|
||||
|
@ -226,7 +220,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
|
|||
|
||||
const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
|
||||
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
if (PSBTInputSigned(psbtx.inputs.at(i))) {
|
||||
continue;
|
||||
}
|
||||
|
@ -239,7 +233,7 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
|
|||
}
|
||||
|
||||
// Update script/keypath information using descriptor data.
|
||||
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
|
||||
UpdatePSBTOutput(provider, psbtx, i);
|
||||
}
|
||||
|
||||
|
@ -879,6 +873,11 @@ const RPCResult decodepsbt_inputs{
|
|||
{
|
||||
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
|
||||
}},
|
||||
{RPCResult::Type::STR_HEX, "previous_txid", /*optional=*/ true, "TXID of the transaction containing the output being spent by this input."},
|
||||
{RPCResult::Type::NUM, "previous_vout", /* optional=*/ true, "Index of the output being spent"},
|
||||
{RPCResult::Type::NUM, "sequence", /* optional=*/ true, "Sequence number for this inputs"},
|
||||
{RPCResult::Type::NUM, "time_locktime", /* optional=*/ true, "Required time-based locktime for this input"},
|
||||
{RPCResult::Type::NUM, "height_locktime", /* optional=*/ true, "Required height-based locktime for this input"},
|
||||
{RPCResult::Type::STR_HEX, "taproot_key_path_sig", /*optional=*/ true, "hex-encoded signature for the Taproot key path spend"},
|
||||
{RPCResult::Type::ARR, "taproot_script_path_sigs", /*optional=*/ true, "",
|
||||
{
|
||||
|
@ -960,6 +959,10 @@ const RPCResult decodepsbt_outputs{
|
|||
{RPCResult::Type::STR, "path", "The path"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::NUM, "amount", /* optional=*/ true, "The amount (nValue) for this output"},
|
||||
{RPCResult::Type::OBJ, "script", /* optional=*/ true, "The output script (scriptPubKey) for this output",
|
||||
{{RPCResult::Type::ELISION, "", "The layout is the same as the output of scriptPubKeys in decoderawtransaction."}},
|
||||
},
|
||||
{RPCResult::Type::STR_HEX, "taproot_internal_key", /*optional=*/ true, "The hex-encoded Taproot x-only internal key"},
|
||||
{RPCResult::Type::ARR, "taproot_tree", /*optional=*/ true, "The tuples that make up the Taproot tree, in depth first search order",
|
||||
{
|
||||
|
@ -1012,7 +1015,7 @@ static RPCHelpMan decodepsbt()
|
|||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "tx", "The decoded network-serialized unsigned transaction.",
|
||||
{RPCResult::Type::OBJ, "tx", /*optional=*/true, "The decoded network-serialized unsigned transaction.",
|
||||
{
|
||||
{RPCResult::Type::ELISION, "", "The layout is the same as the output of decoderawtransaction."},
|
||||
}},
|
||||
|
@ -1025,7 +1028,14 @@ static RPCHelpMan decodepsbt()
|
|||
{RPCResult::Type::STR, "path", "The path"},
|
||||
}},
|
||||
}},
|
||||
{RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"},
|
||||
{RPCResult::Type::NUM, "tx_version", /* optional */ true, "The version number of the unsigned transaction. Not to be confused with PSBT version"},
|
||||
{RPCResult::Type::NUM, "fallback_locktime", /* optional */ true, "The locktime to fallback to if no inputs specify a required locktime."},
|
||||
{RPCResult::Type::NUM, "input_count", /* optional */ true, "The number of inputs in this psbt"},
|
||||
{RPCResult::Type::NUM, "output_count", /* optional */ true, "The number of outputs in this psbt."},
|
||||
{RPCResult::Type::BOOL, "inputs_modifiable", /* optional */ true, "Whether inputs can be modified"},
|
||||
{RPCResult::Type::BOOL, "outputs_modifiable", /* optional */ true, "Whether outputs can be modified"},
|
||||
{RPCResult::Type::BOOL, "has_sighash_single", /* optional */ true, "Whether this PSBT has SIGHASH_SINGLE inputs"},
|
||||
{RPCResult::Type::NUM, "psbt_version", /* optional */ true, "The PSBT version number. Not to be confused with the unsigned transaction version"},
|
||||
{RPCResult::Type::ARR, "proprietary", "The global proprietary map",
|
||||
{
|
||||
{RPCResult::Type::OBJ, "", "",
|
||||
|
@ -1059,10 +1069,12 @@ static RPCHelpMan decodepsbt()
|
|||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
|
||||
// Add the decoded tx
|
||||
UniValue tx_univ(UniValue::VOBJ);
|
||||
TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
|
||||
result.pushKV("tx", std::move(tx_univ));
|
||||
if (psbtx.tx != std::nullopt) {
|
||||
// Add the decoded tx
|
||||
UniValue tx_univ(UniValue::VOBJ);
|
||||
TxToUniv(CTransaction(*psbtx.tx), /*block_hash=*/uint256(), /*entry=*/tx_univ, /*include_hex=*/false);
|
||||
result.pushKV("tx", std::move(tx_univ));
|
||||
}
|
||||
|
||||
// Add the global xpubs
|
||||
UniValue global_xpubs(UniValue::VARR);
|
||||
|
@ -1081,6 +1093,23 @@ static RPCHelpMan decodepsbt()
|
|||
}
|
||||
result.pushKV("global_xpubs", std::move(global_xpubs));
|
||||
|
||||
// Add PSBTv2 stuff
|
||||
if (psbtx.GetVersion() == 2) {
|
||||
if (psbtx.tx_version != std::nullopt) {
|
||||
result.pushKV("tx_version", *psbtx.tx_version);
|
||||
}
|
||||
if (psbtx.fallback_locktime != std::nullopt) {
|
||||
result.pushKV("fallback_locktime", static_cast<uint64_t>(*psbtx.fallback_locktime));
|
||||
}
|
||||
result.pushKV("input_count", (uint64_t)psbtx.inputs.size());
|
||||
result.pushKV("output_count", (uint64_t)psbtx.outputs.size());
|
||||
if (psbtx.m_tx_modifiable != std::nullopt) {
|
||||
result.pushKV("inputs_modifiable", psbtx.m_tx_modifiable->test(0));
|
||||
result.pushKV("outputs_modifiable", psbtx.m_tx_modifiable->test(1));
|
||||
result.pushKV("has_sighash_single", psbtx.m_tx_modifiable->test(2));
|
||||
}
|
||||
}
|
||||
|
||||
// PSBT version
|
||||
result.pushKV("psbt_version", static_cast<uint64_t>(psbtx.GetVersion()));
|
||||
|
||||
|
@ -1128,7 +1157,7 @@ static RPCHelpMan decodepsbt()
|
|||
have_a_utxo = true;
|
||||
}
|
||||
if (input.non_witness_utxo) {
|
||||
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
|
||||
txout = input.non_witness_utxo->vout[*input.prev_out];
|
||||
|
||||
UniValue non_wit(UniValue::VOBJ);
|
||||
TxToUniv(*input.non_witness_utxo, /*block_hash=*/uint256(), /*entry=*/non_wit, /*include_hex=*/false);
|
||||
|
@ -1238,6 +1267,25 @@ static RPCHelpMan decodepsbt()
|
|||
in.pushKV("hash256_preimages", std::move(hash256_preimages));
|
||||
}
|
||||
|
||||
// PSBTv2
|
||||
if (psbtx.GetVersion() == 2) {
|
||||
if (!input.prev_txid.IsNull()) {
|
||||
in.pushKV("previous_txid", input.prev_txid.GetHex());
|
||||
}
|
||||
if (input.prev_out != std::nullopt) {
|
||||
in.pushKV("previous_vout", static_cast<uint64_t>(*input.prev_out));
|
||||
}
|
||||
if (input.sequence != std::nullopt) {
|
||||
in.pushKV("sequence", static_cast<uint64_t>(*input.sequence));
|
||||
}
|
||||
if (input.time_locktime != std::nullopt) {
|
||||
in.pushKV("time_locktime", static_cast<uint64_t>(*input.time_locktime));
|
||||
}
|
||||
if (input.height_locktime!= std::nullopt) {
|
||||
in.pushKV("height_locktime", static_cast<uint64_t>(*input.height_locktime));
|
||||
}
|
||||
}
|
||||
|
||||
// Taproot key path signature
|
||||
if (!input.m_tap_key_sig.empty()) {
|
||||
in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig));
|
||||
|
@ -1362,6 +1410,18 @@ static RPCHelpMan decodepsbt()
|
|||
out.pushKV("bip32_derivs", std::move(keypaths));
|
||||
}
|
||||
|
||||
// PSBTv2 stuff
|
||||
if (psbtx.GetVersion() == 2) {
|
||||
if (output.amount != std::nullopt) {
|
||||
out.pushKV("amount", ValueFromAmount(*output.amount));
|
||||
}
|
||||
if (output.script.has_value()) {
|
||||
UniValue spk(UniValue::VOBJ);
|
||||
ScriptToUniv(*output.script, spk, /*include_hex=*/true, /*include_address=*/true);
|
||||
out.pushKV("script", spk);
|
||||
}
|
||||
}
|
||||
|
||||
// Taproot internal key
|
||||
if (!output.m_tap_internal_key.IsNull()) {
|
||||
out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key));
|
||||
|
@ -1425,8 +1485,8 @@ static RPCHelpMan decodepsbt()
|
|||
outputs.push_back(std::move(out));
|
||||
|
||||
// Fee calculation
|
||||
if (MoneyRange(psbtx.tx->vout[i].nValue) && MoneyRange(output_value + psbtx.tx->vout[i].nValue)) {
|
||||
output_value += psbtx.tx->vout[i].nValue;
|
||||
if (MoneyRange(*output.amount) && MoneyRange(output_value + *output.amount)) {
|
||||
output_value += *output.amount;
|
||||
} else {
|
||||
// Hack to just not show fee later
|
||||
have_all_utxos = false;
|
||||
|
@ -1551,7 +1611,12 @@ static RPCHelpMan createpsbt()
|
|||
return RPCHelpMan{"createpsbt",
|
||||
"\nCreates a transaction in the Partially Signed Transaction format.\n"
|
||||
"Implements the Creator role.\n",
|
||||
CreateTxDoc(),
|
||||
Cat<std::vector<RPCArg>>(
|
||||
CreateTxDoc(),
|
||||
{
|
||||
{"psbt_version", RPCArg::Type::NUM, RPCArg::Default{2}, "The PSBT version number to use."},
|
||||
}
|
||||
),
|
||||
RPCResult{
|
||||
RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
|
||||
},
|
||||
|
@ -1560,7 +1625,6 @@ static RPCHelpMan createpsbt()
|
|||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
|
||||
std::optional<bool> rbf;
|
||||
if (!request.params[3].isNull()) {
|
||||
rbf = request.params[3].get_bool();
|
||||
|
@ -1568,15 +1632,16 @@ static RPCHelpMan createpsbt()
|
|||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf);
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx;
|
||||
psbtx.tx = rawTx;
|
||||
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
|
||||
psbtx.inputs.emplace_back();
|
||||
uint32_t psbt_version = 2;
|
||||
if (!request.params[4].isNull()) {
|
||||
psbt_version = request.params[4].getInt<int>();
|
||||
}
|
||||
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
|
||||
psbtx.outputs.emplace_back();
|
||||
if (psbt_version != 2 && psbt_version != 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "The PSBT version can only be 2 or 0");
|
||||
}
|
||||
|
||||
PartiallySignedTransaction psbtx(rawTx, psbt_version);
|
||||
|
||||
// Serialize the PSBT
|
||||
DataStream ssTx{};
|
||||
ssTx << psbtx;
|
||||
|
@ -1635,14 +1700,7 @@ static RPCHelpMan converttopsbt()
|
|||
}
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx;
|
||||
psbtx.tx = tx;
|
||||
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
|
||||
psbtx.inputs.emplace_back();
|
||||
}
|
||||
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
|
||||
psbtx.outputs.emplace_back();
|
||||
}
|
||||
PartiallySignedTransaction psbtx(tx, /*version=*/2);
|
||||
|
||||
// Serialize the PSBT
|
||||
DataStream ssTx{};
|
||||
|
@ -1734,32 +1792,38 @@ static RPCHelpMan joinpsbts()
|
|||
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
|
||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
||||
}
|
||||
if (psbtx.GetVersion() != 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "joinpsbts only operates on version 0 PSBTs");
|
||||
}
|
||||
psbtxs.push_back(psbtx);
|
||||
// Choose the highest version number
|
||||
if (psbtx.tx->version > best_version) {
|
||||
best_version = psbtx.tx->version;
|
||||
if (*psbtx.tx_version > best_version) {
|
||||
best_version = *psbtx.tx_version;
|
||||
}
|
||||
// Choose the lowest lock time
|
||||
if (psbtx.tx->nLockTime < best_locktime) {
|
||||
best_locktime = psbtx.tx->nLockTime;
|
||||
if (*psbtx.fallback_locktime < best_locktime) {
|
||||
best_locktime = *psbtx.fallback_locktime;
|
||||
}
|
||||
}
|
||||
|
||||
// Create a blank psbt where everything will be added
|
||||
PartiallySignedTransaction merged_psbt;
|
||||
merged_psbt.tx_version = best_version;
|
||||
merged_psbt.fallback_locktime = best_locktime;
|
||||
// TODO: Remove for PSBTv2
|
||||
merged_psbt.tx = CMutableTransaction();
|
||||
merged_psbt.tx->version = best_version;
|
||||
merged_psbt.tx->nLockTime = best_locktime;
|
||||
|
||||
// Merge
|
||||
for (auto& psbt : psbtxs) {
|
||||
for (unsigned int i = 0; i < psbt.tx->vin.size(); ++i) {
|
||||
if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.tx->vin[i].prevout.hash.ToString(), psbt.tx->vin[i].prevout.n));
|
||||
for (unsigned int i = 0; i < psbt.inputs.size(); ++i) {
|
||||
if (!merged_psbt.AddInput(psbt.inputs[i])) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.inputs[i].prev_txid.ToString(), *psbt.inputs[i].prev_out));
|
||||
}
|
||||
}
|
||||
for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) {
|
||||
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
|
||||
for (unsigned int i = 0; i < psbt.outputs.size(); ++i) {
|
||||
merged_psbt.AddOutput(psbt.outputs[i]);
|
||||
}
|
||||
for (auto& xpub_pair : psbt.m_xpubs) {
|
||||
if (merged_psbt.m_xpubs.count(xpub_pair.first) == 0) {
|
||||
|
@ -1782,14 +1846,17 @@ static RPCHelpMan joinpsbts()
|
|||
std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
|
||||
|
||||
PartiallySignedTransaction shuffled_psbt;
|
||||
shuffled_psbt.tx_version = merged_psbt.tx_version;
|
||||
shuffled_psbt.fallback_locktime = merged_psbt.fallback_locktime;
|
||||
// TODO: Remove for PSBTv2
|
||||
shuffled_psbt.tx = CMutableTransaction();
|
||||
shuffled_psbt.tx->version = merged_psbt.tx->version;
|
||||
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
|
||||
for (int i : input_indices) {
|
||||
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
|
||||
shuffled_psbt.AddInput(merged_psbt.inputs[i]);
|
||||
}
|
||||
for (int i : output_indices) {
|
||||
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], merged_psbt.outputs[i]);
|
||||
shuffled_psbt.AddOutput(merged_psbt.outputs[i]);
|
||||
}
|
||||
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());
|
||||
|
||||
|
|
|
@ -607,6 +607,19 @@ struct ChronoFormatter {
|
|||
template <typename U>
|
||||
using LossyChronoFormatter = ChronoFormatter<U, true>;
|
||||
|
||||
class CompactSizeReader
|
||||
{
|
||||
protected:
|
||||
uint64_t& n;
|
||||
public:
|
||||
explicit CompactSizeReader(uint64_t& n_in) : n(n_in) {}
|
||||
|
||||
template<typename Stream>
|
||||
void Unserialize(Stream &s) const {
|
||||
n = ReadCompactSize<Stream>(s);
|
||||
}
|
||||
};
|
||||
|
||||
class CompactSizeWriter
|
||||
{
|
||||
protected:
|
||||
|
|
|
@ -73,6 +73,7 @@ add_executable(test_bitcoin
|
|||
pool_tests.cpp
|
||||
pow_tests.cpp
|
||||
prevector_tests.cpp
|
||||
psbt_tests.cpp
|
||||
raii_event_tests.cpp
|
||||
random_tests.cpp
|
||||
rbf_tests.cpp
|
||||
|
|
|
@ -197,11 +197,11 @@ FUZZ_TARGET_DESERIALIZE(prefilled_transaction_deserialize, {
|
|||
DeserializeFromFuzzingInput(buffer, prefilled_transaction);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(psbt_input_deserialize, {
|
||||
PSBTInput psbt_input;
|
||||
PSBTInput psbt_input(0);
|
||||
DeserializeFromFuzzingInput(buffer, psbt_input);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(psbt_output_deserialize, {
|
||||
PSBTOutput psbt_output;
|
||||
PSBTOutput psbt_output(0);
|
||||
DeserializeFromFuzzingInput(buffer, psbt_output);
|
||||
})
|
||||
FUZZ_TARGET_DESERIALIZE(block_deserialize, {
|
||||
|
|
|
@ -59,7 +59,7 @@ FUZZ_TARGET(psbt)
|
|||
|
||||
for (size_t i = 0; i < psbt.tx->vin.size(); ++i) {
|
||||
CTxOut tx_out;
|
||||
if (psbt.GetInputUTXO(tx_out, i)) {
|
||||
if (psbt.inputs.at(i).GetUTXO(tx_out)) {
|
||||
(void)tx_out.IsNull();
|
||||
(void)tx_out.ToString();
|
||||
}
|
||||
|
@ -84,11 +84,11 @@ FUZZ_TARGET(psbt)
|
|||
psbt_mut = psbt;
|
||||
(void)CombinePSBTs(psbt_mut, {psbt_mut, psbt_merge});
|
||||
psbt_mut = psbt;
|
||||
for (unsigned int i = 0; i < psbt_merge.tx->vin.size(); ++i) {
|
||||
(void)psbt_mut.AddInput(psbt_merge.tx->vin[i], psbt_merge.inputs[i]);
|
||||
for (auto& psbt_in : psbt_merge.inputs) {
|
||||
(void)psbt_mut.AddInput(psbt_in);
|
||||
}
|
||||
for (unsigned int i = 0; i < psbt_merge.tx->vout.size(); ++i) {
|
||||
Assert(psbt_mut.AddOutput(psbt_merge.tx->vout[i], psbt_merge.outputs[i]));
|
||||
for (const auto& psbt_out : psbt_merge.outputs) {
|
||||
Assert(psbt_mut.AddOutput(psbt_out));
|
||||
}
|
||||
psbt_mut.unknown.insert(psbt_merge.unknown.begin(), psbt_merge.unknown.end());
|
||||
}
|
||||
|
|
43
src/test/psbt_tests.cpp
Normal file
43
src/test/psbt_tests.cpp
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2022 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or https://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <psbt.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <test/util/setup_common.h>
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(psbt_tests, BasicTestingSetup)
|
||||
|
||||
void CheckTimeLock(const std::string& base64_psbt, std::optional<uint32_t> timelock)
|
||||
{
|
||||
PartiallySignedTransaction psbt;
|
||||
std::string error;
|
||||
bool decoded = DecodeBase64PSBT(psbt, base64_psbt, error);
|
||||
BOOST_CHECK_MESSAGE(decoded, error);
|
||||
|
||||
uint32_t computed_timelock;
|
||||
bool computed = psbt.ComputeTimeLock(computed_timelock);
|
||||
if (timelock) {
|
||||
BOOST_CHECK(computed);
|
||||
BOOST_CHECK(computed_timelock == timelock);
|
||||
} else {
|
||||
BOOST_CHECK(!computed);
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(psbt2_timelock_test)
|
||||
{
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQQBAQEFAQIB+wQCAAAAAAEOIAsK2SFBnByHGXNdctxzn56p4GONH+TB7vD5lECEgV/IAQ8EAAAAAAABAwgACK8vAAAAAAEEFgAUxDD2TEdW2jENvRoIVXLvKZkmJywAAQMIi73rCwAAAAABBBYAFE3Rk6yWSlasG54cyoRU/i9HT4UTAA==", 0);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 0);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAESBCgjAAAAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiARIEKCMAAAABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 10000);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIBEgQoIwAAAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 1657048460);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEQSLjcRiARIEECcAAAABDiA6Gzs8g31kiep6Mdjmx91QPAAb7z4GlY51dICNaMp4pQEPBAAAAAABEQSMjcRiAAEDCE+TNXcAAAAAAQQWABQLE1LKzQPPaqG388jWOIZxs0peEQA=", 1657048460);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAAAAQ4gOhs7PIN9ZInqejHY5sfdUDwAG+8+BpWOdXSAjWjKeKUBDwQAAAAAAREEjI3EYgABAwhPkzV3AAAAAAEEFgAUCxNSys0Dz2qht/PI1jiGcbNKXhEA", 1657048460);
|
||||
CheckTimeLock("cHNidP8BAgQCAAAAAQMEAAAAAAEEAQIBBQEBAfsEAgAAAAABDiAPdY2/vU2nwWyKMwnDyB4RAPVh6mRttbAXUsSF4b3enwEPBAEAAAABEgQQJwAAAAEOIDobOzyDfWSJ6nox2ObH3VA8ABvvPgaVjnV0gI1oynilAQ8EAAAAAAERBIyNxGIAAQMIT5M1dwAAAAABBBYAFAsTUsrNA89qobfzyNY4hnGzSl4RAA==", std::nullopt);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
|
@ -95,7 +95,7 @@ std::set<int> InterpretSubtractFeeFromOutputInstructions(const UniValue& sffo_in
|
|||
static UniValue FinishTransaction(const std::shared_ptr<CWallet> pwallet, const UniValue& options, const CMutableTransaction& rawTx)
|
||||
{
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx(rawTx);
|
||||
PartiallySignedTransaction psbtx(rawTx, /*version=*/2);
|
||||
|
||||
// First fill transaction with our data without signing,
|
||||
// so external signers are not asked to sign more than once.
|
||||
|
@ -1167,7 +1167,7 @@ static RPCHelpMan bumpfee_helper(std::string method_name)
|
|||
|
||||
result.pushKV("txid", txid.GetHex());
|
||||
} else {
|
||||
PartiallySignedTransaction psbtx(mtx);
|
||||
PartiallySignedTransaction psbtx(mtx, /*version=*/2);
|
||||
bool complete = false;
|
||||
const auto err{pwallet->FillPSBT(psbtx, complete, SIGHASH_DEFAULT, /*sign=*/false, /*bip32derivs=*/true)};
|
||||
CHECK_NONFATAL(!err);
|
||||
|
@ -1719,6 +1719,7 @@ RPCHelpMan walletcreatefundedpsbt()
|
|||
FundTxDoc()),
|
||||
RPCArgOptions{.oneline_description="options"}},
|
||||
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
|
||||
{"psbt_version", RPCArg::Type::NUM, RPCArg::Default(2), "The PSBT version number to use."},
|
||||
},
|
||||
RPCResult{
|
||||
RPCResult::Type::OBJ, "", "",
|
||||
|
@ -1764,7 +1765,15 @@ RPCHelpMan walletcreatefundedpsbt()
|
|||
auto txr = FundTransaction(wallet, rawTx, recipients, options, coin_control, /*override_min_fee=*/true);
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx(CMutableTransaction(*txr.tx));
|
||||
uint32_t psbt_version = 2;
|
||||
if (!request.params[5].isNull()) {
|
||||
psbt_version = request.params[5].getInt<int>();
|
||||
}
|
||||
if (psbt_version != 2 && psbt_version != 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "The PSBT version can only be 2 or 0");
|
||||
}
|
||||
|
||||
PartiallySignedTransaction psbtx(CMutableTransaction(*txr.tx), psbt_version);
|
||||
|
||||
// Fill transaction with out data but don't sign
|
||||
bool bip32derivs = request.params[4].isNull() ? true : request.params[4].get_bool();
|
||||
|
|
|
@ -643,8 +643,7 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
|
|||
if (n_signed) {
|
||||
*n_signed = 0;
|
||||
}
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
const CTxIn& txin = psbtx.tx->vin[i];
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
PSBTInput& input = psbtx.inputs.at(i);
|
||||
|
||||
if (PSBTInputSigned(input)) {
|
||||
|
@ -658,7 +657,7 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
|
|||
|
||||
// Check non_witness_utxo has specified prevout
|
||||
if (input.non_witness_utxo) {
|
||||
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
if (*input.prev_out >= input.non_witness_utxo->vout.size()) {
|
||||
return PSBTError::MISSING_INPUTS;
|
||||
}
|
||||
} else if (input.witness_utxo.IsNull()) {
|
||||
|
@ -677,7 +676,7 @@ std::optional<PSBTError> LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransact
|
|||
}
|
||||
|
||||
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
|
||||
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
||||
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
|
||||
UpdatePSBTOutput(HidingSigningProvider(this, true, !bip32derivs), psbtx, i);
|
||||
}
|
||||
|
||||
|
@ -2570,8 +2569,7 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
|
|||
if (n_signed) {
|
||||
*n_signed = 0;
|
||||
}
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
const CTxIn& txin = psbtx.tx->vin[i];
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
PSBTInput& input = psbtx.inputs.at(i);
|
||||
|
||||
if (PSBTInputSigned(input)) {
|
||||
|
@ -2588,10 +2586,10 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
|
|||
if (!input.witness_utxo.IsNull()) {
|
||||
script = input.witness_utxo.scriptPubKey;
|
||||
} else if (input.non_witness_utxo) {
|
||||
if (txin.prevout.n >= input.non_witness_utxo->vout.size()) {
|
||||
if (*input.prev_out >= input.non_witness_utxo->vout.size()) {
|
||||
return PSBTError::MISSING_INPUTS;
|
||||
}
|
||||
script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey;
|
||||
script = input.non_witness_utxo->vout[*input.prev_out].scriptPubKey;
|
||||
} else {
|
||||
// There's no UTXO so we can just skip this now
|
||||
continue;
|
||||
|
@ -2652,8 +2650,8 @@ std::optional<PSBTError> DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTran
|
|||
}
|
||||
|
||||
// Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
|
||||
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
||||
std::unique_ptr<SigningProvider> keys = GetSolvingProvider(psbtx.tx->vout.at(i).scriptPubKey);
|
||||
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
|
||||
std::unique_ptr<SigningProvider> keys = GetSolvingProvider(*psbtx.outputs.at(i).script);
|
||||
if (!keys) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -74,6 +74,7 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
|
|||
|
||||
// Mutate the transaction so that one of the inputs is invalid
|
||||
psbtx.tx->vin[0].prevout.n = 2;
|
||||
psbtx.inputs[0].prev_out = 2;
|
||||
|
||||
// Try to sign the mutated input
|
||||
SignatureData sigdata;
|
||||
|
|
|
@ -2215,17 +2215,14 @@ std::optional<PSBTError> CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bo
|
|||
}
|
||||
LOCK(cs_wallet);
|
||||
// Get all of the previous transactions
|
||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||
const CTxIn& txin = psbtx.tx->vin[i];
|
||||
PSBTInput& input = psbtx.inputs.at(i);
|
||||
|
||||
for (PSBTInput& input : psbtx.inputs) {
|
||||
if (PSBTInputSigned(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we have no utxo, grab it from the wallet.
|
||||
if (!input.non_witness_utxo) {
|
||||
const uint256& txhash = txin.prevout.hash;
|
||||
const uint256& txhash = input.prev_txid;
|
||||
const auto it = mapWallet.find(txhash);
|
||||
if (it != mapWallet.end()) {
|
||||
const CWalletTx& wtx = it->second;
|
||||
|
|
|
@ -40,7 +40,29 @@
|
|||
"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<",
|
||||
"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"
|
||||
],
|
||||
"invalid_with_msg": [
|
||||
[
|
||||
|
@ -72,7 +94,21 @@
|
|||
"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"
|
||||
],
|
||||
"creator" : [
|
||||
{
|
||||
|
@ -94,6 +130,7 @@
|
|||
"bcrt1qqzh2ngh97ru8dfvgma25d6r595wcwqy0cee4cc": 1
|
||||
}
|
||||
],
|
||||
"version": 0,
|
||||
"result" : "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA="
|
||||
}
|
||||
],
|
||||
|
@ -156,10 +193,10 @@
|
|||
},
|
||||
{
|
||||
"combine" : [
|
||||
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=",
|
||||
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
|
||||
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAK8AECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8ACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAArwAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwA=",
|
||||
"cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAK8AECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACvABAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAArwAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
|
||||
],
|
||||
"result" : "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAKDwECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8KDwECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCg8BAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAAoPAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwoPAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
|
||||
"result" : "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAK8AECAwQFBgcICQ8BAgMEBQYHCAkKCwwNDg8K8AECAwQFBgcIEA8BAgMEBQYHCAkKCwwNDg8ACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PCvABAgMEBQYHCBAPAQIDBAUGBwgJCgsMDQ4PAArwAQIDBAUGBwgJDwECAwQFBgcICQoLDA0ODwrwAQIDBAUGBwgQDwECAwQFBgcICQoLDA0ODwA="
|
||||
}
|
||||
],
|
||||
"finalizer" : [
|
||||
|
|
|
@ -32,6 +32,7 @@ from test_framework.psbt import (
|
|||
PSBT_IN_NON_WITNESS_UTXO,
|
||||
PSBT_IN_WITNESS_UTXO,
|
||||
PSBT_OUT_TAP_TREE,
|
||||
PSBT_OUT_SCRIPT,
|
||||
)
|
||||
from test_framework.script import CScript, OP_TRUE
|
||||
from test_framework.script_util import MIN_STANDARD_TX_NONWITNESS_SIZE
|
||||
|
@ -87,9 +88,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
|
||||
# Modify the raw transaction by changing the output address, so the signature is no longer valid
|
||||
signed_psbt_obj = PSBT.from_base64(signed_psbt)
|
||||
substitute_addr = wallet.getnewaddress()
|
||||
raw = wallet.createrawtransaction([{"txid": utxos[0]["txid"], "vout": utxos[0]["vout"]}], [{substitute_addr: 0.9999}])
|
||||
signed_psbt_obj.g.map[PSBT_GLOBAL_UNSIGNED_TX] = bytes.fromhex(raw)
|
||||
signed_psbt_obj.o[0].map[PSBT_OUT_SCRIPT] = CScript([OP_TRUE])
|
||||
|
||||
# Check that the walletprocesspsbt call succeeds but also recognizes that the transaction is not complete
|
||||
signed_psbt_incomplete = wallet.walletprocesspsbt(signed_psbt_obj.to_base64(), finalize=False)
|
||||
|
@ -161,11 +160,11 @@ class PSBTTest(BitcoinTestFramework):
|
|||
psbtx1 = wallet.walletcreatefundedpsbt([], {target_address: 0.1}, 0, {'fee_rate': 1, 'maxconf': 0})['psbt']
|
||||
|
||||
# Make sure we only had the one input
|
||||
tx1_inputs = self.nodes[0].decodepsbt(psbtx1)['tx']['vin']
|
||||
tx1_inputs = self.nodes[0].decodepsbt(psbtx1)['inputs']
|
||||
assert_equal(len(tx1_inputs), 1)
|
||||
|
||||
utxo1 = tx1_inputs[0]
|
||||
assert_equal(unconfirmed_txid, utxo1['txid'])
|
||||
assert_equal(unconfirmed_txid, utxo1['previous_txid'])
|
||||
|
||||
signed_tx1 = wallet.walletprocesspsbt(psbtx1)
|
||||
txid1 = self.nodes[0].sendrawtransaction(signed_tx1['hex'])
|
||||
|
@ -174,23 +173,23 @@ class PSBTTest(BitcoinTestFramework):
|
|||
assert txid1 in mempool
|
||||
|
||||
self.log.info("Fail to craft a new PSBT that sends more funds with add_inputs = False")
|
||||
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", wallet.walletcreatefundedpsbt, [{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': False})
|
||||
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", wallet.walletcreatefundedpsbt, [{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': False})
|
||||
|
||||
self.log.info("Fail to craft a new PSBT with minconf above highest one")
|
||||
assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 3, 'fee_rate': 10})
|
||||
assert_raises_rpc_error(-4, "Insufficient funds", wallet.walletcreatefundedpsbt, [{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 3, 'fee_rate': 10})
|
||||
|
||||
self.log.info("Fail to broadcast a new PSBT with maxconf 0 due to BIP125 rules to verify it actually chose unconfirmed outputs")
|
||||
psbt_invalid = wallet.walletcreatefundedpsbt([{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'maxconf': 0, 'fee_rate': 10})['psbt']
|
||||
psbt_invalid = wallet.walletcreatefundedpsbt([{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': True, 'maxconf': 0, 'fee_rate': 10})['psbt']
|
||||
signed_invalid = wallet.walletprocesspsbt(psbt_invalid)
|
||||
assert_raises_rpc_error(-26, "bad-txns-spends-conflicting-tx", self.nodes[0].sendrawtransaction, signed_invalid['hex'])
|
||||
|
||||
self.log.info("Craft a replacement adding inputs with highest confs possible")
|
||||
psbtx2 = wallet.walletcreatefundedpsbt([{'txid': utxo1['txid'], 'vout': utxo1['vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 2, 'fee_rate': 10})['psbt']
|
||||
tx2_inputs = self.nodes[0].decodepsbt(psbtx2)['tx']['vin']
|
||||
psbtx2 = wallet.walletcreatefundedpsbt([{'txid': utxo1['previous_txid'], 'vout': utxo1['previous_vout']}], {target_address: 1}, 0, {'add_inputs': True, 'minconf': 2, 'fee_rate': 10})['psbt']
|
||||
tx2_inputs = self.nodes[0].decodepsbt(psbtx2)['inputs']
|
||||
assert_greater_than_or_equal(len(tx2_inputs), 2)
|
||||
for vin in tx2_inputs:
|
||||
if vin['txid'] != unconfirmed_txid:
|
||||
assert_greater_than_or_equal(self.nodes[0].gettxout(vin['txid'], vin['vout'])['confirmations'], 2)
|
||||
if vin['previous_txid'] != unconfirmed_txid:
|
||||
assert_greater_than_or_equal(self.nodes[0].gettxout(vin['previous_txid'], vin['previous_vout'])['confirmations'], 2)
|
||||
|
||||
signed_tx2 = wallet.walletprocesspsbt(psbtx2)
|
||||
txid2 = self.nodes[0].sendrawtransaction(signed_tx2['hex'])
|
||||
|
@ -207,7 +206,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
# 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)
|
||||
assert_equal(decoded_psbt["outputs"][changepos]["script"]["type"], expected_type)
|
||||
|
||||
def run_test(self):
|
||||
# Create and fund a raw tx for sending 10 BTC
|
||||
|
@ -249,7 +248,9 @@ class PSBTTest(BitcoinTestFramework):
|
|||
|
||||
max_tx_weight_sufficient = 1000 # 1k vbytes is enough
|
||||
psbt = self.nodes[0].walletcreatefundedpsbt(outputs=dest_arg,locktime=0, options={"max_tx_weight": max_tx_weight_sufficient})["psbt"]
|
||||
weight = self.nodes[0].decodepsbt(psbt)["tx"]["weight"]
|
||||
psbt = self.nodes[0].walletprocesspsbt(psbt)["psbt"]
|
||||
final_tx = self.nodes[0].finalizepsbt(psbt)["hex"]
|
||||
weight = self.nodes[0].decoderawtransaction(final_tx)["weight"]
|
||||
# ensure the transaction's weight is below the specified max_tx_weight.
|
||||
assert_greater_than_or_equal(max_tx_weight_sufficient, weight)
|
||||
|
||||
|
@ -260,7 +261,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
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)
|
||||
assert_equal(len(self.nodes[0].decodepsbt(psbtx1)['inputs']), 2)
|
||||
|
||||
# Inputs argument can be null
|
||||
self.nodes[0].walletcreatefundedpsbt(None, {self.nodes[2].getnewaddress():10})
|
||||
|
@ -494,15 +495,20 @@ class PSBTTest(BitcoinTestFramework):
|
|||
# Create a psbt spending outputs from nodes 1 and 2
|
||||
psbt_orig = self.nodes[0].createpsbt([utxo1, utxo2], {self.nodes[0].getnewaddress():25.999})
|
||||
|
||||
# Check that the default psbt version is 2
|
||||
assert_equal(self.nodes[0].decodepsbt(psbt_orig)["psbt_version"], 2)
|
||||
|
||||
# Update psbts, should only have data for one input and not the other
|
||||
psbt1 = self.nodes[1].walletprocesspsbt(psbt_orig, False, "ALL")['psbt']
|
||||
psbt1_decoded = self.nodes[0].decodepsbt(psbt1)
|
||||
assert psbt1_decoded['inputs'][0] and not psbt1_decoded['inputs'][1]
|
||||
assert len(psbt1_decoded['inputs'][0].keys()) > 3
|
||||
assert len(psbt1_decoded['inputs'][1].keys()) == 3
|
||||
# Check that BIP32 path was added
|
||||
assert "bip32_derivs" in psbt1_decoded['inputs'][0]
|
||||
psbt2 = self.nodes[2].walletprocesspsbt(psbt_orig, False, "ALL", False)['psbt']
|
||||
psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
|
||||
assert not psbt2_decoded['inputs'][0] and psbt2_decoded['inputs'][1]
|
||||
assert len(psbt2_decoded['inputs'][0].keys()) == 3
|
||||
assert len(psbt2_decoded['inputs'][1].keys()) > 3
|
||||
# Check that BIP32 paths were not added
|
||||
assert "bip32_derivs" not in psbt2_decoded['inputs'][1]
|
||||
|
||||
|
@ -524,33 +530,33 @@ class PSBTTest(BitcoinTestFramework):
|
|||
unspent = self.nodes[0].listunspent()[0]
|
||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height+2, {"replaceable": False, "add_inputs": True}, False)
|
||||
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
||||
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
||||
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
for psbt_in in decoded_psbt["inputs"]:
|
||||
assert_greater_than(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
assert "bip32_derivs" not in psbt_in
|
||||
assert_equal(decoded_psbt["tx"]["locktime"], block_height+2)
|
||||
assert_equal(decoded_psbt["fallback_locktime"], block_height+2)
|
||||
|
||||
# Same construction with only locktime set and RBF explicitly enabled
|
||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([{"txid":unspent["txid"], "vout":unspent["vout"]}], [{self.nodes[2].getnewaddress():unspent["amount"]+1}], block_height, {"replaceable": True, "add_inputs": True}, True)
|
||||
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
||||
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
||||
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
for psbt_in in decoded_psbt["inputs"]:
|
||||
assert_equal(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
assert "bip32_derivs" in psbt_in
|
||||
assert_equal(decoded_psbt["tx"]["locktime"], block_height)
|
||||
assert_equal(decoded_psbt["fallback_locktime"], block_height)
|
||||
|
||||
# Same construction without optional arguments
|
||||
psbtx_info = self.nodes[0].walletcreatefundedpsbt([], [{self.nodes[2].getnewaddress():unspent["amount"]+1}])
|
||||
decoded_psbt = self.nodes[0].decodepsbt(psbtx_info["psbt"])
|
||||
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
||||
assert_equal(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
for psbt_in in decoded_psbt["inputs"]:
|
||||
assert_equal(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
assert "bip32_derivs" in psbt_in
|
||||
assert_equal(decoded_psbt["tx"]["locktime"], 0)
|
||||
assert_equal(decoded_psbt["fallback_locktime"], 0)
|
||||
|
||||
# Same construction without optional arguments, for a node with -walletrbf=0
|
||||
unspent1 = self.nodes[1].listunspent()[0]
|
||||
psbtx_info = self.nodes[1].walletcreatefundedpsbt([{"txid":unspent1["txid"], "vout":unspent1["vout"]}], [{self.nodes[2].getnewaddress():unspent1["amount"]+1}], block_height, {"add_inputs": True})
|
||||
decoded_psbt = self.nodes[1].decodepsbt(psbtx_info["psbt"])
|
||||
for tx_in, psbt_in in zip(decoded_psbt["tx"]["vin"], decoded_psbt["inputs"]):
|
||||
assert_greater_than(tx_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
for psbt_in in decoded_psbt["inputs"]:
|
||||
assert_greater_than(psbt_in["sequence"], MAX_BIP125_RBF_SEQUENCE)
|
||||
assert "bip32_derivs" in psbt_in
|
||||
|
||||
# Make sure change address wallet does not have P2SH innerscript access to results in success
|
||||
|
@ -591,7 +597,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
# BIP 174 Test Vectors
|
||||
|
||||
# Check that unknown values are just passed through
|
||||
unknown_psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACg8BAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA="
|
||||
unknown_psbt = "cHNidP8BAD8CAAAAAf//////////////////////////////////////////AAAAAAD/////AQAAAAAAAAAAA2oBAAAAAAAACvABAgMEBQYHCAkPAQIDBAUGBwgJCgsMDQ4PAAA="
|
||||
unknown_out = self.nodes[0].walletprocesspsbt(unknown_psbt)['psbt']
|
||||
assert_equal(unknown_psbt, unknown_out)
|
||||
|
||||
|
@ -620,7 +626,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
|
||||
# Creator Tests
|
||||
for creator in creators:
|
||||
created_tx = self.nodes[0].createpsbt(inputs=creator['inputs'], outputs=creator['outputs'], replaceable=False)
|
||||
created_tx = self.nodes[0].createpsbt(inputs=creator['inputs'], outputs=creator['outputs'], replaceable=False, psbt_version=creator['version'])
|
||||
assert_equal(created_tx, creator['result'])
|
||||
|
||||
# Signer tests
|
||||
|
@ -675,54 +681,66 @@ class PSBTTest(BitcoinTestFramework):
|
|||
utxo1, utxo2, utxo3 = self.create_outpoints(self.nodes[1], outputs=[{addr1: 11}, {addr2: 11}, {addr3: 11}])
|
||||
self.sync_all()
|
||||
|
||||
psbt_v2_required_keys = ["previous_vout", "sequence", "previous_txid"]
|
||||
|
||||
def test_psbt_input_keys(psbt_input, keys):
|
||||
"""Check that the psbt input has only the expected keys."""
|
||||
keys.extend(["previous_vout", "sequence", "previous_txid"])
|
||||
assert_equal(set(keys), set(psbt_input.keys()))
|
||||
|
||||
# Create a PSBT. None of the inputs are filled initially
|
||||
psbt = self.nodes[1].createpsbt([utxo1, utxo2, utxo3], {self.nodes[0].getnewaddress():32.999})
|
||||
decoded = self.nodes[1].decodepsbt(psbt)
|
||||
test_psbt_input_keys(decoded['inputs'][0], [])
|
||||
test_psbt_input_keys(decoded['inputs'][1], [])
|
||||
test_psbt_input_keys(decoded['inputs'][2], [])
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys)
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys)
|
||||
test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys)
|
||||
|
||||
# Update a PSBT with UTXOs from the node
|
||||
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
|
||||
updated = self.nodes[1].utxoupdatepsbt(psbt)
|
||||
decoded = self.nodes[1].decodepsbt(updated)
|
||||
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo'])
|
||||
|
||||
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
|
||||
descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
|
||||
updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs)
|
||||
decoded = self.nodes[1].decodepsbt(updated)
|
||||
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'bip32_derivs'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo', 'bip32_derivs'])
|
||||
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script'])
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo', 'bip32_derivs'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo', 'bip32_derivs'])
|
||||
test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo', 'witness_utxo', 'bip32_derivs', 'redeem_script'])
|
||||
|
||||
# Cannot join PSBTv2s
|
||||
psbt1 = self.nodes[1].createpsbt(inputs=[utxo1], outputs={self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=0)
|
||||
psbt2 = self.nodes[1].createpsbt(inputs=[utxo1], outputs={self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=2)
|
||||
assert_raises_rpc_error(-8, "joinpsbts only operates on version 0 PSBTs", self.nodes[1].joinpsbts, [psbt1, psbt2])
|
||||
|
||||
# Two PSBTs with a common input should not be joinable
|
||||
psbt1 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')})
|
||||
assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, updated])
|
||||
psbt2 = self.nodes[1].createpsbt([utxo1], {self.nodes[0].getnewaddress():Decimal('10.999')}, psbt_version=0)
|
||||
assert_raises_rpc_error(-8, "exists in multiple PSBTs", self.nodes[1].joinpsbts, [psbt1, psbt2])
|
||||
|
||||
# Join two distinct PSBTs
|
||||
psbt1 = self.nodes[1].createpsbt(inputs=[utxo1, utxo2, utxo3], outputs={self.nodes[0].getnewaddress():32.999}, psbt_version=0)
|
||||
addr4 = self.nodes[1].getnewaddress("", "p2sh-segwit")
|
||||
utxo4 = self.create_outpoints(self.nodes[0], outputs=[{addr4: 5}])[0]
|
||||
self.generate(self.nodes[0], 6)
|
||||
psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')})
|
||||
psbt2 = self.nodes[1].createpsbt([utxo4], {self.nodes[0].getnewaddress():Decimal('4.999')}, psbt_version=0)
|
||||
psbt2 = self.nodes[1].walletprocesspsbt(psbt2)['psbt']
|
||||
psbt2_decoded = self.nodes[0].decodepsbt(psbt2)
|
||||
assert "final_scriptwitness" in psbt2_decoded['inputs'][0] and "final_scriptSig" in psbt2_decoded['inputs'][0]
|
||||
joined = self.nodes[0].joinpsbts([psbt, psbt2])
|
||||
joined = self.nodes[0].joinpsbts([psbt1, psbt2])
|
||||
joined_decoded = self.nodes[0].decodepsbt(joined)
|
||||
assert len(joined_decoded['inputs']) == 4 and len(joined_decoded['outputs']) == 2 and "final_scriptwitness" not in joined_decoded['inputs'][3] and "final_scriptSig" not in joined_decoded['inputs'][3]
|
||||
assert_equal(len(joined_decoded['inputs']), 4)
|
||||
assert_equal(len(joined_decoded['outputs']), 2)
|
||||
assert "final_scriptwitness" not in joined_decoded['inputs'][3]
|
||||
assert "final_scriptSig" not in joined_decoded['inputs'][3]
|
||||
|
||||
# Check that joining shuffles the inputs and outputs
|
||||
# 10 attempts should be enough to get a shuffled join
|
||||
shuffled = False
|
||||
for _ in range(10):
|
||||
shuffled_joined = self.nodes[0].joinpsbts([psbt, psbt2])
|
||||
shuffled_joined = self.nodes[0].joinpsbts([psbt1, psbt2])
|
||||
shuffled |= joined != shuffled_joined
|
||||
if shuffled:
|
||||
break
|
||||
|
@ -812,11 +830,9 @@ class PSBTTest(BitcoinTestFramework):
|
|||
final = signed['hex']
|
||||
|
||||
dec = self.nodes[0].decodepsbt(signed["psbt"])
|
||||
for i, txin in enumerate(dec["tx"]["vin"]):
|
||||
if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]:
|
||||
input_idx = i
|
||||
for psbt_in in dec["inputs"]:
|
||||
if psbt_in["previous_txid"] == ext_utxo["txid"] and psbt_in["previous_vout"] == ext_utxo["vout"]:
|
||||
break
|
||||
psbt_in = dec["inputs"][input_idx]
|
||||
scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
|
||||
witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
|
||||
input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
import base64
|
||||
import struct
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from .messages import (
|
||||
CTransaction,
|
||||
deser_string,
|
||||
deser_compact_size,
|
||||
from_binary,
|
||||
ser_compact_size,
|
||||
)
|
||||
|
@ -100,37 +104,78 @@ class PSBT:
|
|||
self.g = g if g is not None else PSBTMap()
|
||||
self.i = i if i is not None else []
|
||||
self.o = o if o is not None else []
|
||||
self.tx = None
|
||||
self.in_count = len(i) if i is not None else None
|
||||
self.out_count = len(o) if o is not None else None
|
||||
self.version = None
|
||||
|
||||
def deserialize(self, f):
|
||||
assert f.read(5) == b"psbt\xff"
|
||||
self.g = from_binary(PSBTMap, f)
|
||||
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
|
||||
self.tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
|
||||
self.i = [from_binary(PSBTMap, f) for _ in self.tx.vin]
|
||||
self.o = [from_binary(PSBTMap, f) for _ in self.tx.vout]
|
||||
|
||||
self.version = 0
|
||||
if PSBT_GLOBAL_VERSION in self.g.map:
|
||||
assert PSBT_GLOBAL_INPUT_COUNT in self.g.map
|
||||
assert PSBT_GLOBAL_OUTPUT_COUNT in self.g.map
|
||||
self.version = struct.unpack("<I", self.g.map[PSBT_GLOBAL_VERSION])[0]
|
||||
assert self.version in [0, 2]
|
||||
if self.version == 2:
|
||||
self.in_count = deser_compact_size(BytesIO(self.g.map[PSBT_GLOBAL_INPUT_COUNT]))
|
||||
self.out_count = deser_compact_size(BytesIO(self.g.map[PSBT_GLOBAL_OUTPUT_COUNT]))
|
||||
else:
|
||||
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
|
||||
tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
|
||||
self.in_count = len(tx.vin)
|
||||
self.out_count = len(tx.vout)
|
||||
|
||||
self.i = [from_binary(PSBTMap, f) for _ in range(self.in_count)]
|
||||
self.o = [from_binary(PSBTMap, f) for _ in range(self.out_count)]
|
||||
return self
|
||||
|
||||
def serialize(self):
|
||||
assert isinstance(self.g, PSBTMap)
|
||||
assert isinstance(self.i, list) and all(isinstance(x, PSBTMap) for x in self.i)
|
||||
assert isinstance(self.o, list) and all(isinstance(x, PSBTMap) for x in self.o)
|
||||
assert PSBT_GLOBAL_UNSIGNED_TX in self.g.map
|
||||
tx = from_binary(CTransaction, self.g.map[PSBT_GLOBAL_UNSIGNED_TX])
|
||||
assert len(tx.vin) == len(self.i)
|
||||
assert len(tx.vout) == len(self.o)
|
||||
if self.version is not None and self.version == 2:
|
||||
self.g.map[PSBT_GLOBAL_INPUT_COUNT] = ser_compact_size(len(self.i))
|
||||
self.g.map[PSBT_GLOBAL_OUTPUT_COUNT] = ser_compact_size(len(self.o))
|
||||
|
||||
psbt = [x.serialize() for x in [self.g] + self.i + self.o]
|
||||
return b"psbt\xff" + b"".join(psbt)
|
||||
|
||||
def make_blank(self):
|
||||
"""
|
||||
Remove all fields except for PSBT_GLOBAL_UNSIGNED_TX
|
||||
Remove all fields except for required fields depending on version
|
||||
"""
|
||||
for m in self.i + self.o:
|
||||
m.map.clear()
|
||||
if self.version == 0:
|
||||
for m in self.i + self.o:
|
||||
m.map.clear()
|
||||
|
||||
self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]})
|
||||
self.g = PSBTMap(map={PSBT_GLOBAL_UNSIGNED_TX: self.g.map[PSBT_GLOBAL_UNSIGNED_TX]})
|
||||
elif self.version == 2:
|
||||
self.g = PSBTMap(map={
|
||||
PSBT_GLOBAL_TX_VERSION: self.g.map[PSBT_GLOBAL_TX_VERSION],
|
||||
PSBT_GLOBAL_INPUT_COUNT: self.g.map[PSBT_GLOBAL_INPUT_COUNT],
|
||||
PSBT_GLOBAL_OUTPUT_COUNT: self.g.map[PSBT_GLOBAL_OUTPUT_COUNT],
|
||||
PSBT_GLOBAL_VERSION: self.g.map[PSBT_GLOBAL_VERSION],
|
||||
})
|
||||
|
||||
new_i = []
|
||||
for m in self.i:
|
||||
new_i.append(PSBTMap(map={
|
||||
PSBT_IN_PREVIOUS_TXID: m.map[PSBT_IN_PREVIOUS_TXID],
|
||||
PSBT_IN_OUTPUT_INDEX: m.map[PSBT_IN_OUTPUT_INDEX],
|
||||
}))
|
||||
self.i = new_i
|
||||
|
||||
new_o = []
|
||||
for m in self.o:
|
||||
new_o.append(PSBTMap(map={
|
||||
PSBT_OUT_SCRIPT: m.map[PSBT_OUT_SCRIPT],
|
||||
PSBT_OUT_AMOUNT: m.map[PSBT_OUT_AMOUNT],
|
||||
}))
|
||||
self.o = new_o
|
||||
else:
|
||||
assert False
|
||||
|
||||
def to_base64(self):
|
||||
return base64.b64encode(self.serialize()).decode("utf8")
|
||||
|
|
|
@ -640,14 +640,14 @@ def test_watchonly_psbt(self, peer_node, rbf_node, dest_address):
|
|||
{"fee_rate": 1, "add_inputs": False}, True)['psbt']
|
||||
psbt_signed = signer.walletprocesspsbt(psbt=psbt, sign=True, sighashtype="ALL", bip32derivs=True)
|
||||
original_txid = watcher.sendrawtransaction(psbt_signed["hex"])
|
||||
assert_equal(len(watcher.decodepsbt(psbt)["tx"]["vin"]), 1)
|
||||
assert_equal(len(watcher.decodepsbt(psbt)["inputs"]), 1)
|
||||
|
||||
# bumpfee can't be used on watchonly wallets
|
||||
assert_raises_rpc_error(-4, "bumpfee is not available with wallets that have private keys disabled. Use psbtbumpfee instead.", watcher.bumpfee, original_txid)
|
||||
|
||||
# Bump fee, obnoxiously high to add additional watchonly input
|
||||
bumped_psbt = watcher.psbtbumpfee(original_txid, fee_rate=HIGH)
|
||||
assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["tx"]["vin"]), 1)
|
||||
assert_greater_than(len(watcher.decodepsbt(bumped_psbt['psbt'])["inputs"]), 1)
|
||||
assert "txid" not in bumped_psbt
|
||||
assert_equal(bumped_psbt["origfee"], -watcher.gettransaction(original_txid)["fee"])
|
||||
assert not watcher.finalizepsbt(bumped_psbt["psbt"])["complete"]
|
||||
|
|
|
@ -1179,10 +1179,10 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
tx = wallet.send(outputs=[{addr1: 8}], **options)
|
||||
assert tx["complete"]
|
||||
# Check that only the preset inputs were added to the tx
|
||||
decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])['tx']['vin']
|
||||
decoded_psbt_inputs = self.nodes[0].decodepsbt(tx["psbt"])["inputs"]
|
||||
assert_equal(len(decoded_psbt_inputs), 2)
|
||||
for input in decoded_psbt_inputs:
|
||||
assert_equal(input["txid"], source_tx["txid"])
|
||||
assert_equal(input["previous_txid"], source_tx["txid"])
|
||||
|
||||
# Case (5), assert that inputs are added to the tx by explicitly setting add_inputs=true
|
||||
options = {"add_inputs": True, "add_to_wallet": True}
|
||||
|
@ -1221,10 +1221,10 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
})
|
||||
psbt_tx = wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, **options)
|
||||
# Check that only the preset inputs were added to the tx
|
||||
decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])['tx']['vin']
|
||||
decoded_psbt_inputs = self.nodes[0].decodepsbt(psbt_tx["psbt"])["inputs"]
|
||||
assert_equal(len(decoded_psbt_inputs), 2)
|
||||
for input in decoded_psbt_inputs:
|
||||
assert_equal(input["txid"], source_tx["txid"])
|
||||
assert_equal(input["previous_txid"], source_tx["txid"])
|
||||
|
||||
# Case (5), 'walletcreatefundedpsbt' command
|
||||
# Explicit add_inputs=true, no preset inputs
|
||||
|
|
|
@ -39,13 +39,13 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
|
|||
@staticmethod
|
||||
def _check_psbt(psbt, to, value, multisig):
|
||||
"""Helper function for any of the N participants to check the psbt with decodepsbt and verify it is OK before signing."""
|
||||
tx = multisig.decodepsbt(psbt)["tx"]
|
||||
decoded = multisig.decodepsbt(psbt)
|
||||
amount = 0
|
||||
for vout in tx["vout"]:
|
||||
address = vout["scriptPubKey"]["address"]
|
||||
for psbt_out in decoded["outputs"]:
|
||||
address = psbt_out["script"]["address"]
|
||||
assert_equal(multisig.getaddressinfo(address)["ischange"], address != to)
|
||||
if address == to:
|
||||
amount += vout["value"]
|
||||
amount += psbt_out["amount"]
|
||||
assert_approx(amount, float(value), vspan=0.001)
|
||||
|
||||
def participants_create_multisigs(self, external_xpubs, internal_xpubs):
|
||||
|
|
|
@ -431,10 +431,10 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
assert res["complete"]
|
||||
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_address=change_address, change_position=0)
|
||||
assert res["complete"]
|
||||
assert_equal(self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"], change_address)
|
||||
assert_equal(self.nodes[0].decodepsbt(res["psbt"])["outputs"][0]["script"]["address"], change_address)
|
||||
res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1, add_to_wallet=False, change_type="legacy", change_position=0)
|
||||
assert res["complete"]
|
||||
change_address = self.nodes[0].decodepsbt(res["psbt"])["tx"]["vout"][0]["scriptPubKey"]["address"]
|
||||
change_address = self.nodes[0].decodepsbt(res["psbt"])["outputs"][0]["script"]["address"]
|
||||
assert change_address[0] == "m" or change_address[0] == "n"
|
||||
|
||||
self.log.info("Set lock time...")
|
||||
|
@ -537,11 +537,9 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
assert signed["complete"]
|
||||
|
||||
dec = self.nodes[0].decodepsbt(signed["psbt"])
|
||||
for i, txin in enumerate(dec["tx"]["vin"]):
|
||||
if txin["txid"] == ext_utxo["txid"] and txin["vout"] == ext_utxo["vout"]:
|
||||
input_idx = i
|
||||
for psbt_in in dec["inputs"]:
|
||||
if psbt_in["previous_txid"] == ext_utxo["txid"] and psbt_in["previous_vout"] == ext_utxo["vout"]:
|
||||
break
|
||||
psbt_in = dec["inputs"][input_idx]
|
||||
scriptsig_hex = psbt_in["final_scriptSig"]["hex"] if "final_scriptSig" in psbt_in else ""
|
||||
witness_stack_hex = psbt_in["final_scriptwitness"] if "final_scriptwitness" in psbt_in else None
|
||||
input_weight = calculate_input_weight(scriptsig_hex, witness_stack_hex)
|
||||
|
|
|
@ -313,9 +313,9 @@ class SendallTest(BitcoinTestFramework):
|
|||
decoded = self.nodes[0].decodepsbt(psbt)
|
||||
assert_equal(len(decoded["inputs"]), 1)
|
||||
assert_equal(len(decoded["outputs"]), 1)
|
||||
assert_equal(decoded["tx"]["vin"][0]["txid"], utxo["txid"])
|
||||
assert_equal(decoded["tx"]["vin"][0]["vout"], utxo["vout"])
|
||||
assert_equal(decoded["tx"]["vout"][0]["scriptPubKey"]["address"], self.remainder_target)
|
||||
assert_equal(decoded["inputs"][0]["previous_txid"], utxo["txid"])
|
||||
assert_equal(decoded["inputs"][0]["previous_vout"], utxo["vout"])
|
||||
assert_equal(decoded["outputs"][0]["script"]["address"], self.remainder_target)
|
||||
|
||||
@cleanup
|
||||
def sendall_with_minconf(self):
|
||||
|
|
Loading…
Add table
Reference in a new issue