|
5 | 5 |
|
6 | 6 | #include <rpc/rawtransaction_util.h> |
7 | 7 |
|
| 8 | +#include <block_proof.h> |
8 | 9 | #include <coins.h> |
9 | 10 | #include <core_io.h> |
10 | 11 | #include <key_io.h> |
11 | 12 | #include <pegins.h> |
12 | 13 | #include <policy/policy.h> |
13 | 14 | #include <primitives/transaction.h> |
| 15 | +#include <primitives/bitcoin/merkleblock.h> |
| 16 | +#include <primitives/bitcoin/transaction.h> |
14 | 17 | #include <rpc/request.h> |
15 | 18 | #include <rpc/util.h> |
16 | 19 | #include <script/sign.h> |
|
21 | 24 | #include <util/strencodings.h> |
22 | 25 | #include <validation.h> |
23 | 26 |
|
24 | | -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf, const UniValue& assets_in, std::vector<CPubKey>* output_pubkeys_out) |
| 27 | +template<typename T_tx> |
| 28 | +unsigned int GetPeginTxnOutputIndex(const T_tx& txn, const CScript& witnessProgram, const std::vector<std::pair<CScript, CScript>>& fedpegscripts) |
| 29 | +{ |
| 30 | + for (const auto & scripts : fedpegscripts) { |
| 31 | + CScript mainchain_script = GetScriptForWitness(calculate_contract(scripts.second, witnessProgram)); |
| 32 | + if (scripts.first.IsPayToScriptHash()) { |
| 33 | + mainchain_script = GetScriptForDestination(ScriptHash(mainchain_script)); |
| 34 | + } |
| 35 | + for (unsigned int nOut = 0; nOut < txn.vout.size(); nOut++) { |
| 36 | + if (txn.vout[nOut].scriptPubKey == mainchain_script) { |
| 37 | + return nOut; |
| 38 | + } |
| 39 | + } |
| 40 | + } |
| 41 | + return txn.vout.size(); |
| 42 | +} |
| 43 | + |
| 44 | +// Modifies an existing transaction input in-place to be a valid peg-in input, and inserts the witness if deemed valid. |
| 45 | +template<typename T_tx_ref, typename T_merkle_block> |
| 46 | +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) |
| 47 | +{ |
| 48 | + if ((mtx.vin.size() > input_idx && !mtx.vin[input_idx].scriptSig.empty()) || (mtx.witness.vtxinwit.size() > input_idx && !mtx.witness.vtxinwit[input_idx].IsNull())) { |
| 49 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Attempting to add a peg-in to an input that already has a scriptSig or witness"); |
| 50 | + } |
| 51 | + |
| 52 | + CDataStream ssTx(txData, SER_NETWORK, PROTOCOL_VERSION); |
| 53 | + try { |
| 54 | + ssTx >> txBTCRef; |
| 55 | + } |
| 56 | + catch (...) { |
| 57 | + throw JSONRPCError(RPC_TYPE_ERROR, "The included bitcoinTx is malformed. Are you sure that is the whole string?"); |
| 58 | + } |
| 59 | + |
| 60 | + CDataStream ssTxOutProof(txOutProofData, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); |
| 61 | + try { |
| 62 | + ssTxOutProof >> merkleBlock; |
| 63 | + } |
| 64 | + catch (...) { |
| 65 | + throw JSONRPCError(RPC_TYPE_ERROR, "The included txoutproof is malformed. Are you sure that is the whole string?"); |
| 66 | + } |
| 67 | + |
| 68 | + if (!ssTxOutProof.empty()) { |
| 69 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof"); |
| 70 | + } |
| 71 | + |
| 72 | + std::vector<uint256> txHashes; |
| 73 | + std::vector<unsigned int> txIndices; |
| 74 | + if (merkleBlock.txn.ExtractMatches(txHashes, txIndices) != merkleBlock.header.hashMerkleRoot) |
| 75 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof"); |
| 76 | + |
| 77 | + if (txHashes.size() != 1 || txHashes[0] != txBTCRef->GetHash()) |
| 78 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "The txoutproof must contain bitcoinTx and only bitcoinTx"); |
| 79 | + |
| 80 | + CScript witness_script; |
| 81 | + unsigned int nOut = txBTCRef->vout.size(); |
| 82 | + const auto fedpegscripts = GetValidFedpegScripts(::ChainActive().Tip(), Params().GetConsensus(), true /* nextblock_validation */); |
| 83 | + for (const CScript& script : claim_scripts) { |
| 84 | + nOut = GetPeginTxnOutputIndex(*txBTCRef, script, fedpegscripts); |
| 85 | + if (nOut != txBTCRef->vout.size()) { |
| 86 | + witness_script = script; |
| 87 | + break; |
| 88 | + } |
| 89 | + } |
| 90 | + if (nOut == txBTCRef->vout.size()) { |
| 91 | + if (claim_scripts.size() == 1) { |
| 92 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script does not match the given Bitcoin transaction."); |
| 93 | + } else { |
| 94 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Failed to find output in bitcoinTx to the mainchain_address from getpeginaddress"); |
| 95 | + } |
| 96 | + } |
| 97 | + assert(witness_script != CScript()); |
| 98 | + |
| 99 | + int version = -1; |
| 100 | + std::vector<unsigned char> witness_program; |
| 101 | + if (!witness_script.IsWitnessProgram(version, witness_program) || version != 0) { |
| 102 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Given or recovered script is not a v0 witness program."); |
| 103 | + } |
| 104 | + |
| 105 | + CAmount value = 0; |
| 106 | + if (!GetAmountFromParentChainPegin(value, *txBTCRef, nOut)) { |
| 107 | + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to pegin must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex())); |
| 108 | + } |
| 109 | + |
| 110 | + // Add/replace input in mtx |
| 111 | + if (mtx.vin.size() <= input_idx) { |
| 112 | + mtx.vin.resize(input_idx + 1); |
| 113 | + } |
| 114 | + mtx.vin[input_idx] = CTxIn(COutPoint(txHashes[0], nOut), CScript(), ~(uint32_t)0); |
| 115 | + |
| 116 | + // Construct pegin proof |
| 117 | + CScriptWitness pegin_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), witness_script, txBTCRef, merkleBlock); |
| 118 | + |
| 119 | + // Peg-in witness isn't valid, even though the block header is(without depth check) |
| 120 | + // We re-check depth before returning with more descriptive result |
| 121 | + std::string err; |
| 122 | + if (!IsValidPeginWitness(pegin_witness, fedpegscripts, mtx.vin[input_idx].prevout, err, false)) { |
| 123 | + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Constructed peg-in witness is invalid: %s", err)); |
| 124 | + } |
| 125 | + |
| 126 | + // Put input witness in transaction |
| 127 | + mtx.vin[input_idx].m_is_pegin = true; |
| 128 | + CTxInWitness txinwit; |
| 129 | + txinwit.m_pegin_witness = pegin_witness; |
| 130 | + |
| 131 | + if (mtx.witness.vtxinwit.size() <= input_idx) { |
| 132 | + mtx.witness.vtxinwit.resize(input_idx + 1); |
| 133 | + } |
| 134 | + mtx.witness.vtxinwit[input_idx] = txinwit; |
| 135 | +} |
| 136 | + |
| 137 | +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) |
| 138 | +{ |
| 139 | + CreatePegInInputInner(mtx, input_idx, tx_btc, merkle_block, claim_scripts, txData, txOutProofData); |
| 140 | +} |
| 141 | +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) |
| 142 | +{ |
| 143 | + CreatePegInInputInner(mtx, input_idx, tx_btc, merkle_block, claim_scripts, txData, txOutProofData); |
| 144 | +} |
| 145 | + |
| 146 | +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, bool rbf, const UniValue& assets_in, std::vector<CPubKey>* output_pubkeys_out, bool allow_peg_in) |
25 | 147 | { |
26 | 148 | if (inputs_in.isNull() || outputs_in.isNull()) |
27 | 149 | throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); |
@@ -78,8 +200,43 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal |
78 | 200 | } |
79 | 201 |
|
80 | 202 | CTxIn in(COutPoint(txid, nOutput), CScript(), nSequence); |
81 | | - |
82 | 203 | rawTx.vin.push_back(in); |
| 204 | + |
| 205 | + // Get the pegin stuff if it's there |
| 206 | + const UniValue& pegin_tx = find_value(o, "pegin_bitcoin_tx"); |
| 207 | + const UniValue& pegin_tx_proof = find_value(o, "pegin_txout_proof"); |
| 208 | + const UniValue& pegin_script = find_value(o, "pegin_claim_script"); |
| 209 | + if (!pegin_tx.isNull() && !pegin_tx_proof.isNull() && !pegin_script.isNull() && allow_peg_in) { |
| 210 | + if (!IsHex(pegin_script.get_str())) { |
| 211 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Given claim_script is not hex."); |
| 212 | + } |
| 213 | + // If given manually, no need for it to be a witness script |
| 214 | + std::vector<unsigned char> claim_script_bytes(ParseHex(pegin_script.get_str())); |
| 215 | + CScript claim_script(claim_script_bytes.begin(), claim_script_bytes.end()); |
| 216 | + std::set<CScript> claim_scripts; |
| 217 | + claim_scripts.insert(std::move(claim_script)); |
| 218 | + if (Params().GetConsensus().ParentChainHasPow()) { |
| 219 | + Sidechain::Bitcoin::CTransactionRef tx_btc; |
| 220 | + Sidechain::Bitcoin::CMerkleBlock merkle_block; |
| 221 | + CreatePegInInput(rawTx, idx, tx_btc, merkle_block, claim_scripts, ParseHex(pegin_tx.get_str()), ParseHex(pegin_tx_proof.get_str())); |
| 222 | + if (!CheckParentProofOfWork(merkle_block.header.GetHash(), merkle_block.header.nBits, Params().GetConsensus())) { |
| 223 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof"); |
| 224 | + } |
| 225 | + } else { |
| 226 | + CTransactionRef tx_btc; |
| 227 | + CMerkleBlock merkle_block; |
| 228 | + CreatePegInInput(rawTx, idx, tx_btc, merkle_block, claim_scripts, ParseHex(pegin_tx.get_str()), ParseHex(pegin_tx_proof.get_str())); |
| 229 | + if (!CheckProofSignedParent(merkle_block.header, Params().GetConsensus())) { |
| 230 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid tx out proof"); |
| 231 | + } |
| 232 | + } |
| 233 | + } else if (!pegin_tx.isNull() || !pegin_tx_proof.isNull() || !pegin_script.isNull()) { |
| 234 | + if (allow_peg_in) { |
| 235 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "Some but not all pegin_ arguments provided"); |
| 236 | + } else { |
| 237 | + throw JSONRPCError(RPC_INVALID_PARAMETER, "pegin_ arguments provided but this command does not support peg-ins"); |
| 238 | + } |
| 239 | + } |
83 | 240 | } |
84 | 241 |
|
85 | 242 | if (!outputs_is_obj) { |
|
0 commit comments