Merge #755: Be able to fund transactions with peg-ins specified

00eb6c097 Test funding peg-in psbt (Andrew Chow)
1ce74e07f Be able to fund transactions with peg-ins (Andrew Chow)
74c4d3778 Tests for fundrawtransaction with external inputs (Andrew Chow)
c25874672 allow fundtx rpcs to work with external inputs (Andrew Chow)
9486cdf54 Give a better error when tx size estimation fails (Andrew Chow)
8258aaa73 Allow Coin Selection be able to take external inputs (Andrew Chow)
698340b62 Allow CInputCoin to also be constructed with COutPoint and CTxOut (Andrew Chow)

Pull request description:

  Allows `fundrawtransaction` and `walletcreatefundedpsbt` to take transactions with pre-selected peg-in inputs and take them into account when selecting additional inputs to meet the output amounts.

  As a side effect, those RPCs will also now allow pre-selected non-wallet inputs so long as the correct additional solving data is provided (scripts and pubkeys are needed to estimate the size for transaction fees).

  Based on #751

  Ports https://github.com/bitcoin/bitcoin/pull/17211 with some modification to work with elements outputs and such.

Tree-SHA512: 813202adfe2b63780bea01b02975dda393197a489a6c1a9590908ac7252d1d0e5f0984125fbfd1dc271524ddad84dc1778a1e8fa447e61235c7f21c6e6999460
This commit is contained in:
Gregory Sanders 2019-12-04 14:57:57 -05:00
commit 526e802d69
No known key found for this signature in database
GPG key ID: F3F68E2D86A48FDB
10 changed files with 320 additions and 44 deletions

View file

@ -100,11 +100,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

@ -19,5 +19,7 @@ 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();
}

View file

@ -37,6 +37,8 @@ public:
bool m_avoid_partial_spends;
//! 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;
CCoinControl()
{
@ -55,11 +57,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);
@ -77,6 +110,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

@ -3212,7 +3212,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
@ -3330,6 +3330,40 @@ void FundTransaction(CWallet* const pwallet, CMutableTransaction& tx, CAmount& f
}
}
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::unique_ptr<Descriptor> desc = Parse(desc_strs[i].get_str(), desc_out, 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");
@ -3347,6 +3381,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 = *pcoinsTip;
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)) {
@ -3363,7 +3433,7 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() < 1 || request.params.size() > 3)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
throw std::runtime_error(
RPCHelpMan{"fundrawtransaction",
"\nAdd inputs to a transaction until it has enough in value to meet its out value.\n"
@ -3406,6 +3476,25 @@ static UniValue fundrawtransaction(const JSONRPCRequest& request)
"options"},
{"iswitness", RPCArg::Type::BOOL, /* default */ "depends on heuristic tests", "Whether the transaction hex is a serialized witness transaction \n"
" If iswitness is not present, heuristic tests will be used in decoding"},
{"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"
@ -3438,7 +3527,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)));
@ -4581,7 +4670,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
return NullUniValue;
}
if (request.fHelp || request.params.size() < 2 || request.params.size() > 5)
if (request.fHelp || request.params.size() < 2 || request.params.size() > 6)
throw std::runtime_error(
RPCHelpMan{"walletcreatefundedpsbt",
"\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n"
@ -4594,6 +4683,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."},
},
},
},
@ -4642,6 +4734,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"
@ -4672,8 +4783,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], request.params[3]["replaceable"], 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], request.params[3]["replaceable"], 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);
@ -4693,6 +4804,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;
@ -6567,7 +6714,7 @@ static const CRPCCommand commands[] =
// --------------------- ------------------------ ----------------------- ----------
{ "generating", "generate", &generate, {"nblocks","maxtries"} },
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
{ "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"} },
@ -6616,7 +6763,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

@ -1626,13 +1626,13 @@ 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;
if (!ProduceSignature(*this, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
UpdateTransaction(tx, nIn, sigdata);
@ -1640,14 +1640,18 @@ 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;
// 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 (!DummySignInput(this, txNew, nIn, txout, use_max_sig)) {
if (!coin_control || !DummySignInput(&coin_control->m_external_provider, txNew, nIn, txout, use_max_sig)) {
return false;
}
}
nIn++;
@ -1655,28 +1659,33 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
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. We should have already checked that this transaction
// IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our
// wallet, with a valid index into the vout array, and the ability to sign.
// 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);
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)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
return -1;
@ -1688,7 +1697,7 @@ int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet,
{
CMutableTransaction txn;
txn.vin.push_back(CTxIn(COutPoint()));
if (!wallet->DummySignInput(txn, 0, txout, use_max_sig)) {
if (!DummySignInput(wallet, txn, 0, txout, use_max_sig)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
return -1;
@ -2690,8 +2699,18 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
}
mapValueFromPresetInputs[pcoin->GetOutputAsset(outpoint.n)] += amt;
setPresetCoins.insert(CInputCoin(pcoin, outpoint.n));
} else
return false; // TODO: Allow non-wallet inputs
} else {
CTxOut txout;
if (coin_control.GetExternalOutput(outpoint, txout)) {
if (!txout.nValue.IsExplicit() || !txout.nAsset.IsExplicit()) {
return false; // We can't get its value, so abort
}
mapValueFromPresetInputs[txout.nAsset.GetAsset()] += txout.nValue.GetAmount();
setPresetCoins.insert(CInputCoin(outpoint, txout));
} else {
return false;
}
}
}
// remove preset inputs from vCoins
@ -2824,8 +2843,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
coinControl.ListSelected(vPresetInputs);
for (const COutPoint& presetInput : vPresetInputs) {
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(presetInput.hash);
CTxOut txout;
if (it != mapWallet.end()) {
setAssets.insert(it->second.GetOutputAsset(presetInput.n));
} else if (coinControl.GetExternalOutput(presetInput, txout)) {
setAssets.insert(txout.nAsset.GetAsset());
}
}
@ -3162,13 +3184,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;
@ -3509,9 +3536,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");
strFailReason = _("Missing solving data for estimating transaction size");
return false;
}

View file

@ -1084,14 +1084,13 @@ public:
std::string& strFailReason, const CCoinControl& coin_control, bool sign = true, BlindDetails* blind_details = nullptr, const IssuanceDetails* issuance_details = nullptr);
bool CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, std::vector<std::unique_ptr<CReserveKey>>& reservekey, CConnman* connman, CValidationState& state, 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;
CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE};
unsigned int m_confirm_target{DEFAULT_TX_CONFIRM_TARGET};
@ -1445,8 +1444,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

@ -14,7 +14,8 @@ from test_framework.util import (
assert_equal,
bytes_to_hex_str,
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"], bytes_to_hex_str(sample_pegin_struct.serialize()))

View file

@ -777,5 +777,26 @@ 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'])
if __name__ == '__main__':
RawTransactionsTest().main()

View file

@ -524,11 +524,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}])
# Tests added in the 0.18 rebase don't pass on Elements yet.
"""