diff --git a/core/blockchain_test.go b/core/blockchain_test.go index cfc44bf79..ee3a14941 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1483,3 +1483,51 @@ func BenchmarkBlockChain_1x1000Executions(b *testing.B) { benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) } + +// Tests that importing a very large side fork, which is larger than the canon chain, +// but where the difficulty per block is kept low: this means that it will not +// overtake the 'canon' chain until after it's passed canon by about 200 blocks. +func TestLargeOldSidechainWithALowTdChain(t *testing.T) { + + // Generate a canonical chain to act as the main dataset + engine := ethash.NewFaker() + + db := ethdb.NewMemDatabase() + genesis := new(Genesis).MustCommit(db) + // We must use a pretty long chain to ensure that the fork + // doesn't overtake us until after at least 128 blocks post tip + blocks, _ := generateChain(params.TestChainConfig, genesis, engine, db, 6*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }, makeHeaderWithLargeDifficulty) + + // Import the canonical chain + diskdb := ethdb.NewMemDatabase() + new(Genesis).MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil) + if err != nil { + t.Fatalf("failed to create tester chain: %v", err) + } + for i := 0; i < len(blocks); i++ { + if _, err := chain.InsertChain(blocks[i : i+1]); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", i, err) + } + } + // Dereference all the recent tries and ensure no past trie is left in + for i := 0; i < triesInMemory; i++ { + chain.stateCache.TrieDB().Dereference(blocks[len(blocks)-1-i].Root()) + } + + // Generate fork chain, starting from an early block + parent := blocks[10] + fork, _ := generateChain(params.TestChainConfig, parent, engine, db, 256+6*triesInMemory, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{2}) }, makeHeaderWithSmallDifficulty) + + // And now import the fork + if i, err := chain.InsertChain(fork); err != nil { + t.Fatalf("block %d: failed to insert into chain: %v", i, err) + } + head := chain.CurrentBlock() + if got := fork[len(fork)-1].Hash(); got != head.Hash() { + t.Fatalf("head wrong, expected %x got %x", head.Hash(), got) + } + td := chain.GetTd(head.Hash(), head.NumberU64()) + fmt.Printf("td %v", td) +} diff --git a/core/chain_makers.go b/core/chain_makers.go index e3a5537a4..63ca16440 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -48,6 +48,8 @@ type BlockGen struct { engine consensus.Engine } +type headerGenFn func(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header + // SetCoinbase sets the coinbase of the generated block. // It can be called at most once. func (b *BlockGen) SetCoinbase(addr common.Address) { @@ -170,6 +172,10 @@ func (b *BlockGen) OffsetTime(seconds int64) { // values. Inserting them into BlockChain requires use of FakePow or // a similar non-validating proof of work implementation. func GenerateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen)) ([]*types.Block, []types.Receipts) { + return generateChain(config, parent, engine, db, n, gen, makeHeader) +} + +func generateChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen), headerGen headerGenFn) ([]*types.Block, []types.Receipts) { if config == nil { config = params.TestChainConfig } @@ -177,7 +183,8 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse chainreader := &fakeChainReader{config: config} genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) { b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine} - b.header = makeHeader(chainreader, parent, statedb, b.engine) + //b.header = makeHeader(chainreader, parent, statedb, b.engine) + b.header = headerGen(chainreader, parent, statedb, b.engine) // Mutate the state and block according to any hard-fork specs if daoBlock := config.DAOForkBlock; daoBlock != nil { @@ -248,6 +255,54 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S } } +func makeHeaderWithLargeDifficulty(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { + var time *big.Int + if parent.Time() == nil { + time = big.NewInt(1) + } else { + time = new(big.Int).Add(parent.Time(), big.NewInt(1)) // block time is fixed at 10 seconds + } + + return &types.Header{ + Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: engine.CalcDifficulty(chain, time.Uint64(), &types.Header{ + Number: parent.Number(), + Time: new(big.Int).Sub(time, big.NewInt(1)), + Difficulty: parent.Difficulty(), + UncleHash: parent.UncleHash(), + }), + GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: time, + } +} + +func makeHeaderWithSmallDifficulty(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header { + var time *big.Int + if parent.Time() == nil { + time = big.NewInt(30) + } else { + time = new(big.Int).Add(parent.Time(), big.NewInt(30)) // block time is fixed at 10 seconds + } + + return &types.Header{ + Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())), + ParentHash: parent.Hash(), + Coinbase: parent.Coinbase(), + Difficulty: engine.CalcDifficulty(chain, time.Uint64(), &types.Header{ + Number: parent.Number(), + Time: new(big.Int).Sub(time, big.NewInt(30)), + Difficulty: parent.Difficulty(), + UncleHash: parent.UncleHash(), + }), + GasLimit: CalcGasLimit(parent, parent.GasLimit(), parent.GasLimit()), + Number: new(big.Int).Add(parent.Number(), common.Big1), + Time: time, + } +} + // makeHeaderChain creates a deterministic chain of headers rooted at parent. func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header { blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)