-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Atomicity of database updates across block updates #79
Comments
I have an incoming PR that addresses this and many other things by locking around GlobalData. big refactor, I think too big to try and test + discuss/review + merge before alphanet-v5, but probably immediately after. Also, it is on my mental todo to create a DbtHashTable type in twenty_first::storage, just for completeness. I wonder if you have other uses in mind for it? |
I've been thinking about this a lot, and doing a bit of research and experimentation the past few days. As per my comment in #91, we are transactional/serializable in the code, but we don't have true atomicity for block updates because our data is divided into 5 separate stores:
It seems fine for (5) to be separate. Banned IPs are unrelated to the blockchain or wallet state. Even if we somehow put (2,3,4) into a single levelDB, (1) is still separate. It would be an improvement though. There is however, a strong argument to be made that wallet state is logically separate from blockchain state. Indeed, some blockchain(s), eg monero implement only blockchain state in the core/node server. Others implement multiple wallets. Further, user's may have need to move/backup their wallet data without everything else. So it would likely be a mistake to tie them into a single DB. I think then, this means our ideal situation would be that all blockchain state is atomic (1,2,3), and all wallet state (4) is (separately) atomic, as is any peer state, eg banned_ips (5). Anyway, I think these areas are worth further discussion/thought: a) is it any problem for wallet state to be temporarily out of sync with blockchain state? b) are blocks (1) stored on disk in an atomic fashion -- all-or-nothing write for each block update? c) how might we make (1,2,3) an atomic operation? Would this require storing blockchain data inside our DB? or using some kind of write-ahead log scheme perhaps?
I made an experimental note:
yes. So how would we achieve this? It seems we at least would want to combine the block-index and mutator-set. Or maybe the mutator-set Schema could be stored inside the block-index DB, and block-index code remains unchanged. I'm not certain if there are any problems with that or not. It's a possibility. I also started thinking (daydreaming?) about: what would an ideal replacement for levelDB look like. I came up with this list:
I spent a few hours looking around at all the rust in-process DBs. The best match seems to be redb. Well, it doesn't provide async APIs, but there is a simple workaround to make the api async-friendly. But it does support most everything levelDB does, plus sub-tables, transaction api, and more. Also, there is native db (see: reddit discussion) that sits on top of redb and offers derive macros that make it very simple to persist rust structs and other types into the DB. This looks super easy to use and could potentially replace Final thoughts: Switching out the storage layer is a big task and a decision not to be taken lightly. So we should carefully weigh pros/cons. I think we need to consider if levelDB will serve our long-term needs or not. And if not, what is the cost of switching now, vs switching after mainnet, when we already have (hopefully) lots of users? And even if we switched it out, there remains the issue of the |
This is potentially hazardous and different from Bitcoin/Monero because in Neptune the wallet state contains membership proofs which are specific to the blockchain state. If there is a large rift between membership proofs and blockchain state, then UTXOs can become effectively unspendable. Whenever the client stores all blocks on the path from what the current membership proofs are synced to, to the current canonical chain tip, it should be able to recover membership proofs no problem. When this path is not stored, the client relies on the goodwill of peers (and we generally don't want to do that). |
thx for clarifying. I had a doubt about that. I'm also still trying to understand what the ecosystem can/will look like with regards to 3rd party wallet software, light wallets, etc. As in, what does the above mean for eg a 3rd party multi-coin wallet that is trying to implement neptune support? Maybe it means they need to at least impl light-mode, and then request blocks from peers as needed? |
There's been some discussion about bitcoin's behavior, so I looked into it a little, and this is the best description I've found: https://bitcoin.stackexchange.com/a/69762/49396 Reading between the lines, and just from the fact that multiple levelDB are involved, I think they don't actually attempt true ACID type all-or-nothing behavior for blockchain writes. Instead they figure that the blockchain files are the master copy, they are written out lazily from mem on a slow timer. On a restart, the node can detect if a blockchain update didn't finish and restart it. Further, levelDB data is just summary views, and can be rebuilt from the master blockchain if necessary (corrupted or out-of-sync). Presumably there are a bunch of checks during startup to detect problems. Indeed i've seen some of these behaviors myself, have needed to rebuild block index db, etc. Having used ACID databases for many years, I much prefer those characteristics and guarantees. But then do we want to put hundreds of gigs of block data into a DB container? vs a "simple" file format of our own devise? For atomicity of file system writes, a technique I've used often is to write new data in a tmp file, then So I'm honestly a little baffled as to why bitcoin does it the way described in the link above. I imagine/hope there's a good reason, but I'm not seeing the full picture yet. Still, a good argument can be made that if its good enough for bitcoin, it's good enough for us. A counter-argument would be that we are trying to build something better, and if we see opportunity for a better solution, should go for it. So I'm not advocating any approach here, just in analysis mode. almost forgot: I also found this article by Sergio Demian Lerner about relaxing ACID properties in EVM blockchains, which is an interesting read. edit: this bitcointalk thread about levelDB reliability is also relevant/interesting. |
a while back I asked about bitcoin's db atomicity on stack exchange and Pieter Wuille answered here: |
flawless - a durable execution engine for rust. enables recovery from external errors like kill -9, power cut, etc. also enables atomicity across databases, eg a transaction that spans pgsql and mysql. Or in our case, it could be across wallet-db, block-db, block-filestore, etc. it's an interesting approach. probably too late in our dev cycle to adopt it, but good to know of such tools for the toolbox. maybe down the road... |
This logic in
main_loop.rs
refers to persisting the state updates after receiving a new block. To achieve atomicity, there should probably only be one call topersist
which would imply that only one leveldb database was used under the hood. For that, we need to expand the storage crate to handle key/value pairs instead of just lists and singletons, as it currently supports.The text was updated successfully, but these errors were encountered: