Always reject duplicate key-images from second txid
This commit is contained in:
parent
babf25d2ec
commit
571546067f
@ -210,7 +210,7 @@ namespace cryptonote
|
|||||||
// TODO: Investigate why not?
|
// TODO: Investigate why not?
|
||||||
if(!kept_by_block)
|
if(!kept_by_block)
|
||||||
{
|
{
|
||||||
if(have_tx_keyimges_as_spent(tx))
|
if(have_tx_keyimges_as_spent(tx, id))
|
||||||
{
|
{
|
||||||
mark_double_spend(tx);
|
mark_double_spend(tx);
|
||||||
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
|
LOG_PRINT_L1("Transaction with id= "<< id << " used already spent key images");
|
||||||
@ -253,7 +253,7 @@ namespace cryptonote
|
|||||||
meta.last_relayed_time = time(NULL);
|
meta.last_relayed_time = time(NULL);
|
||||||
meta.relayed = relayed;
|
meta.relayed = relayed;
|
||||||
meta.set_relay_method(tx_relay);
|
meta.set_relay_method(tx_relay);
|
||||||
meta.double_spend_seen = have_tx_keyimges_as_spent(tx);
|
meta.double_spend_seen = have_tx_keyimges_as_spent(tx, id);
|
||||||
meta.pruned = tx.pruned;
|
meta.pruned = tx.pruned;
|
||||||
meta.bf_padding = 0;
|
meta.bf_padding = 0;
|
||||||
memset(meta.padding, 0, sizeof(meta.padding));
|
memset(meta.padding, 0, sizeof(meta.padding));
|
||||||
@ -1098,30 +1098,32 @@ namespace cryptonote
|
|||||||
return m_blockchain.get_db().txpool_has_tx(id, tx_category);
|
return m_blockchain.get_db().txpool_has_tx(id, tx_category);
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------
|
||||||
bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx) const
|
bool tx_memory_pool::have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const
|
||||||
{
|
{
|
||||||
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||||
CRITICAL_REGION_LOCAL1(m_blockchain);
|
CRITICAL_REGION_LOCAL1(m_blockchain);
|
||||||
for(const auto& in: tx.vin)
|
for(const auto& in: tx.vin)
|
||||||
{
|
{
|
||||||
CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail
|
CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, true);//should never fail
|
||||||
if(have_tx_keyimg_as_spent(tokey_in.k_image))
|
if(have_tx_keyimg_as_spent(tokey_in.k_image, txid))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------
|
||||||
bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im) const
|
bool tx_memory_pool::have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const
|
||||||
{
|
{
|
||||||
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
CRITICAL_REGION_LOCAL(m_transactions_lock);
|
||||||
bool spent = false;
|
|
||||||
const auto found = m_spent_key_images.find(key_im);
|
const auto found = m_spent_key_images.find(key_im);
|
||||||
if (found != m_spent_key_images.end())
|
if (found != m_spent_key_images.end() && !found->second.empty())
|
||||||
{
|
{
|
||||||
for (const crypto::hash& tx_hash : found->second)
|
// If another tx is using the key image, always return as spent.
|
||||||
spent |= m_blockchain.txpool_tx_matches_category(tx_hash, relay_category::broadcasted);
|
// See `insert_key_images`.
|
||||||
|
if (1 < found->second.size() || *(found->second.cbegin()) != txid)
|
||||||
|
return true;
|
||||||
|
return m_blockchain.txpool_tx_matches_category(txid, relay_category::broadcasted);
|
||||||
}
|
}
|
||||||
return spent;
|
return false;
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------------------------------
|
//---------------------------------------------------------------------------------
|
||||||
void tx_memory_pool::lock() const
|
void tx_memory_pool::lock() const
|
||||||
|
@ -470,10 +470,11 @@ namespace cryptonote
|
|||||||
* @brief check if a transaction in the pool has a given spent key image
|
* @brief check if a transaction in the pool has a given spent key image
|
||||||
*
|
*
|
||||||
* @param key_im the spent key image to look for
|
* @param key_im the spent key image to look for
|
||||||
|
* @param txid hash of the new transaction where `key_im` was seen.
|
||||||
*
|
*
|
||||||
* @return true if the spent key image is present, otherwise false
|
* @return true if the spent key image is present, otherwise false
|
||||||
*/
|
*/
|
||||||
bool have_tx_keyimg_as_spent(const crypto::key_image& key_im) const;
|
bool have_tx_keyimg_as_spent(const crypto::key_image& key_im, const crypto::hash& txid) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief check if any spent key image in a transaction is in the pool
|
* @brief check if any spent key image in a transaction is in the pool
|
||||||
@ -484,10 +485,11 @@ namespace cryptonote
|
|||||||
* @note see tx_pool::have_tx_keyimg_as_spent
|
* @note see tx_pool::have_tx_keyimg_as_spent
|
||||||
*
|
*
|
||||||
* @param tx the transaction to check spent key images of
|
* @param tx the transaction to check spent key images of
|
||||||
|
* @param txid hash of `tx`.
|
||||||
*
|
*
|
||||||
* @return true if any spent key images are present in the pool, otherwise false
|
* @return true if any spent key images are present in the pool, otherwise false
|
||||||
*/
|
*/
|
||||||
bool have_tx_keyimges_as_spent(const transaction& tx) const;
|
bool have_tx_keyimges_as_spent(const transaction& tx, const crypto::hash& txid) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief forget a transaction's spent key images
|
* @brief forget a transaction's spent key images
|
||||||
|
@ -55,7 +55,7 @@ class TransferTest():
|
|||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
print('Resetting blockchain')
|
print('Resetting blockchain')
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
res = daemon.get_height()
|
res = daemon.get_height()
|
||||||
daemon.pop_blocks(res.height - 1)
|
daemon.pop_blocks(res.height - 1)
|
||||||
daemon.flush_txpool()
|
daemon.flush_txpool()
|
||||||
@ -69,7 +69,7 @@ class TransferTest():
|
|||||||
]
|
]
|
||||||
self.wallet = [None] * len(seeds)
|
self.wallet = [None] * len(seeds)
|
||||||
for i in range(len(seeds)):
|
for i in range(len(seeds)):
|
||||||
self.wallet[i] = Wallet(idx = i)
|
self.wallet[i] = Wallet(idx = i + 4)
|
||||||
# close the wallet if any, will throw if none is loaded
|
# close the wallet if any, will throw if none is loaded
|
||||||
try: self.wallet[i].close_wallet()
|
try: self.wallet[i].close_wallet()
|
||||||
except: pass
|
except: pass
|
||||||
@ -77,7 +77,7 @@ class TransferTest():
|
|||||||
|
|
||||||
def mine(self):
|
def mine(self):
|
||||||
print("Mining some blocks")
|
print("Mining some blocks")
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
res = daemon.get_info()
|
res = daemon.get_info()
|
||||||
height = res.height
|
height = res.height
|
||||||
@ -89,7 +89,7 @@ class TransferTest():
|
|||||||
assert res.height == height + 80
|
assert res.height == height + 80
|
||||||
|
|
||||||
def transfer(self):
|
def transfer(self):
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
print("Creating transfer to self")
|
print("Creating transfer to self")
|
||||||
|
|
||||||
@ -508,7 +508,7 @@ class TransferTest():
|
|||||||
def check_get_bulk_payments(self):
|
def check_get_bulk_payments(self):
|
||||||
print('Checking get_bulk_payments')
|
print('Checking get_bulk_payments')
|
||||||
|
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
res = daemon.get_info()
|
res = daemon.get_info()
|
||||||
height = res.height
|
height = res.height
|
||||||
|
|
||||||
@ -544,7 +544,7 @@ class TransferTest():
|
|||||||
def check_get_payments(self):
|
def check_get_payments(self):
|
||||||
print('Checking get_payments')
|
print('Checking get_payments')
|
||||||
|
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
res = daemon.get_info()
|
res = daemon.get_info()
|
||||||
height = res.height
|
height = res.height
|
||||||
|
|
||||||
@ -587,7 +587,8 @@ class TransferTest():
|
|||||||
assert len(res.tx_blob_list) == 1
|
assert len(res.tx_blob_list) == 1
|
||||||
txes[i][1] = res.tx_blob_list[0]
|
txes[i][1] = res.tx_blob_list[0]
|
||||||
|
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
restricted_daemon = Daemon(idx = 2, restricted_rpc = True)
|
||||||
res = daemon.send_raw_transaction(txes[0][1])
|
res = daemon.send_raw_transaction(txes[0][1])
|
||||||
assert res.not_relayed == False
|
assert res.not_relayed == False
|
||||||
assert res.low_mixin == False
|
assert res.low_mixin == False
|
||||||
@ -598,6 +599,18 @@ class TransferTest():
|
|||||||
assert res.overspend == False
|
assert res.overspend == False
|
||||||
assert res.fee_too_low == False
|
assert res.fee_too_low == False
|
||||||
|
|
||||||
|
res = restricted_daemon.send_raw_transaction(txes[0][1])
|
||||||
|
assert res.not_relayed == False
|
||||||
|
assert res.low_mixin == False
|
||||||
|
assert res.double_spend == False
|
||||||
|
assert res.invalid_input == False
|
||||||
|
assert res.invalid_output == False
|
||||||
|
assert res.too_big == False
|
||||||
|
assert res.overspend == False
|
||||||
|
assert res.fee_too_low == False
|
||||||
|
|
||||||
|
res = restricted_daemon.get_transactions([txes[0][0]])
|
||||||
|
assert not 'txs' in res or len(res.txs) == 0
|
||||||
res = daemon.get_transactions([txes[0][0]])
|
res = daemon.get_transactions([txes[0][0]])
|
||||||
assert len(res.txs) >= 1
|
assert len(res.txs) >= 1
|
||||||
tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0]
|
tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0]
|
||||||
@ -615,6 +628,19 @@ class TransferTest():
|
|||||||
assert res.fee_too_low == False
|
assert res.fee_too_low == False
|
||||||
assert res.too_few_outputs == False
|
assert res.too_few_outputs == False
|
||||||
|
|
||||||
|
res = restricted_daemon.send_raw_transaction(txes[1][1])
|
||||||
|
assert res.not_relayed == False
|
||||||
|
assert res.low_mixin == False
|
||||||
|
assert res.double_spend == True
|
||||||
|
assert res.invalid_input == False
|
||||||
|
assert res.invalid_output == False
|
||||||
|
assert res.too_big == False
|
||||||
|
assert res.overspend == False
|
||||||
|
assert res.fee_too_low == False
|
||||||
|
assert res.too_few_outputs == False
|
||||||
|
|
||||||
|
res = restricted_daemon.get_transactions([txes[0][0]])
|
||||||
|
assert not 'txs' in res or len(res.txs) == 0
|
||||||
res = daemon.get_transactions([txes[0][0]])
|
res = daemon.get_transactions([txes[0][0]])
|
||||||
assert len(res.txs) >= 1
|
assert len(res.txs) >= 1
|
||||||
tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0]
|
tx = [tx for tx in res.txs if tx.tx_hash == txes[0][0]][0]
|
||||||
@ -623,13 +649,13 @@ class TransferTest():
|
|||||||
|
|
||||||
def sweep_dust(self):
|
def sweep_dust(self):
|
||||||
print("Sweeping dust")
|
print("Sweeping dust")
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
self.wallet[0].refresh()
|
self.wallet[0].refresh()
|
||||||
res = self.wallet[0].sweep_dust()
|
res = self.wallet[0].sweep_dust()
|
||||||
assert not 'tx_hash_list' in res or len(res.tx_hash_list) == 0 # there's just one, but it cannot meet the fee
|
assert not 'tx_hash_list' in res or len(res.tx_hash_list) == 0 # there's just one, but it cannot meet the fee
|
||||||
|
|
||||||
def sweep_single(self):
|
def sweep_single(self):
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
print("Sending single output")
|
print("Sending single output")
|
||||||
|
|
||||||
@ -685,7 +711,7 @@ class TransferTest():
|
|||||||
assert len([t for t in res.transfers if t.key_image == ki]) == 1
|
assert len([t for t in res.transfers if t.key_image == ki]) == 1
|
||||||
|
|
||||||
def check_destinations(self):
|
def check_destinations(self):
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
print("Checking transaction destinations")
|
print("Checking transaction destinations")
|
||||||
|
|
||||||
@ -741,7 +767,7 @@ class TransferTest():
|
|||||||
self.wallet[0].refresh()
|
self.wallet[0].refresh()
|
||||||
|
|
||||||
def check_tx_notes(self):
|
def check_tx_notes(self):
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
print('Testing tx notes')
|
print('Testing tx notes')
|
||||||
res = self.wallet[0].get_transfers()
|
res = self.wallet[0].get_transfers()
|
||||||
@ -758,7 +784,7 @@ class TransferTest():
|
|||||||
assert res.notes == ['out txid', 'in txid']
|
assert res.notes == ['out txid', 'in txid']
|
||||||
|
|
||||||
def check_rescan(self):
|
def check_rescan(self):
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
print('Testing rescan_spent')
|
print('Testing rescan_spent')
|
||||||
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
||||||
@ -798,7 +824,7 @@ class TransferTest():
|
|||||||
assert sorted(old_t_out, key = lambda k: k['txid']) == sorted(new_t_out, key = lambda k: k['txid'])
|
assert sorted(old_t_out, key = lambda k: k['txid']) == sorted(new_t_out, key = lambda k: k['txid'])
|
||||||
|
|
||||||
def check_is_key_image_spent(self):
|
def check_is_key_image_spent(self):
|
||||||
daemon = Daemon()
|
daemon = Daemon(idx = 2)
|
||||||
|
|
||||||
print('Testing is_key_image_spent')
|
print('Testing is_key_image_spent')
|
||||||
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
res = self.wallet[0].incoming_transfers(transfer_type = 'all')
|
||||||
|
Loading…
Reference in New Issue
Block a user