Replies: 2 comments 13 replies
-
I like the idea of keeping the account Merkle tree in testnet! This will make things much more practical and achievable. I think we should do some benchmarks to understand the implications of this implementation. Specifically:
We should define some reference hardware to run these benchmarks.
Can you elaborate on how this note implementation supports the An alternative mechanic would be to introduce the concept of "note epochs". A note epoch would define some period of time (or number of blocks), lets say its 7 days. We could then have a database key-value map of |
Beta Was this translation helpful? Give feedback.
-
For implement the data store we'd need to pick some persistent database to store the data (I'm discounting such options as using a file system directly or writing our own database as infeasible). Assuming we go with Rust, I did a cursory research of available options, and here is a summary: Rust-native databasesThere are a few Rust-native databases which I came across, most interesting of them are:
Embedded databases with Rust bindings
Client-server databases
Viable optionsAs much as I would love to use a Rust-native database, I don't think the options we have now are compelling (unless I missed something of course). I also don't think we should go with a client-server database at this point to avoid the complexity of dealing with a separate database server. This leaves embedded databases with Rust bindings. Here, I think we care more about stable, well-maintained and well-documented options, which in my mind narrows things down to either RocksDB or SQLite. And at this point, I'm leaning more towards SQLite - thought, we should think through all pros and cons. Also, a very interesting post from Erigon: Choice of storage engine. |
Beta Was this translation helpful? Give feedback.
-
Discussing RPC endpoints in #121, made me thing about the backend needed to support them. Here are my preliminary thoughts on this.
Overall, the node needs to manage 4 separate data sets: accounts, notes, nullifiers, and blocks. I've tried to keep the design as simple as possible (i.e., not create a sophisticated relational database) and also rely on purely in-memory structures in some cases. I think this is fine for testent purposes and we can optimize things later. Let's go through these one-by-one.
Account DB
Account database keeps track of the latest state of accounts and facilitates block production/verification. In my mind, it consists of two parts:
store
portion of account storage.The flat key-value map would be persisted in a database (or on disk), but the Merkle tree would live solely in memory, and for testnet purposes, it would be built every time the node starts up. Later we can optimize this, but I think for testnet (and maybe even beyond) this should be sufficient.
Note DB
Note database keeps track of all notes ever created. In my mind it consists of 3 parts:
The first two of the above components would be persisted in a database (or on disk), but the 3rd component would live solely in memory (and would be built on node start-up). It would be just a simple vector storing
(min_tag, max_tag)
for each block. This index would be used to assistget_notes_by_tag
RPC endpoint.We could also build an index to support mapping
note_hash |-> (block_num, index)
if we want to provideget_note_by_hash
RPC endpoint.For testnet, we won't implement any note data pruning and would assume that nothing gets deleted from note databases.
Nullifier DB
Nullifier database stores nullifiers of consumed notes. For testnet, we won't implement epoch-based nullifiers and will assume that there is a single nullifier database. Similar to the other database, this database would consist of two components:
The first component would be persisted in a database (or on disk), while the second component would live solely in memory and would be built on node start-up.
Block DB
This database would keep track of all produced blocks. In my mind, it consists of the following components:
block_num |-> block_header
.block_num |-> block_data
where block data includes:a. A list of nullifiers created in in this block.
b. A list of
(account_id, account_hash)
tuples for all accounts updated in this block. For public accounts, we'd also need to store state/vault deltas.c. Potentially a list of transaction hashes for transactions executed in this block.
block_num |-> proof
All of the above, except for MMR would be persisted in a database (or on disk). MMR would be our Mmr struct and it would live fully in memory (and instantiated on node start-up).
Maybe using a key-value map for the above is an overkill since all of the data indexed by
block_num
which grows monotonically and without gaps.Also, since all of them use
block_num
as the key, there could be an argument for combining them into a single object. But I think it might make sense to keep them separate as we may want to prune different components differently (i.e., proofs can be discarded much sooner as compared to block header data).Beta Was this translation helpful? Give feedback.
All reactions