Files
wownero/tests/fuzz/fuzz_rpc/initialisation.cpp
T
selsta 0a47ee2c94 tests: harden RPC fuzz harness setup
Initialize RPC server state used by direct handler calls,
let fuzz input choose restricted or unrestricted mode,
and skip iterations when the dummy core fails to initialize.

Also update the RPC fuzz harness documentation after removal
of the old RPC payment setup.
2026-08-26 20:23:47 +02:00

325 lines
11 KiB
C++

#include "initialisation.h"
#include "crypto/crypto.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
#include "cryptonote_basic/cryptonote_format_utils.h"
#include "cryptonote_basic/tx_extra.h"
#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "cryptonote_core/tx_pool.h"
#include "cryptonote_protocol/cryptonote_protocol_handler.h"
#include "p2p/net_node.h"
#include "p2p/net_node.inl"
#include "ringct/rctTypes.h"
#include "serialization/binary_utils.h"
#include "serialization/variant.h"
#include <boost/filesystem.hpp>
#include <boost/program_options/variables_map.hpp>
#include <vector>
#include <string>
#include <functional>
#include <unordered_map>
// For storing valid transaction hashes
std::vector<crypto::hash> cached_tx_hashes;
// Configure a dummy protocol to avoid external requests
bool DummyProtocol::is_synchronized() const {
return true;
}
bool DummyProtocol::relay_transactions(cryptonote::NOTIFY_NEW_TRANSACTIONS::request&, const boost::uuids::uuid&, epee::net_utils::zone, cryptonote::relay_method) {
return true;
}
bool DummyProtocol::relay_block(cryptonote::NOTIFY_NEW_FLUFFY_BLOCK::request&, cryptonote::cryptonote_connection_context&) {
return true;
}
// Function to create and initialise a dummy rpc core object
std::unique_ptr<CoreEnv> initialise_rpc_core() {
auto env = std::make_unique<CoreEnv>();
// Create fresh pointer for dummy protocol and core
env->protocol = std::make_unique<DummyProtocol>();
env->core = std::make_unique<cryptonote::core>(env->protocol.get());
// Configure the rpc core object with default settings
boost::program_options::options_description desc;
cryptonote::core::init_options(desc);
boost::program_options::variables_map vm;
// Add command line arguments to configure the rpc core object to use regression testing mode (FAKECHAIN)
// and a throwaway data directory. Enabling FAKECHAIN mode allows skipping validation logic of the authors
// signature and transactions ID on valid blocks and transactions while keeping other logic.
const boost::filesystem::path data_dir = boost::filesystem::temp_directory_path() / "monero-fuzz-rpc";
std::vector<std::string> args = {"fuzz", "--regtest", "--offline", "--data-dir", data_dir.string()};
std::vector<const char*> argv;
argv.reserve(args.size());
for (const auto& arg : args) {
argv.push_back(arg.c_str());
}
boost::program_options::store(boost::program_options::parse_command_line(argv.size(), argv.data(), desc), vm);
boost::program_options::notify(vm);
// Initialise the dummy core with all the above settings
if (!env->core->init(vm)) {
return nullptr;
}
return env;
}
// Build the core_rpc_server handler object with the configured dummy core object
std::unique_ptr<RpcServerBundle> initialise_rpc_server(cryptonote::core& dummy_core, bool restricted) {
// Create rpc server bundle object
auto bundle = std::make_unique<RpcServerBundle>();
// Allocate new protocol and node server then create RPC server object
bundle->proto_handler = std::make_unique<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>(dummy_core, nullptr, true);
bundle->proto_handler->set_no_sync(false);
bundle->dummy_p2p = std::make_unique<nodetool::node_server<cryptonote::t_cryptonote_protocol_handler<cryptonote::core>>>(*bundle->proto_handler);
bundle->rpc = std::make_unique<cryptonote::core_rpc_server>(dummy_core, *bundle->dummy_p2p, restricted);
return bundle;
}
bool generate_random_blocks(cryptonote::core& core, FuzzedDataProvider& provider) {
static std::vector<cryptonote::block> cached_blocks;
static std::vector<cryptonote::blobdata> cached_txs;
bool use_cached_blocks = provider.ConsumeBool();
bool use_cached_txs = provider.ConsumeBool();
if (!use_cached_blocks || cached_blocks.empty()) {
// Create new cached blocks
cached_blocks.clear();
// Prepare the genesis block (initial base block) in the block chains
cryptonote::block genesis_block;
cryptonote::generate_genesis_block(genesis_block, config::GENESIS_TX, config::GENESIS_NONCE);
cached_blocks.push_back(genesis_block);
// Setup initial values
const size_t median_weight = 300000;
const size_t block_weight = 100000;
const uint8_t version = core.get_blockchain_storage().get_current_hard_fork_version();
// Accumulate coins for genesis block
uint64_t coins = 0;
for (const auto& o : genesis_block.miner_tx.vout) {
coins += o.amount;
}
// Create random number of miners
size_t miner_count = provider.ConsumeIntegralInRange<size_t>(1, 4);
std::vector<cryptonote::account_base> miners(miner_count);
for (auto& miner : miners) {
miner.generate();
}
// Create random number of blocks
size_t total_blocks = provider.ConsumeIntegralInRange<size_t>(2, 15);
for (size_t i = 0; i < total_blocks; ++i) {
// Stop generate blocks if no more bytes left
if (provider.remaining_bytes() <= 0) {
break;
}
// Randomly link a miner identity to this block
cryptonote::block blk;
size_t selected_miner_index = provider.ConsumeIntegralInRange<size_t>(0, miners.size() - 1);
cryptonote::account_base& miner = miners[selected_miner_index];
// Initialise block property with random value
uint64_t height = cached_blocks.size();
crypto::hash prev_id = cached_blocks.back().hash;
blk.major_version = version;
blk.minor_version = version;
blk.timestamp = core.get_blockchain_storage().get_adjusted_time(height);
blk.prev_id = prev_id;
blk.nonce = provider.ConsumeIntegral<uint32_t>();
// Calculate and accumulate reward
uint64_t base_reward = 0, fee = 0;
if (!cryptonote::get_block_reward(median_weight, block_weight, coins, base_reward, version)) {
break;
}
// Link empty miner transaction to block
cryptonote::transaction miner_tx;
cryptonote::construct_miner_tx(
height, median_weight, coins, block_weight,
fee, miner.get_keys().m_account_address,
miner_tx, cryptonote::blobdata(),
1, blk.major_version
);
for (const auto& o : miner_tx.vout) {
coins += o.amount;
}
miner_tx.version = 2;
blk.miner_tx = miner_tx;
// Add the block to the cache
cached_blocks.push_back(blk);
}
}
if (cached_blocks.empty()) {
// No random block generated, exit early to next iteration
return false;
}
if (!use_cached_txs || cached_txs.empty()) {
// Create new cache transactions
cached_txs.clear();
cached_tx_hashes.clear();
// Random number of transactions to generate
const size_t tx_count = provider.ConsumeIntegralInRange<size_t>(1, 4);
for (size_t i = 0; i < tx_count; ++i) {
// Stop generate transactions if no more bytes left
if (provider.remaining_bytes() <= 0) {
break;
}
// Initialise variables
cryptonote::transaction tx;
std::vector<cryptonote::tx_source_entry> sources;
std::vector<cryptonote::tx_destination_entry> destinations;
std::vector<uint8_t> extra;
// Generate sender account
cryptonote::account_base sender;
sender.generate();
// Generate receiver account
cryptonote::account_base receiver;
receiver.generate();
// Generate random transfer amount
uint64_t transfer_amount = provider.ConsumeIntegralInRange<uint64_t>(10000, 1000000000);
uint64_t input_amount = transfer_amount + 10000;
// Prepare source entry
cryptonote::tx_source_entry src{};
src.amount = input_amount;
src.rct = false;
src.real_output = 0;
src.real_output_in_tx_index = 0;
// Derive valid public key
hw::device &hwdev = sender.get_keys().get_device();
cryptonote::keypair tx_key = cryptonote::keypair::generate(hwdev);
crypto::key_derivation derivation;
if (!crypto::generate_key_derivation(sender.get_keys().m_account_address.m_view_public_key, tx_key.sec, derivation)) {
continue;
}
crypto::public_key out_pub_key;
if (!crypto::derive_public_key(derivation, 0, sender.get_keys().m_account_address.m_spend_public_key, out_pub_key)) {
continue;
}
src.real_out_tx_key = tx_key.pub;
// Configure output entry for source_entry
for (size_t j = 0; j < 10; ++j) {
cryptonote::tx_source_entry::output_entry oe;
oe.first = j;
crypto::public_key pk;
if (j == src.real_output) {
pk = out_pub_key;
} else {
cryptonote::keypair fake = cryptonote::keypair::generate(hwdev);
pk = fake.pub;
}
crypto::view_tag vtag{};
vtag.data = static_cast<uint8_t>(provider.ConsumeIntegral<uint8_t>());
cryptonote::txout_to_tagged_key tagged{};
tagged.key = pk;
tagged.view_tag = vtag;
oe.second.dest = *reinterpret_cast<const rct::key*>(&tagged.key);
oe.second.mask = rct::identity();
src.outputs.push_back(oe);
}
sources.push_back(src);
// Prepare destination entry
cryptonote::tx_destination_entry dst;
dst.amount = transfer_amount;
dst.addr = receiver.get_keys().m_account_address;
dst.is_subaddress = false;
destinations.push_back(dst);
// Construct the transaction
crypto::secret_key tx_secret_key;
std::vector<crypto::secret_key> additional_tx_keys;
std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
subaddresses[sender.get_keys().m_account_address.m_spend_public_key] = {0, 0};
std::vector<cryptonote::tx_destination_entry> destinations_copy = destinations;
// Construct transaction objects
bool success = cryptonote::construct_tx_and_get_tx_key(
sender.get_keys(),
subaddresses,
sources,
destinations_copy,
boost::none,
extra,
tx,
tx_secret_key,
additional_tx_keys,
true,
{rct::RangeProofPaddedBulletproof, 0},
true
);
if (!success) {
continue;
}
// Force version and unlock_time
tx.version = 2;
tx.unlock_time = 0;
// Serialise the transaction
cryptonote::blobdata tx_blob;
if (!cryptonote::t_serializable_object_to_blob(tx, tx_blob)) {
continue;
}
// Add the transaction to cache
cached_txs.push_back(tx_blob);
}
}
core.get_blockchain_storage().get_db().batch_start();
bool added_block = false;
for (const auto& blk : cached_blocks) {
cryptonote::block_verification_context bvc{};
if (core.get_blockchain_storage().add_new_block(blk, bvc)) {
added_block = true;
}
}
for (const auto& tx_blob : cached_txs) {
cryptonote::tx_verification_context tvc;
bool accepted = core.handle_incoming_tx(tx_blob, tvc, cryptonote::relay_method::block, true);
if (accepted || tvc.m_added_to_pool) {
// Store legit hashes
cryptonote::transaction tx;
if (cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx)) {
cached_tx_hashes.push_back(cryptonote::get_transaction_hash(tx));
}
}
}
return added_block;
}