Closed hackaugusto closed 9 months ago
some initial ideas:
To put a bit more context around this:
There are two Merkle tree structures which need to be updated with every block: account DB and nullifier DB (I'm ignoring MMR updates as these should be negligible). The number of updates will depend on TPS. Specifically, for every transaction, we'll need to update a state of 1 account, and thus, recompute the root of the account DB. Similarly, for every new nullifier we'll need to re-compute the root of nullifier DB. The tables below show the number of hashes needed to apply a block to the DB for 3 levels of TPS:
100 TPS | 1000 TPS | 10000 TPS | |
---|---|---|---|
Tx / block | 300 | 3000 | 30000 |
MT updates | 600 | 6000 | 60000 |
Hashes | 19K | 192K | 1.9M |
Time (1 thread) | 0.06 sec | 0.6 sec | 6 sec |
Assumptions in the above table:
Ideally, we'd want to be able to apply block updates very quickly, even if the part on the critical path is much smaller than the entire update. I think we should target applying a block in under 50ms. This means that by the time we get to 100 TPS we need to be able to process account and nullifier DB updates in multiple threads.
I was curious if memory layout had a perceivable impact in the hashing speed. So I did some simple benchmarks:
miden_crypto::MerkleTree
constructor works)MerkleTree
a) with monotonically increasing positions b) with random positionsSimpleSmt
a) with monotonically increasing positions b) with random positionsOn my machine the throughput is not affected at all with memory layout. (all versions gave me around ~210K/s). So it seems that using rayon to compute these values is a good solution.
The critical section was reduced to a minimum by https://github.com/0xPolygonMiden/miden-node/pull/70 :
The above approach requires a copy, but will always be the case with a modify-in-place structure.
The store maintains an in-memory version of a few data structures, the TSMT for the nullifiers, the SMT for the accounts, a full MMR. Additionally the store maintains a DB, with long term storage for the aforementioned structures (and anything else that doesn't need to be available in-memory).
The issue is that currently all of the structures mentioned above are written for single writer / single version (e.g. guarded by a
RWLock
that prevents reads while updates are being performed). The consequence is that DB updates and in-memory accesses require synchronization, and may have a performance impact (specially for merkle tree updates, that may require lots of hashing computations).The issue is to discuss possible implementations to improve concurrent usage of these structures.