mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
Merge 526e802d69 into merged_master (Elements PR #755)
This Elements PR includes components of Core PR #17211, which since the refactors to use effective value landed, no longer provides the right error message when a user provides an unowned input from a wallet tx. See https://github.com/bitcoin/bitcoin/pull/17211#pullrequestreview-528389011 This breaks a functional test which was included in this PR, but which conveniently has been changed in the current version of the Core PR. I fixed the behavior (commented, in SelectCoins) rather than updating the test to the most recent version.
This commit is contained in:
commit
c7bf5baf96
11 changed files with 365 additions and 71 deletions
|
|
@ -104,11 +104,13 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
|||
{ "combinerawtransaction", 0, "txs" },
|
||||
{ "fundrawtransaction", 1, "options" },
|
||||
{ "fundrawtransaction", 2, "iswitness" },
|
||||
{ "fundrawtransaction", 3, "solving_data" },
|
||||
{ "walletcreatefundedpsbt", 0, "inputs" },
|
||||
{ "walletcreatefundedpsbt", 1, "outputs" },
|
||||
{ "walletcreatefundedpsbt", 2, "locktime" },
|
||||
{ "walletcreatefundedpsbt", 3, "options" },
|
||||
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
|
||||
{ "walletcreatefundedpsbt", 5, "solving_data" },
|
||||
{ "walletprocesspsbt", 1, "sign" },
|
||||
{ "walletprocesspsbt", 3, "bip32derivs" },
|
||||
{ "walletfillpsbtdata", 1, "bip32derivs" },
|
||||
|
|
|
|||
|
|
@ -8,12 +8,11 @@
|
|||
|
||||
#include <key.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/keyorigin.h>
|
||||
#include <script/script.h>
|
||||
#include <script/standard.h>
|
||||
#include <sync.h>
|
||||
|
||||
struct KeyOriginInfo;
|
||||
|
||||
/** An interface to be implemented by keystores that support signing. */
|
||||
class SigningProvider
|
||||
{
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ void CCoinControl::SetNull()
|
|||
m_confirm_target.reset();
|
||||
m_signal_bip125_rbf.reset();
|
||||
m_fee_mode = FeeEstimateMode::UNSET;
|
||||
m_external_txouts.clear();
|
||||
m_external_provider = FlatSigningProvider();
|
||||
m_min_depth = DEFAULT_MIN_DEPTH;
|
||||
m_max_depth = DEFAULT_MAX_DEPTH;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@
|
|||
#define BITCOIN_WALLET_COINCONTROL_H
|
||||
|
||||
#include <asset.h>
|
||||
#include <chainparams.h>
|
||||
#include <optional.h>
|
||||
#include <outputtype.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/fees.h>
|
||||
#include <primitives/bitcoin/transaction.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/standard.h>
|
||||
|
||||
|
|
@ -45,6 +47,8 @@ public:
|
|||
bool m_avoid_address_reuse;
|
||||
//! Fee estimation mode to control arguments to estimateSmartFee
|
||||
FeeEstimateMode m_fee_mode;
|
||||
//! SigningProvider that has pubkeys and scripts to do spend size estimation for external inputs
|
||||
FlatSigningProvider m_external_provider;
|
||||
//! Minimum chain depth value for coin availability
|
||||
int m_min_depth = DEFAULT_MIN_DEPTH;
|
||||
//! Maximum chain depth value for coin availability
|
||||
|
|
@ -67,11 +71,42 @@ public:
|
|||
return (setSelected.count(output) > 0);
|
||||
}
|
||||
|
||||
bool IsExternalSelected(const COutPoint& output) const
|
||||
{
|
||||
return (m_external_txouts.count(output) > 0);
|
||||
}
|
||||
|
||||
bool GetExternalOutput(const COutPoint& outpoint, CTxOut& txout) const
|
||||
{
|
||||
const auto ext_it = m_external_txouts.find(outpoint);
|
||||
if (ext_it == m_external_txouts.end()) {
|
||||
return false;
|
||||
}
|
||||
txout = ext_it->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Select(const COutPoint& output)
|
||||
{
|
||||
setSelected.insert(output);
|
||||
}
|
||||
|
||||
void SelectExternal(const COutPoint& outpoint, const CTxOut& txout)
|
||||
{
|
||||
setSelected.insert(outpoint);
|
||||
m_external_txouts.emplace(outpoint, txout);
|
||||
}
|
||||
|
||||
void Select(const COutPoint& outpoint, const Sidechain::Bitcoin::CTxOut& txout_in)
|
||||
{
|
||||
setSelected.insert(outpoint);
|
||||
CTxOut txout;
|
||||
txout.scriptPubKey = txout_in.scriptPubKey;
|
||||
txout.nValue.SetToAmount(txout_in.nValue);
|
||||
txout.nAsset.SetToAsset(Params().GetConsensus().pegged_asset);
|
||||
m_external_txouts.emplace(outpoint, txout);
|
||||
}
|
||||
|
||||
void UnSelect(const COutPoint& output)
|
||||
{
|
||||
setSelected.erase(output);
|
||||
|
|
@ -89,6 +124,7 @@ public:
|
|||
|
||||
private:
|
||||
std::set<COutPoint> setSelected;
|
||||
std::map<COutPoint, CTxOut> m_external_txouts;
|
||||
};
|
||||
|
||||
#endif // BITCOIN_WALLET_COINCONTROL_H
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@
|
|||
#define BITCOIN_WALLET_COINSELECTION_H
|
||||
|
||||
#include <amount.h>
|
||||
#include <chainparams.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <primitives/bitcoin/transaction.h>
|
||||
#include <random.h>
|
||||
|
||||
//! target minimum change amount
|
||||
|
|
@ -26,6 +28,41 @@ public:
|
|||
m_input_bytes = input_bytes;
|
||||
}
|
||||
|
||||
CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in)
|
||||
{
|
||||
outpoint = outpoint_in;
|
||||
txout = txout_in;
|
||||
if (txout.nValue.IsExplicit()) {
|
||||
effective_value = txout_in.nValue.GetAmount();
|
||||
value = txout.nValue.GetAmount();
|
||||
asset = txout.nAsset.GetAsset();
|
||||
} else {
|
||||
effective_value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
CInputCoin(const COutPoint& outpoint_in, const CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
|
||||
{
|
||||
m_input_bytes = input_bytes;
|
||||
}
|
||||
|
||||
CInputCoin(const COutPoint& outpoint_in, const Sidechain::Bitcoin::CTxOut& txout_in)
|
||||
{
|
||||
outpoint = outpoint_in;
|
||||
effective_value = txout_in.nValue;
|
||||
txout.SetNull();
|
||||
txout.scriptPubKey = txout_in.scriptPubKey;
|
||||
txout.nValue.SetToAmount(txout_in.nValue);
|
||||
txout.nAsset.SetToAsset(Params().GetConsensus().pegged_asset);
|
||||
asset = Params().GetConsensus().pegged_asset;
|
||||
value = txout_in.nValue;
|
||||
}
|
||||
|
||||
CInputCoin(const COutPoint& outpoint_in, const Sidechain::Bitcoin::CTxOut& txout_in, int input_bytes) : CInputCoin(outpoint_in, txout_in)
|
||||
{
|
||||
m_input_bytes = input_bytes;
|
||||
}
|
||||
|
||||
COutPoint outpoint;
|
||||
CTxOut txout;
|
||||
CAmount effective_value;
|
||||
|
|
|
|||
|
|
@ -3275,7 +3275,7 @@ static UniValue listunspent(const JSONRPCRequest& request)
|
|||
return results;
|
||||
}
|
||||
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options)
|
||||
void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& fee_out, int& change_position, UniValue options, const UniValue& solving_data)
|
||||
{
|
||||
// Make sure the results are valid at least up to the most recent block
|
||||
// the user could have gotten from another RPC command prior to now
|
||||
|
|
@ -3395,6 +3395,41 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|||
coinControl.fAllowWatchOnly = ParseIncludeWatchonly(NullUniValue, *pwallet);
|
||||
}
|
||||
|
||||
if (!solving_data.isNull()) {
|
||||
if (solving_data.exists("pubkeys")) {
|
||||
UniValue pubkey_strs = solving_data["pubkeys"].get_array();
|
||||
for (unsigned int i = 0; i < pubkey_strs.size(); ++i) {
|
||||
std::vector<unsigned char> data(ParseHex(pubkey_strs[i].get_str()));
|
||||
CPubKey pubkey(data.begin(), data.end());
|
||||
if (!pubkey.IsFullyValid()) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s is not a valid public key", pubkey_strs[i].get_str()));
|
||||
}
|
||||
coinControl.m_external_provider.pubkeys.emplace(pubkey.GetID(), pubkey);
|
||||
// Add witnes script for pubkeys
|
||||
CScript wit_script = GetScriptForDestination(WitnessV0KeyHash(pubkey.GetID()));
|
||||
coinControl.m_external_provider.scripts.emplace(CScriptID(wit_script), wit_script);
|
||||
}
|
||||
}
|
||||
|
||||
if (solving_data.exists("scripts")) {
|
||||
UniValue script_strs = solving_data["scripts"].get_array();
|
||||
for (unsigned int i = 0; i < script_strs.size(); ++i) {
|
||||
CScript script = ParseScript(script_strs[i].get_str());
|
||||
coinControl.m_external_provider.scripts.emplace(CScriptID(script), script);
|
||||
}
|
||||
}
|
||||
|
||||
if (solving_data.exists("descriptors")) {
|
||||
UniValue desc_strs = solving_data["descriptors"].get_array();
|
||||
for (unsigned int i = 0; i < desc_strs.size(); ++i) {
|
||||
FlatSigningProvider desc_out;
|
||||
std::string error;
|
||||
std::unique_ptr<Descriptor> desc = Parse(desc_strs[i].get_str(), desc_out, error, true);
|
||||
coinControl.m_external_provider = Merge(coinControl.m_external_provider, desc_out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.vout.size() == 0)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
|
||||
|
||||
|
|
@ -3412,6 +3447,42 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
|
|||
setSubtractFeeFromOutputs.insert(pos);
|
||||
}
|
||||
|
||||
// Check any existing inputs for peg-in data and add to external txouts if so
|
||||
// Fetch specified UTXOs from the UTXO set
|
||||
const auto& fedpegscripts = GetValidFedpegScripts(::ChainActive().Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
std::map<COutPoint, Coin> coins;
|
||||
for (unsigned int i = 0; i < tx.vin.size(); ++i ) {
|
||||
const CTxIn& txin = tx.vin[i];
|
||||
coins[txin.prevout]; // Create empty map entry keyed by prevout.
|
||||
if (txin.m_is_pegin) {
|
||||
std::string err;
|
||||
if (tx.witness.vtxinwit.size() != tx.vin.size() || !IsValidPeginWitness(tx.witness.vtxinwit[i].m_pegin_witness, fedpegscripts, txin.prevout, err, false)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Transaction contains invalid peg-in input: %s", err));
|
||||
}
|
||||
CScriptWitness& pegin_witness = tx.witness.vtxinwit[i].m_pegin_witness;
|
||||
CTxOut txout = GetPeginOutputFromWitness(pegin_witness);
|
||||
coinControl.SelectExternal(txin.prevout, txout);
|
||||
}
|
||||
}
|
||||
CCoinsView viewDummy;
|
||||
CCoinsViewCache view(&viewDummy);
|
||||
{
|
||||
LOCK2(cs_main, mempool.cs);
|
||||
CCoinsViewCache& chain_view = ::ChainstateActive().CoinsTip();
|
||||
CCoinsViewMemPool mempool_view(&chain_view, mempool);
|
||||
for (auto& coin : coins) {
|
||||
if (!mempool_view.GetCoin(coin.first, coin.second)) {
|
||||
// Either the coin is not in the CCoinsViewCache or is spent. Clear it.
|
||||
coin.second.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& coin : coins) {
|
||||
if (!coin.second.out.IsNull()) {
|
||||
coinControl.SelectExternal(coin.first, coin.second.out);
|
||||
}
|
||||
}
|
||||
|
||||
std::string strFailReason;
|
||||
|
||||
if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
|
||||
|
|
@ -3476,6 +3547,25 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
|||
"This boolean should reflect whether the transaction has inputs\n"
|
||||
"(e.g. fully valid, or on-chain transactions), if known by the caller."
|
||||
},
|
||||
{"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature. Used for fee estimation during coin selection.\n",
|
||||
{
|
||||
{"pubkeys", RPCArg::Type::ARR, /* default */ "empty array", "A json array of public keys.\n",
|
||||
{
|
||||
{"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
|
||||
},
|
||||
},
|
||||
{"scripts", RPCArg::Type::ARR, /* default */ "empty array", "A json array of scripts.\n",
|
||||
{
|
||||
{"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
|
||||
},
|
||||
},
|
||||
{"descriptors", RPCArg::Type::ARR, /* default */ "empty array", "A json array of descriptors.\n",
|
||||
{
|
||||
{"descriptor", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A descriptor"},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
RPCResult{
|
||||
"{\n"
|
||||
|
|
@ -3508,7 +3598,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
|
|||
|
||||
CAmount fee;
|
||||
int change_position;
|
||||
FundTransaction(pwallet, tx, fee, change_position, request.params[1]);
|
||||
FundTransaction(pwallet, tx, fee, change_position, request.params[1], request.params[3]);
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("hex", EncodeHexTx(CTransaction(tx)));
|
||||
|
|
@ -4658,6 +4748,9 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
|||
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
|
||||
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
|
||||
{"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"},
|
||||
{"pegin_bitcoin_tx", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress"},
|
||||
{"pegin_txout_proof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A rawtxoutproof (in hex) generated by the mainchain daemon's `gettxoutproof` containing a proof of only bitcoin_tx"},
|
||||
{"pegin_claim_script", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The witness program generated by getpeginaddress."},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -4706,6 +4799,25 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
|||
},
|
||||
"options"},
|
||||
{"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if we know them"},
|
||||
{"solving_data", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "Keys and scripts needed for producing a final transaction with a dummy signature. Used for fee estimation during coin selection.\n",
|
||||
{
|
||||
{"pubkeys", RPCArg::Type::ARR, /* default */ "empty array", "A json array of public keys.\n",
|
||||
{
|
||||
{"pubkey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A public key"},
|
||||
},
|
||||
},
|
||||
{"scripts", RPCArg::Type::ARR, /* default */ "empty array", "A json array of scripts.\n",
|
||||
{
|
||||
{"script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A script"},
|
||||
},
|
||||
},
|
||||
{"descriptors", RPCArg::Type::ARR, /* default */ "empty array", "A json array of descriptors.\n",
|
||||
{
|
||||
{"descriptor", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A descriptor"},
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
RPCResult{
|
||||
"{\n"
|
||||
|
|
@ -4740,8 +4852,8 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
|||
// It's hard to control the behavior of FundTransaction, so we will wait
|
||||
// until after it's done, then extract the blinding keys from the output
|
||||
// nonces.
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, NullUniValue /* CA: assets_in */, nullptr /* output_pubkeys_out */, false /* allow_peg_in */);
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, NullUniValue /* CA: assets_in */, nullptr /* output_pubkeys_out */, true /* allow_peg_in */);
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3], request.params[5]);
|
||||
|
||||
// Make a blank psbt
|
||||
PartiallySignedTransaction psbtx(rawTx);
|
||||
|
|
@ -4761,6 +4873,42 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
|||
throw JSONRPCTransactionError(err);
|
||||
}
|
||||
|
||||
// Add peg-in stuff if it's there
|
||||
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
|
||||
if (psbtx.tx->vin[i].m_is_pegin) {
|
||||
CScriptWitness& pegin_witness = psbtx.tx->witness.vtxinwit[i].m_pegin_witness;
|
||||
CAmount val;
|
||||
VectorReader vr_val(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[0], 0);
|
||||
vr_val >> val;
|
||||
psbtx.inputs[i].value = val;
|
||||
VectorReader vr_asset(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[1], 0);
|
||||
vr_asset >> psbtx.inputs[i].asset;
|
||||
VectorReader vr_genesis(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[2], 0);
|
||||
vr_genesis >> psbtx.inputs[i].genesis_hash;
|
||||
psbtx.inputs[i].claim_script.assign(pegin_witness.stack[3].begin(), pegin_witness.stack[3].end());
|
||||
|
||||
VectorReader vr_tx(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[4], 0);
|
||||
VectorReader vr_proof(SER_NETWORK, PROTOCOL_VERSION, pegin_witness.stack[5], 0);
|
||||
if (Params().GetConsensus().ParentChainHasPow()) {
|
||||
Sidechain::Bitcoin::CTransactionRef tx_btc;
|
||||
vr_tx >> tx_btc;
|
||||
psbtx.inputs[i].peg_in_tx = tx_btc;
|
||||
Sidechain::Bitcoin::CMerkleBlock tx_proof;
|
||||
vr_proof >> tx_proof;
|
||||
psbtx.inputs[i].txout_proof = tx_proof;
|
||||
} else {
|
||||
CTransactionRef tx_btc;
|
||||
vr_tx >> tx_btc;
|
||||
psbtx.inputs[i].peg_in_tx = tx_btc;
|
||||
CMerkleBlock tx_proof;
|
||||
vr_proof >> tx_proof;
|
||||
psbtx.inputs[i].txout_proof = tx_proof;
|
||||
}
|
||||
pegin_witness.SetNull();
|
||||
psbtx.tx->vin[i].m_is_pegin = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize the PSBT
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << psbtx;
|
||||
|
|
@ -6641,7 +6789,7 @@ UniValue getwalletpakinfo(const JSONRPCRequest& request);
|
|||
static const CRPCCommand commands[] =
|
||||
{ // category name actor (function) argNames
|
||||
// --------------------- ------------------------ ----------------------- ----------
|
||||
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
|
||||
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness","solving_data"} },
|
||||
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
|
||||
{ "wallet", "abortrescan", &abortrescan, {} },
|
||||
{ "wallet", "addmultisigaddress", &addmultisigaddress, {"nrequired","keys","label","address_type"} },
|
||||
|
|
@ -6692,7 +6840,7 @@ static const CRPCCommand commands[] =
|
|||
{ "wallet", "signmessage", &signmessage, {"address","message"} },
|
||||
{ "wallet", "signrawtransactionwithwallet", &signrawtransactionwithwallet, {"hexstring","prevtxs","sighashtype"} },
|
||||
{ "wallet", "unloadwallet", &unloadwallet, {"wallet_name"} },
|
||||
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} },
|
||||
{ "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs","solving_data"} },
|
||||
{ "wallet", "walletlock", &walletlock, {} },
|
||||
{ "wallet", "walletpassphrase", &walletpassphrase, {"passphrase","timeout"} },
|
||||
{ "wallet", "walletpassphrasechange", &walletpassphrasechange, {"oldpassphrase","newpassphrase"} },
|
||||
|
|
|
|||
|
|
@ -1403,18 +1403,12 @@ int64_t CWalletTx::GetTxTime() const
|
|||
|
||||
// Helper for producing a max-sized low-S low-R signature (eg 71 bytes)
|
||||
// or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true
|
||||
bool CWallet::DummySignInput(CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig) const
|
||||
static bool DummySignInput(const SigningProvider* provider, CMutableTransaction& tx, const size_t nIn, const CTxOut& txout, bool use_max_sig)
|
||||
{
|
||||
// Fill in dummy signatures for fee calculation.
|
||||
const CScript& scriptPubKey = txout.scriptPubKey;
|
||||
SignatureData sigdata;
|
||||
|
||||
const SigningProvider* provider = GetSigningProvider(scriptPubKey);
|
||||
if (!provider) {
|
||||
// We don't know about this scriptpbuKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1423,14 +1417,19 @@ bool CWallet::DummySignInput(CMutableTransaction& tx, const size_t nIn, const CT
|
|||
}
|
||||
|
||||
// Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 bytes)
|
||||
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const
|
||||
bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control) const
|
||||
{
|
||||
// Fill in dummy signatures for fee calculation.
|
||||
int nIn = 0;
|
||||
for (const auto& txout : txouts)
|
||||
{
|
||||
if (!DummySignInput(txNew, nIn, txout, use_max_sig)) {
|
||||
return false;
|
||||
const SigningProvider* provider = GetSigningProvider(txout.scriptPubKey);
|
||||
// Use max sig if watch only inputs were used or if this particular input is an external input
|
||||
bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || (coin_control && coin_control->IsExternalSelected(txNew.vin[nIn].prevout)));
|
||||
if (!provider || !DummySignInput(provider, txNew, nIn, txout, use_max_sig)) {
|
||||
if (!coin_control || !DummySignInput(&coin_control->m_external_provider, txNew, nIn, txout, use_max_sig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
nIn++;
|
||||
|
|
@ -1491,43 +1490,53 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri
|
|||
return true;
|
||||
}
|
||||
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig)
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control)
|
||||
{
|
||||
std::vector<CTxOut> txouts;
|
||||
// Look up the inputs. The inputs are either in the wallet, or in coin_control.
|
||||
for (const CTxIn& input : tx.vin) {
|
||||
const auto mi = wallet->mapWallet.find(input.prevout.hash);
|
||||
// Can not estimate size without knowing the input details
|
||||
if (mi == wallet->mapWallet.end()) {
|
||||
if (mi != wallet->mapWallet.end()) {
|
||||
assert(input.prevout.n < mi->second.tx->vout.size());
|
||||
txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
|
||||
} else if (coin_control) {
|
||||
CTxOut txout;
|
||||
if (!coin_control->GetExternalOutput(input.prevout, txout)) {
|
||||
return -1;
|
||||
}
|
||||
txouts.emplace_back(txout);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
assert(input.prevout.n < mi->second.tx->vout.size());
|
||||
txouts.emplace_back(mi->second.tx->vout[input.prevout.n]);
|
||||
}
|
||||
return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig);
|
||||
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
|
||||
}
|
||||
|
||||
// txouts needs to be in the order of tx.vin
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig)
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control)
|
||||
{
|
||||
CMutableTransaction txNew(tx);
|
||||
if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) {
|
||||
if (!wallet->DummySignTx(txNew, txouts, coin_control)) {
|
||||
return -1;
|
||||
}
|
||||
return GetVirtualTransactionSize(CTransaction(txNew));
|
||||
}
|
||||
|
||||
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
|
||||
{
|
||||
int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig) {
|
||||
CMutableTransaction txn;
|
||||
txn.vin.push_back(CTxIn(COutPoint()));
|
||||
if (!wallet->DummySignInput(txn, 0, txout, use_max_sig)) {
|
||||
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
|
||||
// implies that we can sign for every input.
|
||||
if (!provider || !DummySignInput(provider, txn, 0, txout, use_max_sig)) {
|
||||
return -1;
|
||||
}
|
||||
return GetVirtualTransactionInputSize(CTransaction(txn));
|
||||
}
|
||||
|
||||
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
|
||||
{
|
||||
const SigningProvider* provider = wallet->GetSigningProvider(txout.scriptPubKey);
|
||||
return CalculateMaximumSignedInputSize(txout, provider, use_max_sig);
|
||||
}
|
||||
|
||||
void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
|
||||
std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const
|
||||
{
|
||||
|
|
@ -2400,33 +2409,66 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
|
|||
for (const COutPoint& outpoint : vPresetInputs)
|
||||
{
|
||||
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
|
||||
if (it != mapWallet.end())
|
||||
{
|
||||
// ELEMENTS: this code pulled from unmerged Core PR #17211
|
||||
int input_bytes = -1;
|
||||
CTxOut txout;
|
||||
CInputCoin coin(outpoint, txout, 0); // dummy initialization
|
||||
if (it != mapWallet.end()) {
|
||||
const CWalletTx& wtx = it->second;
|
||||
// Clearly invalid input, fail
|
||||
if (wtx.tx->vout.size() <= outpoint.n) {
|
||||
return false;
|
||||
}
|
||||
// Just to calculate the marginal byte size
|
||||
CAmount amt = wtx.GetOutputValueOut(outpoint.n);
|
||||
if (amt < 0) {
|
||||
if (wtx.GetOutputValueOut(outpoint.n) < 0) {
|
||||
continue;
|
||||
}
|
||||
CInputCoin coin(&wtx, outpoint.n, wtx.GetSpendSize(outpoint.n, false));
|
||||
mapValueFromPresetInputs[wtx.GetOutputAsset(outpoint.n)] += amt;
|
||||
if (coin.m_input_bytes <= 0) {
|
||||
return false; // Not solvable, can't estimate size for fee
|
||||
}
|
||||
coin.effective_value = coin.value - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
|
||||
if (coin_selection_params.use_bnb) {
|
||||
value_to_select[coin.asset] -= coin.effective_value;
|
||||
} else {
|
||||
value_to_select[coin.asset] -= coin.value;
|
||||
}
|
||||
setPresetCoins.insert(coin);
|
||||
} else {
|
||||
return false; // TODO: Allow non-wallet inputs
|
||||
input_bytes = wtx.GetSpendSize(outpoint.n, false);
|
||||
txout = wtx.tx->vout[outpoint.n];
|
||||
// ELEMENTS: must assign coin from wtx if we can, so the wallet
|
||||
// can look up any confidential amounts/assets
|
||||
coin = CInputCoin(&wtx, outpoint.n, input_bytes);
|
||||
}
|
||||
if (input_bytes == -1) {
|
||||
// The input is external. We either did not find the tx in mapWallet, or we did but couldn't compute the input size with wallet data
|
||||
if (!coin_control.GetExternalOutput(outpoint, txout)) {
|
||||
// Not ours, and we don't have solving data.
|
||||
return false;
|
||||
}
|
||||
input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /* use_max_sig */ true);
|
||||
// ELEMENTS: one more try to get a signed input size: for pegins,
|
||||
// the outpoint is provided as external data but the information
|
||||
// needed to spend is in the wallet (not the external provider,
|
||||
// as the user is expecting the wallet to remember this information
|
||||
// after they called getpeginaddress). So try estimating size with
|
||||
// the wallet rather than the external provider.
|
||||
if (input_bytes == -1) {
|
||||
input_bytes = CalculateMaximumSignedInputSize(txout, this, /* use_max_sig */ true);
|
||||
}
|
||||
if (!txout.nValue.IsExplicit() || !txout.nAsset.IsExplicit()) {
|
||||
return false; // We can't get its value, so abort
|
||||
}
|
||||
coin = CInputCoin(outpoint, txout, input_bytes);
|
||||
}
|
||||
|
||||
mapValueFromPresetInputs[coin.asset] += coin.value;
|
||||
if (coin.m_input_bytes <= 0) {
|
||||
// ELEMENTS: if we're here we can't compute the coin's effective value. At
|
||||
// this point in the rebase this is only used for BnB, and our functional
|
||||
// tests expect the user to get a "missing data" error rather than an
|
||||
// "insufficient funds" error, which means we need some way to make
|
||||
// SelectCoins pass. So rather than "return false;" as in upstream we
|
||||
// just turn off bnb and keep going.
|
||||
coin_selection_params.use_bnb = false;
|
||||
coin.m_input_bytes = 0;
|
||||
}
|
||||
coin.effective_value = coin.value - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes);
|
||||
if (coin_selection_params.use_bnb) {
|
||||
value_to_select[coin.asset] -= coin.effective_value;
|
||||
} else {
|
||||
value_to_select[coin.asset] -= coin.value;
|
||||
}
|
||||
setPresetCoins.insert(coin);
|
||||
}
|
||||
|
||||
// remove preset inputs from vCoins
|
||||
|
|
@ -2528,7 +2570,6 @@ bool CWallet::SignTransaction(CMutableTransaction& tx)
|
|||
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, std::string& strFailReason, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
|
||||
{
|
||||
std::vector<CRecipient> vecSend;
|
||||
std::set<CAsset> setAssets;
|
||||
|
||||
// Turn the txout set into a CRecipient vector.
|
||||
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
|
||||
|
|
@ -2540,9 +2581,6 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
|
|||
return false;
|
||||
}
|
||||
|
||||
// Account for the asset in the possible change destinations.
|
||||
setAssets.insert(txOut.nAsset.GetAsset());
|
||||
|
||||
// Fee outputs should not be added to avoid overpayment of fees
|
||||
if (txOut.IsFee()) {
|
||||
continue;
|
||||
|
|
@ -2892,13 +2930,18 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
|||
std::vector<COutPoint> vPresetInputs;
|
||||
coin_control.ListSelected(vPresetInputs);
|
||||
for (const COutPoint& presetInput : vPresetInputs) {
|
||||
CAsset asset;
|
||||
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
|
||||
if (it == mapWallet.end()) {
|
||||
CTxOut txout;
|
||||
if (it != mapWallet.end()) {
|
||||
asset = it->second.GetOutputAsset(presetInput.n);
|
||||
} else if (coin_control.GetExternalOutput(presetInput, txout)) {
|
||||
asset = txout.nAsset.GetAsset();
|
||||
} else {
|
||||
// Ignore this here, will fail more gracefully later.
|
||||
continue;
|
||||
}
|
||||
|
||||
CAsset asset = it->second.GetOutputAsset(presetInput.n);
|
||||
if (mapScriptChange.find(asset) != mapScriptChange.end()) {
|
||||
// This asset already has a change script.
|
||||
continue;
|
||||
|
|
@ -3247,9 +3290,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std
|
|||
}
|
||||
}
|
||||
|
||||
nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
|
||||
nBytes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, &coin_control);
|
||||
if (nBytes < 0) {
|
||||
strFailReason = _("Signing transaction failed").translated;
|
||||
strFailReason = _("Missing solving data for estimating transaction size").translated;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1045,14 +1045,13 @@ public:
|
|||
*/
|
||||
void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, const BlindDetails* blind_details = nullptr);
|
||||
|
||||
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
|
||||
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const
|
||||
{
|
||||
std::vector<CTxOut> v_txouts(txouts.size());
|
||||
std::copy(txouts.begin(), txouts.end(), v_txouts.begin());
|
||||
return DummySignTx(txNew, v_txouts, use_max_sig);
|
||||
return DummySignTx(txNew, v_txouts, coin_control);
|
||||
}
|
||||
bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const;
|
||||
bool DummySignInput(CMutableTransaction &tx, const size_t nIn, const CTxOut &txout, bool use_max_sig = false) const;
|
||||
bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, const CCoinControl* coin_control = nullptr) const;
|
||||
|
||||
bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
bool ImportPrivKeys(const std::map<CKeyID, CKey>& privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
|
||||
|
|
@ -1354,8 +1353,6 @@ public:
|
|||
|
||||
// Calculate the size of the transaction assuming all signatures are max size
|
||||
// Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
|
||||
// NOTE: this requires that all inputs must be in mapWallet (eg the tx should
|
||||
// be IsAllFromMe).
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
|
||||
int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
|
||||
#endif // BITCOIN_WALLET_WALLET_H
|
||||
|
|
|
|||
|
|
@ -13,7 +13,8 @@ from test_framework.util import (
|
|||
assert_raises_rpc_error,
|
||||
assert_equal,
|
||||
hex_str_to_bytes,
|
||||
find_vout_for_address
|
||||
find_vout_for_address,
|
||||
assert_greater_than
|
||||
)
|
||||
from test_framework import util
|
||||
from test_framework.messages import (
|
||||
|
|
@ -317,6 +318,19 @@ class FedPegTest(BitcoinTestFramework):
|
|||
fin_psbt = sidechain.finalizepsbt(signed_psbt['psbt'])
|
||||
assert_equal(fin_psbt, signed_pegin)
|
||||
|
||||
# Try funding a psbt with the peg-in
|
||||
assert_equal(sidechain.getbalance()['bitcoin'], 50)
|
||||
out_bal = 0
|
||||
outputs.append({sidechain.getnewaddress(): 49.999})
|
||||
for out in outputs:
|
||||
for val in out.values():
|
||||
out_bal += Decimal(val)
|
||||
assert_greater_than(out_bal, 50)
|
||||
pegin_psbt = sidechain.walletcreatefundedpsbt([{"txid":txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"]}], outputs)
|
||||
signed_psbt = sidechain.walletsignpsbt(pegin_psbt['psbt'])
|
||||
fin_psbt = sidechain.finalizepsbt(signed_psbt['psbt'])
|
||||
assert fin_psbt['complete']
|
||||
|
||||
sample_pegin_struct = FromHex(CTransaction(), signed_pegin["hex"])
|
||||
# Round-trip peg-in transaction using python serialization
|
||||
assert_equal(signed_pegin["hex"], sample_pegin_struct.serialize().hex())
|
||||
|
|
|
|||
|
|
@ -768,6 +768,27 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
# The total subtracted from the outputs is equal to the fee.
|
||||
assert_equal(share[0] + share[2] + share[3], result[0]['fee'])
|
||||
|
||||
n0_blind_addr = self.nodes[0].getnewaddress()
|
||||
addr_info = self.nodes[0].getaddressinfo(n0_blind_addr)
|
||||
txid = self.nodes[2].sendtoaddress(addr_info['unconfidential'], 10)
|
||||
self.sync_all()
|
||||
vout = find_vout_for_address(self.nodes[0], txid, n0_blind_addr)
|
||||
self.nodes[0].generate(1)
|
||||
self.sync_all()
|
||||
|
||||
# An external input without solving data should result in an error
|
||||
raw_tx = self.nodes[2].createrawtransaction([{"txid": txid, "vout": vout}], {addr_info['unconfidential']: 20})
|
||||
assert_raises_rpc_error(-4, "Missing solving data for estimating transaction size", self.nodes[2].fundrawtransaction, raw_tx)
|
||||
|
||||
# But funding should work when the solving data is provided
|
||||
funded_tx = self.nodes[2].fundrawtransaction(raw_tx, {}, False, {"pubkeys": [addr_info['pubkey']]})
|
||||
signed_tx = self.nodes[2].signrawtransactionwithwallet(funded_tx['hex'])
|
||||
assert not signed_tx['complete']
|
||||
signed_tx = self.nodes[0].signrawtransactionwithwallet(signed_tx['hex'])
|
||||
assert signed_tx['complete']
|
||||
# Don't send because we didn't blind it so it's not actually valid.
|
||||
# self.nodes[0].sendrawtransaction(signed_tx['hex'])
|
||||
|
||||
def test_subtract_fee_with_presets(self):
|
||||
self.log.info("Test fundrawtxn subtract fee from outputs with preset inputs that are sufficient")
|
||||
|
||||
|
|
|
|||
|
|
@ -559,11 +559,6 @@ class PSBTTest(BitcoinTestFramework):
|
|||
# Some Confidential-Assets-specific tests
|
||||
self.run_ca_tests()
|
||||
|
||||
# Check that peg-ins are disallowed for walletcreatefundedpsbt
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_bitcoin_tx": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_txout_proof": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_claim_script": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
|
||||
self.test_utxo_conversion()
|
||||
|
||||
# Test that psbts with p2pkh outputs are created properly
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue