Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/contract/contracthost.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ 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_
// TODO: Maybe we should apply the same logic to the C++ stack as well somehow
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;
Expand Down
3 changes: 0 additions & 3 deletions src/contract/contracthost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -159,7 +157,6 @@ class ContractHost : public evmc::Host {
int64_t& txGasLimit) :
vm_(vm),
manager_(manager),
eventManager_(eventManager),
storage_(storage),
randomGen_(randomnessSeed),
currentTxContext_(currentTxContext),
Expand Down
1 change: 1 addition & 0 deletions src/contract/contractstack.h
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
146 changes: 0 additions & 146 deletions src/contract/event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,149 +55,3 @@ json Event::serializeForRPC() const {
};
return obj;
}

EventManager::EventManager(const Options& options
) : db_(options.getRootPath() + "/eventsDb/"), options_(options) {
std::vector<DBEntry> 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<Event> EventManager::getEvents(
const uint64_t& fromBlock, const uint64_t& toBlock,
const Address& address, const std::vector<Hash>& topics
) const {
std::vector<Event> 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<Event> EventManager::getEvents(
const Hash& txHash, const uint64_t& blockIndex, const uint64_t& txIndex
) const {
std::vector<Event> 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<Event> EventManager::filterFromMemory(
const uint64_t& fromBlock, const uint64_t& toBlock, const Address& address
) const {
std::vector<Event> 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<Event> EventManager::filterFromDB(
const uint64_t& fromBlock, const uint64_t& toBlock,
const Address& address, const std::vector<Hash>& topics
) const {
// Filter by block range
std::vector<Event> ret;
std::vector<Bytes> 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<Hash>& topics
) const {
if (topics.empty()) return true; // No topic filter applied
const std::vector<Hash>& 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;
}

106 changes: 0 additions & 106 deletions src/contract/event.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
Expand Down Expand Up @@ -172,109 +171,4 @@ struct event_indices : bmi::indexed_by<
bmi::ordered_non_unique<bmi::member<Event, Hash, &Event::txHash_>>
> {};

using EventContainer = bmi::multi_index_container<Event, event_indices>; ///< 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<Event> getEvents(
const uint64_t& fromBlock, const uint64_t& toBlock,
const Address& address = Address(), const std::vector<Hash>& 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<Event> 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<Event> 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<Event> filterFromDB(
const uint64_t& fromBlock, const uint64_t& toBlock,
const Address& address = Address(), const std::vector<Hash>& 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<Hash>& 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
10 changes: 2 additions & 8 deletions src/core/dump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -63,12 +63,6 @@ std::pair<std::vector<DBBatch>, 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;
}
Expand Down
7 changes: 1 addition & 6 deletions src/core/dump.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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<Dumpable*> 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.
Expand All @@ -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(); }
Expand Down
Loading