mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
Merge #741: Allow specifying peg-in data in createrawtransaction
5a4d61169Test raw transaction peg-in (Andrew Chow)ac6f052a6Have find_vout_for_address only get non-confidential address when using elements chain (Andrew Chow)cefe40767Allow specifying peg-in info in raw transaction RPC inputs (Andrew Chow)42babb81fRefactor peg-in input construction into its own function in rpc/rawtransaction.cpp (Andrew Chow)a90d1521cRefactor 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
This commit is contained in:
commit
0d2832a6d1
8 changed files with 260 additions and 121 deletions
|
|
@ -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,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>
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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");
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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."},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4672,7 +4672,7 @@ UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
|
|||
// It's hard to control the behavior of FundTransaction, so we will wait
|
||||
// until after it's done, then extract the blinding keys from the output
|
||||
// nonces.
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */);
|
||||
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]["replaceable"], NullUniValue /* CA: assets_in */, nullptr /* output_pubkeys_out */, false /* allow_peg_in */);
|
||||
FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]);
|
||||
|
||||
// Make a blank psbt
|
||||
|
|
@ -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,51 +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()));
|
||||
}
|
||||
|
||||
CDataStream stream(0, 0);
|
||||
try {
|
||||
stream << value;
|
||||
} catch (...) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Amount serialization is invalid.");
|
||||
}
|
||||
// Need to reinterpret bytes as unsigned chars before adding to witness
|
||||
char* buf = stream.data();
|
||||
unsigned char* membuf = reinterpret_cast<unsigned char*>(buf);
|
||||
std::vector<unsigned char> value_bytes(membuf, membuf + stream.size());
|
||||
|
||||
uint256 genesisBlockHash = Params().ParentGenesisBlockHash();
|
||||
// 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
|
||||
|
|
@ -5558,40 +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()));
|
||||
|
||||
// Strip witness data for proof inclusion since only TXID-covered fields matters
|
||||
CDataStream ssTxBack(SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
|
||||
ssTxBack << txBTC;
|
||||
std::vector<unsigned char> tx_data_stripped(ssTxBack.begin(), ssTxBack.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>(Params().GetConsensus().pegged_asset.begin(), Params().GetConsensus().pegged_asset.end()));
|
||||
stack.push_back(std::vector<unsigned char>(genesisBlockHash.begin(), genesisBlockHash.end()));
|
||||
stack.push_back(std::vector<unsigned char>(witness_script.begin(), witness_script.end()));
|
||||
stack.push_back(tx_data_stripped);
|
||||
stack.push_back(txOutProofData);
|
||||
|
||||
// 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)) +
|
||||
|
|
@ -5611,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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from test_framework.util import (
|
|||
assert_equal,
|
||||
bytes_to_hex_str,
|
||||
hex_str_to_bytes,
|
||||
find_vout_for_address
|
||||
)
|
||||
from test_framework import util
|
||||
from test_framework.messages import (
|
||||
|
|
@ -217,6 +218,7 @@ class FedPegTest(BitcoinTestFramework):
|
|||
addr = addrs["mainchain_address"]
|
||||
assert_equal(sidechain.decodescript(addrs["claim_script"])["type"], "witness_v0_keyhash")
|
||||
txid1 = parent.sendtoaddress(addr, 24)
|
||||
vout = find_vout_for_address(parent, txid1, addr)
|
||||
# 10+2 confirms required to get into mempool and confirm
|
||||
assert_equal(sidechain.getsidechaininfo()["pegin_confirmation_depth"], 10)
|
||||
parent.generate(1)
|
||||
|
|
@ -277,6 +279,18 @@ class FedPegTest(BitcoinTestFramework):
|
|||
raw_pegin = sidechain.createrawpegin(raw, proof)['hex']
|
||||
signed_pegin = sidechain.signrawtransactionwithwallet(raw_pegin)
|
||||
|
||||
# Find the address that the peg-in used
|
||||
outputs = []
|
||||
for pegin_vout in sidechain.decoderawtransaction(raw_pegin)['vout']:
|
||||
if pegin_vout['scriptPubKey']['type'] == 'witness_v0_keyhash':
|
||||
outputs.append({pegin_vout['scriptPubKey']['addresses'][0]: pegin_vout['value']})
|
||||
elif pegin_vout['scriptPubKey']['type'] == 'fee':
|
||||
outputs.append({"fee": pegin_vout['value']})
|
||||
|
||||
# Check the createrawtransaction makes the same unsigned peg-in transaction
|
||||
raw_pegin2 = sidechain.createrawtransaction([{"txid":txid1, "vout": vout, "pegin_bitcoin_tx": raw, "pegin_txout_proof": proof, "pegin_claim_script": addrs["claim_script"]}], outputs)
|
||||
assert_equal(raw_pegin, raw_pegin2)
|
||||
|
||||
sample_pegin_struct = FromHex(CTransaction(), signed_pegin["hex"])
|
||||
# Round-trip peg-in transaction using python serialization
|
||||
assert_equal(signed_pegin["hex"], bytes_to_hex_str(sample_pegin_struct.serialize()))
|
||||
|
|
|
|||
|
|
@ -524,6 +524,14 @@ class PSBTTest(BitcoinTestFramework):
|
|||
# Some Confidential-Assets-specific tests
|
||||
self.run_ca_tests()
|
||||
|
||||
# Check that peg-ins are disallowed
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].createpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_bitcoin_tx": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].createpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_txout_proof": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].createpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_claim_script": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_bitcoin_tx": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_txout_proof": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
assert_raises_rpc_error(-8, 'pegin_ arguments provided but this command does not support peg-ins', self.nodes[0].walletcreatefundedpsbt, [{"txid": "0000000000000000000000000000000000000000000000000000000000000000", "vout": 0, "pegin_claim_script": "00"}], [{self.nodes[0].getnewaddress(): 1}])
|
||||
|
||||
# Tests added in the 0.18 rebase don't pass on Elements yet.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -599,7 +599,9 @@ def find_vout_for_address(node, txid, addr):
|
|||
given address. Raises runtime error exception if not found.
|
||||
"""
|
||||
tx = node.getrawtransaction(txid, True)
|
||||
unblind_addr = node.validateaddress(addr)["unconfidential"]
|
||||
unblind_addr = addr
|
||||
if node.getblockchaininfo()['chain'] == 'elementsregtest':
|
||||
unblind_addr = node.validateaddress(addr)["unconfidential"]
|
||||
for i in range(len(tx["vout"])):
|
||||
if tx["vout"][i]["scriptPubKey"]["type"] == "fee":
|
||||
continue
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue