elements/src/core_write.cpp
Tom Trevethan 13398037b6
set Wno-error for simplicity keep warning but dont error on it
fuzz test fixes

set -DPRODUCTION for simplicity in fuzz tests

set UNDEBUG for simplicity in fuzz tests

update wallet fuzz tests

update test script

fix msvc conversion issues

more msvc vector conversion fixes and substitution of boost libs

fix more hexstr calls

fix hexstr calls in init.cpp

default CMAKE_GENERATOR to Unix Makefiles

more span fixes

specify build bin directory

fix executable locations

fix fuzz tests for c11 and functional test fixes

fix fuzz test executable path

specify legacy wallet for elements functional tests

remove assertion for static initialization order issue

use elements fuzz corpus

print debug log on failure

use heap for blind and asset_blind

test: avoid disk space warning for non-regtest

feature_config_args.py incorrectly assumed that its testnet4 node
would not log a disk space warning.

0683b8ebf3 increased m_assumed_blockchain_size
on testnet4 from 1 to 11 GiB which triggers this bug on more
systems, e.g. a RAM disk.

Prevent the warning by setting -prune for these nodes.

Fix the same issue in feature_signet.py

Github-Pull: #32057
Rebased-From: 20fe41e9e83d510fd467f5a999d55a614b16ef89
2026-06-17 14:15:49 +01:00

425 lines
17 KiB
C++

// Copyright (c) 2009-2022 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 <issuance.h>
#include <key_io.h>
#include <policy/discount.h> // ELEMENTS
#include <script/descriptor.h>
#include <script/script.h>
#include <script/sign.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 <secp256k1_rangeproof.h>
#include <map>
#include <string>
#include <vector>
static secp256k1_context* secp256k1_blind_context = nullptr;
class RPCRawTransaction_ECC_Init {
public:
RPCRawTransaction_ECC_Init() {
assert(secp256k1_blind_context == nullptr);
secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
assert(ctx != nullptr);
secp256k1_blind_context = ctx;
}
~RPCRawTransaction_ECC_Init() {
secp256k1_context *ctx = secp256k1_blind_context;
secp256k1_blind_context = nullptr;
if (ctx) {
secp256k1_context_destroy(ctx);
}
}
};
static RPCRawTransaction_ECC_Init ecc_init_on_load;
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(MakeByteSpan(std::vector<uint8_t>(it2, it - vch.size()))),
HexStr(MakeByteSpan(std::vector<uint8_t>(it - vch.size(), it))));
} else {
ret += strprintf("0x%x ", HexStr(MakeByteSpan(std::vector<uint8_t>(it2, it))));
}
continue;
}
ret += strprintf("0x%x ", HexStr(MakeByteSpan(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(MakeByteSpan(vch)) + strSigHashDecode;
} else {
str += HexStr(MakeByteSpan(vch));
}
}
} else {
str += GetOpName(opcode);
}
}
return str;
}
std::string EncodeHexTx(const CTransaction& tx)
{
DataStream ssTx;
ssTx << TX_WITH_WITNESS(tx);
return HexStr(ssTx);
}
UniValue EncodeHexScriptWitness(const CScriptWitness& witness)
{
UniValue witness_hex(UniValue::VARR);
for (const auto &item : witness.stack) {
witness_hex.push_back(HexStr(MakeByteSpan(item)));
}
return witness_hex;
}
// ELEMENTS:
static void SidechainScriptPubKeyToJSON(const CScript& script, UniValue& out, bool include_hex, bool include_addresses, bool is_parent_chain, const SigningProvider* provider)
{
const std::string prefix = is_parent_chain ? "pegout_" : "";
CTxDestination address;
out.pushKV(prefix + "asm", ScriptToAsmStr(script));
if (include_addresses) {
out.pushKV(prefix + "desc", InferDescriptor(script, provider ? *provider : DUMMY_SIGNING_PROVIDER)->ToString());
}
if (include_hex) {
out.pushKV(prefix + "hex", HexStr(script));
}
std::vector<std::vector<unsigned char>> solns;
const TxoutType type{Solver(script, solns)};
if (include_addresses && ExtractDestination(script, address) && type != TxoutType::PUBKEY) {
if (is_parent_chain) {
out.pushKV(prefix + "address", EncodeParentDestination(address));
} else {
out.pushKV(prefix + "address", EncodeDestination(address));
}
}
out.pushKV(prefix + "type", GetTxnOutputType(type));
}
void ScriptToUniv(const CScript& script, UniValue& out, bool include_hex, bool include_addresses, const SigningProvider* provider)
{
SidechainScriptPubKeyToJSON(script, out, include_hex, include_addresses, false, provider);
uint256 pegout_chain;
CScript pegout_scriptpubkey;
if (script.IsPegoutScript(pegout_chain, pegout_scriptpubkey)) {
out.pushKV("pegout_chain", pegout_chain.GetHex());
SidechainScriptPubKeyToJSON(pegout_scriptpubkey, out, include_hex, include_addresses, true, provider);
}
}
void TxToUniv(const CTransaction& tx, const uint256& block_hash, UniValue& entry, bool include_hex, const CTxUndo* txundo, TxVerbosity verbosity)
{
CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS);
entry.pushKV("txid", tx.GetHash().GetHex());
entry.pushKV("hash", tx.GetWitnessHash().GetHex());
if (g_con_elementsmode) {
entry.pushKV("wtxid", tx.GetWitnessHash().GetHex());
entry.pushKV("withash", tx.GetWitnessOnlyHash().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));
// ELEMENTS: add discountvsize
if (Params().GetAcceptDiscountCT()) {
entry.pushKV("discountvsize", GetDiscountVirtualTransactionSize(tx));
entry.pushKV("discountweight", GetDiscountTransactionWeight(tx));
}
entry.pushKV("locktime", (int64_t)tx.nLockTime);
UniValue vin{UniValue::VARR};
const bool have_undo = txundo != nullptr;
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", o);
in.pushKV("is_pegin", txin.m_is_pegin);
}
if (have_undo) {
const Coin& prev_coin = txundo->vprevout[i];
const CTxOut& prev_txout = prev_coin.out;
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));
if (prev_txout.nValue.IsExplicit()) {
p.pushKV("value", ValueFromAmount(prev_txout.nValue.GetAmount()));
} else {
p.pushKV("value", "<confidential>");
}
p.pushKV("scriptPubKey", o_script_pub_key);
in.pushKV("prevout", p);
}
}
in.pushKV("sequence", (int64_t)txin.nSequence);
// ELEMENTS:
if (tx.witness.vtxinwit.size() > i) {
const CScriptWitness &scriptWitness = tx.witness.vtxinwit[i].scriptWitness;
if (!scriptWitness.IsNull()) {
UniValue txinwitness(UniValue::VARR);
for (const auto &item : scriptWitness.stack) {
txinwitness.push_back(HexStr(MakeByteSpan(item)));
}
in.pushKV("txinwitness", txinwitness);
}
}
if (tx.witness.vtxinwit.size() > i && !tx.witness.vtxinwit[i].m_pegin_witness.IsNull()) {
UniValue pegin_witness(UniValue::VARR);
for (const auto& item : tx.witness.vtxinwit[i].m_pegin_witness.stack) {
pegin_witness.push_back(HexStr(MakeByteSpan(item)));
}
in.pushKV("pegin_witness", pegin_witness);
}
const CAssetIssuance& issuance = txin.assetIssuance;
if (!issuance.IsNull()) {
UniValue issue(UniValue::VOBJ);
issue.pushKV("assetBlindingNonce", issuance.assetBlindingNonce.GetHex());
CAsset asset;
CAsset token;
uint256 entropy;
if (issuance.assetBlindingNonce.IsNull()) {
GenerateAssetEntropy(entropy, txin.prevout, issuance.assetEntropy);
issue.pushKV("assetEntropy", entropy.GetHex());
CalculateAsset(asset, entropy);
CalculateReissuanceToken(token, entropy, issuance.nAmount.IsCommitment());
issue.pushKV("isreissuance", false);
issue.pushKV("token", token.GetHex());
}
else {
issue.pushKV("assetEntropy", issuance.assetEntropy.GetHex());
issue.pushKV("isreissuance", true);
CalculateAsset(asset, issuance.assetEntropy);
}
issue.pushKV("asset", asset.GetHex());
if (issuance.nAmount.IsExplicit()) {
issue.pushKV("assetamount", ValueFromAmount(issuance.nAmount.GetAmount()));
} else if (issuance.nAmount.IsCommitment()) {
issue.pushKV("assetamountcommitment", HexStr(MakeByteSpan(issuance.nAmount.vchCommitment)));
}
if (issuance.nInflationKeys.IsExplicit()) {
issue.pushKV("tokenamount", ValueFromAmount(issuance.nInflationKeys.GetAmount()));
} else if (issuance.nInflationKeys.IsCommitment()) {
issue.pushKV("tokenamountcommitment", HexStr(MakeByteSpan(issuance.nInflationKeys.vchCommitment)));
}
in.pushKV("issuance", issue);
}
// END ELEMENTS
vin.push_back(in);
}
entry.pushKV("vin", std::move(vin));
CAmountMap fee_map{};
UniValue vout(UniValue::VARR);
for (unsigned int i = 0; i < tx.vout.size(); i++) {
const CTxOut& txout = tx.vout[i];
UniValue out(UniValue::VOBJ);
if (txout.nValue.IsExplicit()) {
out.pushKV("value", ValueFromAmount(txout.nValue.GetAmount()));
} else {
int exp;
int mantissa;
uint64_t minv;
uint64_t maxv;
const CTxOutWitness* ptxoutwit = tx.witness.vtxoutwit.size() <= i ? nullptr : &tx.witness.vtxoutwit[i];
if (ptxoutwit) {
if (ptxoutwit->vchRangeproof.size() && secp256k1_rangeproof_info(secp256k1_blind_context, &exp, &mantissa, &minv, &maxv, &ptxoutwit->vchRangeproof[0], ptxoutwit->vchRangeproof.size())) {
if (exp == -1) {
out.pushKV("value", ValueFromAmount((CAmount)minv));
} else {
out.pushKV("value-minimum", ValueFromAmount((CAmount)minv));
out.pushKV("value-maximum", ValueFromAmount((CAmount)maxv));
}
out.pushKV("ct-exponent", exp);
out.pushKV("ct-bits", mantissa);
}
if (ptxoutwit->vchSurjectionproof.size()) {
out.pushKV("surjectionproof", HexStr(MakeByteSpan(ptxoutwit->vchSurjectionproof)));
}
}
out.pushKV("valuecommitment", txout.nValue.GetHex());
}
if (g_con_elementsmode) {
if (txout.IsFee()) {
fee_map[txout.nAsset.GetAsset()] += txout.nValue.GetAmount();
}
if (txout.nAsset.IsExplicit()) {
out.pushKV("asset", txout.nAsset.GetAsset().GetHex());
} else {
out.pushKV("assetcommitment", txout.nAsset.GetHex());
}
out.pushKV("commitmentnonce", txout.nNonce.GetHex());
CPubKey pubkey(txout.nNonce.vchCommitment);
out.pushKV("commitmentnonce_fully_valid", pubkey.IsFullyValid());
}
out.pushKV("n", (int64_t)i);
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, /*out=*/o, /*include_hex=*/true, /*include_address=*/true);
out.pushKV("scriptPubKey", o);
vout.push_back(out);
}
entry.pushKV("vout", std::move(vout));
// ELEMENTS: add fee map rather than single fee. Unlike other areas of the RPC,
// we do not look up labels here and will always use the asset hex (contrast
// `AmountMapToUniv` in rpc/util.cpp. This is because this is a pure function
// so we do not have access to `policyAsset` or `gAssetsDir`. (We will get link
// errors if we try to use these.)
if (g_con_elementsmode) {
UniValue fee_obj(UniValue::VOBJ);
for(std::map<CAsset, CAmount>::const_iterator it = fee_map.begin(); it != fee_map.end(); ++it) {
fee_obj.pushKV(it->first.GetHex(), ValueFromAmount(it->second));
}
entry.pushKV("fee", fee_obj);
}
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".
}
}