mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Refactor peg-in input construction into its own function in rpc/rawtransaction.cpp
This commit is contained in:
parent
a90d1521c8
commit
42babb81f0
3 changed files with 142 additions and 90 deletions
|
|
@ -21,6 +21,8 @@
|
|||
#include <policy/policy.h>
|
||||
#include <policy/rbf.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <primitives/bitcoin/merkleblock.h>
|
||||
#include <primitives/bitcoin/transaction.h>
|
||||
#include <psbt.h>
|
||||
#include <rpc/rawtransaction.h>
|
||||
#include <rpc/server.h>
|
||||
|
|
@ -363,6 +365,124 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
|
|||
return res;
|
||||
}
|
||||
|
||||
template<typename T_tx>
|
||||
unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
|
||||
{
|
||||
for (const auto & scripts : fedpegscripts) {
|
||||
CScript mainchain_script = GetScriptForWitness(calculate_contract(scripts.second, witnessProgram));
|
||||
if (scripts.first.IsPayToScriptHash()) {
|
||||
mainchain_script = GetScriptForDestination(ScriptHash(mainchain_script));
|
||||
}
|
||||
for (unsigned int nOut = 0; nOut < txn.vout.size(); nOut++)
|
||||
if (txn.vout[nOut].scriptPubKey == mainchain_script) {
|
||||
return nOut;
|
||||
}
|
||||
}
|
||||
return txn.vout.size();
|
||||
}
|
||||
|
||||
// Modifies an existing transaction input in-place to be a valid peg-in input, and inserts the witness if deemed valid.
|
||||
template<typename T_tx_ref, typename T_merkle_block>
|
||||
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)
|
||||
{
|
||||
if ((mtx.vin.size() > input_idx && !mtx.vin[input_idx].scriptSig.empty()) || (mtx.witness.vtxinwit.size() > input_idx && !mtx.witness.vtxinwit[input_idx].IsNull())) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Attempting to add a peg-in to an input that already has a scriptSig or witness");
|
||||
}
|
||||
|
||||
CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION);
|
||||
try {
|
||||
ssTx >> txBTCRef;
|
||||
}
|
||||
catch (...) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "The included bitcoinTx is malformed. Are you sure that is the whole string?");
|
||||
}
|
||||
|
||||
CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
try {
|
||||
ssTxOutProof >> merkleBlock;
|
||||
}
|
||||
catch (...) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "The included txoutproof is malformed. Are you sure that is the whole string?");
|
||||
}
|
||||
|
||||
if (!ssTxOutProof.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
|
||||
}
|
||||
|
||||
std::vector<uint256> txHashes;
|
||||
std::vector<unsigned int> txIndices;
|
||||
if (merkleBlock.txn.ExtractMatches(txHashes, txIndices) != merkleBlock.header.hashMerkleRoot)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
|
||||
|
||||
if (txHashes.size() != 1 || txHashes[0] != txBTCRef->GetHash())
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "The txoutproof must contain bitcoinTx and only bitcoinTx");
|
||||
|
||||
CScript witness_script;
|
||||
unsigned int nOut = txBTCRef->vout.size();
|
||||
const auto fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
for (const CScript& script : claim_scripts) {
|
||||
nOut = GetPeginTxnOutputIndex(*txBTCRef, script, fedpegscripts);
|
||||
if (nOut != txBTCRef->vout.size()) {
|
||||
witness_script = script;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (nOut == txBTCRef->vout.size()) {
|
||||
if (claim_scripts.size() == 1) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script does not match the given Bitcoin transaction.");
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress");
|
||||
}
|
||||
}
|
||||
assert(witness_script != CScript());
|
||||
|
||||
int version = -1;
|
||||
std::vector<unsigned char> witness_program;
|
||||
if (!witness_script.IsWitnessProgram(version, witness_program) || version != 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given or recovered script is not a v0 witness program.");
|
||||
}
|
||||
|
||||
CAmount value = 0;
|
||||
if (!GetAmountFromParentChainPegin(value, *txBTCRef, nOut)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
|
||||
}
|
||||
|
||||
// Add/replace input in mtx
|
||||
if (mtx.vin.size() <= input_idx) {
|
||||
mtx.vin.resize(input_idx + 1);
|
||||
}
|
||||
mtx.vin[input_idx] = CTxIn(COutPoint(txHashes[0], nOut), CScript(), ~(uint32_t)0);
|
||||
|
||||
// Construct pegin proof
|
||||
CScriptWitness pegin_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), witness_script, txBTCRef, merkleBlock);
|
||||
|
||||
// Peg-in witness isn't valid, even though the block header is(without depth check)
|
||||
// We re-check depth before returning with more descriptive result
|
||||
std::string err;
|
||||
if (!IsValidPeginWitness(pegin_witness, fedpegscripts, mtx.vin[input_idx].prevout, err, false)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Constructed peg-in witness is invalid: %s", err));
|
||||
}
|
||||
|
||||
// Put input witness in transaction
|
||||
mtx.vin[input_idx].m_is_pegin = true;
|
||||
CTxInWitness txinwit;
|
||||
txinwit.m_pegin_witness = pegin_witness;
|
||||
|
||||
if (mtx.witness.vtxinwit.size() <= input_idx) {
|
||||
mtx.witness.vtxinwit.resize(input_idx + 1);
|
||||
}
|
||||
mtx.witness.vtxinwit[input_idx] = txinwit;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
CreatePegInInputInner(mtx, input_idx, tx_btc, merkle_block, claim_scripts, txData, txOutProofData);
|
||||
}
|
||||
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)
|
||||
{
|
||||
CreatePegInInputInner(mtx, input_idx, tx_btc, merkle_block, claim_scripts, txData, txOutProofData);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (inputs_in.isNull() || outputs_in.isNull())
|
||||
|
|
|
|||
|
|
@ -21,4 +21,8 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
|
|||
Assets output pubkeys in nonces will be used. */
|
||||
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);
|
||||
|
||||
/** Create a peg-in input */
|
||||
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);
|
||||
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);
|
||||
|
||||
#endif // BITCOIN_RPC_RAWTRANSACTION_H
|
||||
|
|
|
|||
|
|
@ -5404,22 +5404,6 @@ UniValue sendtomainchain(const JSONRPCRequest& request)
|
|||
extern UniValue signrawtransaction(const JSONRPCRequest& request);
|
||||
extern UniValue sendrawtransaction(const JSONRPCRequest& request);
|
||||
|
||||
template<typename T_tx>
|
||||
unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector<std::pair<CScript, CScript>>& fedpegscripts)
|
||||
{
|
||||
for (const auto & scripts : fedpegscripts) {
|
||||
CScript mainchain_script = GetScriptForWitness(calculate_contract(scripts.second, witnessProgram));
|
||||
if (scripts.first.IsPayToScriptHash()) {
|
||||
mainchain_script = GetScriptForDestination(ScriptHash(mainchain_script));
|
||||
}
|
||||
for (unsigned int nOut = 0; nOut < txn.vout.size(); nOut++)
|
||||
if (txn.vout[nOut].scriptPubKey == mainchain_script) {
|
||||
return nOut;
|
||||
}
|
||||
}
|
||||
return txn.vout.size();
|
||||
}
|
||||
|
||||
template<typename T_tx_ref, typename T_tx, typename T_merkle_block>
|
||||
static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef, T_tx& tx_aux, T_merkle_block& merkleBlock)
|
||||
{
|
||||
|
|
@ -5457,39 +5441,9 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
}
|
||||
|
||||
std::vector<unsigned char> txData = ParseHex(request.params[0].get_str());
|
||||
CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION);
|
||||
try {
|
||||
ssTx >> txBTCRef;
|
||||
}
|
||||
catch (...) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "The included bitcoinTx is malformed. Are you sure that is the whole string?");
|
||||
}
|
||||
T_tx txBTC(*txBTCRef);
|
||||
|
||||
std::vector<unsigned char> txOutProofData = ParseHex(request.params[1].get_str());
|
||||
CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
try {
|
||||
ssTxOutProof >> merkleBlock;
|
||||
}
|
||||
catch (...) {
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, "The included txoutproof is malformed. Are you sure that is the whole string?");
|
||||
}
|
||||
|
||||
if (!ssTxOutProof.empty()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
|
||||
}
|
||||
|
||||
std::vector<uint256> txHashes;
|
||||
std::vector<unsigned int> txIndices;
|
||||
if (merkleBlock.txn.ExtractMatches(txHashes, txIndices) != merkleBlock.header.hashMerkleRoot)
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
|
||||
|
||||
if (txHashes.size() != 1 || txHashes[0] != txBTC.GetHash())
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "The txoutproof must contain bitcoinTx and only bitcoinTx");
|
||||
|
||||
CScript witness_script;
|
||||
unsigned int nOut = txBTC.vout.size();
|
||||
const auto fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
|
||||
std::set<CScript> claim_scripts;
|
||||
if (request.params.size() > 2) {
|
||||
const std::string claim_script = request.params[2].get_str();
|
||||
if (!IsHex(claim_script)) {
|
||||
|
|
@ -5497,38 +5451,22 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
}
|
||||
// If given manually, no need for it to be a witness script
|
||||
std::vector<unsigned char> witnessBytes(ParseHex(claim_script));
|
||||
witness_script = CScript(witnessBytes.begin(), witnessBytes.end());
|
||||
nOut = GetPeginTxnOutputIndex(txBTC, witness_script, fedpegscripts);
|
||||
if (nOut == txBTC.vout.size()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script does not match the given Bitcoin transaction.");
|
||||
}
|
||||
CScript witness_script(witnessBytes.begin(), witnessBytes.end());
|
||||
claim_scripts.insert(std::move(witness_script));
|
||||
}
|
||||
else {
|
||||
// Look for known wpkh address in wallet
|
||||
for (std::map<CTxDestination, CAddressBookData>::const_iterator iter = pwallet->mapAddressBook.begin(); iter != pwallet->mapAddressBook.end(); ++iter) {
|
||||
CScript dest_script = GetScriptForDestination(iter->first);
|
||||
nOut = GetPeginTxnOutputIndex(txBTC, dest_script, fedpegscripts);
|
||||
if (nOut != txBTC.vout.size()) {
|
||||
witness_script = dest_script;
|
||||
break;
|
||||
}
|
||||
claim_scripts.insert(std::move(dest_script));
|
||||
}
|
||||
}
|
||||
if (nOut == txBTC.vout.size()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress");
|
||||
}
|
||||
assert(witness_script != CScript());
|
||||
|
||||
int version = -1;
|
||||
std::vector<unsigned char> witness_program;
|
||||
if (!witness_script.IsWitnessProgram(version, witness_program) || version != 0) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given or recovered script is not a v0 witness program.");
|
||||
}
|
||||
// Make the tx
|
||||
CMutableTransaction mtx;
|
||||
|
||||
CAmount value = 0;
|
||||
if (!GetAmountFromParentChainPegin(value, txBTC, nOut)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
|
||||
}
|
||||
// Construct pegin input
|
||||
CreatePegInInput(mtx, 0, txBTCRef, merkleBlock, claim_scripts, txData, txOutProofData);
|
||||
|
||||
// Manually construct peg-in transaction, sign it, and send it off.
|
||||
// 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
|
|||
|
||||
pwallet->SetAddressBook(wpkhash, "", "receive");
|
||||
|
||||
// One peg-in input, one wallet output and one fee output
|
||||
CMutableTransaction mtx;
|
||||
mtx.vin.push_back(CTxIn(COutPoint(txHashes[0], nOut), CScript(), ~(uint32_t)0));
|
||||
// mark as peg-in input
|
||||
mtx.vin[0].m_is_pegin = true;
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash)));
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript()));
|
||||
|
||||
// Construct pegin proof
|
||||
CScriptWitness pegin_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), witness_script, txBTCRef, merkleBlock);
|
||||
|
||||
// Peg-in witness isn't valid, even though the block header is(without depth check)
|
||||
// We re-check depth before returning with more descriptive result
|
||||
std::string err;
|
||||
if (!IsValidPeginWitness(pegin_witness, fedpegscripts, mtx.vin[0].prevout, err, false)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Constructed peg-in witness is invalid: %s", err));
|
||||
// Get value for output
|
||||
CAmount value = 0;
|
||||
if (!GetAmountFromParentChainPegin(value, *txBTCRef, mtx.vin[0].prevout.n)) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
|
||||
}
|
||||
|
||||
// Put input witness in transaction
|
||||
CTxInWitness txinwit;
|
||||
txinwit.m_pegin_witness = pegin_witness;
|
||||
mtx.witness.vtxinwit.push_back(txinwit);
|
||||
// one wallet output and one fee output
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, value, GetScriptForDestination(wpkhash)));
|
||||
mtx.vout.push_back(CTxOut(Params().GetConsensus().pegged_asset, 0, CScript()));
|
||||
|
||||
// Estimate fee for transaction, decrement fee output(including witness data)
|
||||
unsigned int nBytes = GetVirtualTransactionSize(CTransaction(mtx)) +
|
||||
|
|
@ -5586,6 +5511,9 @@ static UniValue createrawpegin(const JSONRPCRequest& request, T_tx_ref& txBTCRef
|
|||
// Additional block lee-way to avoid bitcoin block races
|
||||
if (gArgs.GetBoolArg("-validatepegin", Params().GetConsensus().has_parent_chain)) {
|
||||
unsigned int required_depth = Params().GetConsensus().pegin_min_depth + 2;
|
||||
std::vector<uint256> txHashes;
|
||||
std::vector<unsigned int> txIndices;
|
||||
merkleBlock.txn.ExtractMatches(txHashes, txIndices);
|
||||
if (txIndices[0] == 0) {
|
||||
required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY+2);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue