core/blockchain: rework import of pruned canon blocks and canon-prepended sidechains

This commit is contained in:
Martin Holst Swende 2019-02-21 10:11:49 +01:00
parent 92420baba8
commit 2d64d3389f
No known key found for this signature in database
GPG key ID: 683B438C05A5DDF0
2 changed files with 28 additions and 24 deletions

View file

@ -1093,16 +1093,10 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
} }
// Remove already known canon-blocks // Remove already known canon-blocks
var ( var (
start = 0
block, prev *types.Block block, prev *types.Block
) )
for start, block = range chain {
if !bc.IsCanon(block.Hash(), block.NumberU64()) {
break
}
}
// Do a sanity check that the provided chain is actually ordered and linked // Do a sanity check that the provided chain is actually ordered and linked
for i := start + 1; i < len(chain); i++ { for i := 1; i < len(chain); i++ {
block = chain[i] block = chain[i]
prev = chain[i-1] prev = chain[i-1]
if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() { if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() {
@ -1117,7 +1111,7 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
// Pre-checks passed, start the full block imports // Pre-checks passed, start the full block imports
bc.wg.Add(1) bc.wg.Add(1)
bc.chainmu.Lock() bc.chainmu.Lock()
n, events, logs, err := bc.insertChain(chain[start:], true) n, events, logs, err := bc.insertChain(chain, true)
bc.chainmu.Unlock() bc.chainmu.Unlock()
bc.wg.Done() bc.wg.Done()
@ -1165,6 +1159,22 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
it := newInsertIterator(chain, results, bc.Validator()) it := newInsertIterator(chain, results, bc.Validator())
block, err := it.next() block, err := it.next()
// Left-trim all the known blocks
if err == ErrKnownBlock {
// First block (and state) is known
// 1. We did a roll-back, and should now do a re-import
// 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot
// from the canonical chain, which has not been verified.
// Skip all known blocks that are behind us
current := bc.CurrentBlock().NumberU64()
for block != nil && err == ErrKnownBlock && current >= block.NumberU64() {
stats.ignored++
block, err = it.next()
}
// Falls through to the block import
}
switch { switch {
// First block is pruned, insert as sidechain and reorg only if TD grows enough // First block is pruned, insert as sidechain and reorg only if TD grows enough
case err == consensus.ErrPrunedAncestor: case err == consensus.ErrPrunedAncestor:
@ -1184,20 +1194,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
// If there are any still remaining, mark as ignored // If there are any still remaining, mark as ignored
return it.index, events, coalescedLogs, err return it.index, events, coalescedLogs, err
// First block (and state) is known
// 1. We did a roll-back, and should now do a re-import
// 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot
// from the canonical chain, which has not been verified.
case err == ErrKnownBlock:
// Skip all known blocks that behind us
current := bc.CurrentBlock().NumberU64()
for block != nil && err == ErrKnownBlock && current >= block.NumberU64() {
stats.ignored++
block, err = it.next()
}
// Falls through to the block import
// Some other error occurred, abort // Some other error occurred, abort
case err != nil: case err != nil:
stats.ignored += len(it.chain) stats.ignored += len(it.chain)
@ -1323,6 +1319,10 @@ func (bc *BlockChain) insertSidechain(block *types.Block, it *insertIterator) (i
for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() { for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() {
// Check the canonical state root for that number // Check the canonical state root for that number
if number := block.NumberU64(); current.NumberU64() >= number { if number := block.NumberU64(); current.NumberU64() >= number {
if canonical := bc.GetBlockByNumber(number); canonical != nil && canonical.Hash() == block.Hash() {
// Not a sidechain block, this is a re-import of a canon block which has it's state pruned
continue
}
if canonical := bc.GetBlockByNumber(number); canonical != nil && canonical.Root() == block.Root() { if canonical := bc.GetBlockByNumber(number); canonical != nil && canonical.Root() == block.Root() {
// This is most likely a shadow-state attack. When a fork is imported into the // This is most likely a shadow-state attack. When a fork is imported into the
// database, and it eventually reaches a block height which is not pruned, we // database, and it eventually reaches a block height which is not pruned, we

View file

@ -1580,14 +1580,15 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// canon(pruned), side, side... // canon(pruned), side, side...
// Generate fork chain, make it longer than canon // Generate fork chain, make it longer than canon
parent := blocks[lastPrunedIndex+blocksBetweenCommonAncestorAndPruneblock] parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock
parent := blocks[parentIndex]
fork, _ := GenerateChain(params.TestChainConfig, parent, engine, db, 2*triesInMemory, func(i int, b *BlockGen) { fork, _ := GenerateChain(params.TestChainConfig, parent, engine, db, 2*triesInMemory, func(i int, b *BlockGen) {
b.SetCoinbase(common.Address{2}) b.SetCoinbase(common.Address{2})
}) })
// Prepend the parent(s) // Prepend the parent(s)
var sidechain []*types.Block var sidechain []*types.Block
for i := numCanonBlocksInSidechain; i > 0; i-- { for i := numCanonBlocksInSidechain; i > 0; i-- {
sidechain = append(sidechain, blocks[len(blocks)-i]) sidechain = append(sidechain, blocks[parentIndex+1-i])
} }
sidechain = append(sidechain, fork...) sidechain = append(sidechain, fork...)
_, err = chain.InsertChain(sidechain) _, err = chain.InsertChain(sidechain)
@ -1610,6 +1611,9 @@ func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommon
// [ Cn, Cn+1, Cc, Sn+3 ... Sm] // [ Cn, Cn+1, Cc, Sn+3 ... Sm]
// ^ ^ ^ pruned // ^ ^ ^ pruned
func TestPrunedImportSide(t *testing.T) { func TestPrunedImportSide(t *testing.T) {
//glogger := log.NewGlogHandler(log.StreamHandler(os.Stdout, log.TerminalFormat(false)))
//glogger.Verbosity(3)
//log.Root().SetHandler(log.Handler(glogger))
testSideImport(t, 3, 3) testSideImport(t, 3, 3)
testSideImport(t, 3, -3) testSideImport(t, 3, -3)
testSideImport(t, 10, 0) testSideImport(t, 10, 0)