Merge pull request #7169

bbe3b27 tx_pool: full tx revalidation on fork boundaries (moneromooo-monero)
This commit is contained in:
luigi1111 2021-11-09 22:44:39 -06:00
commit c3b1b94453
No known key found for this signature in database
GPG key ID: F4ACA0183641E010
3 changed files with 74 additions and 50 deletions

View file

@ -588,6 +588,7 @@ block Blockchain::pop_block_from_blockchain()
CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block"); CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block");
const uint8_t previous_hf_version = get_current_hard_fork_version();
try try
{ {
m_db->pop_block(popped_block, popped_txs); m_db->pop_block(popped_block, popped_txs);
@ -650,6 +651,13 @@ block Blockchain::pop_block_from_blockchain()
m_tx_pool.on_blockchain_dec(top_block_height, top_block_hash); m_tx_pool.on_blockchain_dec(top_block_height, top_block_hash);
invalidate_block_template_cache(); invalidate_block_template_cache();
const uint8_t new_hf_version = get_current_hard_fork_version();
if (new_hf_version != previous_hf_version)
{
MINFO("Validating txpool for v" << (unsigned)new_hf_version);
m_tx_pool.validate(new_hf_version);
}
return popped_block; return popped_block;
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
@ -4392,6 +4400,19 @@ leave:
get_difficulty_for_next_block(); // just to cache it get_difficulty_for_next_block(); // just to cache it
invalidate_block_template_cache(); invalidate_block_template_cache();
const uint8_t new_hf_version = get_current_hard_fork_version();
if (new_hf_version != hf_version)
{
// the genesis block is added before everything's setup, and the txpool is empty
// when we start from scratch, so we skip this
const bool is_genesis_block = new_height == 1;
if (!is_genesis_block)
{
MGINFO("Validating txpool for v" << (unsigned)new_hf_version);
m_tx_pool.validate(new_hf_version);
}
}
send_miner_notifications(id, already_generated_coins); send_miner_notifications(id, already_generated_coins);
for (const auto& notifier: m_block_notifiers) for (const auto& notifier: m_block_notifiers)

View file

@ -1568,61 +1568,59 @@ namespace cryptonote
{ {
CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL(m_transactions_lock);
CRITICAL_REGION_LOCAL1(m_blockchain); CRITICAL_REGION_LOCAL1(m_blockchain);
size_t tx_weight_limit = get_transaction_weight_limit(version);
std::unordered_set<crypto::hash> remove;
m_txpool_weight = 0; MINFO("Validating txpool contents for v" << (unsigned)version);
m_blockchain.for_all_txpool_txes([this, &remove, tx_weight_limit](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) {
m_txpool_weight += meta.weight; LockedTXN lock(m_blockchain.get_db());
if (meta.weight > tx_weight_limit) {
LOG_PRINT_L1("Transaction " << txid << " is too big (" << meta.weight << " bytes), removing it from pool"); struct tx_entry_t
remove.insert(txid); {
} crypto::hash txid;
else if (m_blockchain.have_tx(txid)) { txpool_tx_meta_t meta;
LOG_PRINT_L1("Transaction " << txid << " is in the blockchain, removing it from pool"); };
remove.insert(txid);
} // get all txids
std::vector<tx_entry_t> txes;
m_blockchain.for_all_txpool_txes([this, &txes](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata_ref*) {
if (!meta.pruned) // skip pruned txes
txes.push_back({txid, meta});
return true; return true;
}, false, relay_category::all); }, false, relay_category::all);
size_t n_removed = 0; // take them all out and add them back in, some might fail
if (!remove.empty()) size_t added = 0;
{ for (auto &e: txes)
LockedTXN lock(m_blockchain.get_db());
for (const crypto::hash &txid: remove)
{ {
try try
{ {
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all); size_t weight;
uint64_t fee;
cryptonote::transaction tx; cryptonote::transaction tx;
if (!parse_and_validate_tx_from_blob(txblob, tx)) // remove pruned ones on startup, they're meant to be temporary cryptonote::blobdata blob;
bool relayed, do_not_relay, double_spend_seen, pruned;
if (!take_tx(e.txid, tx, blob, weight, fee, relayed, do_not_relay, double_spend_seen, pruned))
MERROR("Failed to get tx " << e.txid << " from txpool for re-validation");
cryptonote::tx_verification_context tvc{};
relay_method tx_relay = e.meta.get_relay_method();
if (!add_tx(tx, e.txid, blob, e.meta.weight, tvc, tx_relay, relayed, version))
{ {
MERROR("Failed to parse tx from txpool"); MINFO("Failed to re-validate tx " << e.txid << " for v" << (unsigned)version << ", dropped");
continue; continue;
} }
// remove tx from db first m_blockchain.update_txpool_tx(e.txid, e.meta);
m_blockchain.remove_txpool_tx(txid); ++added;
m_txpool_weight -= get_transaction_weight(tx, txblob.size());
remove_transaction_keyimages(tx, txid);
auto sorted_it = find_tx_in_sorted_container(txid);
if (sorted_it == m_txs_by_fee_and_receive_time.end())
{
LOG_PRINT_L1("Removing tx " << txid << " from tx pool, but it was not found in the sorted txs container!");
}
else
{
m_txs_by_fee_and_receive_time.erase(sorted_it);
}
++n_removed;
} }
catch (const std::exception &e) catch (const std::exception &e)
{ {
MERROR("Failed to remove invalid tx from pool"); MERROR("Failed to re-validate tx from pool");
// continue continue;
} }
} }
lock.commit(); lock.commit();
}
const size_t n_removed = txes.size() - added;
if (n_removed > 0) if (n_removed > 0)
++m_cookie; ++m_cookie;
return n_removed; return n_removed;

View file

@ -106,10 +106,16 @@ static uint32_t lcg()
} }
struct BlockchainAndPool
{
cryptonote::tx_memory_pool txpool;
cryptonote::Blockchain bc;
BlockchainAndPool(): txpool(bc), bc(txpool) {}
};
#define PREFIX_WINDOW(hf_version,window) \ #define PREFIX_WINDOW(hf_version,window) \
std::unique_ptr<cryptonote::Blockchain> bc; \ BlockchainAndPool bap; \
cryptonote::tx_memory_pool txpool(*bc); \ cryptonote::Blockchain *bc = &bap.bc; \
bc.reset(new cryptonote::Blockchain(txpool)); \
struct get_test_options { \ struct get_test_options { \
const std::pair<uint8_t, uint64_t> hard_forks[3]; \ const std::pair<uint8_t, uint64_t> hard_forks[3]; \
const cryptonote::test_options test_options = { \ const cryptonote::test_options test_options = { \
@ -118,8 +124,7 @@ static uint32_t lcg()
}; \ }; \
get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)1), std::make_pair((uint8_t)0, (uint64_t)0)} {} \ get_test_options(): hard_forks{std::make_pair(1, (uint64_t)0), std::make_pair((uint8_t)hf_version, (uint64_t)1), std::make_pair((uint8_t)0, (uint64_t)0)} {} \
} opts; \ } opts; \
cryptonote::Blockchain *blockchain = bc.get(); \ bool r = bc->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \
bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \
ASSERT_TRUE(r) ASSERT_TRUE(r)
#define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW) #define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW)