Merge pull request #10605

59702e0 rpc: speedup block&tx RPC calls (jeffro256)

ACKs: thomasbuilds*, selsta, jpk68
This commit is contained in:
tobtoht
2026-08-28 07:58:16 +00:00
6 changed files with 287 additions and 108 deletions
@@ -279,6 +279,17 @@ namespace cryptonote
get_transaction_prefix_hash(tx, tx_prefix_hash);
return true;
}
//------------------------------------------------------------------
size_t get_tx_version(const blobdata_ref tx_blob)
{
size_t version;
const char* begin = reinterpret_cast<const char*>(tx_blob.data());
const char* end = begin + tx_blob.size();
int read = tools::read_varint(begin, end, version);
if (read <= 0)
throw std::runtime_error("Internal error getting transaction version");
return version;
}
//---------------------------------------------------------------
bool is_v1_tx(const blobdata_ref& tx_blob)
{
@@ -296,6 +307,126 @@ namespace cryptonote
return is_v1_tx(blobdata_ref{tx_blob.data(), tx_blob.size()});
}
//---------------------------------------------------------------
bool get_transaction_unprunable_summary(const blobdata_ref tx_blob, unprunable_summary_t &summary_out)
{
//! @TODO: update for FCMP++:
//! * Allow rct::RctTypeFcmpPlusPlus
//! * Set output length for txout_to_carrot_v1
const unsigned char *p = reinterpret_cast<const unsigned char*>(tx_blob.data());
const unsigned char *end = reinterpret_cast<const unsigned char*>(tx_blob.data() + tx_blob.size());
#define READ_VARINT(v) if (tools::read_varint(p, end, v) <= 0) return false
#define READ_BYTE(v) if (end <= p ) { return false; } else { v = *p; ++p; }
#define SKIP(n) if (end - p < static_cast<std::ptrdiff_t>(n)) { return false; } else { p += (n); }
// read and validate tx version
READ_VARINT(summary_out.version);
if (summary_out.version < 1 || summary_out.version > 2)
return false;
// skip unlock_time
std::uint64_t dummy;
READ_VARINT(dummy);
// read number of inputs
READ_VARINT(summary_out.n_inputs);
// skip n_inputs inputs, checking for coinbase inputs
summary_out.is_coinbase = false;
for (std::size_t i = 0; i < summary_out.n_inputs; ++i)
{
std::size_t input_tag = 0;
READ_BYTE(input_tag);
switch (input_tag)
{
case 0xff: //txin_gen
summary_out.is_coinbase = true;
READ_VARINT(dummy); //height
break;
case 0x02: //txin_to_key
READ_VARINT(dummy); //amount
READ_VARINT(input_tag); //key_offsets.size()
for (;input_tag-->0;)
READ_VARINT(dummy);
SKIP(32); //k_image
break;
default:
return false;
}
}
// read number of outputs
READ_VARINT(summary_out.n_outputs);
// skip n_outputs outputs
for (std::size_t i = 0; i < summary_out.n_outputs; ++i)
{
READ_VARINT(dummy); //amount
std::size_t output_tag = 0;
READ_BYTE(output_tag); //target variant tag
std::ptrdiff_t output_length = 0;
switch (output_tag)
{
case 0x02: //txout_to_key
output_length = 32;
break;
case 0x03: //txout_to_tagged_key
output_length = 32 + 1;
break;
default:
return false;
}
SKIP(output_length);
}
// read extra length and skip that
READ_VARINT(summary_out.extra_len);
if (summary_out.extra_len > CRYPTONOTE_MAX_TX_SIZE)
return false;
SKIP(summary_out.extra_len);
// now `p` is at end of tx prefix...
summary_out.prefix_size = reinterpret_cast<const char*>(p) - tx_blob.data();
if (2 == summary_out.version)
{
// read RingCT type
std::uint8_t rct_type = std::numeric_limits<std::uint8_t>::max();
READ_BYTE(rct_type);
if (rct_type > rct::RCTTypeBulletproofPlus)
return false;
// skip unprunable RingCT fields
if (rct_type != rct::RCTTypeNull)
{
// skip txnFee
READ_VARINT(dummy);
// if RCTTypeSimple, skip pseudoOutputs
if (rct_type == rct::RCTTypeSimple)
{
SKIP(32 * summary_out.n_inputs);
}
// skip ECDH info and amount commitments
const bool short_amount = rct_type >= rct::RCTTypeBulletproof2;
const std::ptrdiff_t ecdh_tuple_len = short_amount ? 8 : 64;
const std::ptrdiff_t output_stuff_len = summary_out.n_outputs * (ecdh_tuple_len + 32);
SKIP(output_stuff_len);
}
}
#undef READ_VARINT
#undef READ_BYTE
#undef SKIP
// now `p` is at end of unprunable part of v2 tx...
summary_out.unprunable_size = reinterpret_cast<const char*>(p) - tx_blob.data();
return true;
}
//---------------------------------------------------------------
bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki, hw::device &hwdev)
{
crypto::key_derivation recv_derivation{};
@@ -531,6 +662,19 @@ namespace cryptonote
return tx.blob_size;
}
//---------------------------------------------------------------
bool prune_transaction_blob(cryptonote::blobdata &tx_blob)
{
// Deserialize unprunable part to retrieve size
unprunable_summary_t desc;
if (!get_transaction_unprunable_summary(tx_blob, desc))
return false;
CHECK_AND_ASSERT_MES(desc.unprunable_size <= tx_blob.size(), false, "Unprunable size is larger than tx blob");
tx_blob.resize(desc.unprunable_size);
return true;
}
//---------------------------------------------------------------
bool get_tx_fee(const transaction& tx, uint64_t & fee)
{
if (tx.version > 1)
@@ -1281,6 +1425,18 @@ namespace cryptonote
return get_transaction_hash(t, res, NULL);
}
//---------------------------------------------------------------
bool calculate_transaction_prunable_hash(const std::size_t tx_version, const blobdata_ref prunable_tx_blob, crypto::hash &res)
{
switch (tx_version)
{
case 2:
cryptonote::get_blob_hash(prunable_tx_blob, res);
return true;
default:
return false;
}
}
//---------------------------------------------------------------
bool calculate_transaction_prunable_hash(const transaction& t, const cryptonote::blobdata_ref *blob, crypto::hash& res)
{
if (t.version == 1)
@@ -1289,7 +1445,8 @@ namespace cryptonote
if (blob && unprunable_size)
{
CHECK_AND_ASSERT_MES(unprunable_size <= blob->size(), false, "Inconsistent transaction unprunable and blob sizes");
cryptonote::get_blob_hash(blobdata_ref(blob->data() + unprunable_size, blob->size() - unprunable_size), res);
return calculate_transaction_prunable_hash(t.version,
blobdata_ref(blob->data() + unprunable_size, blob->size() - unprunable_size), res);
}
else
{