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:
Andrew Poelstra 2020-11-14 16:51:53 +00:00
commit c7bf5baf96
11 changed files with 365 additions and 71 deletions

View file

@ -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" },

View file

@ -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
{

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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"} },

View file

@ -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;
}

View file

@ -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

View file

@ -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())

View file

@ -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")

View file

@ -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