mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-22 06:52:36 +01:00
Merge bitcoin/bitcoin#25939: rpc: In utxoupdatepsbt
also look for the tx in the txindex
6e9f8bb050
rpc, tests: in `utxoupdatepsbt` also look for the transaction in the txindex (ishaanam)a5b4883fb4
rpc: extract psbt updating logic into ProcessPSBT (ishaanam) Pull request description: Previously the `utxoupdatepsbt` RPC, added in #13932, only updated the inputs spending segwit outputs with the `witness_utxo`, because the full transaction is needed for legacy inputs. Before this RPC looked for just the utxos in the utxo set and the mempool. This PR makes it so that if the user has txindex enabled, then the full transaction is looked for there for all inputs. If it is not found in the txindex or txindex isn't enabled, then the mempool is checked for the full transaction. If the transaction an input is spending from is still not found at that point, then the utxo set is searched and the inputs spending segwit outputs are updated with just the utxo. ACKs for top commit: achow101: ACK6e9f8bb050
Xekyo: ACK6e9f8bb050
Tree-SHA512: 078db3c37a1ecd5816d80a42e8bd1341e416d661f508fa5fce0f4e1249fefd7b92a0d45f44957781cb69d0953145ef096ecdd4545ada39062be27742402dac6f
This commit is contained in:
commit
2755aa5121
5 changed files with 133 additions and 88 deletions
32
src/psbt.cpp
32
src/psbt.cpp
|
@ -442,6 +442,38 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
|
||||||
return sig_complete;
|
return sig_complete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type)
|
||||||
|
{
|
||||||
|
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
|
||||||
|
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
|
||||||
|
// Figure out if any non_witness_utxos should be dropped
|
||||||
|
std::vector<unsigned int> to_drop;
|
||||||
|
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||||
|
const auto& input = psbtx.inputs.at(i);
|
||||||
|
int wit_ver;
|
||||||
|
std::vector<unsigned char> wit_prog;
|
||||||
|
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
|
||||||
|
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
|
||||||
|
to_drop.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (wit_ver == 0) {
|
||||||
|
// Segwit v0, so we cannot drop any non_witness_utxos
|
||||||
|
to_drop.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (input.non_witness_utxo) {
|
||||||
|
to_drop.push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop the non_witness_utxos that we can drop
|
||||||
|
for (unsigned int i : to_drop) {
|
||||||
|
psbtx.inputs.at(i).non_witness_utxo = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
|
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
|
||||||
{
|
{
|
||||||
// Finalize input signatures -- in case we have partial signatures that add up to a complete
|
// Finalize input signatures -- in case we have partial signatures that add up to a complete
|
||||||
|
|
|
@ -1231,6 +1231,9 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
|
||||||
**/
|
**/
|
||||||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
|
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
|
||||||
|
|
||||||
|
/** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */
|
||||||
|
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type);
|
||||||
|
|
||||||
/** Counts the unsigned inputs of a PSBT. */
|
/** Counts the unsigned inputs of a PSBT. */
|
||||||
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
|
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,91 @@ static std::vector<RPCArg> CreateTxDoc()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
|
||||||
|
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider)
|
||||||
|
{
|
||||||
|
// Unserialize the transactions
|
||||||
|
PartiallySignedTransaction psbtx;
|
||||||
|
std::string error;
|
||||||
|
if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
|
||||||
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
|
||||||
|
const NodeContext& node = EnsureAnyNodeContext(context);
|
||||||
|
|
||||||
|
// If we can't find the corresponding full transaction for all of our inputs,
|
||||||
|
// this will be used to find just the utxos for the segwit inputs for which
|
||||||
|
// the full transaction isn't found
|
||||||
|
std::map<COutPoint, Coin> coins;
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// The `non_witness_utxo` is the whole previous transaction
|
||||||
|
if (psbt_input.non_witness_utxo) continue;
|
||||||
|
|
||||||
|
CTransactionRef tx;
|
||||||
|
|
||||||
|
// Look in the txindex
|
||||||
|
if (g_txindex) {
|
||||||
|
uint256 block_hash;
|
||||||
|
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
|
||||||
|
}
|
||||||
|
// If we still don't have it look in the mempool
|
||||||
|
if (!tx) {
|
||||||
|
tx = node.mempool->get(tx_in.prevout.hash);
|
||||||
|
}
|
||||||
|
if (tx) {
|
||||||
|
psbt_input.non_witness_utxo = tx;
|
||||||
|
} else {
|
||||||
|
coins[tx_in.prevout]; // 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);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
|
||||||
|
input.witness_utxo = coin.out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||||
|
if (PSBTInputSigned(psbtx.inputs.at(i))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update script/keypath information using descriptor data.
|
||||||
|
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
|
||||||
|
// we don't actually care about those here, in fact.
|
||||||
|
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update script/keypath information using descriptor data.
|
||||||
|
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
||||||
|
UpdatePSBTOutput(provider, psbtx, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
|
||||||
|
|
||||||
|
return psbtx;
|
||||||
|
}
|
||||||
|
|
||||||
static RPCHelpMan getrawtransaction()
|
static RPCHelpMan getrawtransaction()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{
|
return RPCHelpMan{
|
||||||
|
@ -1580,7 +1665,7 @@ static RPCHelpMan converttopsbt()
|
||||||
static RPCHelpMan utxoupdatepsbt()
|
static RPCHelpMan utxoupdatepsbt()
|
||||||
{
|
{
|
||||||
return RPCHelpMan{"utxoupdatepsbt",
|
return RPCHelpMan{"utxoupdatepsbt",
|
||||||
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
|
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n",
|
||||||
{
|
{
|
||||||
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
|
||||||
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
|
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
|
||||||
|
@ -1599,13 +1684,6 @@ static RPCHelpMan utxoupdatepsbt()
|
||||||
},
|
},
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
{
|
{
|
||||||
// Unserialize the transactions
|
|
||||||
PartiallySignedTransaction psbtx;
|
|
||||||
std::string error;
|
|
||||||
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
|
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse descriptors, if any.
|
// Parse descriptors, if any.
|
||||||
FlatSigningProvider provider;
|
FlatSigningProvider provider;
|
||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull()) {
|
||||||
|
@ -1614,53 +1692,12 @@ static RPCHelpMan utxoupdatepsbt()
|
||||||
EvalDescriptorStringOrObject(descs[i], provider);
|
EvalDescriptorStringOrObject(descs[i], provider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We don't actually need private keys further on; hide them as a precaution.
|
// We don't actually need private keys further on; hide them as a precaution.
|
||||||
HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false);
|
const PartiallySignedTransaction& psbtx = ProcessPSBT(
|
||||||
|
request.params[0].get_str(),
|
||||||
// Fetch previous transactions (inputs):
|
request.context,
|
||||||
CCoinsView viewDummy;
|
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false));
|
||||||
CCoinsViewCache view(&viewDummy);
|
|
||||||
{
|
|
||||||
NodeContext& node = EnsureAnyNodeContext(request.context);
|
|
||||||
const CTxMemPool& mempool = EnsureMemPool(node);
|
|
||||||
ChainstateManager& chainman = EnsureChainman(node);
|
|
||||||
LOCK2(cs_main, mempool.cs);
|
|
||||||
CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
|
|
||||||
CCoinsViewMemPool viewMempool(&viewChain, mempool);
|
|
||||||
view.SetBackend(viewMempool); // temporarily switch cache backend to db+mempool view
|
|
||||||
|
|
||||||
for (const CTxIn& txin : psbtx.tx->vin) {
|
|
||||||
view.AccessCoin(txin.prevout); // Load entries from viewChain into view; can fail.
|
|
||||||
}
|
|
||||||
|
|
||||||
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the inputs
|
|
||||||
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
|
||||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
|
||||||
PSBTInput& input = psbtx.inputs.at(i);
|
|
||||||
|
|
||||||
if (input.non_witness_utxo || !input.witness_utxo.IsNull()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
|
|
||||||
|
|
||||||
if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
|
|
||||||
input.witness_utxo = coin.out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update script/keypath information using descriptor data.
|
|
||||||
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
|
|
||||||
// we don't actually care about those here, in fact.
|
|
||||||
SignPSBTInput(public_provider, psbtx, i, &txdata, /*sighash=*/1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update script/keypath information using descriptor data.
|
|
||||||
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
|
|
||||||
UpdatePSBTOutput(public_provider, psbtx, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
|
|
|
@ -2184,34 +2184,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
|
RemoveUnnecessaryTransactions(psbtx, sighash_type);
|
||||||
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
|
|
||||||
// Figure out if any non_witness_utxos should be dropped
|
|
||||||
std::vector<unsigned int> to_drop;
|
|
||||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
|
||||||
const auto& input = psbtx.inputs.at(i);
|
|
||||||
int wit_ver;
|
|
||||||
std::vector<unsigned char> wit_prog;
|
|
||||||
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
|
|
||||||
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
|
|
||||||
to_drop.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (wit_ver == 0) {
|
|
||||||
// Segwit v0, so we cannot drop any non_witness_utxos
|
|
||||||
to_drop.clear();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (input.non_witness_utxo) {
|
|
||||||
to_drop.push_back(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Drop the non_witness_utxos that we can drop
|
|
||||||
for (unsigned int i : to_drop) {
|
|
||||||
psbtx.inputs.at(i).non_witness_utxo = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Complete if every input is now signed
|
// Complete if every input is now signed
|
||||||
complete = true;
|
complete = true;
|
||||||
|
|
|
@ -621,17 +621,17 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
|
# 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)
|
updated = self.nodes[1].utxoupdatepsbt(psbt)
|
||||||
decoded = self.nodes[1].decodepsbt(updated)
|
decoded = self.nodes[1].decodepsbt(updated)
|
||||||
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
|
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo'])
|
||||||
test_psbt_input_keys(decoded['inputs'][1], [])
|
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo'])
|
||||||
test_psbt_input_keys(decoded['inputs'][2], [])
|
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo'])
|
||||||
|
|
||||||
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
|
# 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]]
|
descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
|
||||||
updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs)
|
updated = self.nodes[1].utxoupdatepsbt(psbt=psbt, descriptors=descs)
|
||||||
decoded = self.nodes[1].decodepsbt(updated)
|
decoded = self.nodes[1].decodepsbt(updated)
|
||||||
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs'])
|
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'non_witness_utxo', 'bip32_derivs'])
|
||||||
test_psbt_input_keys(decoded['inputs'][1], [])
|
test_psbt_input_keys(decoded['inputs'][1], ['non_witness_utxo', 'bip32_derivs'])
|
||||||
test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script'])
|
test_psbt_input_keys(decoded['inputs'][2], ['non_witness_utxo','witness_utxo', 'bip32_derivs', 'redeem_script'])
|
||||||
|
|
||||||
# Two PSBTs with a common input should not be joinable
|
# Two PSBTs with a common input should not be joinable
|
||||||
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})
|
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})
|
||||||
|
|
Loading…
Add table
Reference in a new issue