-
Notifications
You must be signed in to change notification settings - Fork 390
Be able to fund transactions with peg-ins specified #755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
698340b
Allow CInputCoin to also be constructed with COutPoint and CTxOut
achow101 8258aaa
Allow Coin Selection be able to take external inputs
achow101 9486cdf
Give a better error when tx size estimation fails
achow101 c258746
allow fundtx rpcs to work with external inputs
achow101 74c4d37
Tests for fundrawtransaction with external inputs
achow101 1ce74e0
Be able to fund transactions with peg-ins
achow101 00eb6c0
Test funding peg-in psbt
achow101 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably need to assert that it's well-formed first, e.g.,
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
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"} }, | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SelectExternal
againThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done