diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index aa9a3130f4..98e642d981 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -363,6 +365,124 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) return res; } +template +unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector>& 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 +static void CreatePegInInputInner(CMutableTransaction& mtx, uint32_t input_idx, T_tx_ref& txBTCRef, T_merkle_block& merkleBlock, const std::set& claim_scripts, const std::vector& txData, const std::vector& 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 txHashes; + std::vector 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 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& claim_scripts, const std::vector& txData, const std::vector& 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& claim_scripts, const std::vector& txData, const std::vector& 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* output_pubkeys_out) { if (inputs_in.isNull() || outputs_in.isNull()) diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index 56eec70cef..42b5e8ba9b 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -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* 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& claim_scripts, const std::vector& txData, const std::vector& txOutProofData); +void CreatePegInInput(CMutableTransaction& mtx, uint32_t input_idx, Sidechain::Bitcoin::CTransactionRef& tx_btc, Sidechain::Bitcoin::CMerkleBlock& merkle_block, const std::set& claim_scripts, const std::vector& txData, const std::vector& txOutProofData); + #endif // BITCOIN_RPC_RAWTRANSACTION_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 696d57052c..1abee184c8 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -5404,22 +5404,6 @@ UniValue sendtomainchain(const JSONRPCRequest& request) extern UniValue signrawtransaction(const JSONRPCRequest& request); extern UniValue sendrawtransaction(const JSONRPCRequest& request); -template -unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector>& 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 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 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 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 txHashes; - std::vector 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 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 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::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 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 txHashes; + std::vector txIndices; + merkleBlock.txn.ExtractMatches(txHashes, txIndices); if (txIndices[0] == 0) { required_depth = std::max(required_depth, (unsigned int)COINBASE_MATURITY+2); }