Skip to content

Commit 4e07967

Browse files
committed
core: fixed chain reorg during splits
Chain reorgs weren't properly handled when a chain was further ahead. Previously we'd end up with mixed chains in our canonical numbering sequence. Added test for this type of forking. ``` /-o-o-o A o-C-+ \-o-o-o-o B ``` Ends up with with C A1, A2, A3, B4
1 parent af73d1d commit 4e07967

File tree

2 files changed

+83
-27
lines changed

2 files changed

+83
-27
lines changed

core/chain_manager.go

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ type ChainManager struct {
7474
eventMux *event.TypeMux
7575
genesisBlock *types.Block
7676
// Last known total difficulty
77-
mu sync.RWMutex
78-
tsmu sync.RWMutex
79-
insertMu sync.Mutex
77+
mu sync.RWMutex
78+
tsmu sync.RWMutex
8079

8180
td *big.Int
8281
currentBlock *types.Block
@@ -321,6 +320,7 @@ func (bc *ChainManager) ResetWithGenesisBlock(gb *types.Block) {
321320
bc.insert(bc.genesisBlock)
322321
bc.currentBlock = bc.genesisBlock
323322
bc.makeCache()
323+
bc.td = gb.Difficulty()
324324
}
325325

326326
// Export writes the active chain to the given writer.
@@ -348,8 +348,6 @@ func (self *ChainManager) Export(w io.Writer) error {
348348
func (bc *ChainManager) insert(block *types.Block) {
349349
key := append(blockNumPre, block.Number().Bytes()...)
350350
bc.blockDb.Put(key, block.Hash().Bytes())
351-
// Push block to cache
352-
bc.cache.Push(block)
353351

354352
bc.blockDb.Put([]byte("LastBlock"), block.Hash().Bytes())
355353
bc.currentBlock = block
@@ -360,6 +358,8 @@ func (bc *ChainManager) write(block *types.Block) {
360358
enc, _ := rlp.EncodeToBytes((*types.StorageBlock)(block))
361359
key := append(blockHashPre, block.Hash().Bytes()...)
362360
bc.blockDb.Put(key, enc)
361+
// Push block to cache
362+
bc.cache.Push(block)
363363
}
364364

365365
// Accessors
@@ -498,9 +498,6 @@ func (self *ChainManager) procFutureBlocks() {
498498
}
499499

500500
func (self *ChainManager) InsertChain(chain types.Blocks) error {
501-
self.insertMu.Lock()
502-
defer self.insertMu.Unlock()
503-
504501
// A queued approach to delivering events. This is generally faster than direct delivery and requires much less mutex acquiring.
505502
var (
506503
queue = make([]interface{}, len(chain))
@@ -557,16 +554,17 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error {
557554
// Compare the TD of the last known block in the canonical chain to make sure it's greater.
558555
// At this point it's possible that a different chain (fork) becomes the new canonical chain.
559556
if block.Td.Cmp(self.td) > 0 {
560-
//if block.Header().Number.Cmp(new(big.Int).Add(cblock.Header().Number, common.Big1)) < 0 {
561-
if block.Number().Cmp(cblock.Number()) <= 0 {
557+
// Check for chain forks. If H(block.num - 1) != block.parent, we're on a fork and need to do some merging
558+
if previous := self.getBlockByNumber(block.NumberU64() - 1); previous.Hash() != block.ParentHash() {
562559
chash := cblock.Hash()
563560
hash := block.Hash()
564561

565562
if glog.V(logger.Info) {
566563
glog.Infof("Split detected. New head #%v (%x) TD=%v, was #%v (%x) TD=%v\n", block.Header().Number, hash[:4], block.Td, cblock.Header().Number, chash[:4], self.td)
567564
}
565+
568566
// during split we merge two different chains and create the new canonical chain
569-
self.merge(self.getBlockByNumber(block.NumberU64()), block)
567+
self.merge(previous, block)
570568

571569
queue[i] = ChainSplitEvent{block, logs}
572570
queueEvent.splitCount++
@@ -592,16 +590,19 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error {
592590
glog.Infof("inserted block #%d (%d TXs %d UNCs) (%x...)\n", block.Number(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4])
593591
}
594592
} else {
593+
if glog.V(logger.Detail) {
594+
glog.Infof("inserted forked block #%d (%d TXs %d UNCs) (%x...)\n", block.Number(), len(block.Transactions()), len(block.Uncles()), block.Hash().Bytes()[0:4])
595+
}
596+
595597
queue[i] = ChainSideEvent{block, logs}
596598
queueEvent.sideCount++
597599
}
600+
self.futureBlocks.Delete(block.Hash())
598601
}
599602
self.mu.Unlock()
600603

601604
stats.processed++
602605

603-
self.futureBlocks.Delete(block.Hash())
604-
605606
}
606607

607608
if (stats.queued > 0 || stats.processed > 0) && bool(glog.V(logger.Info)) {
@@ -615,33 +616,38 @@ func (self *ChainManager) InsertChain(chain types.Blocks) error {
615616
return nil
616617
}
617618

618-
// merge takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
619+
// diff takes two blocks, an old chain and a new chain and will reconstruct the blocks and inserts them
619620
// to be part of the new canonical chain.
620-
func (self *ChainManager) merge(oldBlock, newBlock *types.Block) {
621+
func (self *ChainManager) diff(oldBlock, newBlock *types.Block) types.Blocks {
621622
glog.V(logger.Debug).Infof("Applying diff to %x & %x\n", oldBlock.Hash().Bytes()[:4], newBlock.Hash().Bytes()[:4])
622623

623-
var oldChain, newChain types.Blocks
624-
// First find the split (common ancestor) so we can perform an adequate merge
624+
var newChain types.Blocks
625+
// first find common number
626+
for newBlock = newBlock; newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = self.GetBlock(newBlock.ParentHash()) {
627+
newChain = append(newChain, newBlock)
628+
}
629+
630+
glog.V(logger.Debug).Infoln("Found common number", newBlock.Number())
625631
for {
626-
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
627632
if oldBlock.Hash() == newBlock.Hash() {
628633
break
629634
}
630-
oldChain = append(oldChain, oldBlock)
631635
newChain = append(newChain, newBlock)
636+
637+
oldBlock, newBlock = self.GetBlock(oldBlock.ParentHash()), self.GetBlock(newBlock.ParentHash())
632638
}
633639

640+
return newChain
641+
}
642+
643+
// merge merges two different chain to the new canonical chain
644+
func (self *ChainManager) merge(oldBlock, newBlock *types.Block) {
645+
newChain := self.diff(oldBlock, newBlock)
646+
634647
// insert blocks
635648
for _, block := range newChain {
636649
self.insert(block)
637650
}
638-
639-
if glog.V(logger.Detail) {
640-
for i, oldBlock := range oldChain {
641-
glog.Infof("- %.10v = %x\n", oldBlock.Number(), oldBlock.Hash())
642-
glog.Infof("+ %.10v = %x\n", newChain[i].Number(), newChain[i].Hash())
643-
}
644-
}
645651
}
646652

647653
func (self *ChainManager) update() {

core/chain_manager_test.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010
"testing"
1111

12+
"github.com/ethereum/go-ethereum/core/state"
1213
"github.com/ethereum/go-ethereum/core/types"
1314
"github.com/ethereum/go-ethereum/ethdb"
1415
"github.com/ethereum/go-ethereum/event"
@@ -56,12 +57,14 @@ func testFork(t *testing.T, bman *BlockProcessor, i, N int, f func(td1, td2 *big
5657
}
5758
// Compare difficulties
5859
f(tdpre, td)
60+
61+
// Loop over parents making sure reconstruction is done properly
5962
}
6063

6164
func printChain(bc *ChainManager) {
6265
for i := bc.CurrentBlock().Number().Uint64(); i > 0; i-- {
6366
b := bc.GetBlockByNumber(uint64(i))
64-
fmt.Printf("\t%x\n", b.Hash())
67+
fmt.Printf("\t%x %v\n", b.Hash(), b.Difficulty())
6568
}
6669
}
6770

@@ -344,3 +347,50 @@ func TestGetAncestors(t *testing.T) {
344347
ancestors := chainMan.GetAncestors(chain[len(chain)-1], 4)
345348
fmt.Println(ancestors)
346349
}
350+
351+
type bproc struct{}
352+
353+
func (bproc) Process(*types.Block) (state.Logs, error) { return nil, nil }
354+
355+
func makeChainWithDiff(genesis *types.Block, d []int, seed byte) []*types.Block {
356+
var chain []*types.Block
357+
for i, difficulty := range d {
358+
header := &types.Header{Number: big.NewInt(int64(i + 1)), Difficulty: big.NewInt(int64(difficulty))}
359+
block := types.NewBlockWithHeader(header)
360+
copy(block.HeaderHash[:2], []byte{byte(i + 1), seed})
361+
if i == 0 {
362+
block.ParentHeaderHash = genesis.Hash()
363+
} else {
364+
copy(block.ParentHeaderHash[:2], []byte{byte(i), seed})
365+
}
366+
367+
chain = append(chain, block)
368+
}
369+
return chain
370+
}
371+
372+
func TestReorg(t *testing.T) {
373+
db, _ := ethdb.NewMemDatabase()
374+
var eventMux event.TypeMux
375+
376+
genesis := GenesisBlock(db)
377+
bc := &ChainManager{blockDb: db, stateDb: db, genesisBlock: genesis, eventMux: &eventMux}
378+
bc.cache = NewBlockCache(100)
379+
bc.futureBlocks = NewBlockCache(100)
380+
bc.processor = bproc{}
381+
bc.ResetWithGenesisBlock(genesis)
382+
bc.txState = state.ManageState(bc.State())
383+
384+
chain1 := makeChainWithDiff(genesis, []int{1, 2, 4}, 10)
385+
chain2 := makeChainWithDiff(genesis, []int{1, 2, 3, 4}, 11)
386+
387+
bc.InsertChain(chain1)
388+
bc.InsertChain(chain2)
389+
390+
prev := bc.CurrentBlock()
391+
for block := bc.GetBlockByNumber(bc.CurrentBlock().NumberU64() - 1); block.NumberU64() != 0; prev, block = block, bc.GetBlockByNumber(block.NumberU64()-1) {
392+
if prev.ParentHash() != block.Hash() {
393+
t.Errorf("parent hash mismatch %x - %x", prev.ParentHash(), block.Hash())
394+
}
395+
}
396+
}

0 commit comments

Comments
 (0)