Skip to content

Commit

Permalink
Refactor peg-in input construction into its own function in rpc/rawtr…
Browse files Browse the repository at this point in the history
…ansaction.cpp
  • Loading branch information
achow101 committed Oct 14, 2019
1 parent a90d152 commit 42babb8
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 90 deletions.
120 changes: 120 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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())
Expand Down
4 changes: 4 additions & 0 deletions src/rpc/rawtransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
108 changes: 18 additions & 90 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -5457,78 +5441,32 @@ 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)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script is not hex.");
}
// 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
Expand All @@ -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)) +
Expand All @@ -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);
}
Expand Down

0 comments on commit 42babb8

Please sign in to comment.