Skip to content

Commit

Permalink
Merge #741: Allow specifying peg-in data in createrawtransaction
Browse files Browse the repository at this point in the history
5a4d611 Test raw transaction peg-in (Andrew Chow)
ac6f052 Have find_vout_for_address only get non-confidential address when using elements chain (Andrew Chow)
cefe407 Allow specifying peg-in info in raw transaction RPC inputs (Andrew Chow)
42babb8 Refactor peg-in input construction into its own function in rpc/rawtransaction.cpp (Andrew Chow)
a90d152 Refactor peg-in witness construction to it's own function (Andrew Chow)

Pull request description:

  Adds to `createrawtransaction` the fields `pegin_bitcoin_tx`, `pegin_txout_proof`, and `pegin_claim_script` so that raw transactions can contain peg-in inputs too.

  Since `ConstructTransaction` is used by `createpsbt` and `walletcreatefundedpsbt` but those should not support peg-ins yet (the peg-in data should go into PSBT fields for peg-ins but those don't exist yet), an argument is added to it to optionally reject peg-in data.

Tree-SHA512: fcf3e4be87f92bfc5b00321bec405001d98615cefb76118345e0c759c6bfcb90b6050735960b6fbdbe26479fa11c9273f9963873a8adc1fbad9ed31dc7953ee0
  • Loading branch information
instagibbs committed Oct 16, 2019
2 parents 63701d1 + 5a4d611 commit 0d2832a
Show file tree
Hide file tree
Showing 8 changed files with 260 additions and 121 deletions.
42 changes: 42 additions & 0 deletions src/pegins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -496,3 +496,45 @@ std::vector<std::pair<CScript, CScript>> GetValidFedpegScripts(const CBlockIndex
fedpegscripts.resize(std::min(fedpegscripts.size(), params.total_valid_epochs));
return fedpegscripts;
}

template<typename T_tx_ref, typename T_merkle_block>
CScriptWitness CreatePeginWitnessInner(const CAmount& value, const CAsset& asset, const uint256& genesis_hash, const CScript& claim_script, const T_tx_ref& tx_ref, const T_merkle_block& merkle_block)
{
std::vector<unsigned char> value_bytes;
CVectorWriter ss_val(0, 0, value_bytes, 0);
try {
ss_val << value;
} catch (...) {
throw std::ios_base::failure("Amount serialization is invalid.");
}

// Strip witness data for proof inclusion since only TXID-covered fields matters
CDataStream ss_tx(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
ss_tx << tx_ref;
std::vector<unsigned char> tx_data_stripped(ss_tx.begin(), ss_tx.end());

// Serialize merkle block
CDataStream ss_txout_proof(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
ss_txout_proof << merkle_block;
std::vector<unsigned char> txout_proof_bytes(ss_txout_proof.begin(), ss_txout_proof.end());

// Construct pegin proof
CScriptWitness pegin_witness;
std::vector<std::vector<unsigned char> >& stack = pegin_witness.stack;
stack.push_back(value_bytes);
stack.push_back(std::vector<unsigned char>(asset.begin(), asset.end()));
stack.push_back(std::vector<unsigned char>(genesis_hash.begin(), genesis_hash.end()));
stack.push_back(std::vector<unsigned char>(claim_script.begin(), claim_script.end()));
stack.push_back(tx_data_stripped);
stack.push_back(txout_proof_bytes);
return pegin_witness;
}

CScriptWitness CreatePeginWitness(const CAmount& value, const CAsset& asset, const uint256& genesis_hash, const CScript& claim_script, const CTransactionRef& tx_ref, const CMerkleBlock& merkle_block)
{
return CreatePeginWitnessInner(value, asset, genesis_hash, claim_script, tx_ref, merkle_block);
}
CScriptWitness CreatePeginWitness(const CAmount& value, const CAsset& asset, const uint256& genesis_hash, const CScript& claim_script, const Sidechain::Bitcoin::CTransactionRef& tx_ref, const Sidechain::Bitcoin::CMerkleBlock& merkle_block)
{
return CreatePeginWitnessInner(value, asset, genesis_hash, claim_script, tx_ref, merkle_block);
}
7 changes: 7 additions & 0 deletions src/pegins.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@

#include <amount.h>
#include <consensus/params.h>
#include <merkleblock.h>
#include <primitives/bitcoin/transaction.h>
#include <primitives/bitcoin/merkleblock.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <chain.h>
Expand Down Expand Up @@ -38,4 +40,9 @@ bool MatchLiquidWatchman(const CScript& script);
* scriptPubKey for the script, the second is the witnessScript. */
std::vector<std::pair<CScript, CScript>> GetValidFedpegScripts(const CBlockIndex* pblockindex, const Consensus::Params& params, bool nextblock_validation);

/** Create the peg-in witness stack */
CScriptWitness CreatePeginWitness(const CAmount& value, const CAsset& asset, const uint256& genesis_hash, const CScript& claim_script, const CTransactionRef& tx_ref, const CMerkleBlock& merkle_block);
CScriptWitness CreatePeginWitness(const CAmount& value, const CAsset& asset, const uint256& genesis_hash, const CScript& claim_script, const Sidechain::Bitcoin::CTransactionRef& tx_ref, const Sidechain::Bitcoin::CMerkleBlock& merkle_block);


#endif // BITCOIN_PEGINS_H
165 changes: 162 additions & 3 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <asset.h>
#include <block_proof.h>
#include <chain.h>
#include <coins.h>
#include <compat/byteswap.h>
Expand All @@ -21,6 +22,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,7 +366,125 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request)
return res;
}

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)
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, bool allow_peg_in)
{
if (inputs_in.isNull() || outputs_in.isNull())
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null");
Expand Down Expand Up @@ -422,8 +543,43 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal
}

CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence);

rawTx.vin.push_back(in);

// Get the pegin stuff if it's there
const UniValue& pegin_tx = find_value(o, "pegin_bitcoin_tx");
const UniValue& pegin_tx_proof = find_value(o, "pegin_txout_proof");
const UniValue& pegin_script = find_value(o, "pegin_claim_script");
if (!pegin_tx.isNull() && !pegin_tx_proof.isNull() && !pegin_script.isNull() && allow_peg_in) {
if (!IsHex(pegin_script.get_str())) {
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> claim_script_bytes(ParseHex(pegin_script.get_str()));
CScript claim_script(claim_script_bytes.begin(), claim_script_bytes.end());
std::set<CScript> claim_scripts;
claim_scripts.insert(std::move(claim_script));
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CTransactionRef tx_btc;
Sidechain::Bitcoin::CMerkleBlock merkle_block;
CreatePegInInput(rawTx, idx, tx_btc, merkle_block, claim_scripts, ParseHex(pegin_tx.get_str()), ParseHex(pegin_tx_proof.get_str()));
if (!CheckParentProofOfWork(merkle_block.header.GetHash(), merkle_block.header.nBits, Params().GetConsensus())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
}
} else {
CTransactionRef tx_btc;
CMerkleBlock merkle_block;
CreatePegInInput(rawTx, idx, tx_btc, merkle_block, claim_scripts, ParseHex(pegin_tx.get_str()), ParseHex(pegin_tx_proof.get_str()));
if (!CheckProofSignedParent(merkle_block.header, Params().GetConsensus())) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof");
}
}
} else if (!pegin_tx.isNull() || !pegin_tx_proof.isNull() || !pegin_script.isNull()) {
if (allow_peg_in) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Some but not all pegin_ arguments provided");
} else {
throw JSONRPCError(RPC_INVALID_PARAMETER, "pegin_ arguments provided but this command does not support peg-ins");
}
}
}

if (!outputs_is_obj) {
Expand Down Expand Up @@ -558,6 +714,9 @@ static UniValue createrawtransaction(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, /* default */ "depends on the value of the 'replaceable' and 'locktime' arguments", "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 claim script generated by getpeginaddress."},
},
},
},
Expand Down Expand Up @@ -1952,7 +2111,7 @@ UniValue createpsbt(const JSONRPCRequest& request)
);

std::vector<CPubKey> output_pubkeys;
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4], &output_pubkeys);
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3], request.params[4], &output_pubkeys, false /* allow_peg_in */);

// Make a blank psbt
PartiallySignedTransaction psbtx(rawTx);
Expand Down
6 changes: 5 additions & 1 deletion src/rpc/rawtransaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ UniValue SignTransaction(interfaces::Chain& chain, CMutableTransaction& mtx, con
/** Create a transaction from univalue parameters. If (and only if)
output_pubkeys_out is null, the "nonce hack" of storing Confidential
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);
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, bool allow_peg_in = true);

/** 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
Loading

0 comments on commit 0d2832a

Please sign in to comment.