tx_pool: full tx revalidation on fork boundaries

avoids mining txes after a fork that are invalid by this fork's
rules, but were valid by the previous fork rules at the time
they were verified and added to the txpool.
pull/7169/head
moneromooo-monero 3 years ago
parent c458d5fe40
commit bbe3b276b8
No known key found for this signature in database
GPG Key ID: 686F07454D6CEFC3

@ -588,6 +588,7 @@ block Blockchain::pop_block_from_blockchain()
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
{
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);
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;
}
//------------------------------------------------------------------
@ -4392,6 +4400,19 @@ leave:
get_difficulty_for_next_block(); // just to cache it
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);
for (const auto& notifier: m_block_notifiers)

@ -1568,61 +1568,59 @@ namespace cryptonote
{
CRITICAL_REGION_LOCAL(m_transactions_lock);
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;
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;
if (meta.weight > tx_weight_limit) {
LOG_PRINT_L1("Transaction " << txid << " is too big (" << meta.weight << " bytes), removing it from pool");
remove.insert(txid);
}
else if (m_blockchain.have_tx(txid)) {
LOG_PRINT_L1("Transaction " << txid << " is in the blockchain, removing it from pool");
remove.insert(txid);
}
MINFO("Validating txpool contents for v" << (unsigned)version);
LockedTXN lock(m_blockchain.get_db());
struct tx_entry_t
{
crypto::hash txid;
txpool_tx_meta_t meta;
};
// 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;
}, false, relay_category::all);
size_t n_removed = 0;
if (!remove.empty())
{
LockedTXN lock(m_blockchain.get_db());
for (const crypto::hash &txid: remove)
// take them all out and add them back in, some might fail
size_t added = 0;
for (auto &e: txes)
{
try
{
cryptonote::blobdata txblob = m_blockchain.get_txpool_tx_blob(txid, relay_category::all);
size_t weight;
uint64_t fee;
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;
}
// remove tx from db first
m_blockchain.remove_txpool_tx(txid);
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;
m_blockchain.update_txpool_tx(e.txid, e.meta);
++added;
}
catch (const std::exception &e)
{
MERROR("Failed to remove invalid tx from pool");
// continue
MERROR("Failed to re-validate tx from pool");
continue;
}
}
lock.commit();
}
const size_t n_removed = txes.size() - added;
if (n_removed > 0)
++m_cookie;
return n_removed;

@ -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) \
std::unique_ptr<cryptonote::Blockchain> bc; \
cryptonote::tx_memory_pool txpool(*bc); \
bc.reset(new cryptonote::Blockchain(txpool)); \
BlockchainAndPool bap; \
cryptonote::Blockchain *bc = &bap.bc; \
struct get_test_options { \
const std::pair<uint8_t, uint64_t> hard_forks[3]; \
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)} {} \
} opts; \
cryptonote::Blockchain *blockchain = bc.get(); \
bool r = blockchain->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \
bool r = bc->init(new TestDB(), cryptonote::FAKECHAIN, true, &opts.test_options, 0, NULL); \
ASSERT_TRUE(r)
#define PREFIX(hf_version) PREFIX_WINDOW(hf_version, TEST_LONG_TERM_BLOCK_WEIGHT_WINDOW)

Loading…
Cancel
Save