aboutsummaryrefslogtreecommitdiff
path: root/src/cryptonote_core
diff options
context:
space:
mode:
authorjeffro256 <jeffro256@tutanota.com>2024-01-17 17:17:16 -0600
committerjeffro256 <jeffro256@tutanota.com>2025-03-10 01:32:08 -0500
commit008ba966da88f073f226b299533faca905ceabf8 (patch)
tree1be6c2f8d45b74ad0427e0117c67371c93974f7e /src/cryptonote_core
parentd0118f4778db7e75d7ad2036076e5688cd6f3810 (diff)
downloadmonzero-core-008ba966da88f073f226b299533faca905ceabf8.tar.gz
monzero-core-008ba966da88f073f226b299533faca905ceabf8.tar.xz
monzero-core-008ba966da88f073f226b299533faca905ceabf8.zip
blockchain sync: reduce disk writes from 2 to 1 per tx
Diffstat (limited to 'src/cryptonote_core')
-rw-r--r--src/cryptonote_core/blockchain.cpp350
-rw-r--r--src/cryptonote_core/blockchain.h56
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp480
-rw-r--r--src/cryptonote_core/cryptonote_core.h221
-rw-r--r--src/cryptonote_core/tx_pool.cpp150
-rw-r--r--src/cryptonote_core/tx_pool.h17
-rw-r--r--src/cryptonote_core/tx_verification_utils.cpp197
-rw-r--r--src/cryptonote_core/tx_verification_utils.h62
8 files changed, 799 insertions, 734 deletions
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index fd56b07c6..40f16f5c5 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -40,6 +40,7 @@
#include "blockchain.h"
#include "blockchain_db/blockchain_db.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
+#include "cryptonote_basic/events.h"
#include "cryptonote_config.h"
#include "cryptonote_basic/miner.h"
#include "hardforks/hardforks.h"
@@ -643,12 +644,14 @@ block Blockchain::pop_block_from_blockchain()
// in hf_versions.
uint8_t version = get_ideal_hard_fork_version(m_db->height());
- // We assume that if they were in a block, the transactions are already
- // known to the network as a whole. However, if we had mined that block,
- // that might not be always true. Unlikely though, and always relaying
- // these again might cause a spike of traffic as many nodes re-relay
- // all the transactions in a popped block when a reorg happens.
- bool r = m_tx_pool.add_tx(tx, tvc, relay_method::block, true, version);
+ // We assume that if they were in a block, the transactions are already known to the network
+ // as a whole. However, if we had mined that block, that might not be always true. Unlikely
+ // though, and always relaying these again might cause a spike of traffic as many nodes
+ // re-relay all the transactions in a popped block when a reorg happens. You might notice that
+ // we also set the "nic_verified_hf_version" paramater. Since we know we took this transaction
+ // from the mempool earlier in this function call, when the mempool has the same current fork
+ // version, we can return it without re-verifying the consensus rules on it.
+ const bool r = m_tx_pool.add_tx(tx, tvc, relay_method::block, true, version, version);
if (!r)
{
LOG_ERROR("Error returning transaction to tx_pool");
@@ -660,7 +663,6 @@ block Blockchain::pop_block_from_blockchain()
m_blocks_longhash_table.clear();
m_scan_table.clear();
- m_blocks_txs_check.clear();
uint64_t top_block_height;
crypto::hash top_block_hash = get_tail_id(top_block_height);
@@ -1077,7 +1079,7 @@ bool Blockchain::rollback_blockchain_switching(std::list<block>& original_chain,
for (auto& bl : original_chain)
{
block_verification_context bvc = {};
- bool r = handle_block_to_main_chain(bl, bvc, false);
+ bool r = handle_block_to_main_chain(bl, bvc);
CHECK_AND_ASSERT_MES(r && bvc.m_added_to_main_chain, false, "PANIC! failed to add (again) block while chain switching during the rollback!");
}
@@ -1130,7 +1132,7 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info>
block_verification_context bvc = {};
// add block to main chain
- bool r = handle_block_to_main_chain(bei.bl, bvc, false);
+ bool r = handle_block_to_main_chain(bei.bl, bvc);
// if adding block to main chain failed, rollback to previous state and
// return false
@@ -1165,7 +1167,8 @@ bool Blockchain::switch_to_alternative_blockchain(std::list<block_extended_info>
for (auto& old_ch_ent : disconnected_chain)
{
block_verification_context bvc = {};
- bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc);
+ pool_supplement ps{};
+ bool r = handle_alternative_block(old_ch_ent, get_block_hash(old_ch_ent), bvc, ps);
if(!r)
{
MERROR("Failed to push ex-main chain blocks to alternative chain ");
@@ -1853,7 +1856,8 @@ bool Blockchain::build_alt_chain(const crypto::hash &prev_id, std::list<block_ex
// if that chain is long enough to become the main chain and re-org accordingly
// if so. If not, we need to hang on to the block in case it becomes part of
// a long forked chain eventually.
-bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc)
+bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id,
+ block_verification_context& bvc, pool_supplement& extra_block_txs)
{
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
@@ -1985,6 +1989,47 @@ bool Blockchain::handle_alternative_block(const block& b, const crypto::hash& id
}
bei.cumulative_difficulty += current_diff;
+ // Now that we have the PoW verification out of the way, verify all pool supplement txs
+ tx_verification_context tvc{};
+ if (!ver_non_input_consensus(extra_block_txs, tvc, hf_version))
+ {
+ MERROR_VER("Transaction pool supplement verification failure for alt block " << id);
+ bvc.m_verifivation_failed = true;
+ return false;
+ }
+
+ // Add pool supplement txs to the main mempool with relay_method::block
+ CRITICAL_REGION_LOCAL(m_tx_pool);
+ for (auto& extra_block_tx : extra_block_txs.txs_by_txid)
+ {
+ const crypto::hash& txid = extra_block_tx.first;
+ transaction& tx = extra_block_tx.second.first;
+ const blobdata &tx_blob = extra_block_tx.second.second;
+
+ tx_verification_context tvc{};
+ if ((!m_tx_pool.have_tx(txid, relay_category::legacy) &&
+ !m_db->tx_exists(txid) &&
+ !m_tx_pool.add_tx(tx, tvc, relay_method::block, /*relayed=*/true, hf_version, hf_version))
+ || tvc.m_verifivation_failed)
+ {
+ MERROR_VER("Transaction " << txid <<
+ " in pool supplement failed to enter main pool for alt block " << id);
+ bvc.m_verifivation_failed = true;
+ return false;
+ }
+
+ // If new incoming tx in alt block passed verification and entered the pool, notify ZMQ
+ if (tvc.m_added_to_pool)
+ notify_txpool_event({txpool_event{
+ .tx = tx,
+ .hash = txid,
+ .blob_size = tx_blob.size(),
+ .weight = get_transaction_weight(tx),
+ .res = true}});
+ }
+ extra_block_txs.txs_by_txid.clear();
+ extra_block_txs.nic_verified_hf_version = 0;
+
bei.block_cumulative_weight = cryptonote::get_transaction_weight(b.miner_tx);
for (const crypto::hash &txid: b.tx_hashes)
{
@@ -2779,11 +2824,12 @@ bool Blockchain::have_block(const crypto::hash& id, int *where) const
return have_block_unlocked(id, where);
}
//------------------------------------------------------------------
-bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify/* = true*/)
+bool Blockchain::handle_block_to_main_chain(const block& bl, block_verification_context& bvc)
{
LOG_PRINT_L3("Blockchain::" << __func__);
crypto::hash id = get_block_hash(bl);
- return handle_block_to_main_chain(bl, id, bvc, notify);
+ pool_supplement ps{};
+ return handle_block_to_main_chain(bl, id, bvc, ps);
}
//------------------------------------------------------------------
size_t Blockchain::get_total_transactions() const
@@ -2894,24 +2940,6 @@ bool Blockchain::get_tx_outputs_gindexs(const crypto::hash& tx_id, std::vector<u
return true;
}
//------------------------------------------------------------------
-void Blockchain::on_new_tx_from_block(const cryptonote::transaction &tx)
-{
-#if defined(PER_BLOCK_CHECKPOINT)
- // check if we're doing per-block checkpointing
- if (m_db->height() < m_blocks_hash_check.size())
- {
- TIME_MEASURE_START(a);
- m_blocks_txs_check.push_back(get_transaction_hash(tx));
- TIME_MEASURE_FINISH(a);
- if(m_show_time_stats)
- {
- size_t ring_size = !tx.vin.empty() && tx.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(tx.vin[0]).key_offsets.size() : 0;
- MINFO("HASH: " << "-" << " I/M/O: " << tx.vin.size() << "/" << ring_size << "/" << tx.vout.size() << " H: " << 0 << " chcktx: " << a);
- }
- }
-#endif
-}
-//------------------------------------------------------------------
//FIXME: it seems this function is meant to be merely a wrapper around
// another function of the same name, this one adding one bit of
// functionality. Should probably move anything more than that
@@ -2951,12 +2979,9 @@ bool Blockchain::check_tx_inputs(transaction& tx, uint64_t& max_used_block_heigh
return true;
}
//------------------------------------------------------------------
-bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc) const
+bool Blockchain::check_tx_outputs(const transaction& tx, tx_verification_context &tvc, std::uint8_t hf_version)
{
LOG_PRINT_L3("Blockchain::" << __func__);
- CRITICAL_REGION_LOCAL(m_blockchain_lock);
-
- const uint8_t hf_version = m_hardfork->get_current_version();
// from hard fork 2, we forbid dust and compound outputs
if (hf_version >= 2) {
@@ -4007,26 +4032,6 @@ bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) cons
return check_block_timestamp(timestamps, b, median_ts);
}
//------------------------------------------------------------------
-void Blockchain::return_tx_to_pool(std::vector<std::pair<transaction, blobdata>> &txs)
-{
- uint8_t version = get_current_hard_fork_version();
- for (auto& tx : txs)
- {
- cryptonote::tx_verification_context tvc = AUTO_VAL_INIT(tvc);
- // We assume that if they were in a block, the transactions are already
- // known to the network as a whole. However, if we had mined that block,
- // that might not be always true. Unlikely though, and always relaying
- // these again might cause a spike of traffic as many nodes re-relay
- // all the transactions in a popped block when a reorg happens.
- const size_t weight = get_transaction_weight(tx.first, tx.second.size());
- const crypto::hash tx_hash = get_transaction_hash(tx.first);
- if (!m_tx_pool.add_tx(tx.first, tx_hash, tx.second, weight, tvc, relay_method::block, true, version))
- {
- MERROR("Failed to return taken transaction with hash: " << get_transaction_hash(tx.first) << " to tx_pool");
- }
- }
-}
-//------------------------------------------------------------------
bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids)
{
CRITICAL_REGION_LOCAL(m_tx_pool);
@@ -4052,7 +4057,8 @@ bool Blockchain::flush_txes_from_pool(const std::vector<crypto::hash> &txids)
// Needs to validate the block and acquire each transaction from the
// transaction mem_pool, then pass the block and transactions to
// m_db->add_block()
-bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc, bool notify/* = true*/)
+bool Blockchain::handle_block_to_main_chain(const block& bl, const crypto::hash& id,
+ block_verification_context& bvc, pool_supplement& extra_block_txs)
{
LOG_PRINT_L3("Blockchain::" << __func__);
@@ -4204,10 +4210,66 @@ leave:
goto leave;
}
+ // verify all non-input consensus rules for txs inside the pool supplement (if not inside checkpoint zone)
+#if defined(PER_BLOCK_CHECKPOINT)
+ if (!fast_check)
+#endif
+ {
+ tx_verification_context tvc{};
+ // If fail non-input consensus rule checking...
+ if (!ver_non_input_consensus(extra_block_txs, tvc, hf_version))
+ {
+ MERROR_VER("Pool supplement provided for block with id: " << id << " failed to pass validation");
+ bvc.m_verifivation_failed = true;
+ goto leave;
+ }
+ }
+
size_t coinbase_weight = get_transaction_weight(bl.miner_tx);
size_t cumulative_block_weight = coinbase_weight;
std::vector<std::pair<transaction, blobdata>> txs;
+ // txid weight mempool?
+ std::vector<std::tuple<crypto::hash, size_t, bool>> txs_meta;
+
+ // This will be the data sent to the ZMQ pool listeners for txs which skipped the mempool
+ std::vector<txpool_event> txpool_events;
+
+ // this lambda returns relevant txs back to the mempool
+ auto return_txs_to_pool = [this, &txs, &txs_meta, &hf_version]()
+ {
+ if (txs_meta.size() != txs.size())
+ {
+ MERROR("BUG: txs_meta and txs not matching size!!!");
+ return;
+ }
+
+ for (size_t i = 0; i < txs.size(); ++i)
+ {
+ // if this transaction wasn't ever in the pool, don't return it back to the pool
+ const bool found_in_pool = std::get<2>(txs_meta[i]);
+ if (!found_in_pool)
+ continue;
+
+ transaction &tx = txs[i].first;
+ const crypto::hash &txid = std::get<0>(txs_meta[i]);
+ const blobdata &tx_blob = txs[i].second;
+ const size_t tx_weight = std::get<1>(txs_meta[i]);
+
+ // We assume that if they were in a block, the transactions are already known to the network
+ // as a whole. However, if we had mined that block, that might not be always true. Unlikely
+ // though, and always relaying these again might cause a spike of traffic as many nodes
+ // re-relay all the transactions in a popped block when a reorg happens. You might notice that
+ // we also set the "nic_verified_hf_version" paramater. Since we know we took this transaction
+ // from the mempool earlier in this function call, when the mempool has the same current fork
+ // version, we can return it without re-verifying the consensus rules on it.
+ cryptonote::tx_verification_context tvc{};
+ if (!m_tx_pool.add_tx(tx, txid, tx_blob, tx_weight, tvc, relay_method::block, true,
+ hf_version, hf_version))
+ MERROR("Failed to return taken transaction with hash: " << txid << " to tx_pool");
+ }
+ };
+
key_images_container keys;
uint64_t fee_summary = 0;
@@ -4220,18 +4282,14 @@ leave:
// XXX old code adds miner tx here
- size_t tx_index = 0;
// Iterate over the block's transaction hashes, grabbing each
- // from the tx_pool and validating them. Each is then added
+ // from the tx_pool (or from extra_block_txs) and validating them. Each is then added
// to txs. Keys spent in each are added to <keys> by the double spend check.
txs.reserve(bl.tx_hashes.size());
+ txs_meta.reserve(bl.tx_hashes.size());
+ txpool_events.reserve(bl.tx_hashes.size());
for (const crypto::hash& tx_id : bl.tx_hashes)
{
- transaction tx_tmp;
- blobdata txblob;
- size_t tx_weight = 0;
- uint64_t fee = 0;
- bool relayed = false, do_not_relay = false, double_spend_seen = false, pruned = false;
TIME_MEASURE_START(aa);
// XXX old code does not check whether tx exists
@@ -4239,21 +4297,73 @@ leave:
{
MERROR("Block with id: " << id << " attempting to add transaction already in blockchain with id: " << tx_id);
bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
- goto leave;
+ return_txs_to_pool();
+ return false;
}
TIME_MEASURE_FINISH(aa);
t_exists += aa;
TIME_MEASURE_START(bb);
- // get transaction with hash <tx_id> from tx_pool
- if(!m_tx_pool.take_tx(tx_id, tx_tmp, txblob, tx_weight, fee, relayed, do_not_relay, double_spend_seen, pruned))
+ // get transaction with hash <tx_id> from m_tx_pool or extra_block_txs
+ // tx info we want:
+ // * tx as `cryptonote::transaction`
+ // * blob
+ // * weight
+ // * fee
+ // * is pruned?
+ txs.emplace_back();
+ transaction &tx = txs.back().first;
+ blobdata &txblob = txs.back().second;
+ size_t tx_weight{};
+ uint64_t fee{};
+ bool pruned{};
+
+ /*
+ * Try pulling transaction data from the mempool proper first. If that fails, then try pulling
+ * from the block supplement. We add txs pulled from the block to the txpool events for future
+ * notifications, since if the tx skipped the mempool, then listeners have not yet received a
+ * notification for this tx.
+ */
+ bool _unused1, _unused2, _unused3;
+ const bool found_tx_in_pool{
+ m_tx_pool.take_tx(tx_id, tx, txblob, tx_weight, fee,
+ _unused1, _unused2, _unused3, pruned, /*suppress_missing_msgs=*/true)
+ };
+ bool find_tx_failure{!found_tx_in_pool};
+ if (!found_tx_in_pool) // if not in mempool:
+ {
+ const auto extra_txs_it{extra_block_txs.txs_by_txid.find(tx_id)};
+ if (extra_txs_it != extra_block_txs.txs_by_txid.end()) // if in block supplement:
+ {
+ tx = std::move(extra_txs_it->second.first);
+ txblob = std::move(extra_txs_it->second.second);
+ tx_weight = get_transaction_weight(tx, txblob.size());
+ fee = get_tx_fee(tx);
+ pruned = tx.pruned;
+ extra_block_txs.txs_by_txid.erase(extra_txs_it);
+ txpool_events.emplace_back(txpool_event{tx, tx_id, txblob.size(), tx_weight, true});
+ find_tx_failure = false;
+ }
+ }
+
+ // @TODO: We should move this section (checking if the daemon has all txs from the block) to
+ // right after the PoW check. Since it's now expected the node will sometimes not have all txs
+ // in its pool at this point nor the txs included as fluffy txs (and will need to re-request
+ // missing fluffy txs), then the node will sometimes waste cycles doing verification for some
+ // txs twice.
+ if (find_tx_failure) // did not find txid in mempool or provided extra block txs
{
- MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
+ const bool fully_supplemented_block = extra_block_txs.txs_by_txid.size() >= bl.tx_hashes.size();
+ if (fully_supplemented_block)
+ MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
+ else
+ LOG_PRINT_L2("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
+ txs.pop_back(); // We push to the back preemptively. On fail, we need txs & txs_meta to match size
bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
- goto leave;
+ bvc.m_missing_txs = true;
+ return_txs_to_pool();
+ return false;
}
if (pruned)
++n_pruned;
@@ -4263,8 +4373,7 @@ leave:
// add the transaction to the temp list of transactions, so we can either
// store the list of transactions all at once or return the ones we've
// taken from the tx_pool back to it if the block fails verification.
- txs.push_back(std::make_pair(std::move(tx_tmp), std::move(txblob)));
- transaction &tx = txs.back().first;
+ txs_meta.emplace_back(tx_id, tx_weight, found_tx_in_pool);
TIME_MEASURE_START(dd);
// FIXME: the storage should not be responsible for validation.
@@ -4297,30 +4406,12 @@ leave:
//TODO: why is this done? make sure that keeping invalid blocks makes sense.
add_block_as_invalid(bl, id);
MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions");
- MERROR_VER("tx_index " << tx_index << ", m_blocks_txs_check " << m_blocks_txs_check.size() << ":");
- for (const auto &h: m_blocks_txs_check) MERROR_VER(" " << h);
- bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
- goto leave;
- }
- }
-#if defined(PER_BLOCK_CHECKPOINT)
- else
- {
- // ND: if fast_check is enabled for blocks, there is no need to check
- // the transaction inputs, but do some sanity checks anyway.
- if (tx_index >= m_blocks_txs_check.size() || memcmp(&m_blocks_txs_check[tx_index++], &tx_id, sizeof(tx_id)) != 0)
- {
- MERROR_VER("Block with id: " << id << " has at least one transaction (id: " << tx_id << ") with wrong inputs.");
- //TODO: why is this done? make sure that keeping invalid blocks makes sense.
- add_block_as_invalid(bl, id);
- MERROR_VER("Block with id " << id << " added as invalid because of wrong inputs in transactions");
bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
- goto leave;
+ return_txs_to_pool();
+ return false;
}
}
-#endif
+
TIME_MEASURE_FINISH(cc);
t_checktx += cc;
fee_summary += fee;
@@ -4338,8 +4429,6 @@ leave:
cumulative_block_weight = m_blocks_hash_check[blockchain_height].second;
}
- m_blocks_txs_check.clear();
-
TIME_MEASURE_START(vmt);
uint64_t base_reward = 0;
uint64_t already_generated_coins = blockchain_height ? m_db->get_block_already_generated_coins(blockchain_height - 1) : 0;
@@ -4347,8 +4436,8 @@ leave:
{
MERROR_VER("Block with id: " << id << " has incorrect miner transaction");
bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
- goto leave;
+ return_txs_to_pool();
+ return false;
}
TIME_MEASURE_FINISH(vmt);
@@ -4386,7 +4475,7 @@ leave:
LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what());
m_batch_success = false;
bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
+ return_txs_to_pool();
return false;
}
catch (const std::exception& e)
@@ -4395,7 +4484,7 @@ leave:
LOG_ERROR("Error adding block with hash: " << id << " to blockchain, what = " << e.what());
m_batch_success = false;
bvc.m_verifivation_failed = true;
- return_tx_to_pool(txs);
+ return_txs_to_pool();
return false;
}
}
@@ -4448,6 +4537,9 @@ leave:
const crypto::hash seedhash = get_block_id_by_height(crypto::rx_seedheight(new_height));
send_miner_notifications(new_height, seedhash, id, already_generated_coins);
+ // Make sure that txpool notifications happen BEFORE block notifications
+ notify_txpool_event(std::move(txpool_events));
+
for (const auto& notifier: m_block_notifiers)
notifier(new_height - 1, {std::addressof(bl), 1});
@@ -4575,7 +4667,14 @@ bool Blockchain::update_next_cumulative_weight_limit(uint64_t *long_term_effecti
return true;
}
//------------------------------------------------------------------
-bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
+bool Blockchain::add_new_block(const block& bl_, block_verification_context& bvc)
+{
+ pool_supplement ps{};
+ return add_new_block(bl_, bvc, ps);
+}
+//------------------------------------------------------------------
+bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc,
+ pool_supplement& extra_block_txs)
{
try
{
@@ -4589,7 +4688,6 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
{
LOG_PRINT_L3("block with id = " << id << " already exists");
bvc.m_already_exists = true;
- m_blocks_txs_check.clear();
return false;
}
@@ -4599,14 +4697,12 @@ bool Blockchain::add_new_block(const block& bl, block_verification_context& bvc)
//chain switching or wrong block
bvc.m_added_to_main_chain = false;
rtxn_guard.stop();
- bool r = handle_alternative_block(bl, id, bvc);
- m_blocks_txs_check.clear();
- return r;
+ return handle_alternative_block(bl, id, bvc, extra_block_txs);
//never relay alternative blocks
}
rtxn_guard.stop();
- return handle_block_to_main_chain(bl, id, bvc);
+ return handle_block_to_main_chain(bl, id, bvc, extra_block_txs);
}
catch (const std::exception &e)
@@ -4776,7 +4872,6 @@ bool Blockchain::cleanup_handle_incoming_blocks(bool force_sync)
TIME_MEASURE_FINISH(t1);
m_blocks_longhash_table.clear();
m_scan_table.clear();
- m_blocks_txs_check.clear();
// when we're well clear of the precomputed hashes, free the memory
if (!m_blocks_hash_check.empty() && m_db->height() > m_blocks_hash_check.size() + 4096)
@@ -5298,6 +5393,27 @@ bool Blockchain::prepare_handle_incoming_blocks(const std::vector<block_complete
return true;
}
+void Blockchain::prepare_handle_incoming_block_no_preprocess(const size_t block_byte_estimate)
+{
+ // acquire locks
+ m_tx_pool.lock();
+ CRITICAL_REGION_LOCAL1(m_blockchain_lock);
+
+ // increment sync byte counter to trigger sync against database backing store
+ // later in cleanup_handle_incoming_blocks()
+ m_bytes_to_sync += block_byte_estimate;
+
+ // spin until we start a batch
+ while (!m_db->batch_start(1, block_byte_estimate)) {
+ m_blockchain_lock.unlock();
+ m_tx_pool.unlock();
+ epee::misc_utils::sleep_no_w(1000);
+ m_tx_pool.lock();
+ m_blockchain_lock.lock();
+ }
+ m_batch_success = true;
+}
+
void Blockchain::add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata &blob, const txpool_tx_meta_t &meta)
{
m_db->add_txpool_tx(txid, blob, meta);
@@ -5357,6 +5473,12 @@ void Blockchain::set_user_options(uint64_t maxthreads, bool sync_on_blocks, uint
m_max_prepare_blocks_threads = maxthreads;
}
+void Blockchain::set_txpool_notify(TxpoolNotifyCallback&& notify)
+{
+ std::lock_guard<decltype(m_txpool_notifier_mutex)> lg(m_txpool_notifier_mutex);
+ m_txpool_notifier = notify;
+}
+
void Blockchain::add_block_notify(BlockNotifyCallback&& notify)
{
if (notify)
@@ -5375,6 +5497,22 @@ void Blockchain::add_miner_notify(MinerNotifyCallback&& notify)
}
}
+void Blockchain::notify_txpool_event(std::vector<txpool_event>&& event) const
+{
+ std::lock_guard<decltype(m_txpool_notifier_mutex)> lg(m_txpool_notifier_mutex);
+ if (m_txpool_notifier)
+ {
+ try
+ {
+ m_txpool_notifier(std::move(event));
+ }
+ catch (const std::exception &e)
+ {
+ MDEBUG("During Blockchain::notify_txpool_event(), ignored exception: " << e.what());
+ }
+ }
+}
+
void Blockchain::safesyncmode(const bool onoff)
{
/* all of this is no-op'd if the user set a specific
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 42246fca2..675bcdb9b 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -91,6 +91,7 @@ namespace cryptonote
*/
typedef std::function<const epee::span<const unsigned char>(cryptonote::network_type network)> GetCheckpointsCallback;
+ typedef boost::function<void(std::vector<txpool_event>)> TxpoolNotifyCallback;
typedef boost::function<void(uint64_t /* height */, epee::span<const block> /* blocks */)> BlockNotifyCallback;
typedef boost::function<void(uint8_t /* major_version */, uint64_t /* height */, const crypto::hash& /* prev_id */, const crypto::hash& /* seed_hash */, difficulty_type /* diff */, uint64_t /* median_weight */, uint64_t /* already_generated_coins */, const std::vector<tx_block_template_backlog_entry>& /* tx_backlog */)> MinerNotifyCallback;
@@ -255,6 +256,16 @@ namespace cryptonote
bool prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks);
/**
+ * @brief prepare the blockchain for handling an incoming block, without performing preprocessing
+ *
+ * @param block_byte_estimate an estimate of the byte size of the block & its transactions
+ *
+ * This function should *always* be followed up by a call to cleanup_handle_incoming_blocks()
+ * later in the same thread.
+ */
+ void prepare_handle_incoming_block_no_preprocess(const size_t block_byte_estimate);
+
+ /**
* @brief incoming blocks post-processing, cleanup, and disk sync
*
* @param force_sync if true, and Blockchain is handling syncing to disk, always sync
@@ -351,11 +362,15 @@ namespace cryptonote
*
* @param bl_ the block to be added
* @param bvc metadata about the block addition's success/failure
+ * @param extra_block_txs txs belonging to this block that may not be in the mempool
*
* @return true on successful addition to the blockchain, else false
*/
bool add_new_block(const block& bl_, block_verification_context& bvc);
+ bool add_new_block(const block& bl_, block_verification_context& bvc,
+ pool_supplement& extra_block_txs);
+
/**
* @brief clears the blockchain and starts a new one
*
@@ -705,10 +720,13 @@ namespace cryptonote
*
* @param tx the transaction to check the outputs of
* @param tvc returned info about tx verification
+ * @param hf_version hard fork version
*
* @return false if any outputs do not conform, otherwise true
*/
- bool check_tx_outputs(const transaction& tx, tx_verification_context &tvc) const;
+ static bool check_tx_outputs(const transaction& tx,
+ tx_verification_context &tvc,
+ std::uint8_t hf_version);
/**
* @brief gets the block weight limit based on recent blocks
@@ -823,6 +841,13 @@ namespace cryptonote
blockchain_db_sync_mode sync_mode, bool fast_sync);
/**
+ * @brief sets a txpool notify object to call for every new tx used to add a new block
+ *
+ * @param notify the notify object to call at every new tx used to add a new block
+ */
+ void set_txpool_notify(TxpoolNotifyCallback&& notify);
+
+ /**
* @brief sets a block notify object to call for every new block
*
* @param notify the notify object to call at every new block
@@ -844,6 +869,11 @@ namespace cryptonote
void set_reorg_notify(const std::shared_ptr<tools::Notify> &notify) { m_reorg_notify = notify; }
/**
+ * @brief Notify this Blockchain's txpool notifier about a txpool event
+ */
+ void notify_txpool_event(std::vector<txpool_event>&& event) const;
+
+ /**
* @brief Put DB in safe sync mode
*/
void safesyncmode(const bool onoff);
@@ -1077,13 +1107,6 @@ namespace cryptonote
void cancel();
/**
- * @brief called when we see a tx originating from a block
- *
- * Used for handling txes from historical blocks in a fast way
- */
- void on_new_tx_from_block(const cryptonote::transaction &tx);
-
- /**
* @brief returns the timestamps of the last N blocks
*/
std::vector<time_t> get_last_block_timestamps(unsigned int blocks) const;
@@ -1159,7 +1182,6 @@ namespace cryptonote
// Keccak hashes for each block and for fast pow checking
std::vector<std::pair<crypto::hash, crypto::hash>> m_blocks_hash_of_hashes;
std::vector<std::pair<crypto::hash, uint64_t>> m_blocks_hash_check;
- std::vector<crypto::hash> m_blocks_txs_check;
blockchain_db_sync_mode m_db_sync_mode;
bool m_fast_sync;
@@ -1219,6 +1241,9 @@ namespace cryptonote
bool m_batch_success;
+ TxpoolNotifyCallback m_txpool_notifier;
+ mutable std::mutex m_txpool_notifier_mutex;
+
/* `boost::function` is used because the implementation never allocates if
the callable object has a single `std::shared_ptr` or `std::weap_ptr`
internally. Whereas, the libstdc++ `std::function` will allocate. */
@@ -1332,11 +1357,10 @@ namespace cryptonote
*
* @param bl the block to be added
* @param bvc metadata concerning the block's validity
- * @param notify if set to true, sends new block notification on success
*
* @return true if the block was added successfully, otherwise false
*/
- bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc, bool notify = true);
+ bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc);
/**
* @brief validate and add a new block to the end of the blockchain
@@ -1348,11 +1372,12 @@ namespace cryptonote
* @param bl the block to be added
* @param id the hash of the block
* @param bvc metadata concerning the block's validity
- * @param notify if set to true, sends new block notification on success
+ * @param extra_block_txs txs belonging to this block that may not be in the mempool
*
* @return true if the block was added successfully, otherwise false
*/
- bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc, bool notify = true);
+ bool handle_block_to_main_chain(const block& bl, const crypto::hash& id,
+ block_verification_context& bvc, pool_supplement& extra_block_txs);
/**
* @brief validate and add a new block to an alternate blockchain
@@ -1364,10 +1389,12 @@ namespace cryptonote
* @param b the block to be added
* @param id the hash of the block
* @param bvc metadata concerning the block's validity
+ * @param extra_block_txs txs belonging to this block that may not be in the mempool
*
* @return true if the block was added successfully, otherwise false
*/
- bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc);
+ bool handle_alternative_block(const block& b, const crypto::hash& id,
+ block_verification_context& bvc, pool_supplement& extra_block_txs);
/**
* @brief builds a list of blocks connecting a block to the main chain
@@ -1552,7 +1579,6 @@ namespace cryptonote
* @return true
*/
bool update_next_cumulative_weight_limit(uint64_t *long_term_effective_median_block_weight = NULL);
- void return_tx_to_pool(std::vector<std::pair<transaction, blobdata>> &txs);
/**
* @brief make sure a transaction isn't attempting a double-spend
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index d34c92723..d96305f3e 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -55,6 +55,7 @@ using namespace epee;
#include "rpc/zmq_pub.h"
#include "common/notify.h"
#include "hardforks/hardforks.h"
+#include "tx_verification_utils.h"
#include "version.h"
#include <boost/filesystem.hpp>
@@ -66,8 +67,6 @@ DISABLE_VS_WARNINGS(4355)
#define MERROR_VER(x) MCERROR("verify", x)
-#define BAD_SEMANTICS_TXES_MAX_SIZE 100
-
// basically at least how many bytes the block itself serializes to without the miner tx
#define BLOCK_SIZE_SANITY_LEEWAY 100
@@ -178,11 +177,6 @@ namespace cryptonote
, "Relay blocks as fluffy blocks (obsolete, now default)"
, true
};
- static const command_line::arg_descriptor<bool> arg_no_fluffy_blocks = {
- "no-fluffy-blocks"
- , "Relay blocks as normal blocks"
- , false
- };
static const command_line::arg_descriptor<size_t> arg_max_txpool_weight = {
"max-txpool-weight"
, "Set maximum txpool weight in bytes."
@@ -270,13 +264,6 @@ namespace cryptonote
{
m_blockchain_storage.set_enforce_dns_checkpoints(enforce_dns);
}
- //-----------------------------------------------------------------------------------
- void core::set_txpool_listener(boost::function<void(std::vector<txpool_event>)> zmq_pub)
- {
- CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
- m_zmq_pub = std::move(zmq_pub);
- }
-
//-----------------------------------------------------------------------------------------------
bool core::update_checkpoints(const bool skip_dns /* = false */)
{
@@ -341,7 +328,6 @@ namespace cryptonote
command_line::add_arg(desc, arg_block_sync_size);
command_line::add_arg(desc, arg_check_updates);
command_line::add_arg(desc, arg_fluffy_blocks);
- command_line::add_arg(desc, arg_no_fluffy_blocks);
command_line::add_arg(desc, arg_test_dbg_lock_sleep);
command_line::add_arg(desc, arg_offline);
command_line::add_arg(desc, arg_disable_dns_checkpoints);
@@ -389,7 +375,6 @@ namespace cryptonote
set_enforce_dns_checkpoints(command_line::get_arg(vm, arg_dns_checkpoints));
test_drop_download_height(command_line::get_arg(vm, arg_test_drop_download_height));
- m_fluffy_blocks_enabled = !get_arg(vm, arg_no_fluffy_blocks);
m_offline = get_arg(vm, arg_offline);
m_disable_dns_checkpoints = get_arg(vm, arg_disable_dns_checkpoints);
@@ -784,359 +769,81 @@ namespace cryptonote
return false;
}
//-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash)
+ bool core::handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed)
{
tvc = {};
- if(tx_blob.blob.size() > get_max_tx_size())
- {
- LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_blob.blob.size() << ", rejected");
- tvc.m_verifivation_failed = true;
- tvc.m_too_big = true;
- return false;
- }
-
- tx_hash = crypto::null_hash;
+ TRY_ENTRY();
- bool r;
- if (tx_blob.prunable_hash == crypto::null_hash)
- {
- r = parse_tx_from_blob(tx, tx_hash, tx_blob.blob);
- }
- else
- {
- r = parse_and_validate_tx_base_from_blob(tx_blob.blob, tx);
- if (r)
- {
- tx.set_prunable_hash(tx_blob.prunable_hash);
- tx_hash = cryptonote::get_pruned_transaction_hash(tx, tx_blob.prunable_hash);
- tx.set_hash(tx_hash);
- }
- }
+ CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
- if (!r)
+ if (tx_blob.size() > get_max_tx_size())
{
- LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to parse, rejected");
+ LOG_PRINT_L1("WRONG TRANSACTION BLOB, too big size " << tx_blob.size() << ", rejected");
tvc.m_verifivation_failed = true;
+ tvc.m_too_big = true;
return false;
}
- //std::cout << "!"<< tx.vin.size() << std::endl;
- bad_semantics_txes_lock.lock();
- for (int idx = 0; idx < 2; ++idx)
+ transaction tx;
+ crypto::hash txid;
+ if (!parse_and_validate_tx_from_blob(tx_blob, tx, txid))
{
- if (bad_semantics_txes[idx].find(tx_hash) != bad_semantics_txes[idx].end())
- {
- bad_semantics_txes_lock.unlock();
- LOG_PRINT_L1("Transaction already seen with bad semantics, rejected");
- tvc.m_verifivation_failed = true;
- return false;
- }
- }
- bad_semantics_txes_lock.unlock();
-
- uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
- const size_t max_tx_version = version == 1 ? 1 : 2;
- if (tx.version == 0 || tx.version > max_tx_version)
- {
- // v2 is the latest one we know
- MERROR_VER("Bad tx version (" << tx.version << ", max is " << max_tx_version << ")");
+ LOG_PRINT_L1("Incoming transactions failed to parse, rejected");
tvc.m_verifivation_failed = true;
return false;
}
- return true;
- }
- //-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash)
- {
- if(!check_tx_syntax(tx))
- {
- LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " syntax, rejected");
- tvc.m_verifivation_failed = true;
+ const uint64_t tx_weight = get_transaction_weight(tx, tx_blob.size());
+ if (!add_new_tx(tx, txid, tx_blob, tx_weight, tvc, tx_relay, relayed))
return false;
- }
- return true;
- }
- //-----------------------------------------------------------------------------------------------
- void core::set_semantics_failed(const crypto::hash &tx_hash)
- {
- LOG_PRINT_L1("WRONG TRANSACTION BLOB, Failed to check tx " << tx_hash << " semantic, rejected");
- bad_semantics_txes_lock.lock();
- bad_semantics_txes[0].insert(tx_hash);
- if (bad_semantics_txes[0].size() >= BAD_SEMANTICS_TXES_MAX_SIZE)
+ if (tvc.m_verifivation_failed)
{
- std::swap(bad_semantics_txes[0], bad_semantics_txes[1]);
- bad_semantics_txes[0].clear();
- }
- bad_semantics_txes_lock.unlock();
- }
- //-----------------------------------------------------------------------------------------------
- static bool is_canonical_bulletproof_layout(const std::vector<rct::Bulletproof> &proofs)
- {
- if (proofs.size() != 1)
+ MERROR_VER("Transaction verification failed: " << txid);
return false;
- const size_t sz = proofs[0].V.size();
- if (sz == 0 || sz > BULLETPROOF_MAX_OUTPUTS)
- return false;
- return true;
- }
- //-----------------------------------------------------------------------------------------------
- static bool is_canonical_bulletproof_plus_layout(const std::vector<rct::BulletproofPlus> &proofs)
- {
- if (proofs.size() != 1)
- return false;
- const size_t sz = proofs[0].V.size();
- if (sz == 0 || sz > BULLETPROOF_PLUS_MAX_OUTPUTS)
- return false;
- return true;
- }
- //-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block)
- {
- bool ret = true;
- if (keeped_by_block && get_blockchain_storage().is_within_compiled_block_hash_area())
- {
- MTRACE("Skipping semantics check for tx kept by block in embedded hash area");
- return true;
- }
-
- std::vector<const rct::rctSig*> rvv;
- for (size_t n = 0; n < tx_info.size(); ++n)
- {
- if (!check_tx_semantic(*tx_info[n].tx, keeped_by_block))
- {
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- continue;
- }
-
- if (tx_info[n].tx->version < 2)
- continue;
- const rct::rctSig &rv = tx_info[n].tx->rct_signatures;
- switch (rv.type) {
- case rct::RCTTypeNull:
- // coinbase should not come here, so we reject for all other types
- MERROR_VER("Unexpected Null rctSig type");
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- break;
- case rct::RCTTypeSimple:
- if (!rct::verRctSemanticsSimple(rv))
- {
- MERROR_VER("rct signature semantics check failed");
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- break;
- }
- break;
- case rct::RCTTypeFull:
- if (!rct::verRct(rv, true))
- {
- MERROR_VER("rct signature semantics check failed");
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- break;
- }
- break;
- case rct::RCTTypeBulletproof:
- case rct::RCTTypeBulletproof2:
- case rct::RCTTypeCLSAG:
- if (!is_canonical_bulletproof_layout(rv.p.bulletproofs))
- {
- MERROR_VER("Bulletproof does not have canonical form");
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- break;
- }
- rvv.push_back(&rv); // delayed batch verification
- break;
- case rct::RCTTypeBulletproofPlus:
- if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus))
- {
- MERROR_VER("Bulletproof_plus does not have canonical form");
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- break;
- }
- rvv.push_back(&rv); // delayed batch verification
- break;
- default:
- MERROR_VER("Unknown rct type: " << rv.type);
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- break;
- }
}
- if (!rvv.empty() && !rct::verRctSemanticsSimple(rvv))
+ else if (tvc.m_verifivation_impossible)
{
- LOG_PRINT_L1("One transaction among this group has bad semantics, verifying one at a time");
- ret = false;
- const bool assumed_bad = rvv.size() == 1; // if there's only one tx, it must be the bad one
- for (size_t n = 0; n < tx_info.size(); ++n)
- {
- if (!tx_info[n].result)
- continue;
- if (tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproof2 && tx_info[n].tx->rct_signatures.type != rct::RCTTypeCLSAG && tx_info[n].tx->rct_signatures.type != rct::RCTTypeBulletproofPlus)
- continue;
- if (assumed_bad || !rct::verRctSemanticsSimple(tx_info[n].tx->rct_signatures))
- {
- set_semantics_failed(tx_info[n].tx_hash);
- tx_info[n].tvc.m_verifivation_failed = true;
- tx_info[n].result = false;
- }
- }
- }
-
- return ret;
- }
- //-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_txs(const epee::span<const tx_blob_entry> tx_blobs, epee::span<tx_verification_context> tvc, relay_method tx_relay, bool relayed)
- {
- TRY_ENTRY();
-
- if (tx_blobs.size() != tvc.size())
- {
- MERROR("tx_blobs and tx_verification_context spans must have equal size");
- return false;
- }
-
- std::vector<txpool_event> results(tx_blobs.size());
-
- CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
-
- tools::threadpool& tpool = tools::threadpool::getInstanceForCompute();
- tools::threadpool::waiter waiter(tpool);
- epee::span<tx_blob_entry>::const_iterator it = tx_blobs.begin();
- for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
- tpool.submit(&waiter, [&, i, it] {
- try
- {
- results[i].res = handle_incoming_tx_pre(*it, tvc[i], results[i].tx, results[i].hash);
- }
- catch (const std::exception &e)
- {
- MERROR_VER("Exception in handle_incoming_tx_pre: " << e.what());
- tvc[i].m_verifivation_failed = true;
- results[i].res = false;
- }
- });
- }
- if (!waiter.wait())
- return false;
- it = tx_blobs.begin();
- std::vector<bool> already_have(tx_blobs.size(), false);
- for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
- if (!results[i].res)
- continue;
- if(m_mempool.have_tx(results[i].hash, relay_category::legacy))
- {
- LOG_PRINT_L2("tx " << results[i].hash << "already have transaction in tx_pool");
- already_have[i] = true;
- }
- else if(m_blockchain_storage.have_tx(results[i].hash))
- {
- LOG_PRINT_L2("tx " << results[i].hash << " already have transaction in blockchain");
- already_have[i] = true;
- }
- else
- {
- tpool.submit(&waiter, [&, i, it] {
- try
- {
- results[i].res = handle_incoming_tx_post(*it, tvc[i], results[i].tx, results[i].hash);
- }
- catch (const std::exception &e)
- {
- MERROR_VER("Exception in handle_incoming_tx_post: " << e.what());
- tvc[i].m_verifivation_failed = true;
- results[i].res = false;
- }
- });
- }
- }
- if (!waiter.wait())
+ MERROR_VER("Transaction verification impossible: " << txid);
return false;
-
- std::vector<tx_verification_batch_info> tx_info;
- tx_info.reserve(tx_blobs.size());
- for (size_t i = 0; i < tx_blobs.size(); i++) {
- if (!results[i].res || already_have[i])
- continue;
- tx_info.push_back({&results[i].tx, results[i].hash, tvc[i], results[i].res});
}
- if (!tx_info.empty())
- handle_incoming_tx_accumulated_batch(tx_info, tx_relay == relay_method::block);
-
- bool valid_events = false;
- bool ok = true;
- it = tx_blobs.begin();
- for (size_t i = 0; i < tx_blobs.size(); i++, ++it) {
- if (!results[i].res)
- {
- ok = false;
- continue;
- }
- if (tx_relay == relay_method::block)
- get_blockchain_storage().on_new_tx_from_block(results[i].tx);
- if (already_have[i])
- continue;
-
- results[i].blob_size = it->blob.size();
- results[i].weight = results[i].tx.pruned ? get_pruned_transaction_weight(results[i].tx) : get_transaction_weight(results[i].tx, it->blob.size());
- ok &= add_new_tx(results[i].tx, results[i].hash, tx_blobs[i].blob, results[i].weight, tvc[i], tx_relay, relayed);
-
- if(tvc[i].m_verifivation_failed)
- {MERROR_VER("Transaction verification failed: " << results[i].hash);}
- else if(tvc[i].m_verifivation_impossible)
- {MERROR_VER("Transaction verification impossible: " << results[i].hash);}
-
- if(tvc[i].m_added_to_pool && results[i].tx.extra.size() <= MAX_TX_EXTRA_SIZE)
- {
- MDEBUG("tx added: " << results[i].hash);
- valid_events = true;
- }
- else
- results[i].res = false;
+ else if (!tvc.m_added_to_pool)
+ {
+ MDEBUG("Transaction " << txid << " not added to pool");
+ return true;
}
- if (valid_events && m_zmq_pub && matches_category(tx_relay, relay_category::legacy))
- m_zmq_pub(std::move(results));
+ MDEBUG("tx added to pool: " << txid);
- return ok;
- CATCH_ENTRY_L0("core::handle_incoming_txs()", false);
- }
- //-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed)
- {
- return handle_incoming_txs({std::addressof(tx_blob), 1}, {std::addressof(tvc), 1}, tx_relay, relayed);
+ return true;
+ CATCH_ENTRY_L0("core::handle_incoming_tx()", false);
}
//-----------------------------------------------------------------------------------------------
- bool core::check_tx_semantic(const transaction& tx, bool keeped_by_block) const
+ bool core::check_tx_semantic(const transaction& tx, tx_verification_context& tvc,
+ uint8_t hf_version)
{
if(!tx.vin.size())
{
MERROR_VER("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx));
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
return false;
}
if(!check_inputs_types_supported(tx))
{
MERROR_VER("unsupported input types for tx id= " << get_transaction_hash(tx));
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
return false;
}
if(!check_outs_valid(tx))
{
MERROR_VER("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx));
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_output = true;
return false;
}
if (tx.version > 1)
@@ -1144,6 +851,8 @@ namespace cryptonote
if (tx.rct_signatures.outPk.size() != tx.vout.size())
{
MERROR_VER("tx with mismatched vout/outPk count, rejected for tx id= " << get_transaction_hash(tx));
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_output = true;
return false;
}
}
@@ -1151,6 +860,8 @@ namespace cryptonote
if(!check_money_overflow(tx))
{
MERROR_VER("tx has money overflow, rejected for tx id= " << get_transaction_hash(tx));
+ tvc.m_verifivation_failed = true;
+ tvc.m_overspend = true;
return false;
}
@@ -1163,40 +874,43 @@ namespace cryptonote
if(amount_in <= amount_out)
{
MERROR_VER("tx with wrong amounts: ins " << amount_in << ", outs " << amount_out << ", rejected for tx id= " << get_transaction_hash(tx));
+ tvc.m_verifivation_failed = true;
+ tvc.m_overspend = true;
return false;
}
}
// for version > 1, ringct signatures check verifies amounts match
- if(!keeped_by_block && get_transaction_weight(tx) >= m_blockchain_storage.get_current_cumulative_block_weight_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE)
- {
- MERROR_VER("tx is too large " << get_transaction_weight(tx) << ", expected not bigger than " << m_blockchain_storage.get_current_cumulative_block_weight_limit() - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE);
- return false;
- }
-
//check if tx use different key images
if(!check_tx_inputs_keyimages_diff(tx))
{
MERROR_VER("tx uses a single key image more than once");
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
return false;
}
- const uint8_t hf_version = m_blockchain_storage.get_current_hard_fork_version();
if (!check_tx_inputs_ring_members_diff(tx, hf_version))
{
MERROR_VER("tx uses duplicate ring members");
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
return false;
}
if (!check_tx_inputs_keyimages_domain(tx))
{
MERROR_VER("tx uses key image not in the valid domain");
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
return false;
}
if (!check_output_types(tx, hf_version))
{
MERROR_VER("tx does not use valid output type(s)");
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_output = true;
return false;
}
@@ -1294,7 +1008,7 @@ namespace cryptonote
return std::pair<boost::multiprecision::uint128_t, boost::multiprecision::uint128_t>(emission_amount, total_fee_amount);
}
//-----------------------------------------------------------------------------------------------
- bool core::check_tx_inputs_keyimages_diff(const transaction& tx) const
+ bool core::check_tx_inputs_keyimages_diff(const transaction& tx)
{
std::unordered_set<crypto::key_image> ki;
for(const auto& in: tx.vin)
@@ -1306,7 +1020,7 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const
+ bool core::check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version)
{
if (hf_version >= 6)
{
@@ -1321,7 +1035,7 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
- bool core::check_tx_inputs_keyimages_domain(const transaction& tx) const
+ bool core::check_tx_inputs_keyimages_domain(const transaction& tx)
{
std::unordered_set<crypto::key_image> ki;
for(const auto& in: tx.vin)
@@ -1362,7 +1076,20 @@ namespace cryptonote
}
uint8_t version = m_blockchain_storage.get_current_hard_fork_version();
- return m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_relay, relayed, version);
+ const bool res = m_mempool.add_tx(tx, tx_hash, blob, tx_weight, tvc, tx_relay, relayed, version);
+
+ // If new incoming tx passed verification and entered the pool, notify ZMQ
+ if (!tvc.m_verifivation_failed && tvc.m_added_to_pool && matches_category(tx_relay, relay_category::legacy))
+ {
+ m_blockchain_storage.notify_txpool_event({txpool_event{
+ .tx = tx,
+ .hash = tx_hash,
+ .blob_size = blob.size(),
+ .weight = tx_weight,
+ .res = true}});
+ }
+
+ return res;
}
//-----------------------------------------------------------------------------------------------
bool core::relay_txpool_transactions()
@@ -1412,14 +1139,11 @@ namespace cryptonote
//-----------------------------------------------------------------------------------------------
bool core::notify_txpool_event(const epee::span<const cryptonote::blobdata> tx_blobs, epee::span<const crypto::hash> tx_hashes, epee::span<const cryptonote::transaction> txs, const std::vector<bool> &just_broadcasted) const
{
- if (!m_zmq_pub)
- return true;
-
if (tx_blobs.size() != tx_hashes.size() || tx_blobs.size() != txs.size() || tx_blobs.size() != just_broadcasted.size())
return false;
/* Publish txs via ZMQ that are "just broadcasted" by the daemon. This is
- done here in addition to `handle_incoming_txs` in order to guarantee txs
+ done here in order to guarantee txs
are pub'd via ZMQ when we know the daemon has/will broadcast to other
nodes & *after* the tx is visible in the pool. This should get called
when the user submits a tx to a daemon in the "fluff" epoch relaying txs
@@ -1438,7 +1162,7 @@ namespace cryptonote
results[i].res = just_broadcasted[i];
}
- m_zmq_pub(std::move(results));
+ m_blockchain_storage.notify_txpool_event(std::move(results));
return true;
}
@@ -1468,7 +1192,7 @@ namespace cryptonote
m_mempool.set_relayed(epee::to_span(tx_hashes), tx_relay, just_broadcasted);
- if (m_zmq_pub && matches_category(tx_relay, relay_category::legacy))
+ if (matches_category(tx_relay, relay_category::legacy))
notify_txpool_event(tx_blobs, epee::to_span(tx_hashes), epee::to_span(txs), just_broadcasted);
}
//-----------------------------------------------------------------------------------------------
@@ -1573,7 +1297,7 @@ namespace cryptonote
if(bvc.m_added_to_main_chain)
{
cryptonote_connection_context exclude_context = {};
- NOTIFY_NEW_BLOCK::request arg = AUTO_VAL_INIT(arg);
+ NOTIFY_NEW_FLUFFY_BLOCK::request arg{};
arg.current_blockchain_height = m_blockchain_storage.get_current_blockchain_height();
std::vector<crypto::hash> missed_txs;
std::vector<cryptonote::blobdata> txs;
@@ -1611,11 +1335,11 @@ namespace cryptonote
m_blockchain_storage.safesyncmode(onoff);
}
//-----------------------------------------------------------------------------------------------
- bool core::add_new_block(const block& b, block_verification_context& bvc)
+ bool core::add_new_block(const block& b, block_verification_context& bvc,
+ pool_supplement& extra_block_txs)
{
- return m_blockchain_storage.add_new_block(b, bvc);
+ return m_blockchain_storage.add_new_block(b, bvc, extra_block_txs);
}
-
//-----------------------------------------------------------------------------------------------
bool core::prepare_handle_incoming_blocks(const std::vector<block_complete_entry> &blocks_entry, std::vector<block> &blocks)
{
@@ -1641,7 +1365,16 @@ namespace cryptonote
}
//-----------------------------------------------------------------------------------------------
- bool core::handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, bool update_miner_blocktemplate)
+ bool core::handle_incoming_block(const blobdata& block_blob, const block *b,
+ block_verification_context& bvc, bool update_miner_blocktemplate)
+ {
+ pool_supplement ps{};
+ return handle_incoming_block(block_blob, b, bvc, ps, update_miner_blocktemplate);
+ }
+
+ //-----------------------------------------------------------------------------------------------
+ bool core::handle_incoming_block(const blobdata& block_blob, const block *b,
+ block_verification_context& bvc, pool_supplement& extra_block_txs, bool update_miner_blocktemplate)
{
TRY_ENTRY();
@@ -1668,7 +1401,7 @@ namespace cryptonote
}
b = &lb;
}
- add_new_block(*b, bvc);
+ add_new_block(*b, bvc, extra_block_txs);
if(update_miner_blocktemplate && bvc.m_added_to_main_chain)
update_miner_block_template();
return true;
@@ -1676,6 +1409,39 @@ namespace cryptonote
CATCH_ENTRY_L0("core::handle_incoming_block()", false);
}
//-----------------------------------------------------------------------------------------------
+ bool core::handle_single_incoming_block(const blobdata& block_blob,
+ const block *b,
+ block_verification_context& bvc,
+ pool_supplement& extra_block_txs,
+ bool update_miner_blocktemplate)
+ {
+ // Note: this estimate can be quite far off since fluffy blocks won't contain all their
+ // transactions in the payload, but also this value doesn't *need* to be super precise. It
+ // is used to trigger database backing store syncing once it hits a threshold, and since
+ // we under-count the byte size here, it might result in under-syncing the backing store.
+ // If force refresh is enabled, though, which the user turns on if they are vigilant about
+ // saving each block, then it doesn't matter either way: cleanup_handle_incoming_blocks()
+ // always triggers a sync.
+ size_t block_total_bytes = block_blob.size();
+ for (const auto &t : extra_block_txs.txs_by_txid)
+ block_total_bytes += t.second.second.size();
+
+ CRITICAL_REGION_LOCAL(m_incoming_tx_lock);
+
+ // Match each call to prepare_handle_incoming_block_no_preprocess() with a call to
+ // cleanup_handle_incoming_blocks()
+ m_blockchain_storage.prepare_handle_incoming_block_no_preprocess(block_total_bytes);
+ const auto auto_cleanup = epee::misc_utils::create_scope_leave_handler([this](){
+ this->m_blockchain_storage.cleanup_handle_incoming_blocks();
+ });
+
+ return handle_incoming_block(block_blob,
+ b,
+ bvc,
+ extra_block_txs,
+ update_miner_blocktemplate);
+ }
+ //-----------------------------------------------------------------------------------------------
// Used by the RPC server to check the size of an incoming
// block_blob
bool core::check_incoming_block_size(const blobdata& block_blob) const
@@ -1717,16 +1483,6 @@ namespace cryptonote
return m_blockchain_storage.have_block(id, where);
}
//-----------------------------------------------------------------------------------------------
- bool core::parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob) const
- {
- return parse_and_validate_tx_from_blob(blob, tx, tx_hash);
- }
- //-----------------------------------------------------------------------------------------------
- bool core::check_tx_syntax(const transaction& tx) const
- {
- return true;
- }
- //-----------------------------------------------------------------------------------------------
bool core::get_pool_transactions_info(const std::vector<crypto::hash>& txids, std::vector<std::pair<crypto::hash, tx_memory_pool::tx_details>>& txs, bool include_sensitive_txes) const
{
return m_mempool.get_transactions_info(txids, txs, include_sensitive_txes);
@@ -2077,14 +1833,6 @@ namespace cryptonote
return true;
}
//-----------------------------------------------------------------------------------------------
- void core::flush_bad_txs_cache()
- {
- bad_semantics_txes_lock.lock();
- for (int idx = 0; idx < 2; ++idx)
- bad_semantics_txes[idx].clear();
- bad_semantics_txes_lock.unlock();
- }
- //-----------------------------------------------------------------------------------------------
void core::flush_invalid_blocks()
{
m_blockchain_storage.flush_invalid_blocks();
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index efab56405..362060b54 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -126,60 +126,63 @@ namespace cryptonote
*
* @return true if the transaction was accepted, false otherwise
*/
- bool handle_incoming_tx(const tx_blob_entry& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed);
+ bool handle_incoming_tx(const blobdata& tx_blob, tx_verification_context& tvc, relay_method tx_relay, bool relayed);
- /**
- * @brief handles a list of incoming transactions
+ /**
+ * @brief handles a single incoming block
*
- * Parses incoming transactions and, if nothing is obviously wrong,
- * passes them along to the transaction pool
+ * periodic update to checkpoints is triggered here
+ * Attempts to add the block to the Blockchain and, on success,
+ * optionally updates the miner's block template.
*
- * @pre `tx_blobs.size() == tvc.size()`
+ * Unlike handle_incoming_block(), a write transaction is created in this method, which means
+ * the caller doesn't have to call prepare_handle_incoming_blocks() nor
+ * cleanup_handle_incoming_blocks() surrounding this call.
*
- * @param tx_blobs the txs to handle
- * @param tvc metadata about the transactions' validity
- * @param tx_relay how the transaction was received.
- * @param relayed whether or not the transactions were relayed to us
+ * @param block_blob the block to be added
+ * @param block the block to be added, or NULL
+ * @param bvc return-by-reference metadata context about the block's validity
+ * @param extra_block_txs txs belonging to this block that may not be in the mempool
+ * @param update_miner_blocktemplate whether or not to update the miner's block template
*
- * @return true if the transactions were accepted, false otherwise
+ * @return false if loading new checkpoints fails, or the block is not
+ * added, otherwise true
*/
- bool handle_incoming_txs(epee::span<const tx_blob_entry> tx_blobs, epee::span<tx_verification_context> tvc, relay_method tx_relay, bool relayed);
+ bool handle_single_incoming_block(const blobdata& block_blob,
+ const block *b,
+ block_verification_context& bvc,
+ pool_supplement& extra_block_txs,
+ bool update_miner_blocktemplate = true);
/**
- * @brief handles a list of incoming transactions
- *
- * Parses incoming transactions and, if nothing is obviously wrong,
- * passes them along to the transaction pool
- *
- * @param tx_blobs the txs to handle
- * @param tvc metadata about the transactions' validity
- * @param tx_relay how the transaction was received.
- * @param relayed whether or not the transactions were relayed to us
- *
- * @return true if the transactions were accepted, false otherwise
- */
- bool handle_incoming_txs(const std::vector<tx_blob_entry>& tx_blobs, std::vector<tx_verification_context>& tvc, relay_method tx_relay, bool relayed)
- {
- tvc.resize(tx_blobs.size());
- return handle_incoming_txs(epee::to_span(tx_blobs), epee::to_mut_span(tvc), tx_relay, relayed);
- }
-
- /**
- * @brief handles an incoming block
+ * @brief handles an incoming block as part of a batch
*
* periodic update to checkpoints is triggered here
* Attempts to add the block to the Blockchain and, on success,
* optionally updates the miner's block template.
*
+ * Prerequisite: There must be an active write transaction for the blockchain storage on this
+ * thread. Typically, this is done by calling prepare_handle_incoming_blocks() on
+ * this thread before calls to handle_incoming_block(). Then, after calls to
+ * handle_incoming_block(), a call to cleanup_handle_incoming_blocks() is made
+ * on this thread to either abort or commit the write transaction.
+ *
* @param block_blob the block to be added
* @param block the block to be added, or NULL
* @param bvc return-by-reference metadata context about the block's validity
+ * @param extra_block_txs txs belonging to this block that may not be in the mempool
* @param update_miner_blocktemplate whether or not to update the miner's block template
*
* @return false if loading new checkpoints fails, or the block is not
* added, otherwise true
*/
- bool handle_incoming_block(const blobdata& block_blob, const block *b, block_verification_context& bvc, bool update_miner_blocktemplate = true);
+ bool handle_incoming_block(const blobdata& block_blob, const block *b,
+ block_verification_context& bvc,
+ bool update_miner_blocktemplate = true);
+
+ bool handle_incoming_block(const blobdata& block_blob, const block *b,
+ block_verification_context& bvc, pool_supplement& extra_block_txs,
+ bool update_miner_blocktemplate = true);
/**
* @copydoc Blockchain::prepare_handle_incoming_blocks
@@ -465,13 +468,6 @@ namespace cryptonote
void set_enforce_dns_checkpoints(bool enforce_dns);
/**
- * @brief set a listener for txes being added to the txpool
- *
- * @param callable to notify, or empty function to disable.
- */
- void set_txpool_listener(boost::function<void(std::vector<txpool_event>)> zmq_pub);
-
- /**
* @brief set whether or not to enable or disable DNS checkpoints
*
* @param disble whether to disable DNS checkpoints
@@ -828,13 +824,6 @@ namespace cryptonote
bool is_update_available() const { return m_update_available; }
/**
- * @brief get whether fluffy blocks are enabled
- *
- * @return whether fluffy blocks are enabled
- */
- bool fluffy_blocks_enabled() const { return m_fluffy_blocks_enabled; }
-
- /**
* @brief check a set of hashes against the precompiled hash set
*
* @return number of usable blocks
@@ -898,11 +887,6 @@ namespace cryptonote
bool has_block_weights(uint64_t height, uint64_t nblocks) const;
/**
- * @brief flushes the bad txs cache
- */
- void flush_bad_txs_cache();
-
- /**
* @brief flushes the invalid block cache
*/
void flush_invalid_blocks();
@@ -916,6 +900,55 @@ namespace cryptonote
*/
bool get_txpool_complement(const std::vector<crypto::hash> &hashes, std::vector<cryptonote::blobdata> &txes);
+ /**
+ * @brief validates some simple properties of a transaction
+ *
+ * Currently checks: tx has inputs,
+ * tx inputs all of supported type(s),
+ * tx outputs valid (type, key, amount),
+ * input and output total amounts don't overflow,
+ * output amount <= input amount,
+ * tx not too large,
+ * each input has a different key image.
+ *
+ * @param tx the transaction to check
+ * @param tvc tx verification context where extra fail flags are stored
+ * @param hf_version hard fork version
+ *
+ * @return true if all the checks pass, otherwise false
+ */
+ static bool check_tx_semantic(const transaction& tx, tx_verification_context& tvc,
+ uint8_t hf_version);
+
+ /**
+ * @brief verify that each input key image in a transaction is unique
+ *
+ * @param tx the transaction to check
+ *
+ * @return false if any key image is repeated, otherwise true
+ */
+ static bool check_tx_inputs_keyimages_diff(const transaction& tx);
+
+ /**
+ * @brief verify that each ring uses distinct members
+ *
+ * @param tx the transaction to check
+ * @param hf_version the hard fork version rules to use
+ *
+ * @return false if any ring uses duplicate members, true otherwise
+ */
+ static bool check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version);
+
+ /**
+ * @brief verify that each input key image in a transaction is in
+ * the valid domain
+ *
+ * @param tx the transaction to check
+ *
+ * @return false if any key image is not in the valid domain, otherwise true
+ */
+ static bool check_tx_inputs_keyimages_domain(const transaction& tx);
+
private:
/**
@@ -951,7 +984,8 @@ namespace cryptonote
*
* @note see Blockchain::add_new_block
*/
- bool add_new_block(const block& b, block_verification_context& bvc);
+ bool add_new_block(const block& b, block_verification_context& bvc,
+ pool_supplement& extra_block_txs);
/**
* @brief load any core state stored on disk
@@ -963,49 +997,6 @@ namespace cryptonote
bool load_state_data();
/**
- * @copydoc parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const
- *
- * @note see parse_tx_from_blob(transaction&, crypto::hash&, crypto::hash&, const blobdata&) const
- */
- bool parse_tx_from_blob(transaction& tx, crypto::hash& tx_hash, const blobdata& blob) const;
-
- /**
- * @brief check a transaction's syntax
- *
- * For now this does nothing, but it may check something about the tx
- * in the future.
- *
- * @param tx the transaction to check
- *
- * @return true
- */
- bool check_tx_syntax(const transaction& tx) const;
-
- /**
- * @brief validates some simple properties of a transaction
- *
- * Currently checks: tx has inputs,
- * tx inputs all of supported type(s),
- * tx outputs valid (type, key, amount),
- * input and output total amounts don't overflow,
- * output amount <= input amount,
- * tx not too large,
- * each input has a different key image.
- *
- * @param tx the transaction to check
- * @param keeped_by_block if the transaction has been in a block
- *
- * @return true if all the checks pass, otherwise false
- */
- bool check_tx_semantic(const transaction& tx, bool keeped_by_block) const;
- void set_semantics_failed(const crypto::hash &tx_hash);
-
- bool handle_incoming_tx_pre(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash);
- bool handle_incoming_tx_post(const tx_blob_entry& tx_blob, tx_verification_context& tvc, cryptonote::transaction &tx, crypto::hash &tx_hash);
- struct tx_verification_batch_info { const cryptonote::transaction *tx; crypto::hash tx_hash; tx_verification_context &tvc; bool &result; };
- bool handle_incoming_tx_accumulated_batch(std::vector<tx_verification_batch_info> &tx_info, bool keeped_by_block);
-
- /**
* @copydoc miner::on_block_chain_update
*
* @note see miner::on_block_chain_update
@@ -1024,35 +1015,6 @@ namespace cryptonote
bool handle_command_line(const boost::program_options::variables_map& vm);
/**
- * @brief verify that each input key image in a transaction is unique
- *
- * @param tx the transaction to check
- *
- * @return false if any key image is repeated, otherwise true
- */
- bool check_tx_inputs_keyimages_diff(const transaction& tx) const;
-
- /**
- * @brief verify that each ring uses distinct members
- *
- * @param tx the transaction to check
- * @param hf_version the hard fork version rules to use
- *
- * @return false if any ring uses duplicate members, true otherwise
- */
- bool check_tx_inputs_ring_members_diff(const transaction& tx, const uint8_t hf_version) const;
-
- /**
- * @brief verify that each input key image in a transaction is in
- * the valid domain
- *
- * @param tx the transaction to check
- *
- * @return false if any key image is not in the valid domain, otherwise true
- */
- bool check_tx_inputs_keyimages_domain(const transaction& tx) const;
-
- /**
* @brief attempts to relay any transactions in the mempool which need it
*
* @return true
@@ -1139,9 +1101,6 @@ namespace cryptonote
time_t start_time;
- std::unordered_set<crypto::hash> bad_semantics_txes[2];
- boost::mutex bad_semantics_txes_lock;
-
enum {
UPDATES_DISABLED,
UPDATES_NOTIFY,
@@ -1153,15 +1112,9 @@ namespace cryptonote
size_t m_last_update_length;
boost::mutex m_update_mutex;
- bool m_fluffy_blocks_enabled;
bool m_offline;
- /* `boost::function` is used because the implementation never allocates if
- the callable object has a single `std::shared_ptr` or `std::weap_ptr`
- internally. Whereas, the libstdc++ `std::function` will allocate. */
-
std::shared_ptr<tools::Notify> m_block_rate_notify;
- boost::function<void(std::vector<txpool_event>)> m_zmq_pub;
};
}
diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp
index 0af6cab85..b44fcbe28 100644
--- a/src/cryptonote_core/tx_pool.cpp
+++ b/src/cryptonote_core/tx_pool.cpp
@@ -36,6 +36,7 @@
#include "tx_pool.h"
#include "cryptonote_tx_utils.h"
#include "cryptonote_basic/cryptonote_boost_serialization.h"
+#include "cryptonote_basic/events.h"
#include "cryptonote_config.h"
#include "blockchain.h"
#include "blockchain_db/locked_txn.h"
@@ -43,6 +44,8 @@
#include "common/boost_serialization_helper.h"
#include "int-util.h"
#include "misc_language.h"
+#include "misc_log_ex.h"
+#include "tx_verification_utils.h"
#include "warnings.h"
#include "common/perf_timer.h"
#include "crypto/hash.h"
@@ -110,15 +113,6 @@ namespace cryptonote
return amount * ACCEPT_THRESHOLD;
}
- uint64_t get_transaction_weight_limit(uint8_t version)
- {
- // from v8, limit a tx to 50% of the minimum block weight
- if (version >= 8)
- return get_min_block_weight(version) / 2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
- else
- return get_min_block_weight(version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
- }
-
// external lock must be held for the comparison+set to work properly
void set_if_less(std::atomic<time_t>& next_check, const time_t candidate) noexcept
{
@@ -141,7 +135,10 @@ namespace cryptonote
// corresponding lists.
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/ const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version)
+ bool tx_memory_pool::add_tx(transaction &tx, /*const crypto::hash& tx_prefix_hash,*/
+ const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight,
+ tx_verification_context& tvc, relay_method tx_relay, bool relayed,
+ uint8_t version, uint8_t nic_verified_hf_version)
{
const bool kept_by_block = (tx_relay == relay_method::block);
@@ -149,13 +146,6 @@ namespace cryptonote
CRITICAL_REGION_LOCAL(m_transactions_lock);
PERF_TIMER(add_tx);
- if (tx.version == 0)
- {
- // v0 never accepted
- LOG_PRINT_L1("transaction version 0 is invalid");
- tvc.m_verifivation_failed = true;
- return false;
- }
// we do not accept transactions that timed out before, unless they're
// kept_by_block
@@ -167,49 +157,24 @@ namespace cryptonote
return false;
}
- if(!check_inputs_types_supported(tx))
+ if (version != nic_verified_hf_version && !cryptonote::ver_non_input_consensus(tx, tvc, version))
{
- tvc.m_verifivation_failed = true;
- tvc.m_invalid_input = true;
+ LOG_PRINT_L1("transaction " << id << " failed non-input consensus rule checks");
+ tvc.m_verifivation_failed = true; // should already be set, but just in case
return false;
}
- // fee per kilobyte, size rounded up.
uint64_t fee;
-
- if (tx.version == 1)
- {
- uint64_t inputs_amount = 0;
- if(!get_inputs_money_amount(tx, inputs_amount))
- {
- tvc.m_verifivation_failed = true;
- return false;
- }
-
- uint64_t outputs_amount = get_outs_money_amount(tx);
- if(outputs_amount > inputs_amount)
- {
- LOG_PRINT_L1("transaction use more money than it has: use " << print_money(outputs_amount) << ", have " << print_money(inputs_amount));
- tvc.m_verifivation_failed = true;
- tvc.m_overspend = true;
- return false;
- }
- else if(outputs_amount == inputs_amount)
- {
- LOG_PRINT_L1("transaction fee is zero: outputs_amount == inputs_amount, rejecting.");
- tvc.m_verifivation_failed = true;
- tvc.m_fee_too_low = true;
- return false;
- }
-
- fee = inputs_amount - outputs_amount;
- }
- else
+ bool fee_good = false;
+ try
{
- fee = tx.rct_signatures.txnFee;
+ // get_tx_fee() can throw. It shouldn't throw because we check preconditions in
+ // ver_non_input_consensus(), but let's put it in a try block just in case.
+ fee = get_tx_fee(tx);
+ fee_good = kept_by_block || m_blockchain.check_fee(tx_weight, fee);
}
-
- if (!kept_by_block && !m_blockchain.check_fee(tx_weight, fee))
+ catch(...) {}
+ if (!fee_good) // if fee calculation failed or fee in relayed tx is too low...
{
tvc.m_verifivation_failed = true;
tvc.m_fee_too_low = true;
@@ -217,15 +182,6 @@ namespace cryptonote
return false;
}
- size_t tx_weight_limit = get_transaction_weight_limit(version);
- if ((!kept_by_block || version >= HF_VERSION_PER_BYTE_FEE) && tx_weight > tx_weight_limit)
- {
- LOG_PRINT_L1("transaction is too heavy: " << tx_weight << " bytes, maximum weight: " << tx_weight_limit);
- tvc.m_verifivation_failed = true;
- tvc.m_too_big = true;
- return false;
- }
-
size_t tx_extra_size = tx.extra.size();
if (!kept_by_block && tx_extra_size > MAX_TX_EXTRA_SIZE)
{
@@ -261,14 +217,6 @@ namespace cryptonote
}
}
- if (!m_blockchain.check_tx_outputs(tx, tvc))
- {
- LOG_PRINT_L1("Transaction with id= "<< id << " has at least one invalid output");
- tvc.m_verifivation_failed = true;
- tvc.m_invalid_output = true;
- return false;
- }
-
// assume failure during verification steps until success is certain
tvc.m_verifivation_failed = true;
@@ -382,13 +330,13 @@ namespace cryptonote
add_tx_to_transient_lists(id, meta.fee / (double)(tx_weight ? tx_weight : 1), receive_time);
}
lock.commit();
+ tvc.m_added_to_pool = !existing_tx;
}
catch (const std::exception &e)
{
MERROR("internal error: error adding transaction to txpool: " << e.what());
return false;
}
- tvc.m_added_to_pool = true;
static_assert(unsigned(relay_method::none) == 0, "expected relay_method::none value to be zero");
if(meta.fee > 0 && tx_relay != relay_method::forward)
@@ -407,14 +355,16 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version)
+ bool tx_memory_pool::add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay,
+ bool relayed, uint8_t version, uint8_t nic_verified_hf_version)
{
crypto::hash h = null_hash;
cryptonote::blobdata bl;
t_serializable_object_to_blob(tx, bl);
if (bl.size() == 0 || !get_transaction_hash(tx, h))
return false;
- return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, tx_relay, relayed, version);
+ return add_tx(tx, h, bl, get_transaction_weight(tx, bl.size()), tvc, tx_relay, relayed, version,
+ nic_verified_hf_version);
}
//---------------------------------------------------------------------------------
size_t tx_memory_pool::get_txpool_weight() const
@@ -580,7 +530,7 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------------------------
- bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned)
+ bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned, const bool suppress_missing_msgs)
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain);
@@ -592,7 +542,10 @@ namespace cryptonote
txpool_tx_meta_t meta;
if (!m_blockchain.get_txpool_tx_meta(id, meta))
{
- MERROR("Failed to find tx_meta in txpool");
+ if (!suppress_missing_msgs)
+ {
+ MERROR("Failed to find tx_meta in txpool");
+ }
return false;
}
txblob = m_blockchain.get_txpool_tx_blob(id, relay_category::all);
@@ -1466,44 +1419,21 @@ namespace cryptonote
bool parsed;
} lazy_tx(txblob, txid, tx);
- //not the best implementation at this time, sorry :(
- //check is ring_signature already checked ?
- if(txd.max_used_block_id == null_hash)
- {//not checked, lets try to check
+ const std::uint64_t top_block_height{m_blockchain.get_current_blockchain_height() - 1};
+ const crypto::hash top_block_hash{m_blockchain.get_block_id_by_height(top_block_height)};
- if(txd.last_failed_id != null_hash && m_blockchain.get_current_blockchain_height() > txd.last_failed_height && txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height))
- return false;//we already sure that this tx is broken for this height
+ if (txd.last_failed_id == top_block_hash)
+ return false; // we are already sure that this tx isn't passing for this exact chain
- tx_verification_context tvc;
- if(!check_tx_inputs([&lazy_tx]()->cryptonote::transaction&{ return lazy_tx(); }, txid, txd.max_used_block_height, txd.max_used_block_id, tvc))
- {
- txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1;
- txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height);
- return false;
- }
- }else
- {
- if(txd.max_used_block_height >= m_blockchain.get_current_blockchain_height())
- return false;
- if(true)
- {
- //if we already failed on this height and id, skip actual ring signature check
- if(txd.last_failed_id == m_blockchain.get_block_id_by_height(txd.last_failed_height))
- return false;
- //check ring signature again, it is possible (with very small chance) that this transaction become again valid
- tx_verification_context tvc;
- if(!check_tx_inputs([&lazy_tx]()->cryptonote::transaction&{ return lazy_tx(); }, txid, txd.max_used_block_height, txd.max_used_block_id, tvc))
- {
- txd.last_failed_height = m_blockchain.get_current_blockchain_height()-1;
- txd.last_failed_id = m_blockchain.get_block_id_by_height(txd.last_failed_height);
- return false;
- }
- }
- }
- //if we here, transaction seems valid, but, anyway, check for key_images collisions with blockchain, just to be sure
- if(m_blockchain.have_tx_keyimges_as_spent(lazy_tx()))
+ tx_verification_context tvc{};
+ if (!check_tx_inputs([&lazy_tx]()->cryptonote::transaction&{ return lazy_tx(); },
+ txid,
+ txd.max_used_block_height,
+ txd.max_used_block_id,
+ tvc))
{
- txd.double_spend_seen = true;
+ txd.last_failed_height = top_block_height;
+ txd.last_failed_id = top_block_hash;
return false;
}
diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h
index b295ac1c2..df79aea1d 100644
--- a/src/cryptonote_core/tx_pool.h
+++ b/src/cryptonote_core/tx_pool.h
@@ -113,7 +113,9 @@ namespace cryptonote
* @tx_relay how the transaction was received
* @param tx_weight the transaction's weight
*/
- bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob, size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version);
+ bool add_tx(transaction &tx, const crypto::hash &id, const cryptonote::blobdata &blob,
+ size_t tx_weight, tx_verification_context& tvc, relay_method tx_relay, bool relayed,
+ uint8_t version, uint8_t nic_verified_hf_version = 0);
/**
* @brief add a transaction to the transaction pool
@@ -128,10 +130,18 @@ namespace cryptonote
* @tx_relay how the transaction was received
* @param relayed was this transaction from the network or a local client?
* @param version the version used to create the transaction
+ * @param nic_verified_hf_version hard fork which "tx" is known to pass non-input consensus test
+ *
+ * If "nic_verified_hf_version" parameter is equal to "version" parameter, then we skip the
+ * asserting `ver_non_input_consensus(tx)`, which greatly speeds up block popping and returning
+ * txs to mempool for txs which we know will pass the test. If nothing is known about how "tx"
+ * passes the non-input consensus tests (e.g. for newly received relayed txs), then leave
+ * "nic_verified_hf_version" as its default value of 0 (there is no v0 fork).
*
* @return true if the transaction passes validations, otherwise false
*/
- bool add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed, uint8_t version);
+ bool add_tx(transaction &tx, tx_verification_context& tvc, relay_method tx_relay, bool relayed,
+ uint8_t version, uint8_t nic_verified_hf_version = 0);
/**
* @brief takes a transaction with the given hash from the pool
@@ -145,10 +155,11 @@ namespace cryptonote
* @param do_not_relay return-by-reference is transaction not to be relayed to the network?
* @param double_spend_seen return-by-reference was a double spend seen for that transaction?
* @param pruned return-by-reference is the tx pruned
+ * @param suppress_missing_msgs suppress warning msgs when txid is missing (optional, defaults to `false`)
*
* @return true unless the transaction cannot be found in the pool
*/
- bool take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned);
+ bool take_tx(const crypto::hash &id, transaction &tx, cryptonote::blobdata &txblob, size_t& tx_weight, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen, bool &pruned, bool suppress_missing_msgs = false);
/**
* @brief checks if the pool has a transaction with the given hash
diff --git a/src/cryptonote_core/tx_verification_utils.cpp b/src/cryptonote_core/tx_verification_utils.cpp
index a93ef2f25..e79adcfb0 100644
--- a/src/cryptonote_core/tx_verification_utils.cpp
+++ b/src/cryptonote_core/tx_verification_utils.cpp
@@ -26,8 +26,12 @@
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#include <boost/iterator/transform_iterator.hpp>
+
#include "cryptonote_core/blockchain.h"
+#include "cryptonote_core/cryptonote_core.h"
#include "cryptonote_core/tx_verification_utils.h"
+#include "hardforks/hardforks.h"
#include "ringct/rctSigs.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
@@ -105,11 +109,104 @@ static crypto::hash calc_tx_mixring_hash(const transaction& tx, const rct::ctkey
return tx_and_mixring_hash;
}
+static bool is_canonical_bulletproof_layout(const std::vector<rct::Bulletproof> &proofs)
+{
+ if (proofs.size() != 1)
+ return false;
+ const size_t sz = proofs[0].V.size();
+ if (sz == 0 || sz > BULLETPROOF_MAX_OUTPUTS)
+ return false;
+ return true;
+}
+
+static bool is_canonical_bulletproof_plus_layout(const std::vector<rct::BulletproofPlus> &proofs)
+{
+ if (proofs.size() != 1)
+ return false;
+ const size_t sz = proofs[0].V.size();
+ if (sz == 0 || sz > BULLETPROOF_PLUS_MAX_OUTPUTS)
+ return false;
+ return true;
+}
+
+template <class TxForwardIt>
+static bool ver_non_input_consensus_templated(TxForwardIt tx_begin, TxForwardIt tx_end,
+ tx_verification_context& tvc, std::uint8_t hf_version)
+{
+ std::vector<const rct::rctSig*> rvv;
+ rvv.reserve(static_cast<size_t>(std::distance(tx_begin, tx_end)));
+
+ const size_t max_tx_version = hf_version < HF_VERSION_DYNAMIC_FEE ? 1 : 2;
+
+ const size_t tx_weight_limit = get_transaction_weight_limit(hf_version);
+
+ for (; tx_begin != tx_end; ++tx_begin)
+ {
+ const transaction& tx = *tx_begin;
+ const uint64_t blob_size = get_transaction_blob_size(tx);
+
+ // Rule 1
+ if (blob_size > get_max_tx_size())
+ {
+ tvc.m_verifivation_failed = true;
+ tvc.m_too_big = true;
+ return false;
+ }
+
+ // Rule 2 & 3
+ if (tx.version == 0 || tx.version > max_tx_version)
+ {
+ tvc.m_verifivation_failed = true;
+ return false;
+ }
+
+ // Rule 4
+ const size_t tx_weight = get_transaction_weight(tx, blob_size);
+ if (hf_version >= HF_VERSION_PER_BYTE_FEE && tx_weight > tx_weight_limit)
+ {
+ tvc.m_verifivation_failed = true;
+ tvc.m_too_big = true;
+ return false;
+ }
+
+ // Rule 5
+ if (!core::check_tx_semantic(tx, tvc, hf_version))
+ return false;
+
+ // Rule 6
+ if (!Blockchain::check_tx_outputs(tx, tvc, hf_version) || tvc.m_verifivation_failed)
+ return false;
+
+ // We only want to check RingCT semantics if this is actually a RingCT transaction
+ if (tx.version >= 2)
+ rvv.push_back(&tx.rct_signatures);
+ }
+
+ // Rule 7
+ if (!ver_mixed_rct_semantics(std::move(rvv)))
+ {
+ tvc.m_verifivation_failed = true;
+ tvc.m_invalid_input = true;
+ return false;
+ }
+
+ return true;
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace cryptonote
{
+uint64_t get_transaction_weight_limit(const uint8_t hf_version)
+{
+ // from v8, limit a tx to 50% of the minimum block weight
+ if (hf_version >= HF_VERSION_PER_BYTE_FEE)
+ return get_min_block_weight(hf_version) / 2 - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
+ else
+ return get_min_block_weight(hf_version) - CRYPTONOTE_COINBASE_BLOB_RESERVED_SIZE;
+}
+
bool ver_rct_non_semantics_simple_cached
(
transaction& tx,
@@ -164,4 +261,104 @@ bool ver_rct_non_semantics_simple_cached
return true;
}
+bool ver_mixed_rct_semantics(std::vector<const rct::rctSig*> rvv)
+{
+ size_t batch_rv_size = 0; // this acts as an "end" iterator to the last simple batchable sig ptr
+ for (size_t i = 0; i < rvv.size(); ++i)
+ {
+ const rct::rctSig& rv = *rvv[i];
+
+ bool is_batchable_rv = false;
+
+ switch (rv.type)
+ {
+ case rct::RCTTypeNull:
+ // coinbase should not come here, so we reject for all other types
+ MERROR("Unexpected Null rctSig type");
+ return false;
+ break;
+ case rct::RCTTypeSimple:
+ if (!rct::verRctSemanticsSimple(rv))
+ {
+ MERROR("rct signature semantics check failed: type simple");
+ return false;
+ }
+ break;
+ case rct::RCTTypeFull:
+ if (!rct::verRct(rv, /*semantics=*/true))
+ {
+ MERROR("rct signature semantics check failed: type full");
+ return false;
+ }
+ break;
+ case rct::RCTTypeBulletproof:
+ case rct::RCTTypeBulletproof2:
+ case rct::RCTTypeCLSAG:
+ if (!is_canonical_bulletproof_layout(rv.p.bulletproofs))
+ {
+ MERROR("Bulletproof does not have canonical form");
+ return false;
+ }
+ is_batchable_rv = true;
+ break;
+ case rct::RCTTypeBulletproofPlus:
+ if (!is_canonical_bulletproof_plus_layout(rv.p.bulletproofs_plus))
+ {
+ MERROR("Bulletproof_plus does not have canonical form");
+ return false;
+ }
+ is_batchable_rv = true;
+ break;
+ default:
+ MERROR("Unknown rct type: " << rv.type);
+ return false;
+ break;
+ }
+
+ // Save this ring sig for later, as we will attempt simple RCT semantics batch verification
+ if (is_batchable_rv)
+ rvv[batch_rv_size++] = rvv[i];
+ }
+
+ if (batch_rv_size) // if any simple, batchable ring sigs...
+ {
+ rvv.resize(batch_rv_size);
+ if (!rct::verRctSemanticsSimple(rvv))
+ {
+ MERROR("rct signature semantics check failed: simple-style batch verification failed");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool ver_non_input_consensus(const transaction& tx, tx_verification_context& tvc,
+ std::uint8_t hf_version)
+{
+ return ver_non_input_consensus_templated(&tx, &tx + 1, tvc, hf_version);
+}
+
+bool ver_non_input_consensus(const pool_supplement& ps, tx_verification_context& tvc,
+ const std::uint8_t hf_version)
+{
+ // We already verified the pool supplement for this hard fork version! Yippee!
+ if (ps.nic_verified_hf_version == hf_version)
+ return true;
+
+ const auto it_transform = [] (const decltype(ps.txs_by_txid)::value_type& in)
+ -> const transaction& { return in.second.first; };
+ const auto tx_begin = boost::make_transform_iterator(ps.txs_by_txid.cbegin(), it_transform);
+ const auto tx_end = boost::make_transform_iterator(ps.txs_by_txid.cend(), it_transform);
+
+ // Perform the checks...
+ const bool verified = ver_non_input_consensus_templated(tx_begin, tx_end, tvc, hf_version);
+
+ // Cache the hard fork version on success
+ if (verified)
+ ps.nic_verified_hf_version = hf_version;
+
+ return verified;
+}
+
} // namespace cryptonote
diff --git a/src/cryptonote_core/tx_verification_utils.h b/src/cryptonote_core/tx_verification_utils.h
index ccd401d2a..d4ab93b7f 100644
--- a/src/cryptonote_core/tx_verification_utils.h
+++ b/src/cryptonote_core/tx_verification_utils.h
@@ -30,10 +30,19 @@
#include "common/data_cache.h"
#include "cryptonote_basic/cryptonote_basic.h"
+#include "cryptonote_basic/verification_context.h"
namespace cryptonote
{
+/**
+ * @brief Get the maximum transaction weight for a given hardfork
+ *
+ * @param hf_version hard fork version
+ * @return the maximum unconditional transaction weight
+ */
+uint64_t get_transaction_weight_limit(uint8_t hf_version);
+
// Modifying this value should not affect consensus. You can adjust it for performance needs
static constexpr const size_t RCT_VER_CACHE_SIZE = 8192;
@@ -75,4 +84,57 @@ bool ver_rct_non_semantics_simple_cached
std::uint8_t rct_type_to_cache
);
+/**
+ * @brief Verify the semantics of a group of RingCT signatures as a batch (if applicable)
+ *
+ * Coinbase txs or other transaction with a RingCT type of RCTTypeNull will fail to verify.
+ *
+ * @param rvv list of signatures to verify
+ * @return true if all signatures verified semantics successfully, false otherwise
+ */
+bool ver_mixed_rct_semantics(std::vector<const rct::rctSig*> rvv);
+
+/**
+ * @brief Used to provide transaction info that skips the mempool to block handling code
+ */
+struct pool_supplement
+{
+ // Map of supplemental tx info that we might need to validate a block
+ // Maps TXID -> transaction and blob
+ std::unordered_map<crypto::hash, std::pair<transaction, blobdata>> txs_by_txid;
+ // If non-zero, then consider all the txs' non-input consensus (NIC) rules verified for this
+ // hard fork. User: If you add an unverified transaction to txs_by_txid, set this field to zero!
+ mutable std::uint8_t nic_verified_hf_version = 0;
+};
+
+/**
+ * @brief Verify every non-input consensus rule for a group of non-coinbase transactions
+ *
+ * List of checks that we do for each transaction:
+ * 1. Check tx blob size < get_max_tx_size()
+ * 2. Check tx version != 0
+ * 3. Check tx version is less than maximum for given hard fork version
+ * 4. Check tx weight < get_transaction_weight_limit()
+ * 5. Passes core::check_tx_semantic()
+ * 6. Passes Blockchain::check_tx_outputs()
+ * 7. Passes ver_mixed_rct_semantics() [Uses batch RingCT verification when applicable]
+ *
+ * For pool_supplement input:
+ * We assume the structure of the pool supplement is already correct: for each value entry, the
+ * cryptonote::transaction matches its corresponding blobdata and the TXID map key is correctly
+ * calculated for that transaction. We use the .nic_verified_hf_version field to skip verification
+ * for the pool supplement if hf_version matches, and we cache that version on success.
+ *
+ * @param tx single transaction to verify
+ * @param pool_supplement pool supplement to verify
+ * @param tvc relevant flags will be set for if/why verification failed
+ * @param hf_version Hard fork version to run rules against
+ * @return true if all relevant transactions verify, false otherwise
+ */
+bool ver_non_input_consensus(const transaction& tx, tx_verification_context& tvc,
+ std::uint8_t hf_version);
+
+bool ver_non_input_consensus(const pool_supplement& ps, tx_verification_context& tvc,
+ std::uint8_t hf_version);
+
} // namespace cryptonote