-
Notifications
You must be signed in to change notification settings - Fork 20.4k
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
core: write chain data in atomic way #20287
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks correct to me, though I have some minor questions. I think we should run this a bit on some nodes -- it's really tricky to figure out possible flaws using 👀 only.
core/blockchain.go
Outdated
batch := bc.db.NewBatch() | ||
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64()) | ||
if updateHeads { | ||
bc.hc.WriteHeadHeader(batch, block.Header()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Although the batch hasn't been flushed yet, the call to WriteHeadHeader
internally calls hc.SetCurrentHeader(head)
. I suppose it's safe enough anyway,just wanted to point out that there's a brief interval of mismatch between what's in memory here and what's flushed..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the previous code used block.Hash()
which remembers the hash - here it becomes recalculated. Could you make WriteHeadHeader
take the hash as input?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for pointing out here. You are right I can't call WriteHeadHeader
here directly. Although the interval is very short, but it's still better to prevent it. I will use rawdb.WriteHeadHeaderHash(db, head.Hash())
instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, have the tests, and find if we want this guarantee that "all changes be pushed into disk first and then update the in-memory flags", then we have to break the WriteHeadHeader
into 2 separate steps.
It's total fine, but just it's not a good approach. WriteHeadHeader
is kind of self-contained
function which processes all logics in the headerchain. It's weird to call rawdb.WriteHeadHeaderHash(db, head.Hash())
directly.
bc.hc.SetCurrentHeader(block.Header()) | ||
rawdb.WriteHeadFastBlockHash(bc.db, block.Hash()) | ||
|
||
rawdb.WriteHeadFastBlockHash(batch, block.Hash()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same issue here. We're pushing the head fast-block into the batch, but updating the memory-marker immediately.
core/headerchain.go
Outdated
// and write the marker into database. | ||
func (hc *HeaderChain) WriteHeadHeader(db ethdb.KeyValueWriter, head *types.Header) { | ||
rawdb.WriteHeadHeaderHash(db, head.Hash()) | ||
hc.SetCurrentHeader(head) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably not be done here (while still only writing to batch), but done by caller after the batch is committed
bc.currentFastBlock.Store(block) | ||
headFastBlockGauge.Update(int64(block.NumberU64())) | ||
} | ||
if err := batch.Write(); err != nil { | ||
log.Crit("Failed to update chain indexes and markers", "err", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should only update the markers here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for caches imho
|
||
if err := batch.Write(); err != nil { | ||
log.Crit("Failed to reset genesis block", "err", err) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets update the genesisBlock and hc.SetGenesis after the components are written?
99ba3c9
to
71048f7
Compare
71048f7
to
61ba56f
Compare
if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), td); err != nil { | ||
return err | ||
batch := bc.db.NewBatch() | ||
rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new WriteTd
method does not store things in to the tdCache
, right? Don't we need to add it there explicitly now? Afaict that's only done through headerchain, not blockchain, so I'm not sure it will get there if we sync block by block?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's quite annoying. In theory, td is a part of header chain data, but some times we write the whole block with the given td. So we need to explicitly set the td into the cache.
Btw we never update the other cache(bodyCache
, receiptCache
) when we write the block.
// Note all the components of block(td, hash->number map, header, body, receipts) | ||
// should be written atomically. BlockBatch is used for containing all components. | ||
blockBatch := bc.db.NewBatch() | ||
rawdb.WriteTd(blockBatch, block.Hash(), block.NumberU64(), externTd) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same question about tdcache as above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see anything wrong with this PR. Let's hope for the best? ;P
* core: write chain data in atomic way * core, light: address comments * core, light: fix linter * core, light: address comments
This PR changes the way to write chain data.
Now we can classify the chain data into two parts:
The reason we classify it is:
So that there are two separate data structures
headerchain
andblockchain
.The pain point is both
header chain data
andblock chain data
are divided into several componentsFor
header chain
, we have:For
block chain
, we have:So in order to ensure the integrity of the chain data, all the components have to be written in an atomic way.
So this is the first thing this PR does.
And what's more, except the chain data, there are some additional index data.
For
header chain
, there are:For
block chain
, there are:So for these indexes, they also have to be written in an atomic way. But they are independent with chain data. The basic work flow is: write the chain data first irrelevant of the canonical status, then commit the indexes if it's a canonical header or block.
So this PR also ensures all indexes are written in an atomic way.
So finally we can address some issues caused by uncompleteness of chain data.