mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-03-02 02:59:46 +01:00
fa6801366drefactor: [rpc] Remove confusing and brittle integral casts (take 2) (MarcoFalke) Pull request description: Second take for cases which I seem to have missed in commit94ddc2dced. When constructing an UniValue from integral values, historically (long ago), in some cases casts where needed. With the current UniValue constructor, only very few are actually needed. Keeping the unused casts around is: * confusing, because code readers do not understand why they are needed * brittle, because some may copy them into new places, where they will lead to hard-to-find logic bugs, such as the ones fixed in pull https://github.com/bitcoin/bitcoin/pull/34112 So fix all issues by removing them, except for a few cases, where casting was required: * `static_cast<bool>(fCoinBase)`, because `bool{fCoinBase}` only works on modern compilers. This hardening refactor does not fix any bugs and does not change any behavior. ACKs for top commit: Sjors: ACKfa6801366dsedited: ACKfa6801366dTree-SHA512: 77f03f496ea2a42987720cb4a36eb4e7a0d5b512ed7f029e41dd54c04fb4c1680f7cc13514acbc9f1bae991be4de3cf31c5a0d27c016a030f5749d132603f71e
535 lines
19 KiB
C++
535 lines
19 KiB
C++
// Copyright (c) 2009-present The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <core_io.h>
|
|
|
|
#include <addresstype.h>
|
|
#include <coins.h>
|
|
#include <consensus/amount.h>
|
|
#include <consensus/consensus.h>
|
|
#include <consensus/validation.h>
|
|
#include <crypto/hex_base.h>
|
|
#include <key_io.h>
|
|
// IWYU incorrectly suggests replacing this header
|
|
// with forward declarations.
|
|
// See https://github.com/include-what-you-use/include-what-you-use/issues/1886.
|
|
#include <primitives/block.h> // IWYU pragma: keep
|
|
#include <primitives/transaction.h>
|
|
#include <script/descriptor.h>
|
|
#include <script/interpreter.h>
|
|
#include <script/script.h>
|
|
#include <script/signingprovider.h>
|
|
#include <script/solver.h>
|
|
#include <serialize.h>
|
|
#include <streams.h>
|
|
#include <tinyformat.h>
|
|
#include <uint256.h>
|
|
#include <undo.h>
|
|
#include <univalue.h>
|
|
#include <util/check.h>
|
|
#include <util/result.h>
|
|
#include <util/strencodings.h>
|
|
#include <util/string.h>
|
|
#include <util/translation.h>
|
|
|
|
#include <algorithm>
|
|
#include <compare>
|
|
#include <cstdint>
|
|
#include <exception>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
using util::SplitString;
|
|
|
|
namespace {
|
|
class OpCodeParser
|
|
{
|
|
private:
|
|
std::map<std::string, opcodetype> mapOpNames;
|
|
|
|
public:
|
|
OpCodeParser()
|
|
{
|
|
for (unsigned int op = 0; op <= MAX_OPCODE; ++op) {
|
|
// Allow OP_RESERVED to get into mapOpNames
|
|
if (op < OP_NOP && op != OP_RESERVED) {
|
|
continue;
|
|
}
|
|
|
|
std::string strName = GetOpName(static_cast<opcodetype>(op));
|
|
if (strName == "OP_UNKNOWN") {
|
|
continue;
|
|
}
|
|
mapOpNames[strName] = static_cast<opcodetype>(op);
|
|
// Convenience: OP_ADD and just ADD are both recognized:
|
|
if (strName.starts_with("OP_")) {
|
|
mapOpNames[strName.substr(3)] = static_cast<opcodetype>(op);
|
|
}
|
|
}
|
|
}
|
|
opcodetype Parse(const std::string& s) const
|
|
{
|
|
auto it = mapOpNames.find(s);
|
|
if (it == mapOpNames.end()) throw std::runtime_error("script parse error: unknown opcode");
|
|
return it->second;
|
|
}
|
|
};
|
|
|
|
opcodetype ParseOpCode(const std::string& s)
|
|
{
|
|
static const OpCodeParser ocp;
|
|
return ocp.Parse(s);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
CScript ParseScript(const std::string& s)
|
|
{
|
|
CScript result;
|
|
|
|
std::vector<std::string> words = SplitString(s, " \t\n");
|
|
|
|
for (const std::string& w : words) {
|
|
if (w.empty()) {
|
|
// Empty string, ignore. (SplitString doesn't combine multiple separators)
|
|
} else if (std::all_of(w.begin(), w.end(), ::IsDigit) ||
|
|
(w.front() == '-' && w.size() > 1 && std::all_of(w.begin() + 1, w.end(), ::IsDigit)))
|
|
{
|
|
// Number
|
|
const auto num{ToIntegral<int64_t>(w)};
|
|
|
|
// limit the range of numbers ParseScript accepts in decimal
|
|
// since numbers outside -0xFFFFFFFF...0xFFFFFFFF are illegal in scripts
|
|
if (!num.has_value() || num > int64_t{0xffffffff} || num < -1 * int64_t{0xffffffff}) {
|
|
throw std::runtime_error("script parse error: decimal numeric value only allowed in the "
|
|
"range -0xFFFFFFFF...0xFFFFFFFF");
|
|
}
|
|
|
|
result << num.value();
|
|
} else if (w.starts_with("0x") && w.size() > 2 && IsHex(std::string(w.begin() + 2, w.end()))) {
|
|
// Raw hex data, inserted NOT pushed onto stack:
|
|
std::vector<unsigned char> raw = ParseHex(std::string(w.begin() + 2, w.end()));
|
|
result.insert(result.end(), raw.begin(), raw.end());
|
|
} else if (w.size() >= 2 && w.front() == '\'' && w.back() == '\'') {
|
|
// Single-quoted string, pushed as data. NOTE: this is poor-man's
|
|
// parsing, spaces/tabs/newlines in single-quoted strings won't work.
|
|
std::vector<unsigned char> value(w.begin() + 1, w.end() - 1);
|
|
result << value;
|
|
} else {
|
|
// opcode, e.g. OP_ADD or ADD:
|
|
result << ParseOpCode(w);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/// Check that all of the input and output scripts of a transaction contain valid opcodes
|
|
static bool CheckTxScriptsSanity(const CMutableTransaction& tx)
|
|
{
|
|
// Check input scripts for non-coinbase txs
|
|
if (!CTransaction(tx).IsCoinBase()) {
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
|
if (!tx.vin[i].scriptSig.HasValidOps() || tx.vin[i].scriptSig.size() > MAX_SCRIPT_SIZE) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
// Check output scripts
|
|
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
|
if (!tx.vout[i].scriptPubKey.HasValidOps() || tx.vout[i].scriptPubKey.size() > MAX_SCRIPT_SIZE) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool DecodeTx(CMutableTransaction& tx, const std::vector<unsigned char>& tx_data, bool try_no_witness, bool try_witness)
|
|
{
|
|
// General strategy:
|
|
// - Decode both with extended serialization (which interprets the 0x0001 tag as a marker for
|
|
// the presence of witnesses) and with legacy serialization (which interprets the tag as a
|
|
// 0-input 1-output incomplete transaction).
|
|
// - Restricted by try_no_witness (which disables legacy if false) and try_witness (which
|
|
// disables extended if false).
|
|
// - Ignore serializations that do not fully consume the hex string.
|
|
// - If neither succeeds, fail.
|
|
// - If only one succeeds, return that one.
|
|
// - If both decode attempts succeed:
|
|
// - If only one passes the CheckTxScriptsSanity check, return that one.
|
|
// - If neither or both pass CheckTxScriptsSanity, return the extended one.
|
|
|
|
CMutableTransaction tx_extended, tx_legacy;
|
|
bool ok_extended = false, ok_legacy = false;
|
|
|
|
// Try decoding with extended serialization support, and remember if the result successfully
|
|
// consumes the entire input.
|
|
if (try_witness) {
|
|
SpanReader ssData{tx_data};
|
|
try {
|
|
ssData >> TX_WITH_WITNESS(tx_extended);
|
|
if (ssData.empty()) ok_extended = true;
|
|
} catch (const std::exception&) {
|
|
// Fall through.
|
|
}
|
|
}
|
|
|
|
// Optimization: if extended decoding succeeded and the result passes CheckTxScriptsSanity,
|
|
// don't bother decoding the other way.
|
|
if (ok_extended && CheckTxScriptsSanity(tx_extended)) {
|
|
tx = std::move(tx_extended);
|
|
return true;
|
|
}
|
|
|
|
// Try decoding with legacy serialization, and remember if the result successfully consumes the entire input.
|
|
if (try_no_witness) {
|
|
SpanReader ssData{tx_data};
|
|
try {
|
|
ssData >> TX_NO_WITNESS(tx_legacy);
|
|
if (ssData.empty()) ok_legacy = true;
|
|
} catch (const std::exception&) {
|
|
// Fall through.
|
|
}
|
|
}
|
|
|
|
// If legacy decoding succeeded and passes CheckTxScriptsSanity, that's our answer, as we know
|
|
// at this point that extended decoding either failed or doesn't pass the sanity check.
|
|
if (ok_legacy && CheckTxScriptsSanity(tx_legacy)) {
|
|
tx = std::move(tx_legacy);
|
|
return true;
|
|
}
|
|
|
|
// If extended decoding succeeded, and neither decoding passes sanity, return the extended one.
|
|
if (ok_extended) {
|
|
tx = std::move(tx_extended);
|
|
return true;
|
|
}
|
|
|
|
// If legacy decoding succeeded and extended didn't, return the legacy one.
|
|
if (ok_legacy) {
|
|
tx = std::move(tx_legacy);
|
|
return true;
|
|
}
|
|
|
|
// If none succeeded, we failed.
|
|
return false;
|
|
}
|
|
|
|
bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness, bool try_witness)
|
|
{
|
|
if (!IsHex(hex_tx)) {
|
|
return false;
|
|
}
|
|
|
|
std::vector<unsigned char> txData(ParseHex(hex_tx));
|
|
return DecodeTx(tx, txData, try_no_witness, try_witness);
|
|
}
|
|
|
|
bool DecodeHexBlockHeader(CBlockHeader& header, const std::string& hex_header)
|
|
{
|
|
if (!IsHex(hex_header)) return false;
|
|
|
|
const std::vector<unsigned char> header_data{ParseHex(hex_header)};
|
|
try {
|
|
SpanReader{header_data} >> header;
|
|
} catch (const std::exception&) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
|
|
{
|
|
if (!IsHex(strHexBlk))
|
|
return false;
|
|
|
|
std::vector<unsigned char> blockData(ParseHex(strHexBlk));
|
|
try {
|
|
SpanReader{blockData} >> TX_WITH_WITNESS(block);
|
|
}
|
|
catch (const std::exception&) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
util::Result<int> SighashFromStr(const std::string& sighash)
|
|
{
|
|
static const std::map<std::string, int> map_sighash_values = {
|
|
{std::string("DEFAULT"), int(SIGHASH_DEFAULT)},
|
|
{std::string("ALL"), int(SIGHASH_ALL)},
|
|
{std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)},
|
|
{std::string("NONE"), int(SIGHASH_NONE)},
|
|
{std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)},
|
|
{std::string("SINGLE"), int(SIGHASH_SINGLE)},
|
|
{std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)},
|
|
};
|
|
const auto& it = map_sighash_values.find(sighash);
|
|
if (it != map_sighash_values.end()) {
|
|
return it->second;
|
|
} else {
|
|
return util::Error{Untranslated("'" + sighash + "' is not a valid sighash parameter.")};
|
|
}
|
|
}
|
|
|
|
UniValue ValueFromAmount(const CAmount amount)
|
|
{
|
|
static_assert(COIN > 1);
|
|
int64_t quotient = amount / COIN;
|
|
int64_t remainder = amount % COIN;
|
|
if (amount < 0) {
|
|
quotient = -quotient;
|
|
remainder = -remainder;
|
|
}
|
|
return UniValue(UniValue::VNUM,
|
|
strprintf("%s%d.%08d", amount < 0 ? "-" : "", quotient, remainder));
|
|
}
|
|
|
|
std::string FormatScript(const CScript& script)
|
|
{
|
|
std::string ret;
|
|
CScript::const_iterator it = script.begin();
|
|
opcodetype op;
|
|
while (it != script.end()) {
|
|
CScript::const_iterator it2 = it;
|
|
std::vector<unsigned char> vch;
|
|
if (script.GetOp(it, op, vch)) {
|
|
if (op == OP_0) {
|
|
ret += "0 ";
|
|
continue;
|
|
} else if ((op >= OP_1 && op <= OP_16) || op == OP_1NEGATE) {
|
|
ret += strprintf("%i ", op - OP_1NEGATE - 1);
|
|
continue;
|
|
} else if (op >= OP_NOP && op <= OP_NOP10) {
|
|
std::string str(GetOpName(op));
|
|
if (str.substr(0, 3) == std::string("OP_")) {
|
|
ret += str.substr(3, std::string::npos) + " ";
|
|
continue;
|
|
}
|
|
}
|
|
if (vch.size() > 0) {
|
|
ret += strprintf("0x%x 0x%x ", HexStr(std::vector<uint8_t>(it2, it - vch.size())),
|
|
HexStr(std::vector<uint8_t>(it - vch.size(), it)));
|
|
} else {
|
|
ret += strprintf("0x%x ", HexStr(std::vector<uint8_t>(it2, it)));
|
|
}
|
|
continue;
|
|
}
|
|
ret += strprintf("0x%x ", HexStr(std::vector<uint8_t>(it2, script.end())));
|
|
break;
|
|
}
|
|
return ret.substr(0, ret.empty() ? ret.npos : ret.size() - 1);
|
|
}
|
|
|
|
const std::map<unsigned char, std::string> mapSigHashTypes = {
|
|
{static_cast<unsigned char>(SIGHASH_ALL), std::string("ALL")},
|
|
{static_cast<unsigned char>(SIGHASH_ALL|SIGHASH_ANYONECANPAY), std::string("ALL|ANYONECANPAY")},
|
|
{static_cast<unsigned char>(SIGHASH_NONE), std::string("NONE")},
|
|
{static_cast<unsigned char>(SIGHASH_NONE|SIGHASH_ANYONECANPAY), std::string("NONE|ANYONECANPAY")},
|
|
{static_cast<unsigned char>(SIGHASH_SINGLE), std::string("SINGLE")},
|
|
{static_cast<unsigned char>(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY), std::string("SINGLE|ANYONECANPAY")},
|
|
};
|
|
|
|
std::string SighashToStr(unsigned char sighash_type)
|
|
{
|
|
const auto& it = mapSigHashTypes.find(sighash_type);
|
|
if (it == mapSigHashTypes.end()) return "";
|
|
return it->second;
|
|
}
|
|
|
|
/**
|
|
* Create the assembly string representation of a CScript object.
|
|
* @param[in] script CScript object to convert into the asm string representation.
|
|
* @param[in] fAttemptSighashDecode Whether to attempt to decode sighash types on data within the script that matches the format
|
|
* of a signature. Only pass true for scripts you believe could contain signatures. For example,
|
|
* pass false, or omit the this argument (defaults to false), for scriptPubKeys.
|
|
*/
|
|
std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDecode)
|
|
{
|
|
std::string str;
|
|
opcodetype opcode;
|
|
std::vector<unsigned char> vch;
|
|
CScript::const_iterator pc = script.begin();
|
|
while (pc < script.end()) {
|
|
if (!str.empty()) {
|
|
str += " ";
|
|
}
|
|
if (!script.GetOp(pc, opcode, vch)) {
|
|
str += "[error]";
|
|
return str;
|
|
}
|
|
if (0 <= opcode && opcode <= OP_PUSHDATA4) {
|
|
if (vch.size() <= static_cast<std::vector<unsigned char>::size_type>(4)) {
|
|
str += strprintf("%d", CScriptNum(vch, false).getint());
|
|
} else {
|
|
// the IsUnspendable check makes sure not to try to decode OP_RETURN data that may match the format of a signature
|
|
if (fAttemptSighashDecode && !script.IsUnspendable()) {
|
|
std::string strSigHashDecode;
|
|
// goal: only attempt to decode a defined sighash type from data that looks like a signature within a scriptSig.
|
|
// this won't decode correctly formatted public keys in Pubkey or Multisig scripts due to
|
|
// the restrictions on the pubkey formats (see IsCompressedOrUncompressedPubKey) being incongruous with the
|
|
// checks in CheckSignatureEncoding.
|
|
if (CheckSignatureEncoding(vch, SCRIPT_VERIFY_STRICTENC, nullptr)) {
|
|
const unsigned char chSigHashType = vch.back();
|
|
const auto it = mapSigHashTypes.find(chSigHashType);
|
|
if (it != mapSigHashTypes.end()) {
|
|
strSigHashDecode = "[" + it->second + "]";
|
|
vch.pop_back(); // remove the sighash type byte. it will be replaced by the decode.
|
|
}
|
|
}
|
|
str += HexStr(vch) + strSigHashDecode;
|
|
} else {
|
|
str += HexStr(vch);
|
|
}
|
|
}
|
|
} else {
|
|
str += GetOpName(opcode);
|
|
}
|
|
}
|
|
return str;
|
|
}
|
|
|
|
std::string EncodeHexTx(const CTransaction& tx)
|
|
{
|
|
DataStream ssTx;
|
|
ssTx << TX_WITH_WITNESS(tx);
|
|
return HexStr(ssTx);
|
|
}
|
|
|
|
void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex, bool include_address, const SigningProvider* provider)
|
|
{
|
|
CTxDestination address;
|
|
|
|
out.pushKV("asm", ScriptToAsmStr(script));
|
|
if (include_address) {
|
|
out.pushKV("desc", InferDescriptor(script, provider ? *provider : DUMMY_SIGNING_PROVIDER)->ToString());
|
|
}
|
|
if (include_hex) {
|
|
out.pushKV("hex", HexStr(script));
|
|
}
|
|
|
|
std::vector<std::vector<unsigned char>> solns;
|
|
const TxoutType type{Solver(script, solns)};
|
|
|
|
if (include_address && ExtractDestination(script, address) && type != TxoutType::PUBKEY) {
|
|
out.pushKV("address", EncodeDestination(address));
|
|
}
|
|
out.pushKV("type", GetTxnOutputType(type));
|
|
}
|
|
|
|
void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex, const CTxUndo* txundo, TxVerbosity verbosity, std::function<bool(const CTxOut&)> is_change_func)
|
|
{
|
|
CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS);
|
|
|
|
entry.pushKV("txid", tx.GetHash().GetHex());
|
|
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
|
|
entry.pushKV("version", tx.version);
|
|
entry.pushKV("size", tx.ComputeTotalSize());
|
|
entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
|
|
entry.pushKV("weight", GetTransactionWeight(tx));
|
|
entry.pushKV("locktime", tx.nLockTime);
|
|
|
|
UniValue vin{UniValue::VARR};
|
|
vin.reserve(tx.vin.size());
|
|
|
|
// If available, use Undo data to calculate the fee. Note that txundo == nullptr
|
|
// for coinbase transactions and for transactions where undo data is unavailable.
|
|
const bool have_undo = txundo != nullptr;
|
|
CAmount amt_total_in = 0;
|
|
CAmount amt_total_out = 0;
|
|
|
|
for (unsigned int i = 0; i < tx.vin.size(); i++) {
|
|
const CTxIn& txin = tx.vin[i];
|
|
UniValue in(UniValue::VOBJ);
|
|
if (tx.IsCoinBase()) {
|
|
in.pushKV("coinbase", HexStr(txin.scriptSig));
|
|
} else {
|
|
in.pushKV("txid", txin.prevout.hash.GetHex());
|
|
in.pushKV("vout", txin.prevout.n);
|
|
UniValue o(UniValue::VOBJ);
|
|
o.pushKV("asm", ScriptToAsmStr(txin.scriptSig, true));
|
|
o.pushKV("hex", HexStr(txin.scriptSig));
|
|
in.pushKV("scriptSig", std::move(o));
|
|
}
|
|
if (!tx.vin[i].scriptWitness.IsNull()) {
|
|
UniValue txinwitness(UniValue::VARR);
|
|
txinwitness.reserve(tx.vin[i].scriptWitness.stack.size());
|
|
for (const auto& item : tx.vin[i].scriptWitness.stack) {
|
|
txinwitness.push_back(HexStr(item));
|
|
}
|
|
in.pushKV("txinwitness", std::move(txinwitness));
|
|
}
|
|
if (have_undo) {
|
|
const Coin& prev_coin = txundo->vprevout[i];
|
|
const CTxOut& prev_txout = prev_coin.out;
|
|
|
|
amt_total_in += prev_txout.nValue;
|
|
|
|
if (verbosity == TxVerbosity::SHOW_DETAILS_AND_PREVOUT) {
|
|
UniValue o_script_pub_key(UniValue::VOBJ);
|
|
ScriptToUniv(prev_txout.scriptPubKey, /*out=*/o_script_pub_key, /*include_hex=*/true, /*include_address=*/true);
|
|
|
|
UniValue p(UniValue::VOBJ);
|
|
p.pushKV("generated", static_cast<bool>(prev_coin.fCoinBase));
|
|
p.pushKV("height", prev_coin.nHeight);
|
|
p.pushKV("value", ValueFromAmount(prev_txout.nValue));
|
|
p.pushKV("scriptPubKey", std::move(o_script_pub_key));
|
|
in.pushKV("prevout", std::move(p));
|
|
}
|
|
}
|
|
in.pushKV("sequence", txin.nSequence);
|
|
vin.push_back(std::move(in));
|
|
}
|
|
entry.pushKV("vin", std::move(vin));
|
|
|
|
UniValue vout(UniValue::VARR);
|
|
vout.reserve(tx.vout.size());
|
|
for (unsigned int i = 0; i < tx.vout.size(); i++) {
|
|
const CTxOut& txout = tx.vout[i];
|
|
|
|
UniValue out(UniValue::VOBJ);
|
|
|
|
out.pushKV("value", ValueFromAmount(txout.nValue));
|
|
out.pushKV("n", i);
|
|
|
|
UniValue o(UniValue::VOBJ);
|
|
ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
|
|
out.pushKV("scriptPubKey", std::move(o));
|
|
|
|
if (is_change_func && is_change_func(txout)) {
|
|
out.pushKV("ischange", true);
|
|
}
|
|
|
|
vout.push_back(std::move(out));
|
|
|
|
if (have_undo) {
|
|
amt_total_out += txout.nValue;
|
|
}
|
|
}
|
|
entry.pushKV("vout", std::move(vout));
|
|
|
|
if (have_undo) {
|
|
const CAmount fee = amt_total_in - amt_total_out;
|
|
CHECK_NONFATAL(MoneyRange(fee));
|
|
entry.pushKV("fee", ValueFromAmount(fee));
|
|
}
|
|
|
|
if (!block_hash.IsNull()) {
|
|
entry.pushKV("blockhash", block_hash.GetHex());
|
|
}
|
|
|
|
if (include_hex) {
|
|
entry.pushKV("hex", EncodeHexTx(tx)); // The hex-encoded transaction. Used the name "hex" to be consistent with the verbose output of "getrawtransaction".
|
|
}
|
|
}
|