diff --git a/src/contract/contracthost.cpp b/src/contract/contracthost.cpp index 19d0603b..f9d2b1d1 100644 --- a/src/contract/contracthost.cpp +++ b/src/contract/contracthost.cpp @@ -26,7 +26,7 @@ ContractHost::~ContractHost() { if (!this->mustRevert_) { // When the execution is complete and we need to commit the changes to the storage we must do the following steps: // - Commit all the SafeBase variables - // - Commit all the emitted events to the EventManager + // - Commit all the emitted events to the storage // We only need to commit the C++ stack, EVM operates directly on the storage (only requiring reverts) // There is no permanent temporary storage for the EVM stack like on the SaveBase variables // Instead, we use a journaling system with ContractStack to store the original values of the this->storage_ @@ -34,8 +34,8 @@ ContractHost::~ContractHost() { for (auto& var : this->stack_.getUsedVars()) { var.get().commit(); } - for (auto&& event : this->stack_.getEvents()) { - this->eventManager_.registerEvent(std::move(event)); + for (const auto& event : this->stack_.getEvents()) { + this->storage_.putEvent(event); } for (const auto& contractPair : this->stack_.getContracts()) { const auto& [address, contract] = contractPair; diff --git a/src/contract/contracthost.h b/src/contract/contracthost.h index eb704d07..bda2ba9a 100644 --- a/src/contract/contracthost.h +++ b/src/contract/contracthost.h @@ -67,7 +67,6 @@ class ContractHost : public evmc::Host { }; evmc_vm* vm_; DumpManager& manager_; - EventManager& eventManager_; Storage& storage_; mutable ContractStack stack_; mutable RandomGen randomGen_; // Random generator for the contract. @@ -146,7 +145,6 @@ class ContractHost : public evmc::Host { public: ContractHost(evmc_vm* vm, DumpManager& manager, - EventManager& eventManager, Storage& storage, const Hash& randomnessSeed, const evmc_tx_context& currentTxContext, @@ -159,7 +157,6 @@ class ContractHost : public evmc::Host { int64_t& txGasLimit) : vm_(vm), manager_(manager), - eventManager_(eventManager), storage_(storage), randomGen_(randomnessSeed), currentTxContext_(currentTxContext), diff --git a/src/contract/contractstack.h b/src/contract/contractstack.h index 8c366960..f5acdc45 100644 --- a/src/contract/contractstack.h +++ b/src/contract/contractstack.h @@ -4,6 +4,7 @@ #include <../utils/strings.h> #include <../utils/safehash.h> #include <../contract/event.h> +#include "contract.h" /** * ContractStack is a class/object required to initialize a sequence of contract executions (1 tx == 1 contract stack). diff --git a/src/contract/event.cpp b/src/contract/event.cpp index 11ac7aac..eec54853 100644 --- a/src/contract/event.cpp +++ b/src/contract/event.cpp @@ -55,149 +55,3 @@ json Event::serializeForRPC() const { }; return obj; } - -EventManager::EventManager(const Options& options -) : db_(options.getRootPath() + "/eventsDb/"), options_(options) { - std::vector allEvents = this->db_.getBatch(DBPrefix::events); - for (const DBEntry& event : allEvents) { - Event e(Utils::bytesToString(event.value)); // Create a new Event object by deserializing - this->events_.insert(std::move(e)); // Use insert for MultiIndex container - } -} - -void EventManager::dump() { - DBBatch batchedOperations; - for (const auto& e : this->events_) { - // Build the key (block height + tx index + log index + address), then - // serialize the value to a JSON string and insert into the batch - Bytes key = Utils::makeBytes(bytes::join( - Utils::uint64ToBytes(e.getBlockIndex()), - Utils::uint64ToBytes(e.getTxIndex()), - Utils::uint64ToBytes(e.getLogIndex()), - e.getAddress() - )); - batchedOperations.push_back(key, Utils::stringToBytes(e.serializeToJson()), DBPrefix::events); - } - // Batch save to database and clear the list - this->db_.putBatch(batchedOperations); - this->events_.clear(); -} - -std::vector EventManager::getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { - std::vector ret; - // Check if block range is within limits - if ( - uint64_t heightDiff = std::max(fromBlock, toBlock) - std::min(fromBlock, toBlock); - heightDiff > this->options_.getEventBlockCap() - ) { - throw std::out_of_range( - "Block range too large for event querying! Max allowed is " + - std::to_string(this->options_.getEventBlockCap()) - ); - } - // Fetch from memory, then match topics from memory - for (const Event& e : this->filterFromMemory(fromBlock, toBlock, address)) { - if (this->matchTopics(e, topics) && ret.size() < this->options_.getEventLogCap()) { - ret.push_back(e); - } - } - if (ret.size() >= this->options_.getEventLogCap()) return ret; - // Fetch from database if we have space left - for (Event& e : this->filterFromDB(fromBlock, toBlock, address, topics)) { - if (ret.size() >= this->options_.getEventLogCap()) break; - ret.push_back(std::move(e)); - } - return ret; -} - -std::vector EventManager::getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex -) const { - std::vector ret; - // Fetch from memory - const auto& txHashIndex = this->events_.get<2>(); // txHash is the third index - auto [start, end] = txHashIndex.equal_range(txHash); - for (auto it = start; it != end; it++) { - if (ret.size() >= this->options_.getEventLogCap()) break; - const Event& e = *it; - if (e.getBlockIndex() == blockIndex && e.getTxIndex() == txIndex) ret.push_back(e); - } - // Fetch from DB - for ( - Bytes fetchBytes = Utils::makeBytes(bytes::join( - DBPrefix::events, Utils::uint64ToBytes(blockIndex), Utils::uint64ToBytes(txIndex) - )); - DBEntry entry : this->db_.getBatch(fetchBytes) - ) { - if (ret.size() >= this->options_.getEventLogCap()) break; - Event e(Utils::bytesToString(entry.value)); - ret.push_back(e); - } - return ret; -} - -std::vector EventManager::filterFromMemory( - const uint64_t& fromBlock, const uint64_t& toBlock, const Address& address -) const { - std::vector ret; - if (address != Address()) { - auto& addressIndex = this->events_.get<1>(); - for ( - auto it = addressIndex.lower_bound(address); - it != addressIndex.end() && it->getBlockIndex() <= toBlock; - it++ - ) { if (it->getBlockIndex() >= fromBlock) ret.push_back(*it); } - } else { - const auto& blockIndex = this->events_.get<0>(); - for (const Event& e : blockIndex) { - uint64_t idx = e.getBlockIndex(); - if (idx >= fromBlock && idx <= toBlock) ret.push_back(e); - } - } - return ret; -} - -std::vector EventManager::filterFromDB( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { - // Filter by block range - std::vector ret; - std::vector dbKeys; - Bytes startBytes; - Bytes endBytes; - Utils::appendBytes(startBytes, Utils::uint64ToBytes(fromBlock)); - Utils::appendBytes(endBytes, Utils::uint64ToBytes(toBlock)); - - // Get the keys first, based on block height, then filter by address if there is one - for (Bytes key : this->db_.getKeys(DBPrefix::events, startBytes, endBytes)) { - uint64_t nHeight = Utils::bytesToUint64(Utils::create_view_span(key, 0, 8)); - Address addr(Utils::create_view_span(key, 24, 20)); - if ( - (fromBlock <= nHeight && nHeight <= toBlock) && - (address == Address() || address == addr) - ) dbKeys.push_back(key); - } - - // Get the key values - for (DBEntry item : this->db_.getBatch(DBPrefix::events, dbKeys)) { - if (ret.size() >= this->options_.getEventLogCap()) break; - Event e(Utils::bytesToString(item.value)); - if (this->matchTopics(e, topics)) ret.push_back(e); - } - return ret; -} - -bool EventManager::matchTopics( - const Event& event, const std::vector& topics -) const { - if (topics.empty()) return true; // No topic filter applied - const std::vector& eventTopics = event.getTopics(); - if (eventTopics.size() < topics.size()) return false; - for (size_t i = 0; i < topics.size(); i++) if (topics.at(i) != eventTopics[i]) return false; - return true; -} - diff --git a/src/contract/event.h b/src/contract/event.h index 95716a1b..b5b00883 100644 --- a/src/contract/event.h +++ b/src/contract/event.h @@ -22,7 +22,6 @@ See the LICENSE.txt file in the project root for more information. #include "../utils/utils.h" #include "abi.h" -#include "contract.h" #include #include @@ -172,109 +171,4 @@ struct event_indices : bmi::indexed_by< bmi::ordered_non_unique> > {}; -using EventContainer = bmi::multi_index_container; ///< Alias for the event multi-index container. - -/** - * Class that holds all events emitted by contracts in the blockchain. - * Responsible for registering, managing and saving/loading events to/from the database. - */ -class EventManager { - private: - // TODO: keep up to 1000 (maybe 10000? 100000? 1M seems too much) events in memory, dump older ones to DB (this includes checking save/load - maybe this should be a deque?) - EventContainer events_; ///< List of all emitted events in memory. Older ones FIRST, newer ones LAST. - // Mutable is needed because we used to dump stuff through the construction - // now we do through the dump() method, so we need to mark it as mutable. - // but dump() should be const, so we need to mark it as mutable. - mutable DB db_; ///< EventManager Database. - const Options& options_; ///< Reference to the Options singleton. - - public: - /** - * Constructor; Automatically loads events from the database. - * @param db The database to use. - * @param options The Options singleton to use (for event caps). - */ - EventManager(const Options& options); - - ~EventManager() = default; ///< Destructor. - - // TODO: maybe a periodicSaveToDB() just like on Storage? - - /** - * Get all the events emitted under the given inputs. - * Used by "eth_getLogs", from where parameters are defined on an HTTP request - * (directly from the http/jsonrpc submodules, through handle_request() on httpparser). - * @param fromBlock The initial block height to look for. - * @param toBlock The final block height to look for. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of matching events, limited by the block and/or log caps set above. - * @throw std::out_of_range if specified block range exceeds the limit set in Options. - */ - std::vector getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; - - /** - * Overload of getEvents() used by "eth_getTransactionReceipts", where - * parameters are filtered differently (by exact tx, not a range). - * @param txHash The hash of the transaction to look for events. - * @param blockIndex The height of the block to look for events. - * @param txIndex The index of the transaction to look for events. - * @return A list of matching events, limited by the block and/or log caps set above. - */ - std::vector getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex - ) const; - - /** - * Filter events in memory. Used by getEvents(). - * @param fromBlock The starting block range to query. - * @param toBlock Tne ending block range to query. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @return A list of found events. - */ - std::vector filterFromMemory( - const uint64_t& fromBlock, const uint64_t& toBlock, const Address& address = Address() - ) const; - - /** - * Filter events in the database. Used by getEvents(). - * @param fromBlock The starting block range to query. - * @param toBlock Tne ending block range to query. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of found events. - */ - std::vector filterFromDB( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; - - /** - * Check if all topics of an event match the desired topics from a query. - * TOPIC ORDER AND QUANTITY MATTERS. e.g.: - * - event is {"a", "b", "c", "d"}, filter is {"a", "b", "c"} = MATCH - * - event is {"a", "b", "c"}, filter is {"a", "c", "b"} = NO MATCH - * - event is {"a", "b"}, filter is {"a", "b", "c"} = NO MATCH - * @param event The event to match. - * @param topics The queried topics to match for. Defaults to empty (no filter). - * @return `true` if all topics match, `false` otherwise. - */ - bool matchTopics(const Event& event, const std::vector& topics = {}) const; - - /** - * Register the event. - */ - void registerEvent(Event&& event) noexcept { this->events_.insert(std::move(event)); } - - /** - * Dump function. - * Actually triggers the saving of events to the database. - * It is NOT const because it clear the events_ list after dumping. - */ - void dump(); -}; - #endif // EVENT_H diff --git a/src/core/dump.cpp b/src/core/dump.cpp index aed5a159..eb3ae2e1 100644 --- a/src/core/dump.cpp +++ b/src/core/dump.cpp @@ -9,8 +9,8 @@ See the LICENSE.txt file in the project root for more information. #include "../contract/event.h" DumpManager::DumpManager( - const Storage& storage, const Options& options, EventManager& eventManager, std::shared_mutex& stateMutex -) : options_(options), storage_(storage), stateMutex_(stateMutex), eventManager_(eventManager) {} + const Storage& storage, const Options& options, std::shared_mutex& stateMutex +) : options_(options), storage_(storage), stateMutex_(stateMutex) {} void DumpManager::pushBack(Dumpable* dumpable) { // Check if latest Dumpable* is the same as the one we trying to append @@ -63,12 +63,6 @@ std::pair, uint64_t> DumpManager::dumpState() const { for (auto i = 0; i < nThreads; ++i) for (auto j = 0; j < outputs[i].size(); ++j) batches.emplace_back(outputs[i][j]); - - // Also dump the events - // EventManager has its own database. - // We just need to make sure that its data is dumped - // At the same block height as the state data - this->eventManager_.dump(); } return ret; } diff --git a/src/core/dump.h b/src/core/dump.h index 7ca8b774..e3a694ea 100644 --- a/src/core/dump.h +++ b/src/core/dump.h @@ -18,9 +18,6 @@ See the LICENSE.txt file in the project root for more information. #include "storage.h" #include "../utils/db.h" -// Forward declaration -class EventManager; - /// Abstraction of a dumpable object (an object that can be dumped to the database). class Dumpable { public: @@ -38,7 +35,6 @@ class DumpManager : public Log::LogicalLocationProvider { const Storage& storage_; ///< Reference to the storage object std::shared_mutex& stateMutex_; ///< Mutex for managing read/write access to the state object. std::vector dumpables_; /// List of Dumpable objects. - EventManager& eventManager_; /// Reference to the EventManager object. /** * Auxiliary function used by async calls that processes a little slice of dumps in a separate thread. @@ -53,10 +49,9 @@ class DumpManager : public Log::LogicalLocationProvider { * Constructor. * @param storage Reference to the Storage object. * @param options Reference to the Options singleton. - * @param eventManager Reference to the EventManager object. * @param stateMutex Reference to the state mutex. */ - DumpManager(const Storage& storage, const Options& options, EventManager& eventManager, std::shared_mutex& stateMutex); + DumpManager(const Storage& storage, const Options& options, std::shared_mutex& stateMutex); /// Log instance from Storage. std::string getLogicalLocation() const override { return storage_.getLogicalLocation(); } diff --git a/src/core/state.cpp b/src/core/state.cpp index 14de3d4c..0b5a07e5 100644 --- a/src/core/state.cpp +++ b/src/core/state.cpp @@ -18,8 +18,7 @@ State::State( ) : vm_(evmc_create_evmone()), options_(options), storage_(storage), - eventManager_(options_), - dumpManager_(storage_, options_, this->eventManager_, this->stateMutex_), + dumpManager_(storage_, options_, this->stateMutex_), dumpWorker_(options_, storage_, dumpManager_), p2pManager_(p2pManager), rdpos_(db, dumpManager_, storage, p2pManager, options) @@ -224,7 +223,6 @@ void State::processTransaction( ContractHost host( this->vm_, this->dumpManager_, - this->eventManager_, this->storage_, randomSeed, txContext, @@ -463,7 +461,6 @@ Bytes State::ethCall(const evmc_message& callInfo) { return ContractHost( this->vm_, this->dumpManager_, - this->eventManager_, this->storage_, randomSeed, txContext, @@ -495,7 +492,6 @@ int64_t State::estimateGas(const evmc_message& callInfo) { ContractHost( this->vm_, this->dumpManager_, - this->eventManager_, this->storage_, randomSeed, evmc_tx_context(), @@ -532,21 +528,6 @@ std::vector
State::getEvmContracts() const { return contracts; } -std::vector State::getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address, const std::vector& topics -) const { - std::shared_lock lock(this->stateMutex_); - return this->eventManager_.getEvents(fromBlock, toBlock, address, topics); -} - -std::vector State::getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex -) const { - std::shared_lock lock(this->stateMutex_); - return this->eventManager_.getEvents(txHash, blockIndex, txIndex); -} - Bytes State::getContractCode(const Address &addr) const { std::shared_lock lock(this->stateMutex_); auto it = this->accounts_.find(addr); diff --git a/src/core/state.h b/src/core/state.h index 855205b1..0a1f95c6 100644 --- a/src/core/state.h +++ b/src/core/state.h @@ -34,7 +34,6 @@ class State : public Dumpable, public Log::LogicalLocationProvider { evmc_vm* vm_; ///< Pointer to the EVMC VM. const Options& options_; ///< Reference to the options singleton. Storage& storage_; ///< Reference to the blockchain's storage. - EventManager eventManager_; ///< Event manager object. Responsible for storing events emitted in contract calls. DumpManager dumpManager_; ///< The Dump Worker object DumpWorker dumpWorker_; ///< Dump Manager object P2P::ManagerNormal& p2pManager_; ///< Reference to the P2P connection manager. @@ -257,34 +256,6 @@ class State : public Dumpable, public Log::LogicalLocationProvider { /// Get a list of Addresss which are EVM contracts. std::vector
getEvmContracts() const; - /** - * Get all the events emitted under the given inputs. - * Parameters are defined when calling "eth_getLogs" on an HTTP request - * (directly from the http/jsonrpc submodules, through handle_request() on httpparser). - * They're supposed to be all "optional" at that point, but here they're - * all required, even if all of them turn out to be empty. - * @param fromBlock The initial block height to look for. - * @param toBlock The final block height to look for. - * @param address The address to look for. Defaults to empty (look for all available addresses). - * @param topics The topics to filter by. Defaults to empty (look for all available topics). - * @return A list of matching events. - */ - std::vector getEvents( - const uint64_t& fromBlock, const uint64_t& toBlock, - const Address& address = Address(), const std::vector& topics = {} - ) const; - - /** - * Overload of getEvents() for transaction receipts. - * @param txHash The hash of the transaction to look for events. - * @param blockIndex The height of the block to look for events. - * @param txIndex The index of the transaction to look for events. - * @return A list of matching events. - */ - std::vector getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex - ) const; - DBBatch dump() const final; ///< State dumping function. auto getPendingTxs() const { diff --git a/src/core/storage.cpp b/src/core/storage.cpp index 88564d59..e36a1577 100644 --- a/src/core/storage.cpp +++ b/src/core/storage.cpp @@ -9,6 +9,14 @@ See the LICENSE.txt file in the project root for more information. #include "storage.h" +static bool topicsMatch(const Event& event, const std::vector& topics) { + if (topics.empty()) return true; // No topic filter applied + const std::vector& eventTopics = event.getTopics(); + if (eventTopics.size() < topics.size()) return false; + for (size_t i = 0; i < topics.size(); i++) if (topics.at(i) != eventTopics[i]) return false; + return true; +} + static void storeBlock(DB& db, const FinalizedBlock& block, bool indexingEnabled) { DBBatch batch; batch.push_back(block.getHash(), block.serializeBlock(), DBPrefix::blocks); @@ -29,7 +37,9 @@ static void storeBlock(DB& db, const FinalizedBlock& block, bool indexingEnabled } Storage::Storage(std::string instanceIdStr, const Options& options) - : db_(options.getRootPath() + "/blocksDb/"), options_(options), instanceIdStr_(std::move(instanceIdStr)) + : blocksDb_(options.getRootPath() + "/blocksDb/"), + eventsDb_(options.getRootPath() + "/eventsDb/"), + options_(options), instanceIdStr_(std::move(instanceIdStr)) { // Initialize the blockchain if latest block doesn't exist. LOGINFO("Loading blockchain from DB"); @@ -37,10 +47,10 @@ Storage::Storage(std::string instanceIdStr, const Options& options) // Get the latest block from the database LOGINFO("Loading latest block"); - const Bytes latestBlockHash = db_.getLastByPrefix(DBPrefix::heightToBlock); + const Bytes latestBlockHash = blocksDb_.getLastByPrefix(DBPrefix::heightToBlock); if (latestBlockHash.empty()) throw DynamicException("Latest block hash not found in DB"); - const Bytes latestBlockBytes = db_.get(latestBlockHash, DBPrefix::blocks); + const Bytes latestBlockBytes = blocksDb_.get(latestBlockHash, DBPrefix::blocks); if (latestBlockBytes.empty()) throw DynamicException("Latest block bytes not found in DB"); latest_ = std::make_shared(FinalizedBlock::fromBytes(latestBlockBytes, this->options_.getChainID())); @@ -52,19 +62,19 @@ void Storage::initializeBlockchain() { const auto& genesis = options_.getGenesisBlock(); if ( - const Bytes latestBlockHash = db_.getLastByPrefix(DBPrefix::heightToBlock); + const Bytes latestBlockHash = blocksDb_.getLastByPrefix(DBPrefix::heightToBlock); latestBlockHash.empty() ) { if (genesis.getNHeight() != 0) throw DynamicException("Genesis block height is not 0"); - storeBlock(db_, genesis, options_.getIndexingMode() != IndexingMode::DISABLED); + storeBlock(blocksDb_, genesis, options_.getIndexingMode() != IndexingMode::DISABLED); LOGINFO(std::string("Created genesis block: ") + Hex::fromBytes(genesis.getHash()).get()); } // Sanity check for genesis block. (check if genesis in DB matches genesis in Options) - const Hash genesisInDBHash(db_.get(Utils::uint64ToBytes(0), DBPrefix::heightToBlock)); + const Hash genesisInDBHash(blocksDb_.get(Utils::uint64ToBytes(0), DBPrefix::heightToBlock)); FinalizedBlock genesisInDB = FinalizedBlock::fromBytes( - db_.get(genesisInDBHash, DBPrefix::blocks), options_.getChainID() + blocksDb_.get(genesisInDBHash, DBPrefix::blocks), options_.getChainID() ); if (genesis != genesisInDB) { @@ -93,39 +103,39 @@ void Storage::pushBlock(FinalizedBlock block) { } auto newBlock = std::make_shared(std::move(block)); latest_.store(newBlock); - storeBlock(db_, *newBlock, options_.getIndexingMode() != IndexingMode::DISABLED); + storeBlock(blocksDb_, *newBlock, options_.getIndexingMode() != IndexingMode::DISABLED); } -bool Storage::blockExists(const Hash& hash) const { return db_.has(hash, DBPrefix::blocks); } +bool Storage::blockExists(const Hash& hash) const { return blocksDb_.has(hash, DBPrefix::blocks); } -bool Storage::blockExists(uint64_t height) const { return db_.has(Utils::uint64ToBytes(height), DBPrefix::heightToBlock); } +bool Storage::blockExists(uint64_t height) const { return blocksDb_.has(Utils::uint64ToBytes(height), DBPrefix::heightToBlock); } -bool Storage::txExists(const Hash& tx) const { return db_.has(tx, DBPrefix::txToBlock); } +bool Storage::txExists(const Hash& tx) const { return blocksDb_.has(tx, DBPrefix::txToBlock); } std::shared_ptr Storage::getBlock(const Hash& hash) const { - Bytes blockBytes = db_.get(hash, DBPrefix::blocks); + Bytes blockBytes = blocksDb_.get(hash, DBPrefix::blocks); if (blockBytes.empty()) return nullptr; return std::make_shared(FinalizedBlock::fromBytes(blockBytes, this->options_.getChainID())); } std::shared_ptr Storage::getBlock(uint64_t height) const { - Bytes blockHash = db_.get(Utils::uint64ToBytes(height), DBPrefix::heightToBlock); + Bytes blockHash = blocksDb_.get(Utils::uint64ToBytes(height), DBPrefix::heightToBlock); if (blockHash.empty()) return nullptr; - Bytes blockBytes = db_.get(blockHash, DBPrefix::blocks); + Bytes blockBytes = blocksDb_.get(blockHash, DBPrefix::blocks); return std::make_shared(FinalizedBlock::fromBytes(blockBytes, this->options_.getChainID())); } std::tuple< const std::shared_ptr, const Hash, const uint64_t, const uint64_t > Storage::getTx(const Hash& tx) const { - const Bytes txData = db_.get(tx, DBPrefix::txToBlock); + const Bytes txData = blocksDb_.get(tx, DBPrefix::txToBlock); if (txData.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); const bytes::View txDataView = txData; const Hash blockHash(txDataView.subspan(0, 32)); const uint64_t blockIndex = Utils::bytesToUint32(txDataView.subspan(32, 4)); const uint64_t blockHeight = Utils::bytesToUint64(txDataView.subspan(36, 8)); - const Bytes blockData = db_.get(blockHash, DBPrefix::blocks); + const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); return std::make_tuple( std::make_shared(getTxFromBlockWithIndex(blockData, blockIndex)), @@ -136,7 +146,7 @@ std::tuple< std::tuple< const std::shared_ptr, const Hash, const uint64_t, const uint64_t > Storage::getTxByBlockHashAndIndex(const Hash& blockHash, const uint64_t blockIndex) const { - const Bytes blockData = db_.get(blockHash, DBPrefix::blocks); + const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); if (blockData.empty()) std::make_tuple(nullptr, Hash(), 0u, 0u); const uint64_t blockHeight = Utils::bytesToUint64(bytes::View(blockData).subspan(201, 8)); @@ -149,10 +159,10 @@ std::tuple< std::tuple< const std::shared_ptr, const Hash, const uint64_t, const uint64_t > Storage::getTxByBlockNumberAndIndex(uint64_t blockHeight, uint64_t blockIndex) const { - const Bytes blockHash = db_.get(Utils::uint64ToBytes(blockHeight), DBPrefix::heightToBlock); + const Bytes blockHash = blocksDb_.get(Utils::uint64ToBytes(blockHeight), DBPrefix::heightToBlock); if (blockHash.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); - const Bytes blockData = db_.get(blockHash, DBPrefix::blocks); + const Bytes blockData = blocksDb_.get(blockHash, DBPrefix::blocks); if (blockData.empty()) return std::make_tuple(nullptr, Hash(), 0u, 0u); return std::make_tuple( @@ -172,11 +182,11 @@ void Storage::putCallTrace(const Hash& txHash, const trace::Call& callTrace) { Bytes serial; zpp::bits::out out(serial); out(callTrace).or_throw(); - db_.put(txHash, serial, DBPrefix::txToCallTrace); + blocksDb_.put(txHash, serial, DBPrefix::txToCallTrace); } std::optional Storage::getCallTrace(const Hash& txHash) const { - Bytes serial = db_.get(txHash, DBPrefix::txToCallTrace); + Bytes serial = blocksDb_.get(txHash, DBPrefix::txToCallTrace); if (serial.empty()) return std::nullopt; trace::Call callTrace; @@ -190,11 +200,11 @@ void Storage::putTxAdditionalData(const TxAdditionalData& txData) { Bytes serialized; zpp::bits::out out(serialized); out(txData).or_throw(); - db_.put(txData.hash, serialized, DBPrefix::txToAdditionalData); + blocksDb_.put(txData.hash, serialized, DBPrefix::txToAdditionalData); } std::optional Storage::getTxAdditionalData(const Hash& txHash) const { - Bytes serialized = db_.get(txHash, DBPrefix::txToAdditionalData); + Bytes serialized = blocksDb_.get(txHash, DBPrefix::txToAdditionalData); if (serialized.empty()) return std::nullopt; TxAdditionalData txData; @@ -203,3 +213,79 @@ std::optional Storage::getTxAdditionalData(const Hash& txHash) return txData; } + +void Storage::putEvent(const Event& event) { + const Bytes key = Utils::makeBytes(bytes::join( + Utils::uint64ToBytes(event.getBlockIndex()), + Utils::uint64ToBytes(event.getTxIndex()), + Utils::uint64ToBytes(event.getLogIndex()), + event.getAddress() + )); + + eventsDb_.put(key, Utils::stringToBytes(event.serializeToJson()), DBPrefix::events); +} + +std::vector Storage::getEvents(uint64_t fromBlock, uint64_t toBlock, const Address& address, const std::vector& topics) const { + if (toBlock < fromBlock) { + std::swap(fromBlock, toBlock); + } + + if (uint64_t count = toBlock - fromBlock + 1; count > options_.getEventBlockCap()) { + throw std::out_of_range( + "Block range too large for event querying! Max allowed is " + + std::to_string(this->options_.getEventBlockCap()) + ); + } + + std::vector events; + std::vector keys; + + const Bytes startBytes = Utils::makeBytes(Utils::uint64ToBytes(fromBlock)); + const Bytes endBytes = Utils::makeBytes(Utils::uint64ToBytes(toBlock)); + + for (Bytes key : eventsDb_.getKeys(DBPrefix::events, startBytes, endBytes)) { + uint64_t nHeight = Utils::bytesToUint64(Utils::create_view_span(key, 0, 8)); + Address currentAddress(Utils::create_view_span(key, 24, 20)); + + if (fromBlock > nHeight || toBlock < nHeight) { + continue; + } + + if (address == currentAddress || address == Address()) { + keys.push_back(std::move(key)); + } + } + + for (DBEntry item : eventsDb_.getBatch(DBPrefix::events, keys)) { + if (events.size() >= options_.getEventLogCap()) { + break; + } + + Event event(Utils::bytesToString(item.value)); + + if (topicsMatch(event, topics)) { + events.push_back(std::move(event)); + } + } + + return events; +} + +std::vector Storage::getEvents(uint64_t blockIndex, uint64_t txIndex) const { + std::vector events; + + for ( + Bytes fetchBytes = Utils::makeBytes(bytes::join( + DBPrefix::events, Utils::uint64ToBytes(blockIndex), Utils::uint64ToBytes(txIndex) + )); + DBEntry entry : eventsDb_.getBatch(fetchBytes) + ) { + if (events.size() >= options_.getEventLogCap()) { + break; + } + + events.emplace_back(Utils::bytesToString(entry.value)); + } + + return events; +} diff --git a/src/core/storage.h b/src/core/storage.h index 9fa46ced..fdd7e621 100644 --- a/src/core/storage.h +++ b/src/core/storage.h @@ -16,24 +16,22 @@ See the LICENSE.txt file in the project root for more information. #include "../utils/safehash.h" #include "../utils/utils.h" #include "../utils/options.h" - #include "../contract/calltracer.h" - +#include "../contract/event.h" #include "../libs/zpp_bits.h" - #include "../bytes/join.h" /** * Abstraction of the blockchain history. * Used to store blocks in memory and on disk, and helps the State process * new blocks, transactions and RPC queries. - * TODO: Replace std::unordered_map with boost::unordered_flat_map AFTER we finish Storage refactor (Currently being done by Leonardo) */ class Storage : public Log::LogicalLocationProvider { // TODO: possibly replace `std::shared_ptr` with a better solution. private: std::atomic> latest_; - DB db_; ///< Database object that contains all the blockchain blocks + DB blocksDb_; ///< Database object that contains all the blockchain blocks + DB eventsDb_; ///< DB exclusive to events (should be removed in future) const Options& options_; ///< Reference to the options singleton. const std::string instanceIdStr_; ///< Identifier for logging @@ -129,16 +127,59 @@ class Storage : public Log::LogicalLocationProvider { /// Get the number of blocks currently in the chain (nHeight of latest block + 1). uint64_t currentChainSize() const; - // TODO: document these later - + /** + * Stores additional transaction data + * @param txData The additional transaction data + */ void putTxAdditionalData(const TxAdditionalData& txData); + /** + * Retrieves the stored additional transaction data + * @param txHash The target transaction hash + * @return The transaction data if existent, or an empty optional otherwise + */ std::optional getTxAdditionalData(const Hash& txHash) const; + /** + * Stores a transaction call trace + * @param txHash The transaction hash + * @param callTrace The call trace of the transaction + */ void putCallTrace(const Hash& txHash, const trace::Call& callTrace); + /** + * Retrieves the stored call trace of the target transaction + * @param txHash The target transaction hash + * @return The transation call trace if existent, or an empty optional otherwise + */ std::optional getCallTrace(const Hash& txHash) const; + /** + * Stores an event + * @param event The event to be stored + */ + void putEvent(const Event& event); + + /** + * Retrieves all events from a given range of block numbers + * @param fromBlock The initial block number (included) + * @param toBlock The last block number (included) + * @return The requested events if existent, or an empty vector otherwise + */ + std::vector getEvents(uint64_t fromBlock, uint64_t toBlock, const Address& address, const std::vector& topics) const; + + /** + * Retrieves all events from given block index and transaction index + * @param blockIndex The number of the block that contains the transaction + * @param txIndex The transaction index within the block + * @return The requested events if existent, or an empty vector otherwise + */ + std::vector getEvents(uint64_t blockIndex, uint64_t txIndex) const; + + /** + * Returns the indexing mode of the storage + * @returns The indexing mode of the storage + */ inline IndexingMode getIndexingMode() const { return options_.getIndexingMode(); } }; diff --git a/src/net/http/jsonrpc/call.cpp b/src/net/http/jsonrpc/call.cpp index d15f7719..ddb13502 100644 --- a/src/net/http/jsonrpc/call.cpp +++ b/src/net/http/jsonrpc/call.cpp @@ -79,7 +79,7 @@ json call(const json& request, State& state, const Storage& storage, else if (method == "eth_feeHistory") result = jsonrpc::eth_feeHistory(request, storage); else if (method == "eth_getLogs") - result = jsonrpc::eth_getLogs(request, storage, state); + result = jsonrpc::eth_getLogs(request, storage); else if (method == "eth_getBalance") result = jsonrpc::eth_getBalance(request, storage, state); else if (method == "eth_getTransactionCount") @@ -95,7 +95,7 @@ json call(const json& request, State& state, const Storage& storage, else if (method == "eth_getTransactionByBlockNumberAndIndex") result = jsonrpc::eth_getTransactionByBlockNumberAndIndex(request, storage); else if (method == "eth_getTransactionReceipt") - result = jsonrpc::eth_getTransactionReceipt(request, storage, state); + result = jsonrpc::eth_getTransactionReceipt(request, storage); else if (method == "eth_getUncleByBlockHashAndIndex") result = jsonrpc::eth_getUncleByBlockHashAndIndex(); else if (method == "txpool_content") diff --git a/src/net/http/jsonrpc/methods.cpp b/src/net/http/jsonrpc/methods.cpp index 0d4672ce..ab1876dc 100644 --- a/src/net/http/jsonrpc/methods.cpp +++ b/src/net/http/jsonrpc/methods.cpp @@ -274,7 +274,7 @@ json eth_feeHistory(const json& request, const Storage& storage) { return ret; } -json eth_getLogs(const json& request, const Storage& storage, const State& state) { +json eth_getLogs(const json& request, const Storage& storage) { const auto [logsObj] = parseAllParams(request); const auto getBlockByHash = [&storage] (const Hash& hash) { return getBlockNumber(storage, hash); }; @@ -298,7 +298,7 @@ json eth_getLogs(const json& request, const Storage& storage, const State& state json result = json::array(); - for (const auto& event : state.getEvents(fromBlock, toBlock, address.value_or(Address{}), topics)) + for (const auto& event : storage.getEvents(fromBlock, toBlock, address.value_or(Address{}), topics)) result.push_back(event.serializeForRPC()); return result; @@ -457,7 +457,7 @@ json eth_getTransactionByBlockNumberAndIndex(const json& request, const Storage& return json::value_t::null; } -json eth_getTransactionReceipt(const json& request, const Storage& storage, const State& state) { +json eth_getTransactionReceipt(const json& request, const Storage& storage) { requiresIndexing(storage, "eth_getTransactionReceipt"); const auto [txHash] = parseAllParams(request); @@ -485,7 +485,7 @@ json eth_getTransactionReceipt(const json& request, const Storage& storage, cons ret["logsBloom"] = Hash().hex(true); ret["type"] = "0x2"; ret["status"] = txAddData.succeeded ? "0x1" : "0x0"; - for (const Event& e : state.getEvents(txHash, blockHeight, txIndex)) { + for (const Event& e : storage.getEvents(blockHeight, txIndex)) { ret["logs"].push_back(e.serializeForRPC()); } return ret; @@ -564,7 +564,7 @@ json debug_traceBlockByNumber(const json& request, const Storage& storage) { } json debug_traceTransaction(const json& request, const Storage& storage) { - requiresDebugIndexing(storage, "debug_traceBlockByNumber"); + requiresDebugIndexing(storage, "debug_traceTransaction"); json res; auto [txHash, traceJson] = parseAllParams(request); diff --git a/src/net/http/jsonrpc/methods.h b/src/net/http/jsonrpc/methods.h index 57171426..3d5f0587 100644 --- a/src/net/http/jsonrpc/methods.h +++ b/src/net/http/jsonrpc/methods.h @@ -100,7 +100,7 @@ json eth_gasPrice(const json& request); json eth_feeHistory(const json& request, const Storage& storage); -json eth_getLogs(const json& request, const Storage& storage, const State& state); +json eth_getLogs(const json& request, const Storage& storage); json eth_getBalance(const json& request, const Storage& storage, const State& state); @@ -116,7 +116,7 @@ json eth_getTransactionByBlockHashAndIndex(const json& request, const Storage& s json eth_getTransactionByBlockNumberAndIndex(const json& request, const Storage& storage); -json eth_getTransactionReceipt(const json& request, const Storage& storage, const State& state); +json eth_getTransactionReceipt(const json& request, const Storage& storage); json eth_getUncleByBlockHashAndIndex(); diff --git a/tests/sdktestsuite.hpp b/tests/sdktestsuite.hpp index 5711040d..9ce7debe 100644 --- a/tests/sdktestsuite.hpp +++ b/tests/sdktestsuite.hpp @@ -374,8 +374,8 @@ class SDKTestSuite { */ const std::vector getEvents(const Hash& tx) const { auto txBlock = this->storage_.getTx(tx); - return this->state_.getEvents( - std::get<0>(txBlock)->hash(), std::get<3>(txBlock), std::get<2>(txBlock) + return this->storage_.getEvents( + std::get<3>(txBlock), std::get<2>(txBlock) ); } @@ -852,7 +852,7 @@ class SDKTestSuite { std::vector getEvents( const uint64_t& fromBlock, const uint64_t& toBlock, const Address& address, const std::vector& topics - ) { return this->state_.getEvents(fromBlock, toBlock, address, topics); } + ) { return this->storage_.getEvents(fromBlock, toBlock, address, topics); } /** * Overload of getEvents() used by "eth_getTransactionReceipts", where @@ -862,9 +862,8 @@ class SDKTestSuite { * @param txIndex The index of the transaction to look for events. * @return A list of matching events, limited by the block and/or log caps set above. */ - std::vector getEvents( - const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex - ) { return this->state_.getEvents(txHash, blockIndex, txIndex); } + std::vector getEvents(const uint64_t& blockIndex, const uint64_t& txIndex + ) { return this->storage_.getEvents(blockIndex, txIndex); } /** * Get all events emitted by a given confirmed transaction. @@ -872,7 +871,7 @@ class SDKTestSuite { */ std::vector getEvents(const Hash& txHash) { auto tx = this->storage_.getTx(txHash); - return this->state_.getEvents(std::get<0>(tx)->hash(), std::get<3>(tx), std::get<2>(tx)); + return this->storage_.getEvents(std::get<3>(tx), std::get<2>(tx)); } /** diff --git a/tests/statetest.hpp b/tests/statetest.hpp index 24581000..71bd488f 100644 --- a/tests/statetest.hpp +++ b/tests/statetest.hpp @@ -28,7 +28,6 @@ class StateTest : public State { ContractHost host( this->vm_, this->dumpManager_, - this->eventManager_, this->storage_, randomness, txContext,