refactor: [move-only] Merge core_io module
What changed, and why it matters
This commit is a pure code reorganization: it merges the contents of two source files (core_read.cpp and core_write.cpp) into a single new file (core_io.cpp) and updates build scripts accordingly. No program behavior changes, no security fixes, and no vulnerability is introduced.
No security action needed. Treat as routine maintenance/refactoring.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit is labeled [move-only] and the diff confirms it. core_read.cpp and core_write.cpp are deleted and their contents are concatenated into a new core_io.cpp. CMakeLists.txt is updated to compile only core_io.cpp, the circular-dependencies.py mapping is removed, and the IWYU enforcement pattern is updated from core_read.cpp to core_io.cpp. core_io.h loses the per-file section comments. The code itself is byte-for-byte identical to the originals, so this is a non-functional refactor.
Changed components
src/core_io.cppsrc/core_io.hsrc/core_read.cppsrc/core_write.cppsrc/CMakeLists.txtcontrib/devtools/circular-dependencies.pyci/test/03_test_script.shInspect captured patch +535 / −553
diff --git a/ci/test/03_test_script.sh b/ci/test/03_test_script.sh
index 0bb7bcbf..050d9e6a 100755
--- a/ci/test/03_test_script.sh
+++ b/ci/test/03_test_script.sh
@@ -215,7 +215,7 @@ fi
if [[ "${RUN_IWYU}" == true ]]; then
# TODO: Consider enforcing IWYU across the entire codebase.
- FILES_WITH_ENFORCED_IWYU="/src/((crypto|index|kernel)/.*\\.cpp|node/blockstorage.cpp|node/utxo_snapshot.cpp|core_read.cpp|signet.cpp)"
+ FILES_WITH_ENFORCED_IWYU="/src/((crypto|index|kernel)/.*\\.cpp|node/blockstorage.cpp|node/utxo_snapshot.cpp|core_io.cpp|signet.cpp)"
jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns)))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_errors.json"
jq --arg patterns "$FILES_WITH_ENFORCED_IWYU" 'map(select(.file | test($patterns) | not))' "${BASE_BUILD_DIR}/compile_commands.json" > "${BASE_BUILD_DIR}/compile_commands_iwyu_warnings.json"
diff --git a/contrib/devtools/circular-dependencies.py b/contrib/devtools/circular-dependencies.py
index af19b3a0..7c058171 100755
--- a/contrib/devtools/circular-dependencies.py
+++ b/contrib/devtools/circular-dependencies.py
@@ -6,11 +6,6 @@
import sys
import re
-MAPPING = {
- 'core_read.cpp': 'core_io.cpp',
- 'core_write.cpp': 'core_io.cpp',
-}
-
# Directories with header-based modules, where the assumption that .cpp files
# define functions and variables declared in corresponding .h files is
# incorrect.
@@ -19,8 +14,6 @@ HEADER_MODULE_PATHS = [
]
def module_name(path):
- if path in MAPPING:
- path = MAPPING[path]
if any(path.startswith(dirpath) for dirpath in HEADER_MODULE_PATHS):
return path
if path.endswith(".h"):
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a1e0c5f1..cf1f26c9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -106,8 +106,7 @@ add_library(bitcoin_common STATIC EXCLUDE_FROM_ALL
common/system.cpp
common/url.cpp
compressor.cpp
- core_read.cpp
- core_write.cpp
+ core_io.cpp
deploymentinfo.cpp
external_signer.cpp
init/common.cpp
diff --git a/src/core_io.cpp b/src/core_io.cpp
new file mode 100644
index 00000000..05d7e527
--- /dev/null
+++ b/src/core_io.cpp
@@ -0,0 +1,532 @@
+// 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 <key_io.h>
+#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 contains 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) {
+ DataStream 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) {
+ DataStream 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)};
+ DataStream ser_header{header_data};
+ try {
+ ser_header >> 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));
+ DataStream ssBlock(blockData);
+ try {
+ ssBlock >> 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.GetTotalSize());
+ entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
+ entry.pushKV("weight", GetTransactionWeight(tx));
+ entry.pushKV("locktime", (int64_t)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", (int64_t)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", bool(prev_coin.fCoinBase));
+ p.pushKV("height", uint64_t(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", (int64_t)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", (int64_t)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".
+ }
+}
diff --git a/src/core_io.h b/src/core_io.h
index 113a961f..904f5a86 100644
--- a/src/core_io.h
+++ b/src/core_io.h
@@ -8,9 +8,8 @@
#include <consensus/amount.h>
#include <util/result.h>
+#include <functional>
#include <string>
-#include <vector>
-#include <optional>
class CBlock;
class CBlockHeader;
@@ -32,7 +31,6 @@ enum class TxVerbosity {
SHOW_DETAILS_AND_PREVOUT //!< The same as previous option with information about prevouts if available
};
-// core_read.cpp
CScript ParseScript(const std::string& s);
std::string ScriptToAsmStr(const CScript& script, bool fAttemptSighashDecode = false);
[[nodiscard]] bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no_witness = false, bool try_witness = true);
@@ -41,7 +39,6 @@ bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
[[nodiscard]] util::Result<int> SighashFromStr(const std::string& sighash);
-// core_write.cpp
UniValue ValueFromAmount(CAmount amount);
std::string FormatScript(const CScript& script);
std::string EncodeHexTx(const CTransaction& tx);
diff --git a/src/core_read.cpp b/src/core_read.cpp
deleted file mode 100644
index 935f4659..00000000
--- a/src/core_read.cpp
+++ /dev/null
@@ -1,264 +0,0 @@
-// 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 <primitives/block.h> // IWYU pragma: keep
-#include <primitives/transaction.h>
-#include <script/interpreter.h>
-#include <script/script.h>
-#include <serialize.h>
-#include <streams.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 <map>
-#include <optional>
-#include <span>
-#include <stdexcept>
-#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 contains 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) {
- DataStream 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) {
- DataStream 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)};
- DataStream ser_header{header_data};
- try {
- ser_header >> 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));
- DataStream ssBlock(blockData);
- try {
- ssBlock >> 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.")};
- }
-}
diff --git a/src/core_write.cpp b/src/core_write.cpp
deleted file mode 100644
index e4f23458..00000000
--- a/src/core_write.cpp
+++ /dev/null
@@ -1,275 +0,0 @@
-// 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 <common/system.h>
-#include <consensus/amount.h>
-#include <consensus/consensus.h>
-#include <consensus/validation.h>
-#include <key_io.h>
-#include <script/descriptor.h>
-#include <script/script.h>
-#include <script/solver.h>
-#include <serialize.h>
-#include <streams.h>
-#include <undo.h>
-#include <univalue.h>
-#include <util/check.h>
-#include <util/strencodings.h>
-
-#include <map>
-#include <string>
-#include <vector>
-
-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.GetTotalSize());
- entry.pushKV("vsize", (GetTransactionWeight(tx) + WITNESS_SCALE_FACTOR - 1) / WITNESS_SCALE_FACTOR);
- entry.pushKV("weight", GetTransactionWeight(tx));
- entry.pushKV("locktime", (int64_t)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", (int64_t)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", bool(prev_coin.fCoinBase));
- p.pushKV("height", uint64_t(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", (int64_t)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", (int64_t)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".
- }
-}
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.