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.
This commit is contained in:
parent
c458d5fe40
commit
bbe3b276b8
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue