mirror of
https://github.com/monero-project/monero.git
synced 2025-08-14 08:45:44 -04:00
track double spending in the txpool
Transactions in the txpool are marked when another transaction is seen double spending one or more of its inputs. This is then exposed wherever appropriate. Note that being marked with this "double spend seen" flag does NOT mean this transaction IS a double spend and will never be mined: it just means that the network has seen at least another transaction spending at least one of the same inputs, so care should be taken to wait for a few confirmations before acting upon that transaction (ie, mostly of use for merchants wanting to accept unconfirmed transactions).
This commit is contained in:
parent
3dd31d33fa
commit
ccf53a566c
16 changed files with 216 additions and 62 deletions
|
@ -3166,9 +3166,9 @@ bool Blockchain::flush_txes_from_pool(const std::list<crypto::hash> &txids)
|
|||
cryptonote::transaction tx;
|
||||
size_t blob_size;
|
||||
uint64_t fee;
|
||||
bool relayed, do_not_relay;
|
||||
bool relayed, do_not_relay, double_spend_seen;
|
||||
MINFO("Removing txid " << txid << " from the pool");
|
||||
if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay))
|
||||
if(m_tx_pool.have_tx(txid) && !m_tx_pool.take_tx(txid, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen))
|
||||
{
|
||||
MERROR("Failed to remove txid " << txid << " from the pool");
|
||||
res = false;
|
||||
|
@ -3351,7 +3351,7 @@ leave:
|
|||
transaction tx;
|
||||
size_t blob_size = 0;
|
||||
uint64_t fee = 0;
|
||||
bool relayed = false, do_not_relay = false;
|
||||
bool relayed = false, do_not_relay = false, double_spend_seen = false;
|
||||
TIME_MEASURE_START(aa);
|
||||
|
||||
// XXX old code does not check whether tx exists
|
||||
|
@ -3368,7 +3368,7 @@ leave:
|
|||
TIME_MEASURE_START(bb);
|
||||
|
||||
// get transaction with hash <tx_id> from tx_pool
|
||||
if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay))
|
||||
if(!m_tx_pool.take_tx(tx_id, tx, blob_size, fee, relayed, do_not_relay, double_spend_seen))
|
||||
{
|
||||
MERROR_VER("Block with id: " << id << " has at least one unknown transaction with id: " << tx_id);
|
||||
bvc.m_verifivation_failed = true;
|
||||
|
@ -4381,12 +4381,12 @@ void Blockchain::load_compiled_in_block_hashes()
|
|||
|
||||
size_t blob_size;
|
||||
uint64_t fee;
|
||||
bool relayed, do_not_relay;
|
||||
bool relayed, do_not_relay, double_spend_seen;
|
||||
transaction pool_tx;
|
||||
for(const transaction &tx : txs)
|
||||
{
|
||||
crypto::hash tx_hash = get_transaction_hash(tx);
|
||||
m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay);
|
||||
m_tx_pool.take_tx(tx_hash, pool_tx, blob_size, fee, relayed, do_not_relay, double_spend_seen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,6 +187,7 @@ namespace cryptonote
|
|||
{
|
||||
if(have_tx_keyimges_as_spent(tx))
|
||||
{
|
||||
mark_double_spend(tx);
|
||||
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
|
||||
tvc.m_verifivation_failed = true;
|
||||
tvc.m_double_spend = true;
|
||||
|
@ -228,6 +229,7 @@ namespace cryptonote
|
|||
meta.last_relayed_time = time(NULL);
|
||||
meta.relayed = relayed;
|
||||
meta.do_not_relay = do_not_relay;
|
||||
meta.double_spend_seen = have_tx_keyimges_as_spent(tx);
|
||||
memset(meta.padding, 0, sizeof(meta.padding));
|
||||
try
|
||||
{
|
||||
|
@ -266,6 +268,7 @@ namespace cryptonote
|
|||
meta.last_relayed_time = time(NULL);
|
||||
meta.relayed = relayed;
|
||||
meta.do_not_relay = do_not_relay;
|
||||
meta.double_spend_seen = false;
|
||||
memset(meta.padding, 0, sizeof(meta.padding));
|
||||
|
||||
try
|
||||
|
@ -354,7 +357,7 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------------------------
|
||||
bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay)
|
||||
bool tx_memory_pool::take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||
|
@ -377,6 +380,7 @@ namespace cryptonote
|
|||
fee = meta.fee;
|
||||
relayed = meta.relayed;
|
||||
do_not_relay = meta.do_not_relay;
|
||||
double_spend_seen = meta.double_spend_seen;
|
||||
|
||||
// remove first, in case this throws, so key images aren't removed
|
||||
m_blockchain.remove_txpool_tx(id);
|
||||
|
@ -594,6 +598,8 @@ namespace cryptonote
|
|||
uint64_t age = now - meta.receive_time + (now == meta.receive_time);
|
||||
agebytes[age].txs++;
|
||||
agebytes[age].bytes += meta.blob_size;
|
||||
if (meta.double_spend_seen)
|
||||
++stats.num_double_spends;
|
||||
return true;
|
||||
});
|
||||
stats.bytes_med = epee::misc_utils::median(sizes);
|
||||
|
@ -649,6 +655,7 @@ namespace cryptonote
|
|||
m_blockchain.for_all_txpool_txes([&tx_infos, key_image_infos](const crypto::hash &txid, const txpool_tx_meta_t &meta, const cryptonote::blobdata *bd){
|
||||
tx_info txi;
|
||||
txi.id_hash = epee::string_tools::pod_to_hex(txid);
|
||||
txi.tx_blob = *bd;
|
||||
transaction tx;
|
||||
if (!parse_and_validate_tx_from_blob(*bd, tx))
|
||||
{
|
||||
|
@ -668,6 +675,7 @@ namespace cryptonote
|
|||
txi.relayed = meta.relayed;
|
||||
txi.last_relayed_time = meta.last_relayed_time;
|
||||
txi.do_not_relay = meta.do_not_relay;
|
||||
txi.double_spend_seen = meta.double_spend_seen;
|
||||
tx_infos.push_back(txi);
|
||||
return true;
|
||||
}, true);
|
||||
|
@ -712,6 +720,7 @@ namespace cryptonote
|
|||
txi.relayed = meta.relayed;
|
||||
txi.last_relayed_time = meta.last_relayed_time;
|
||||
txi.do_not_relay = meta.do_not_relay;
|
||||
txi.double_spend_seen = meta.double_spend_seen;
|
||||
tx_infos.push_back(txi);
|
||||
return true;
|
||||
}, true);
|
||||
|
@ -843,7 +852,10 @@ namespace cryptonote
|
|||
}
|
||||
//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(tx))
|
||||
{
|
||||
txd.double_spend_seen = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
//transaction is ok.
|
||||
return true;
|
||||
|
@ -871,6 +883,39 @@ namespace cryptonote
|
|||
return true;
|
||||
}
|
||||
//---------------------------------------------------------------------------------
|
||||
void tx_memory_pool::mark_double_spend(const transaction &tx)
|
||||
{
|
||||
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||
LockedTXN lock(m_blockchain);
|
||||
for(size_t i = 0; i!= tx.vin.size(); i++)
|
||||
{
|
||||
CHECKED_GET_SPECIFIC_VARIANT(tx.vin[i], const txin_to_key, itk, void());
|
||||
const key_images_container::const_iterator it = m_spent_key_images.find(itk.k_image);
|
||||
if (it != m_spent_key_images.end())
|
||||
{
|
||||
for (const crypto::hash &txid: it->second)
|
||||
{
|
||||
txpool_tx_meta_t meta = m_blockchain.get_txpool_tx_meta(txid);
|
||||
if (!meta.double_spend_seen)
|
||||
{
|
||||
MDEBUG("Marking " << txid << " as double spending " << itk.k_image);
|
||||
meta.double_spend_seen = true;
|
||||
try
|
||||
{
|
||||
m_blockchain.update_txpool_tx(txid, meta);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
MERROR("Failed to update tx meta: " << e.what());
|
||||
// continue, not fatal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------------------------------------
|
||||
std::string tx_memory_pool::print_pool(bool short_format) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
@ -890,6 +935,7 @@ namespace cryptonote
|
|||
ss << "blob_size: " << meta.blob_size << std::endl
|
||||
<< "fee: " << print_money(meta.fee) << std::endl
|
||||
<< "kept_by_block: " << (meta.kept_by_block ? 'T' : 'F') << std::endl
|
||||
<< "double_spend_seen: " << (meta.double_spend_seen ? 'T' : 'F') << std::endl
|
||||
<< "max_used_block_height: " << meta.max_used_block_height << std::endl
|
||||
<< "max_used_block_id: " << meta.max_used_block_id << std::endl
|
||||
<< "last_failed_height: " << meta.last_failed_height << std::endl
|
||||
|
|
|
@ -137,10 +137,11 @@ namespace cryptonote
|
|||
* @param fee the transaction fee
|
||||
* @param relayed return-by-reference was transaction relayed to us by the network?
|
||||
* @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?
|
||||
*
|
||||
* @return true unless the transaction cannot be found in the pool
|
||||
*/
|
||||
bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay);
|
||||
bool take_tx(const crypto::hash &id, transaction &tx, size_t& blob_size, uint64_t& fee, bool &relayed, bool &do_not_relay, bool &double_spend_seen);
|
||||
|
||||
/**
|
||||
* @brief checks if the pool has a transaction with the given hash
|
||||
|
@ -391,6 +392,8 @@ namespace cryptonote
|
|||
time_t last_relayed_time; //!< the last time the transaction was relayed to the network
|
||||
bool relayed; //!< whether or not the transaction has been relayed to the network
|
||||
bool do_not_relay; //!< to avoid relay this transaction to the network
|
||||
|
||||
bool double_spend_seen; //!< true iff another tx was seen double spending this one
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -478,6 +481,11 @@ namespace cryptonote
|
|||
*/
|
||||
bool is_transaction_ready_to_go(txpool_tx_meta_t& txd, transaction &tx) const;
|
||||
|
||||
/**
|
||||
* @brief mark all transactions double spending the one passed
|
||||
*/
|
||||
void mark_double_spend(const transaction &tx);
|
||||
|
||||
//TODO: confirm the below comments and investigate whether or not this
|
||||
// is the desired behavior
|
||||
//! map key images to transactions which spent them
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue