aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Holst Swende <martin@swende.se>2019-02-21 18:36:49 +0800
committerPéter Szilágyi <peterke@gmail.com>2019-02-21 18:36:49 +0800
commit8577b5b020d963ef9972981bbfc62b8930d3e9c9 (patch)
tree2cc991813e966eb7755c4f0a27adf68cb766bbd4
parent628a0bde3f6667a00cd857c4f5143cb31f038615 (diff)
downloadgo-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.tar
go-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.tar.gz
go-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.tar.bz2
go-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.tar.lz
go-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.tar.xz
go-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.tar.zst
go-tangerine-8577b5b020d963ef9972981bbfc62b8930d3e9c9.zip
core: more tests for sidechain import, fixes #19105 (#19113)
* blockchain: more tests for sidechain import, fixes #19105 * core/blockchain: rework import of pruned canon blocks and canon-prepended sidechains * core/blockchain: minor clarity change * core/blockchain: remove unused method
-rw-r--r--core/blockchain.go53
-rw-r--r--core/blockchain_test.go82
2 files changed, 115 insertions, 20 deletions
diff --git a/core/blockchain.go b/core/blockchain.go
index b6605e66c..a3121cfd3 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -1084,15 +1084,21 @@ func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
if len(chain) == 0 {
return 0, nil
}
+ // Remove already known canon-blocks
+ var (
+ block, prev *types.Block
+ )
// Do a sanity check that the provided chain is actually ordered and linked
for i := 1; i < len(chain); i++ {
- if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
+ block = chain[i]
+ prev = chain[i-1]
+ if block.NumberU64() != prev.NumberU64()+1 || block.ParentHash() != prev.Hash() {
// Chain broke ancestry, log a message (programming error) and skip insertion
- log.Error("Non contiguous block insert", "number", chain[i].Number(), "hash", chain[i].Hash(),
- "parent", chain[i].ParentHash(), "prevnumber", chain[i-1].Number(), "prevhash", chain[i-1].Hash())
+ log.Error("Non contiguous block insert", "number", block.Number(), "hash", block.Hash(),
+ "parent", block.ParentHash(), "prevnumber", prev.Number(), "prevhash", prev.Hash())
- return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, chain[i-1].NumberU64(),
- chain[i-1].Hash().Bytes()[:4], i, chain[i].NumberU64(), chain[i].Hash().Bytes()[:4], chain[i].ParentHash().Bytes()[:4])
+ return 0, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", i-1, prev.NumberU64(),
+ prev.Hash().Bytes()[:4], i, block.NumberU64(), block.Hash().Bytes()[:4], block.ParentHash().Bytes()[:4])
}
}
// Pre-checks passed, start the full block imports
@@ -1146,6 +1152,22 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
it := newInsertIterator(chain, results, bc.Validator())
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 {
// First block is pruned, insert as sidechain and reorg only if TD grows enough
case err == consensus.ErrPrunedAncestor:
@@ -1165,20 +1187,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, []
// If there are any still remaining, mark as ignored
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
case err != nil:
stats.ignored += len(it.chain)
@@ -1304,7 +1312,12 @@ func (bc *BlockChain) insertSidechain(block *types.Block, it *insertIterator) (i
for ; block != nil && (err == consensus.ErrPrunedAncestor); block, err = it.next() {
// Check the canonical state root for that number
if number := block.NumberU64(); current.NumberU64() >= number {
- if canonical := bc.GetBlockByNumber(number); canonical != nil && canonical.Root() == block.Root() {
+ canonical := bc.GetBlockByNumber(number)
+ if 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 != nil && canonical.Root() == block.Root() {
// 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
// just found that the state already exist! This means that the sidechain block
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index a16b3ba8a..1ac7b03fc 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -1521,3 +1521,85 @@ func TestLowDiffLongChain(t *testing.T) {
header = chain.GetHeader(header.ParentHash, number-1)
}
}
+
+// Tests that importing a sidechain (S), where
+// - S is sidechain, containing blocks [Sn...Sm]
+// - C is canon chain, containing blocks [G..Cn..Cm]
+// - A common ancestor is placed at prune-point + blocksBetweenCommonAncestorAndPruneblock
+// - The sidechain S is prepended with numCanonBlocksInSidechain blocks from the canon chain
+func testSideImport(t *testing.T, numCanonBlocksInSidechain, blocksBetweenCommonAncestorAndPruneblock int) {
+
+ // Generate a canonical chain to act as the main dataset
+ engine := ethash.NewFaker()
+ db := ethdb.NewMemDatabase()
+ genesis := new(Genesis).MustCommit(db)
+
+ // Generate and import the canonical chain
+ blocks, _ := GenerateChain(params.TestChainConfig, genesis, engine, db, 2*triesInMemory, nil)
+ 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)
+ }
+ if n, err := chain.InsertChain(blocks); err != nil {
+ t.Fatalf("block %d: failed to insert into chain: %v", n, err)
+ }
+
+ lastPrunedIndex := len(blocks) - triesInMemory - 1
+ lastPrunedBlock := blocks[lastPrunedIndex]
+ firstNonPrunedBlock := blocks[len(blocks)-triesInMemory]
+
+ // Verify pruning of lastPrunedBlock
+ if chain.HasBlockAndState(lastPrunedBlock.Hash(), lastPrunedBlock.NumberU64()) {
+ t.Errorf("Block %d not pruned", lastPrunedBlock.NumberU64())
+ }
+ // Verify firstNonPrunedBlock is not pruned
+ if !chain.HasBlockAndState(firstNonPrunedBlock.Hash(), firstNonPrunedBlock.NumberU64()) {
+ t.Errorf("Block %d pruned", firstNonPrunedBlock.NumberU64())
+ }
+ // Generate the sidechain
+ // First block should be a known block, block after should be a pruned block. So
+ // canon(pruned), side, side...
+
+ // Generate fork chain, make it longer than canon
+ parentIndex := lastPrunedIndex + blocksBetweenCommonAncestorAndPruneblock
+ parent := blocks[parentIndex]
+ fork, _ := GenerateChain(params.TestChainConfig, parent, engine, db, 2*triesInMemory, func(i int, b *BlockGen) {
+ b.SetCoinbase(common.Address{2})
+ })
+ // Prepend the parent(s)
+ var sidechain []*types.Block
+ for i := numCanonBlocksInSidechain; i > 0; i-- {
+ sidechain = append(sidechain, blocks[parentIndex+1-i])
+ }
+ sidechain = append(sidechain, fork...)
+ _, err = chain.InsertChain(sidechain)
+ if err != nil {
+ t.Errorf("Got error, %v", 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)
+ }
+}
+
+// Tests that importing a sidechain (S), where
+// - S is sidechain, containing blocks [Sn...Sm]
+// - C is canon chain, containing blocks [G..Cn..Cm]
+// - The common ancestor Cc is pruned
+// - The first block in S: Sn, is == Cn
+// That is: the sidechain for import contains some blocks already present in canon chain.
+// So the blocks are
+// [ Cn, Cn+1, Cc, Sn+3 ... Sm]
+// ^ ^ ^ pruned
+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, 10, 0)
+ testSideImport(t, 1, 10)
+ testSideImport(t, 1, -10)
+}