diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index b290f81f3..72492f9ae 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -407,6 +407,17 @@ transaction BlockchainDB::get_pruned_tx(const crypto::hash& h) const return tx; } +std::vector BlockchainDB::has_key_images(const epee::span img) const +{ + std::vector spent(img.size(), true); + for (std::size_t i = 0; i < img.size(); ++i) + { + const crypto::key_image &ki = img[i]; + spent[i] = this->has_key_image(ki); + } + return spent; +} + void BlockchainDB::reset_stats() { num_calls = 0; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 42591dc99..2a82fc29a 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -1510,6 +1510,15 @@ public: */ virtual bool has_key_image(const crypto::key_image& img) const = 0; + /** + * @brief check if key images are stored as spent + * + * @param img the key images to check for + * + * @return true at element `i` if the `img[i]` is present, otherwise false + */ + virtual std::vector has_key_images(const epee::span img) const; + /** * @brief add a txpool transaction * diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index d5422e9c2..3acc8f52f 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -3612,6 +3612,27 @@ bool BlockchainLMDB::has_key_image(const crypto::key_image& img) const return ret; } +std::vector BlockchainLMDB::has_key_images(const epee::span img) const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + std::vector ret(img.size(), true); + + TXN_PREFIX_RDONLY(); + RCURSOR(spent_keys); + + for (std::size_t i = 0; i < img.size(); ++i) + { + crypto::key_image ki = img[i]; + MDB_val k = {sizeof(ki), reinterpret_cast(&ki)}; + ret[i] = (mdb_cursor_get(m_cur_spent_keys, const_cast(&zerokval), &k, MDB_GET_BOTH) == 0); + } + + TXN_POSTFIX_RDONLY(); + return ret; +} + bool BlockchainLMDB::for_all_key_images(std::function f) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index 45b3f3709..1fad9f46f 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -282,6 +282,8 @@ public: bool has_key_image(const crypto::key_image& img) const override; + std::vector has_key_images(const epee::span img) const override; + void add_txpool_tx(const crypto::hash &txid, const cryptonote::blobdata_ref &blob, const txpool_tx_meta_t& meta) override; void update_txpool_tx(const crypto::hash &txid, const txpool_tx_meta_t& meta) override; uint64_t get_txpool_tx_count(relay_category category = relay_category::broadcasted) const override; diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 44a427e95..98ca5722d 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -3206,6 +3206,17 @@ bool Blockchain::have_tx_keyimges_as_spent(const transaction &tx) const } return false; } +//------------------------------------------------------------------ +std::vector Blockchain::have_tx_keyimges_as_spent(const epee::span key_imgs) const +{ + LOG_PRINT_L3("Blockchain::" << __func__); + // WARNING: this function does not take m_blockchain_lock, and thus should only call read only + // m_db functions which do not depend on one another (ie, no getheight + gethash(height-1), as + // well as not accessing class members, even read only (ie, m_invalid_blocks). The caller must + // lock if it is otherwise needed. + return m_db->has_key_images(key_imgs); +} +//------------------------------------------------------------------ bool Blockchain::expand_transaction_2(transaction &tx, const crypto::hash &tx_prefix_hash, const std::vector> &pubkeys) { PERF_TIMER(expand_transaction_2); diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index 78f47b6b5..25ae3e920 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -286,6 +286,15 @@ namespace cryptonote */ bool have_tx_keyimges_as_spent(const transaction &tx) const; + /** + * @brief check if key images are already spent on the blockchain + * + * @param key_imgs the key images to search for + * + * @return true at element `i` iff `key_imgs[i]` is already spent in the blockchain, else false + */ + std::vector have_tx_keyimges_as_spent(const epee::span key_imgs) const; + /** * @brief check if a key image is already spent on the blockchain * diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index a537d8b80..c7d301775 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -933,11 +933,7 @@ namespace cryptonote //----------------------------------------------------------------------------------------------- bool core::are_key_images_spent(const std::vector& key_im, std::vector &spent) const { - spent.clear(); - for(auto& ki: key_im) - { - spent.push_back(m_blockchain_storage.have_tx_keyimg_as_spent(ki)); - } + spent = m_blockchain_storage.have_tx_keyimges_as_spent(epee::to_span(key_im)); return true; } //----------------------------------------------------------------------------------------------- diff --git a/src/cryptonote_core/tx_pool.h b/src/cryptonote_core/tx_pool.h index 88afc3257..8528e63c8 100644 --- a/src/cryptonote_core/tx_pool.h +++ b/src/cryptonote_core/tx_pool.h @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -450,9 +449,6 @@ namespace cryptonote */ void reduce_txpool_weight(size_t weight); -#define CURRENT_MEMPOOL_ARCHIVE_VER 11 -#define CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER 13 - /** * @brief information about a single transaction */ @@ -724,38 +720,3 @@ private: friend struct BlockchainAndPool; }; } - -namespace boost -{ - namespace serialization - { - template - void serialize(archive_t & ar, cryptonote::tx_memory_pool::tx_details& td, const unsigned int version) - { - ar & td.blob_size; - ar & td.fee; - ar & td.tx; - ar & td.max_used_block_height; - ar & td.max_used_block_id; - ar & td.last_failed_height; - ar & td.last_failed_id; - ar & td.receive_time; - ar & td.last_relayed_time; - ar & td.relayed; - if (version < 11) - return; - ar & td.kept_by_block; - if (version < 12) - return; - ar & td.do_not_relay; - if (version < 13) - return; - ar & td.weight; - } - } -} -BOOST_CLASS_VERSION(cryptonote::tx_memory_pool, CURRENT_MEMPOOL_ARCHIVE_VER) -BOOST_CLASS_VERSION(cryptonote::tx_memory_pool::tx_details, CURRENT_MEMPOOL_TX_DETAILS_ARCHIVE_VER) - - - diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index f1964e380..c1b5199f5 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -1269,6 +1269,7 @@ namespace cryptonote CHECK_PAYMENT_MIN1(req, res, req.key_images.size() * COST_PER_KEY_IMAGE, false); + // parse key images from request std::vector key_images; for(const auto& ki_hex_str: req.key_images) { @@ -1286,44 +1287,53 @@ namespace cryptonote crypto::key_image &ki = key_images.emplace_back(); memcpy(&ki, b.data(), sizeof(crypto::key_image)); } + + // check key images in blockchain std::vector spent_status; bool r = m_core.are_key_images_spent(key_images, spent_status); - if(!r) + if (!r || spent_status.size() != key_images.size()) { res.status = "Failed"; return true; } res.spent_status.clear(); + res.spent_status.reserve(spent_status.size()); for (size_t n = 0; n < spent_status.size(); ++n) res.spent_status.push_back(spent_status[n] ? COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_BLOCKCHAIN : COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT); - // check the pool too - std::vector txs; - std::vector ki; - r = m_core.get_pool_transactions_and_spent_keys_info(txs, ki, !request_has_rpc_origin || !restricted); - if(!r) + // filter out known spent key images + std::vector filtered_key_images; + std::vector filtered_key_image_idxs; + filtered_key_images.reserve(key_images.size()); + filtered_key_image_idxs.reserve(key_images.size()); + for (std::size_t i = 0; i < key_images.size(); ++i) + { + if (!spent_status.at(i)) + { + filtered_key_images.push_back(key_images.at(i)); + filtered_key_image_idxs.push_back(i); + } + } + if (filtered_key_images.size() != filtered_key_image_idxs.size()) { res.status = "Failed"; return true; } - for (std::vector::const_iterator i = ki.begin(); i != ki.end(); ++i) + + // check the pool too + spent_status.clear(); + r = m_core.are_key_images_spent_in_pool(filtered_key_images, spent_status); + if (!r || spent_status.size() != filtered_key_images.size()) { - crypto::hash hash; - crypto::key_image spent_key_image; - if (parse_hash256(i->id_hash, hash)) + res.status = "Failed"; + return true; + } + for (std::size_t i = 0; i < spent_status.size(); ++i) + { + if (spent_status.at(i)) { - memcpy(&spent_key_image, &hash, sizeof(hash)); // a bit dodgy, should be other parse functions somewhere - for (size_t n = 0; n < res.spent_status.size(); ++n) - { - if (res.spent_status[n] == COMMAND_RPC_IS_KEY_IMAGE_SPENT::UNSPENT) - { - if (key_images[n] == spent_key_image) - { - res.spent_status[n] = COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL; - break; - } - } - } + const std::size_t res_idx = filtered_key_image_idxs.at(i); + res.spent_status.at(res_idx) = COMMAND_RPC_IS_KEY_IMAGE_SPENT::SPENT_IN_POOL; } }