From fa569d5b2b5eef99700042a2f02b1582cb68b35f Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 9 Apr 2019 12:46:25 +0800 Subject: [PATCH] core: import known blocks if they can be inserted as canonical blocks --- core/blockchain.go | 47 +++++- core/blockchain_test.go | 338 +++++++++++++++++++++++++++------------- 2 files changed, 271 insertions(+), 114 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 9fa5b09f95..6d63383f45 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1144,13 +1144,23 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] stats.ignored++ block, err = it.next() } + // First block is still known block, the only scenario here is: + // We did a roll-back, and we want to re-import a batch of known blocks while a part + // of known blocks are higher than current head block. + if err == ErrKnownBlock { + block, err = bc.insertKnownChain(block, it) + + if bc.CurrentBlock().NumberU64() != current { + lastCanon = bc.CurrentBlock() + } + } // Falls through to the block import } switch { // First block is pruned, insert as sidechain and reorg only if TD grows enough case err == consensus.ErrPrunedAncestor: - return bc.insertSidechain(block, it) + return bc.insertSideChain(block, it) // First block is future, shove it (and all children) to the future queue (unknown ancestor) case err == consensus.ErrFutureBlock || (err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(it.first().ParentHash())): @@ -1313,13 +1323,44 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] return it.index, events, coalescedLogs, err } -// insertSidechain is called when an import batch hits upon a pruned ancestor +// insertKnownChain inserts a batch of known blocks which are higher than current +// head block. +func (bc *BlockChain) insertKnownChain(block *types.Block, it *insertIterator) (*types.Block, error) { + var ( + externTd *big.Int + knownBlocks []*types.Block + + err = ErrKnownBlock + current = bc.CurrentBlock() + localTd = bc.GetTd(current.Hash(), current.NumberU64()) + ) + for ; block != nil && (err == ErrKnownBlock); block, err = it.next() { + if externTd == nil { + externTd = bc.GetTd(block.ParentHash(), block.NumberU64()-1) + } + externTd = new(big.Int).Add(externTd, block.Difficulty()) + + // Short circuit if the known block cannot be imported as a new + // canonical block. + if block.ParentHash() != current.Hash() || externTd.Cmp(localTd) <= 0 { + break + } + knownBlocks = append(knownBlocks, block) + localTd, current = new(big.Int).Add(localTd, block.Difficulty()), block + } + if len(knownBlocks) > 0 { + bc.insert(knownBlocks[len(knownBlocks)-1]) + } + return block, err +} + +// insertSideChain is called when an import batch hits upon a pruned ancestor // error, which happens when a sidechain with a sufficiently old fork-block is // found. // // The method writes all (header-and-body-valid) blocks to disk, then tries to // switch over to the new chain if the TD exceeded the current chain. -func (bc *BlockChain) insertSidechain(block *types.Block, it *insertIterator) (int, []interface{}, []*types.Log, error) { +func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, []interface{}, []*types.Log, error) { var ( externTd *big.Int current = bc.CurrentBlock() diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 80a949d904..59678c601a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -1564,117 +1564,6 @@ func TestLargeReorgTrieGC(t *testing.T) { } } -// Benchmarks large blocks with value transfers to non-existing accounts -func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { - var ( - signer = types.HomesteadSigner{} - testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) - bankFunds = big.NewInt(100000000000000000) - gspec = Genesis{ - Config: params.TestChainConfig, - Alloc: GenesisAlloc{ - testBankAddress: {Balance: bankFunds}, - common.HexToAddress("0xc0de"): { - Code: []byte{0x60, 0x01, 0x50}, - Balance: big.NewInt(0), - }, // push 1, pop - }, - GasLimit: 100e6, // 100 M - } - ) - // Generate the original common chain segment and the two competing forks - engine := ethash.NewFaker() - db := rawdb.NewMemoryDatabase() - genesis := gspec.MustCommit(db) - - blockGenerator := func(i int, block *BlockGen) { - block.SetCoinbase(common.Address{1}) - for txi := 0; txi < numTxs; txi++ { - uniq := uint64(i*numTxs + txi) - recipient := recipientFn(uniq) - //recipient := common.BigToAddress(big.NewInt(0).SetUint64(1337 + uniq)) - tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey) - if err != nil { - b.Error(err) - } - block.AddTx(tx) - } - } - - shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, numBlocks, blockGenerator) - b.StopTimer() - b.ResetTimer() - for i := 0; i < b.N; i++ { - // Import the shared chain and the original canonical one - diskdb := rawdb.NewMemoryDatabase() - gspec.MustCommit(diskdb) - - chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil) - if err != nil { - b.Fatalf("failed to create tester chain: %v", err) - } - b.StartTimer() - if _, err := chain.InsertChain(shared); err != nil { - b.Fatalf("failed to insert shared chain: %v", err) - } - b.StopTimer() - if got := chain.CurrentBlock().Transactions().Len(); got != numTxs*numBlocks { - b.Fatalf("Transactions were not included, expected %d, got %d", numTxs*numBlocks, got) - - } - } -} -func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) { - var ( - numTxs = 1000 - numBlocks = 1 - ) - - recipientFn := func(nonce uint64) common.Address { - return common.BigToAddress(big.NewInt(0).SetUint64(1337 + nonce)) - } - dataFn := func(nonce uint64) []byte { - return nil - } - - benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) -} -func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) { - var ( - numTxs = 1000 - numBlocks = 1 - ) - b.StopTimer() - b.ResetTimer() - - recipientFn := func(nonce uint64) common.Address { - return common.BigToAddress(big.NewInt(0).SetUint64(1337)) - } - dataFn := func(nonce uint64) []byte { - return nil - } - - benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) -} -func BenchmarkBlockChain_1x1000Executions(b *testing.B) { - var ( - numTxs = 1000 - numBlocks = 1 - ) - b.StopTimer() - b.ResetTimer() - - recipientFn := func(nonce uint64) common.Address { - return common.BigToAddress(big.NewInt(0).SetUint64(0xc0de)) - } - dataFn := func(nonce uint64) []byte { - return nil - } - - 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. @@ -1812,6 +1701,181 @@ func TestPrunedImportSide(t *testing.T) { testSideImport(t, 1, -10) } +func TestInsertKnownHeaders(t *testing.T) { testInsertKnownChainData(t, "headers") } +func TestInsertKnownReceiptChain(t *testing.T) { testInsertKnownChainData(t, "receiptChain") } +func TestInsertKnownBlocks(t *testing.T) { testInsertKnownChainData(t, "blocks") } + +func testInsertKnownChainData(t *testing.T, typ string) { + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + + db := rawdb.NewMemoryDatabase() + genesis := new(Genesis).MustCommit(db) + + blocks, receipts := GenerateChain(params.TestChainConfig, genesis, engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + blocks2, receipts2 := GenerateChain(params.TestChainConfig, blocks[len(blocks)-1], engine, db, 64, func(i int, b *BlockGen) { b.SetCoinbase(common.Address{1}) }) + + // Import the shared chain and the original canonical one + diskdb := rawdb.NewMemoryDatabase() + 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) + } + + var rollback []common.Hash + for i := len(blocks) / 2; i < len(blocks); i++ { + rollback = append(rollback, blocks[i].Hash()) + } + + if typ == "blocks" { + if _, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + // The imported blocks are all known blocks and not higher than + // head block. + if _, err := chain.InsertChain(blocks); err != nil { + t.Fatalf("failed to insert known chain: %v", err) + } + + // A part of imported blocks are known blocks. Besides a part of + // known blocks are higher than head blocks. + chain.Rollback(rollback) + if _, err := chain.InsertChain(append(blocks, blocks2...)); err != nil { + t.Fatalf("failed to insert chain with known block as prefix: %v", err) + } + if chain.CurrentBlock().Hash() != blocks2[len(blocks2)-1].Hash() { + t.Fatalf("failed to insert chain with known block as prefix, want head block %v, have head block %v", + blocks2[len(blocks2)-1].Hash().Hex(), chain.CurrentBlock().Hash().Hex()) + } + } else if typ == "headers" { + headers, headers2 := make([]*types.Header, 0, len(blocks)), make([]*types.Header, 0, len(blocks2)) + for _, block := range blocks { + headers = append(headers, block.Header()) + } + for _, block := range blocks2 { + headers2 = append(headers2, block.Header()) + } + if _, err := chain.InsertHeaderChain(headers, 1); err != nil { + t.Fatalf("failed to insert header chain: %v", err) + } + + // The imported headers are all known headers and not higher than + // head block. + if _, err := chain.InsertHeaderChain(headers, 1); err != nil { + t.Fatalf("failed to insert known header chain: %v", err) + } + + // A part of imported headers are known headers. Besides a part of + // known headers are higher than head headers. + chain.Rollback(rollback) + if _, err := chain.InsertHeaderChain(append(headers, headers2...), 1); err != nil { + t.Fatalf("failed to insert header chain with known headers as prefix: %v", err) + } + if chain.CurrentHeader().Hash() != headers2[len(headers2)-1].Hash() { + t.Fatalf("failed to insert header chain with known header as prefix, want head header %v, have head header %v", + headers2[len(headers2)-1].Hash().Hex(), chain.CurrentBlock().Hash().Hex()) + } + } else { + headers, headers2 := make([]*types.Header, 0, len(blocks)), make([]*types.Header, 0, len(blocks2)) + for _, block := range blocks { + headers = append(headers, block.Header()) + } + for _, block := range blocks2 { + headers2 = append(headers2, block.Header()) + } + if _, err := chain.InsertHeaderChain(headers, 1); err != nil { + t.Fatalf("failed to insert header chain: %v", err) + } + if _, err := chain.InsertReceiptChain(blocks, receipts); err != nil { + t.Fatalf("failed to insert receipt chain: %v", err) + } + + // The imported receipt chain are all known and not higher than + // head fast block. + if _, err := chain.InsertReceiptChain(blocks, receipts); err != nil { + t.Fatalf("failed to insert receipt chain: %v", err) + } + // A part of imported headers are known headers. Besides a part of + // known headers are higher than head headers. + chain.Rollback(rollback) + if _, err := chain.InsertHeaderChain(append(headers, headers2...), 1); err != nil { + t.Fatalf("failed to insert header chain with known headers as prefix: %v", err) + } + if _, err := chain.InsertReceiptChain(append(blocks, blocks2...), append(receipts, receipts2...)); err != nil { + t.Fatalf("failed to insert header chain with known headers as prefix: %v", err) + } + if chain.CurrentFastBlock().Hash() != blocks2[len(blocks2)-1].Hash() { + t.Fatalf("failed to insert header chain with known header as prefix, want head header %v, have head header %v", + blocks2[len(blocks2)-1].Hash().Hex(), chain.CurrentFastBlock().Hash().Hex()) + } + } +} + +// Benchmarks large blocks with value transfers to non-existing accounts +func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) { + var ( + signer = types.HomesteadSigner{} + testBankKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) + bankFunds = big.NewInt(100000000000000000) + gspec = Genesis{ + Config: params.TestChainConfig, + Alloc: GenesisAlloc{ + testBankAddress: {Balance: bankFunds}, + common.HexToAddress("0xc0de"): { + Code: []byte{0x60, 0x01, 0x50}, + Balance: big.NewInt(0), + }, // push 1, pop + }, + GasLimit: 100e6, // 100 M + } + ) + // Generate the original common chain segment and the two competing forks + engine := ethash.NewFaker() + db := rawdb.NewMemoryDatabase() + genesis := gspec.MustCommit(db) + + blockGenerator := func(i int, block *BlockGen) { + block.SetCoinbase(common.Address{1}) + for txi := 0; txi < numTxs; txi++ { + uniq := uint64(i*numTxs + txi) + recipient := recipientFn(uniq) + //recipient := common.BigToAddress(big.NewInt(0).SetUint64(1337 + uniq)) + tx, err := types.SignTx(types.NewTransaction(uniq, recipient, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testBankKey) + if err != nil { + b.Error(err) + } + block.AddTx(tx) + } + } + + shared, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, numBlocks, blockGenerator) + b.StopTimer() + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Import the shared chain and the original canonical one + diskdb := rawdb.NewMemoryDatabase() + gspec.MustCommit(diskdb) + + chain, err := NewBlockChain(diskdb, nil, params.TestChainConfig, engine, vm.Config{}, nil) + if err != nil { + b.Fatalf("failed to create tester chain: %v", err) + } + b.StartTimer() + if _, err := chain.InsertChain(shared); err != nil { + b.Fatalf("failed to insert shared chain: %v", err) + } + b.StopTimer() + if got := chain.CurrentBlock().Transactions().Len(); got != numTxs*numBlocks { + b.Fatalf("Transactions were not included, expected %d, got %d", numTxs*numBlocks, got) + + } + } +} + // getLongAndShortChains returns two chains, // A is longer, B is heavier func getLongAndShortChains() (*BlockChain, []*types.Block, []*types.Block, error) { @@ -1931,3 +1995,55 @@ func TestReorgToShorterRemovesCanonMappingHeaderChain(t *testing.T) { t.Errorf("expected header to be gone: %v", headerByNum.Number.Uint64()) } } + +func BenchmarkBlockChain_1x1000ValueTransferToNonexisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(1337 + nonce)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} + +func BenchmarkBlockChain_1x1000ValueTransferToExisting(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(1337)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} + +func BenchmarkBlockChain_1x1000Executions(b *testing.B) { + var ( + numTxs = 1000 + numBlocks = 1 + ) + b.StopTimer() + b.ResetTimer() + + recipientFn := func(nonce uint64) common.Address { + return common.BigToAddress(big.NewInt(0).SetUint64(0xc0de)) + } + dataFn := func(nonce uint64) []byte { + return nil + } + + benchmarkLargeNumberOfValueToNonexisting(b, numTxs, numBlocks, recipientFn, dataFn) +} \ No newline at end of file