mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-13 12:33:42 +02:00
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
425 lines
17 KiB
C++
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".
|
|
}
|
|
}
|