Skip to content

Commit

Permalink
Implement updatepsbtpegin RPC
Browse files Browse the repository at this point in the history
  • Loading branch information
achow101 committed Aug 25, 2020
1 parent 2c5251e commit 78dac69
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "sendtoaddress", 9 , "ignoreblindfail" },
{ "createrawtransaction", 4, "output_assets" },
{ "calculateasset", 1, "vout" },
{ "updatepsbtpegin", 1, "input" },
{ "updatepsbtpegin", 2, "value" },

};
// clang-format on
Expand Down
152 changes: 152 additions & 0 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3107,6 +3107,157 @@ UniValue calculateasset(const JSONRPCRequest& request)
return out;
}

UniValue updatepsbtpegin(const JSONRPCRequest& request)
{
if (!g_con_elementsmode)
throw std::runtime_error("PSBT operations are disabled when not in elementsmode.\n");

if (request.fHelp || request.params.size() < 1 || request.params.size() > 7)
throw std::runtime_error(
RPCHelpMan{"updatepsbtpegin",
"\nFill in Peg-in input data for a particular input in a PSBT. Data is filled if provided.\n"
"The Peg-in witness will be constructed and finalized if everything is available.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO,"The elements PSBT to update"},
{"input", RPCArg::Type::NUM, RPCArg::Optional::NO, "The index of the input to update"},
{"value", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The value of the peg-in"},
{"bitcoin_tx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The raw bitcoin transaction (in hex) depositing bitcoin to the mainchain_address generated by getpeginaddress"},
{"txout_proof", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A rawtxoutproof (in hex) generated by the mainchain daemon'sgettxoutproof containing a proof of only bitcoin_tx"},
{"claim_script", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The witness program generated by getpeginaddress."},
{"genesis_hash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hash of the genesis block of the chain the bitcoin_tx is in"},
},
RPCResult{
" \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n"
},
RPCExamples{
HelpExampleCli("updatepsbtpegin", "psbt 0")
},
}.ToString());


RPCTypeCheck(request.params, {UniValue::VNUM, UniValue::VNUM, UniValue::VSTR, UniValue::VSTR, UniValue::VSTR}, true);

// Unserialize the transaction
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}

// Get the input to update
int input_index = request.params[1].get_int();
PSBTInput& input = psbtx.inputs[input_index];

// Peg-in value
if (!request.params[2].isNull()) {
CAmount value = AmountFromValue(request.params[2]);
input.peg_in_value = value;
}

// Peg-in tx
if (!request.params[3].isNull()) {
const std::vector<unsigned char> tx_data = ParseHex(request.params[3].get_str());
CDataStream ss_tx(tx_data, SER_NETWORK, PROTOCOL_VERSION);
try {
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CTransactionRef tx_btc;
ss_tx >> tx_btc;
input.peg_in_tx = tx_btc;
} else {
CTransactionRef tx_btc;
const std::vector<unsigned char> tx_data = ParseHex(request.params[3].get_str());
CDataStream ss_tx(tx_data, SER_NETWORK, PROTOCOL_VERSION);
ss_tx >> tx_btc;
input.peg_in_tx = tx_btc;
}
} catch (...) {
throw JSONRPCError(RPC_TYPE_ERROR, "The bitcoin_tx is malformed");
}
}

// Txout proof
if (!request.params[4].isNull()) {
const std::vector<unsigned char> proof_data = ParseHex(request.params[4].get_str());
CDataStream ss_proof(proof_data, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS);
try {
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CMerkleBlock merkle_block;
ss_proof >> merkle_block;
input.txout_proof = merkle_block;
} else {
CMerkleBlock merkle_block;
ss_proof >> merkle_block;
input.txout_proof = merkle_block;
}
} catch (...) {
throw JSONRPCError(RPC_TYPE_ERROR, "The txout proof is malformed");
}
if (!ss_proof.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid txout proof");
}
}

// Claim script
if (!request.params[5].isNull()) {
std::vector<unsigned char> script_bytes = ParseHexV(request.params[5], "claim_script");
CScript script(script_bytes.begin(), script_bytes.end());
input.claim_script = script;
}

// Genesis Hash
if (!request.params[6].isNull()) {
input.genesis_hash = ParseHashV(request.params[6], "genesis_hash");
}

// Finalize
if (input.peg_in_tx.which() > 0 && input.txout_proof.which() > 0 && !input.claim_script.empty() && !input.genesis_hash.IsNull() && input.peg_in_value && input.peg_in_witness.IsNull()) {
const auto fedpegscripts = GetValidFedpegScripts(chainActive.Tip(), Params().GetConsensus(), true /* nextblock_validation */);
std::vector<uint256> tx_hashes;
std::vector<unsigned int> tx_indices;
if (Params().GetConsensus().ParentChainHasPow()) {
Sidechain::Bitcoin::CMerkleBlock merkle_block = boost::get<Sidechain::Bitcoin::CMerkleBlock>(input.txout_proof);
Sidechain::Bitcoin::CTransactionRef tx_ref = boost::get<Sidechain::Bitcoin::CTransactionRef>(input.peg_in_tx);
if (merkle_block.txn.ExtractMatches(tx_hashes, tx_indices) != merkle_block.header.hashMerkleRoot) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Peg-in txout proof is invalid");
}
if (tx_hashes.size() != 1 || tx_hashes[0] != tx_ref->GetHash()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "The txout proof must contain the bitcoin_tx and only the bitcoin_tx");
}

unsigned int vout = GetPeginTxnOutputIndex(*tx_ref, input.claim_script, fedpegscripts);
if (vout == tx_ref->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "claim_script does not match the bitcoin_tx");
}
CAmount value;
if (!GetAmountFromParentChainPegin(value, *tx_ref, vout)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to peg-in must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
}
input.peg_in_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), input.claim_script, tx_ref, merkle_block);
} else {
CMerkleBlock merkle_block = boost::get<CMerkleBlock>(input.txout_proof);
CTransactionRef tx_ref = boost::get<CTransactionRef>(input.peg_in_tx);
if (merkle_block.txn.ExtractMatches(tx_hashes, tx_indices) != merkle_block.header.hashMerkleRoot) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Peg-in txout proof is invalid");
}
if (tx_hashes.size() != 1 || tx_hashes[0] != tx_ref->GetHash()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "The txout proof must contain the bitcoin_tx and only the bitcoin_tx");
}

unsigned int vout = GetPeginTxnOutputIndex(*tx_ref, input.claim_script, fedpegscripts);
if (vout == tx_ref->vout.size()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "claim_script does not match the bitcoin_tx");
}
CAmount value;
if (!GetAmountFromParentChainPegin(value, *tx_ref, vout)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Amounts to peg-in must be explicit and asset must be %s", Params().GetConsensus().parent_pegged_asset.GetHex()));
}
input.peg_in_witness = CreatePeginWitness(value, Params().GetConsensus().pegged_asset, Params().ParentGenesisBlockHash(), input.claim_script, tx_ref, merkle_block);
}
}

return EncodePSBT(psbtx);
}

// END ELEMENTS
//

Expand Down Expand Up @@ -3138,6 +3289,7 @@ static const CRPCCommand commands[] =
{ "rawtransactions", "rawreissueasset", &rawreissueasset, {"transaction", "reissuances"}},
{ "rawtransactions", "rawblindrawtransaction", &rawblindrawtransaction, {"hexstring", "inputblinder", "inputamount", "inputasset", "inputassetblinder", "totalblinder", "ignoreblindfail"} },
{ "rawtransactions", "calculateasset", &calculateasset, {"txid", "vout", "asset_entropy"} },
{ "rawtransactions", "updatepsbtpegin", &updatepsbtpegin, {"psbt", "input", "value", "bitcoin_tx", "txout_proof", "claim_script"} },
};
// clang-format on

Expand Down

0 comments on commit 78dac69

Please sign in to comment.