How the database works
How the database is structured on SparqNet
Since the Subnet, up until the moment of writing this document, is running inside a sandbox and interfacing with AvalancheGo's VM, it's not possible to use our own database. We have to use the database provided by AvalancheGo via gRPC.
The database itself is a simple key/value database similar to Google's LevelDB (in fact we're using it internally), but modified so that it's possible to batch read and write using a logic structure based on prefixes.
The database has the following prefixes:
0001 -- Key: Block Hash | Value: Block
0002 -- Key: Block nHeight | Value: Block Hash
0003 -- Key: Tx Hash | Value: Transactions
0004 -- Key: Address | Value: Native Balance + nNonce
0005 -- ERC20 Tokens/State
0006 -- ERC721 Tokens/State
0007 -- Key: Tx Hash | Value: Block Hash
Those prefixes are concatenated to the start of the key, so an entry that would have, for example, a key named
"abc"and a value of
"123", if inserted to the
"0003"prefix, would be like this inside the database:
In the database implementation, there's a DBPrefix to reference each prefix in a simpler way:
0001 = DBPrefix::blocks
0002 = DBPrefix::blockHeightMaps
0003 = DBPrefix::transactions
0004 = DBPrefix::nativeAccounts
0005 = DBPrefix::erc20Tokens
0006 = DBPrefix::erc721Tokens
0007 = DBPrefix::TxToBlocks
0008 = DBPrefix::validators
There are also three structs, namely:
DBServer- struct that contains the host and version of the database that will be connected to.
DBEntry- struct that contains an entry to be inserted or read by the database and has only two members: key and value, which are both strings.
WriteBatchRequest- struct that contains multiple
DBEntrysto be inserted and/or deleted all at once.
The class that abstracts the database itself and its operations is called DBService. The constructor requires a file system path, it opens the database (if it exists) or creates it on the spot (if it doesn't exist) at that moment.
To close the database, call the
DBService::closefunction. Aside from using the structures above, it also uses an internal pointer to a
leveldb::DBobject and a group of member functions that abstract the main CRUD operations for LevelDB:
DBService::has- checks if a key exists in a given database prefix.
DBService::get- gets a value from a key in a given database prefix, if it exists.
DBService::put- inserts a key and value in a given database prefix. Due to the way LevelDB works, updating an entry is the same as inserting a different value in a key that already exists.
DBService::del- deletes a key in a given database prefix.
DBService::writeBatch- same as put + del but batched. The function requires a
WriteBatchRequeststruct, therefore, all operations in it are done in one go.
DBService::readBatch- same as get but returns all entries in a given database prefix. This function has two overloads - the first one returns all entries and requires only the prefix, and the second one returns only values from specific keys and requires both the prefix and a key list.
DBService::removeKeyPrefix- helper function that removes the prefix from a given key (e.g. key
"123", after going through this function it would return