Skip to content
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

Merged
merged 4 commits into from
Jan 17, 2020

Conversation

rjl493456442
Copy link
Member

@rjl493456442 rjl493456442 commented Nov 14, 2019

This PR changes the way to write chain data.

Now we can classify the chain data into two parts:

  • Header chain data
  • Block chain data

The reason we classify it is:

  • for light client, it only maintains the header chain
  • for sync, we write header chain first and then fulfill the remaining data like body and receipts.

So that there are two separate data structures headerchain and blockchain.

The pain point is both header chain data and block chain data are divided into several components
For header chain, we have:

  • header
  • total difficulty
  • hash -> number mapping

For block chain, we have:

  • header
  • total difficulty
  • hash -> number mapping
  • body
  • receipts

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:

  • canonical index(number -> hash mapping)
  • head header flag

For block chain, there are:

  • canonical index(number -> hash mapping)
  • head header flag
  • tx indexes(tx hash -> canonical block number)
  • head fast block flag
  • head full block flag.

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.

Copy link
Contributor

@holiman holiman left a 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 Show resolved Hide resolved
batch := bc.db.NewBatch()
rawdb.WriteCanonicalHash(batch, block.Hash(), block.NumberU64())
if updateHeads {
bc.hc.WriteHeadHeader(batch, block.Header())
Copy link
Contributor

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..

Copy link
Contributor

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?

Copy link
Member Author

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

Copy link
Member Author

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())
Copy link
Member

@karalabe karalabe Nov 19, 2019

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.

// and write the marker into database.
func (hc *HeaderChain) WriteHeadHeader(db ethdb.KeyValueWriter, head *types.Header) {
rawdb.WriteHeadHeaderHash(db, head.Hash())
hc.SetCurrentHeader(head)
Copy link
Contributor

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)
}
Copy link
Member

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.

Copy link
Member

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)
}
Copy link
Member

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?

core/blockchain.go Outdated Show resolved Hide resolved
@rjl493456442 rjl493456442 force-pushed the make-write-atomic branch 2 times, most recently from 99ba3c9 to 71048f7 Compare November 20, 2019 02:27
core/blockchain.go Outdated Show resolved Hide resolved
core/blockchain.go Outdated Show resolved Hide resolved
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)
Copy link
Contributor

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?

Copy link
Member Author

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)
Copy link
Contributor

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

Copy link
Contributor

@holiman holiman left a 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

@karalabe karalabe added this to the 1.9.10 milestone Jan 17, 2020
Copy link
Member

@karalabe karalabe left a 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

vdamle pushed a commit to kaleido-io/quorum that referenced this pull request Jan 22, 2021
vdamle pushed a commit to kaleido-io/quorum that referenced this pull request Jan 26, 2021
enriquefynn pushed a commit to enriquefynn/go-ethereum that referenced this pull request Mar 10, 2021
* core: write chain data in atomic way

* core, light: address comments

* core, light: fix linter

* core, light: address comments
gzliudan added a commit to gzliudan/XDPoSChain that referenced this pull request Dec 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants