Skip to content

Commit 42babb8

Browse files
committed
Refactor peg-in input construction into its own function in rpc/rawtransaction.cpp
1 parent a90d152 commit 42babb8

3 files changed

Lines changed: 142 additions & 90 deletions

File tree

src/rpc/rawtransaction.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
#include <policy/policy.h>
2222
#include <policy/rbf.h>
2323
#include <primitives/transaction.h>
24+
#include <primitives/bitcoin/merkleblock.h>
25+
#include <primitives/bitcoin/transaction.h>
2426
#include <psbt.h>
2527
#include <rpc/rawtransaction.h>
2628
#include <rpc/server.h>
@@ -363,6 +365,124 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
363365
return res;
364366
}
365367

368+
template<typename T_tx>
369+
unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
370+
{
371+
for (const auto & scripts : fedpegscripts) {
372+
CScript mainchain_script = GetScriptForWitness(calculate_contract(scripts.second, witnessProgram));
373+
if (scripts.first.IsPayToScriptHash()) {
374+
mainchain_script = GetScriptForDestination(ScriptHash(mainchain_script));
375+
}
376+
for (unsigned int nOut = 0; nOut < txn.vout.size(); nOut++)
377+
if (txn.vout[nOut].scriptPubKey == mainchain_script) {
378+
return nOut;
379+
}
380+
}
381+
return txn.vout.size();
382+
}
383+
384+
// Modifies an existing transaction input in-place to be a valid peg-in input, and inserts the witness if deemed valid.
385+
template<typename T_tx_ref, typename T_merkle_block>
386+
static void CreatePegInInputInner(CMutableTransaction& mtx, uint32_t input_idx, T_tx_ref& txBTCRef, T_merkle_block& merkleBlock, const std::set<CScript>& claim_scripts, const std::vector<unsigned char>& txData, const std::vector<unsigned char>& txOutProofData)
387+
{
388+
if ((mtx.vin.size() > input_idx && !mtx.vin[input_idx].scriptSig.empty()) || (mtx.witness.vtxinwit.size() > input_idx && !mtx.witness.vtxinwit[input_idx].IsNull())) {
389+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Attempting to add a peg-in to an input that already has a scriptSig or witness");
390+
}
391+
392+
CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION);
393+
try {
394+
ssTx >> txBTCRef;
395+
}
396+
catch (...) {
397+
throw JSONRPCError(RPC_TYPE_ERROR, "The included bitcoinTx is malformed. Are you sure that is the whole string?");
398+
}
399+
400+
CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
401+
try {
402+
ssTxOutProof >> merkleBlock;
403+
}
404+
catch (...) {
405+
throw JSONRPCError(RPC_TYPE_ERROR, "The included txoutproof is malformed. Are you sure that is the whole string?");
406+
}
407+
408+
if (!ssTxOutProof.empty()) {
409+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
410+
}
411+
412+
std::vector<uint256> txHashes;
413+
std::vector<unsigned int> txIndices;
414+
if (merkleBlock.txn.ExtractMatches(txHashes, txIndices) != merkleBlock.header.hashMerkleRoot)
415+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
416+
417+
if (txHashes.size() != 1 || txHashes[0] != txBTCRef->GetHash())
418+
throw JSONRPCError(RPC_INVALID_PARAMETER, "The txoutproof must contain bitcoinTx and only bitcoinTx");
419+
420+
CScript witness_script;
421+
unsigned int nOut = txBTCRef->vout.size();
422+
const auto fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
423+
for (const CScript& script : claim_scripts) {
424+
nOut = GetPeginTxnOutputIndex(*txBTCRef, script, fedpegscripts);
425+
if (nOut != txBTCRef->vout.size()) {
426+
witness_script = script;
427+
break;
428+
}
429+
}
430+
if (nOut == txBTCRef->vout.size()) {
431+
if (claim_scripts.size() == 1) {
432+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script does not match the given Bitcoin transaction.");
433+
} else {
434+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress");
435+
}
436+
}
437+
assert(witness_script != CScript());
438+
439+
int version = -1;
440+
std::vector<unsigned char> witness_program;
441+
if (!witness_script.IsWitnessProgram(version, witness_program) || version != 0) {
442+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given or recovered script is not a v0 witness program.");
443+
}
444+
445+
CAmount value = 0;
446+
if (!GetAmountFromParentChainPegin(value, *txBTCRef, nOut)) {
447+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
448+
}
449+
450+
// Add/replace input in mtx
451+
if (mtx.vin.size() <= input_idx) {
452+
mtx.vin.resize(input_idx + 1);
453+
}
454+
mtx.vin[input_idx] = CTxIn(COutPoint(txHashes[0], nOut), CScript(), ~(uint32_t)0);
455+
456+
// Construct pegin proof
457+
CScriptWitness pegin_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), witness_script, txBTCRef, merkleBlock);
458+
459+
// Peg-in witness isn't valid, even though the block header is(without depth check)
460+
// We re-check depth before returning with more descriptive result
461+
std::string err;
462+
if (!IsValidPeginWitness(pegin_witness, fedpegscripts, mtx.vin[input_idx].prevout, err, false)) {
463+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Constructed peg-in witness is invalid: %s", err));
464+
}
465+
466+
// Put input witness in transaction
467+
mtx.vin[input_idx].m_is_pegin = true;
468+
CTxInWitness txinwit;
469+
txinwit.m_pegin_witness = pegin_witness;
470+
471+
if (mtx.witness.vtxinwit.size() <= input_idx) {
472+
mtx.witness.vtxinwit.resize(input_idx + 1);
473+
}
474+
mtx.witness.vtxinwit[input_idx] = txinwit;
475+
}
476+
477+
void CreatePegInInput(CMutableTransaction& mtx, uint32_t input_idx, CTransactionRef& tx_btc, CMerkleBlock& merkle_block, const std::set<CScript>& claim_scripts, const std::vector<unsigned char>& txData, const std::vector<unsigned char>& txOutProofData)
478+
{
479+
CreatePegInInputInner(mtx, input_idx, tx_btc, merkle_block, claim_scripts, txData, txOutProofData);
480+
}
481+
void CreatePegInInput(CMutableTransaction& mtx, uint32_t input_idx, Sidechain::Bitcoin::CTransactionRef& tx_btc, Sidechain::Bitcoin::CMerkleBlock& merkle_block, const std::set<CScript>& claim_scripts, const std::vector<unsigned char>& txData, const std::vector<unsigned char>& txOutProofData)
482+
{
483+
CreatePegInInputInner(mtx, input_idx, tx_btc, merkle_block, claim_scripts, txData, txOutProofData);
484+
}
485+
366486
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in, std::vector<CPubKey>* output_pubkeys_out)
367487
{
368488
if (inputs_in.isNull() || outputs_in.isNull())

src/rpc/rawtransaction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,8 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
2121
Assets output pubkeys in nonces will be used. */
2222
CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf, const UniValue& assets_in, std::vector<CPubKey>* output_pubkeys_out = nullptr);
2323

24+
/** Create a peg-in input */
25+
void CreatePegInInput(CMutableTransaction& mtx, uint32_t input_idx, CTransactionRef& tx_btc, CMerkleBlock& merkle_block, const std::set<CScript>& claim_scripts, const std::vector<unsigned char>& txData, const std::vector<unsigned char>& txOutProofData);
26+
void CreatePegInInput(CMutableTransaction& mtx, uint32_t input_idx, Sidechain::Bitcoin::CTransactionRef& tx_btc, Sidechain::Bitcoin::CMerkleBlock& merkle_block, const std::set<CScript>& claim_scripts, const std::vector<unsigned char>& txData, const std::vector<unsigned char>& txOutProofData);
27+
2428
#endif // BITCOIN_RPC_RAWTRANSACTION_H

src/wallet/rpcwallet.cpp

Lines changed: 18 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5404,22 +5404,6 @@ UniValue sendtomainchain(const JSONRPCRequest& request)
54045404
extern UniValue signrawtransaction(const JSONRPCRequest& request);
54055405
extern UniValue sendrawtransaction(const JSONRPCRequest& request);
54065406

5407-
template<typename T_tx>
5408-
unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
5409-
{
5410-
for (const auto & scripts : fedpegscripts) {
5411-
CScript mainchain_script = GetScriptForWitness(calculate_contract(scripts.second, witnessProgram));
5412-
if (scripts.first.IsPayToScriptHash()) {
5413-
mainchain_script = GetScriptForDestination(ScriptHash(mainchain_script));
5414-
}
5415-
for (unsigned int nOut = 0; nOut < txn.vout.size(); nOut++)
5416-
if (txn.vout[nOut].scriptPubKey == mainchain_script) {
5417-
return nOut;
5418-
}
5419-
}
5420-
return txn.vout.size();
5421-
}
5422-
54235407
template<typename T_tx_ref, typename T_tx, typename T_merkle_block>
54245408
static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef, T_tx& tx_aux, T_merkle_block& merkleBlock)
54255409
{
@@ -5457,78 +5441,32 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
54575441
}
54585442

54595443
std::vector<unsigned char> txData = ParseHex(request.params[0].get_str());
5460-
CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION);
5461-
try {
5462-
ssTx >> txBTCRef;
5463-
}
5464-
catch (...) {
5465-
throw JSONRPCError(RPC_TYPE_ERROR, "The included bitcoinTx is malformed. Are you sure that is the whole string?");
5466-
}
5467-
T_tx txBTC(*txBTCRef);
5468-
54695444
std::vector<unsigned char> txOutProofData = ParseHex(request.params[1].get_str());
5470-
CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
5471-
try {
5472-
ssTxOutProof >> merkleBlock;
5473-
}
5474-
catch (...) {
5475-
throw JSONRPCError(RPC_TYPE_ERROR, "The included txoutproof is malformed. Are you sure that is the whole string?");
5476-
}
54775445

5478-
if (!ssTxOutProof.empty()) {
5479-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
5480-
}
5481-
5482-
std::vector<uint256> txHashes;
5483-
std::vector<unsigned int> txIndices;
5484-
if (merkleBlock.txn.ExtractMatches(txHashes, txIndices) != merkleBlock.header.hashMerkleRoot)
5485-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
5486-
5487-
if (txHashes.size() != 1 || txHashes[0] != txBTC.GetHash())
5488-
throw JSONRPCError(RPC_INVALID_PARAMETER, "The txoutproof must contain bitcoinTx and only bitcoinTx");
5489-
5490-
CScript witness_script;
5491-
unsigned int nOut = txBTC.vout.size();
5492-
const auto fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
5446+
std::set<CScript> claim_scripts;
54935447
if (request.params.size() > 2) {
54945448
const std::string claim_script = request.params[2].get_str();
54955449
if (!IsHex(claim_script)) {
54965450
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script is not hex.");
54975451
}
54985452
// If given manually, no need for it to be a witness script
54995453
std::vector<unsigned char> witnessBytes(ParseHex(claim_script));
5500-
witness_script = CScript(witnessBytes.begin(), witnessBytes.end());
5501-
nOut = GetPeginTxnOutputIndex(txBTC, witness_script, fedpegscripts);
5502-
if (nOut == txBTC.vout.size()) {
5503-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script does not match the given Bitcoin transaction.");
5504-
}
5454+
CScript witness_script(witnessBytes.begin(), witnessBytes.end());
5455+
claim_scripts.insert(std::move(witness_script));
55055456
}
55065457
else {
55075458
// Look for known wpkh address in wallet
55085459
for (std::map<CTxDestination, CAddressBookData>::const_iterator iter = pwallet->mapAddressBook.begin(); iter != pwallet->mapAddressBook.end(); ++iter) {
55095460
CScript dest_script = GetScriptForDestination(iter->first);
5510-
nOut = GetPeginTxnOutputIndex(txBTC, dest_script, fedpegscripts);
5511-
if (nOut != txBTC.vout.size()) {
5512-
witness_script = dest_script;
5513-
break;
5514-
}
5461+
claim_scripts.insert(std::move(dest_script));
55155462
}
55165463
}
5517-
if (nOut == txBTC.vout.size()) {
5518-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress");
5519-
}
5520-
assert(witness_script != CScript());
55215464

5522-
int version = -1;
5523-
std::vector<unsigned char> witness_program;
5524-
if (!witness_script.IsWitnessProgram(version, witness_program) || version != 0) {
5525-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given or recovered script is not a v0 witness program.");
5526-
}
5465+
// Make the tx
5466+
CMutableTransaction mtx;
55275467

5528-
CAmount value = 0;
5529-
if (!GetAmountFromParentChainPegin(value, txBTC, nOut)) {
5530-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
5531-
}
5468+
// Construct pegin input
5469+
CreatePegInInput(mtx, 0, txBTCRef, merkleBlock, claim_scripts, txData, txOutProofData);
55325470

55335471
// Manually construct peg-in transaction, sign it, and send it off.
55345472
// Decrement the output value as much as needed given the total vsize to
@@ -5545,28 +5483,15 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
55455483

55465484
pwallet->SetAddressBook(wpkhash, "", "receive");
55475485

5548-
// One peg-in input, one wallet output and one fee output
5549-
CMutableTransaction mtx;
5550-
mtx.vin.push_back(CTxIn(COutPoint(txHashes[0], nOut), CScript(), ~(uint32_t)0));
5551-
// mark as peg-in input
5552-
mtx.vin[0].m_is_pegin = true;
5553-
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash)));
5554-
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript()));
5555-
5556-
// Construct pegin proof
5557-
CScriptWitness pegin_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), witness_script, txBTCRef, merkleBlock);
5558-
5559-
// Peg-in witness isn't valid, even though the block header is(without depth check)
5560-
// We re-check depth before returning with more descriptive result
5561-
std::string err;
5562-
if (!IsValidPeginWitness(pegin_witness, fedpegscripts, mtx.vin[0].prevout, err, false)) {
5563-
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Constructed peg-in witness is invalid: %s", err));
5486+
// Get value for output
5487+
CAmount value = 0;
5488+
if (!GetAmountFromParentChainPegin(value, *txBTCRef, mtx.vin[0].prevout.n)) {
5489+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
55645490
}
55655491

5566-
// Put input witness in transaction
5567-
CTxInWitness txinwit;
5568-
txinwit.m_pegin_witness = pegin_witness;
5569-
mtx.witness.vtxinwit.push_back(txinwit);
5492+
// one wallet output and one fee output
5493+
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash)));
5494+
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript()));
55705495

55715496
// Estimate fee for transaction, decrement fee output(including witness data)
55725497
unsigned int nBytes = GetVirtualTransactionSize(CTransaction(mtx)) +
@@ -5586,6 +5511,9 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
55865511
// Additional block lee-way to avoid bitcoin block races
55875512
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
55885513
unsigned int required_depth = Params().GetConsensus().pegin_min_depth + 2;
5514+
std::vector<uint256> txHashes;
5515+
std::vector<unsigned int> txIndices;
5516+
merkleBlock.txn.ExtractMatches(txHashes, txIndices);
55895517
if (txIndices[0] == 0) {
55905518
required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY+2);
55915519
}

0 commit comments

Comments
 (0)