diff options
author | Felix Lange <fjl@twurst.com> | 2015-06-27 08:57:53 +0800 |
---|---|---|
committer | Jeffrey Wilcke <geffobscura@gmail.com> | 2015-06-30 00:51:47 +0800 |
commit | e0e5f747765cec7050dd5ea4eed896e73ef0ab1f (patch) | |
tree | 98247ef4c9296c4a8725d467d852481227ae503e /eth/downloader | |
parent | ceaf1c080b4047c26d433e778d4d3142f4e29d24 (diff) | |
download | dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.tar dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.tar.gz dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.tar.bz2 dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.tar.lz dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.tar.xz dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.tar.zst dexon-e0e5f747765cec7050dd5ea4eed896e73ef0ab1f.zip |
eth/downloader, eth/fetcher: use core.GenerateChain in tests
TestMadeupParentBlockChainAttack has been deleted because it was too
hard to port and the attack that it checks the prevention of is being
averted in a different way (through a protocol change).
Diffstat (limited to 'eth/downloader')
-rw-r--r-- | eth/downloader/downloader_test.go | 253 |
1 files changed, 97 insertions, 156 deletions
diff --git a/eth/downloader/downloader_test.go b/eth/downloader/downloader_test.go index fb8aa7e4b..7feca8782 100644 --- a/eth/downloader/downloader_test.go +++ b/eth/downloader/downloader_test.go @@ -1,7 +1,7 @@ package downloader import ( - "encoding/binary" + "crypto/rand" "errors" "fmt" "math/big" @@ -12,61 +12,47 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" ) var ( - knownHash = common.Hash{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - unknownHash = common.Hash{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2} - bannedHash = common.Hash{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3} - - genesis = createBlock(1, common.Hash{}, knownHash) + testdb, _ = ethdb.NewMemDatabase() + genesis = core.GenesisBlockForTesting(testdb, common.Address{}, big.NewInt(0)) ) -// idCounter is used by the createHashes method the generate deterministic but unique hashes -var idCounter = int64(2) // #1 is the genesis block - -// createHashes generates a batch of hashes rooted at a specific point in the chain. -func createHashes(amount int, root common.Hash) (hashes []common.Hash) { - hashes = make([]common.Hash, amount+1) - hashes[len(hashes)-1] = root - - for i := 0; i < len(hashes)-1; i++ { - binary.BigEndian.PutUint64(hashes[i][:8], uint64(idCounter)) - idCounter++ - } - return -} - -// createBlock assembles a new block at the given chain height. -func createBlock(i int, parent, hash common.Hash) *types.Block { - header := &types.Header{ - Hash: hash, - Number: big.NewInt(int64(i)) - } - block := types.NewBlockWithHeader(header) - block.HeaderHash = hash - block.ParentHeaderHash = parent - return block -} - -// copyBlock makes a deep copy of a block suitable for local modifications. -func copyBlock(block *types.Block) *types.Block { - return createBlock(int(block.Number().Int64()), block.ParentHeaderHash, block.HeaderHash) -} - -// createBlocksFromHashes assembles a collection of blocks, each having a correct -// place in the given hash chain. -func createBlocksFromHashes(hashes []common.Hash) map[common.Hash]*types.Block { - blocks := make(map[common.Hash]*types.Block) - for i := 0; i < len(hashes); i++ { - parent := knownHash - if i < len(hashes)-1 { - parent = hashes[i+1] - } - blocks[hashes[i]] = createBlock(len(hashes)-i, parent, hashes[i]) - } - return blocks +// makeChain creates a chain of n blocks starting at and including +// parent. the returned hash chain is ordered head->parent. +func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) { + blocks := core.GenerateChain(parent, testdb, n, func(i int, gen *core.BlockGen) { + gen.SetCoinbase(common.Address{seed}) + }) + hashes := make([]common.Hash, n+1) + hashes[len(hashes)-1] = parent.Hash() + blockm := make(map[common.Hash]*types.Block, n+1) + blockm[parent.Hash()] = parent + for i, b := range blocks { + hashes[len(hashes)-i-2] = b.Hash() + blockm[b.Hash()] = b + } + return hashes, blockm +} + +// makeChainFork creates two chains of length n, such that h1[:f] and +// h2[:f] are different but have a common suffix of length n-f. +func makeChainFork(n, f int, parent *types.Block) (h1, h2 []common.Hash, b1, b2 map[common.Hash]*types.Block) { + // Create the common suffix. + h, b := makeChain(n-f-1, 0, parent) + // Create the forks. + h1, b1 = makeChain(f, 1, b[h[0]]) + h1 = append(h1, h[1:]...) + h2, b2 = makeChain(f, 2, b[h[0]]) + h2 = append(h2, h[1:]...) + for hash, block := range b { + b1[hash] = block + b2[hash] = block + } + return h1, h2, b1, b2 } // downloadTester is a test simulator for mocking out local block chain. @@ -84,8 +70,8 @@ type downloadTester struct { // newTester creates a new downloader test mocker. func newTester() *downloadTester { tester := &downloadTester{ - ownHashes: []common.Hash{knownHash}, - ownBlocks: map[common.Hash]*types.Block{knownHash: genesis}, + ownHashes: []common.Hash{genesis.Hash()}, + ownBlocks: map[common.Hash]*types.Block{genesis.Hash(): genesis}, peerHashes: make(map[string][]common.Hash), peerBlocks: make(map[string]map[common.Hash]*types.Block), } @@ -139,10 +125,9 @@ func (dl *downloadTester) newSlowPeer(id string, hashes []common.Hash, blocks ma // Assign the owned hashes and blocks to the peer (deep copy) dl.peerHashes[id] = make([]common.Hash, len(hashes)) copy(dl.peerHashes[id], hashes) - dl.peerBlocks[id] = make(map[common.Hash]*types.Block) for hash, block := range blocks { - dl.peerBlocks[id][hash] = copyBlock(block) + dl.peerBlocks[id][hash] = block } } return err @@ -213,8 +198,7 @@ func (dl *downloadTester) peerGetBlocksFn(id string, delay time.Duration) func([ func TestSynchronisation(t *testing.T) { // Create a small enough block chain to download and the tester targetBlocks := blockCacheLimit - 15 - hashes := createHashes(targetBlocks, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(targetBlocks, 0, genesis) tester := newTester() tester.newPeer("peer", hashes, blocks) @@ -245,8 +229,7 @@ func TestInactiveDownloader(t *testing.T) { func TestCancel(t *testing.T) { // Create a small enough block chain to download and the tester targetBlocks := blockCacheLimit - 15 - hashes := createHashes(targetBlocks, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(targetBlocks, 0, genesis) tester := newTester() tester.newPeer("peer", hashes, blocks) @@ -273,8 +256,7 @@ func TestCancel(t *testing.T) { func TestThrottling(t *testing.T) { // Create a long block chain to download and the tester targetBlocks := 8 * blockCacheLimit - hashes := createHashes(targetBlocks, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(targetBlocks, 0, genesis) tester := newTester() tester.newPeer("peer", hashes, blocks) @@ -330,9 +312,7 @@ func TestMultiSynchronisation(t *testing.T) { // Create various peers with various parts of the chain targetPeers := 16 targetBlocks := targetPeers*blockCacheLimit - 15 - - hashes := createHashes(targetBlocks, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(targetBlocks, 0, genesis) tester := newTester() for i := 0; i < targetPeers; i++ { @@ -365,9 +345,7 @@ func TestSlowSynchronisation(t *testing.T) { targetCycles := 2 targetBlocks := targetCycles*blockCacheLimit - 15 targetIODelay := time.Second - - hashes := createHashes(targetBlocks, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(targetBlocks, 0, genesis) tester.newSlowPeer("fast", hashes, blocks, 0) tester.newSlowPeer("slow", hashes, blocks, targetIODelay) @@ -392,14 +370,12 @@ func TestSlowSynchronisation(t *testing.T) { func TestNonExistingParentAttack(t *testing.T) { tester := newTester() - // Forge a single-link chain with a forged header - hashes := createHashes(1, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(1, 0, genesis) tester.newPeer("valid", hashes, blocks) - hashes = createHashes(1, knownHash) - blocks = createBlocksFromHashes(hashes) - blocks[hashes[0]].ParentHeaderHash = unknownHash + wrongblock := types.NewBlock(&types.Header{}, nil, nil, nil) + wrongblock.Td = blocks[hashes[0]].Td + hashes, blocks = makeChain(1, 0, wrongblock) tester.newPeer("attack", hashes, blocks) // Try and sync with the malicious node and check that it fails @@ -424,8 +400,7 @@ func TestRepeatingHashAttack(t *testing.T) { // TODO: Is this thing valid?? tester := newTester() // Create a valid chain, but drop the last link - hashes := createHashes(blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(blockCacheLimit, 0, genesis) tester.newPeer("valid", hashes, blocks) tester.newPeer("attack", hashes[:len(hashes)-1], blocks) @@ -455,11 +430,10 @@ func TestNonExistingBlockAttack(t *testing.T) { tester := newTester() // Create a valid chain, but forge the last link - hashes := createHashes(blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(blockCacheLimit, 0, genesis) tester.newPeer("valid", hashes, blocks) - hashes[len(hashes)/2] = unknownHash + hashes[len(hashes)/2] = common.Hash{} tester.newPeer("attack", hashes, blocks) // Try and sync with the malicious node and check that it fails @@ -478,8 +452,7 @@ func TestInvalidHashOrderAttack(t *testing.T) { tester := newTester() // Create a valid long chain, but reverse some hashes within - hashes := createHashes(4*blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(4*blockCacheLimit, 0, genesis) tester.newPeer("valid", hashes, blocks) chunk1 := make([]common.Hash, blockCacheLimit) @@ -509,11 +482,15 @@ func TestMadeupHashChainAttack(t *testing.T) { crossCheckCycle = 25 * time.Millisecond // Create a long chain of hashes without backing blocks - hashes := createHashes(4*blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(4*blockCacheLimit, 0, genesis) + + randomHashes := make([]common.Hash, 1024*blockCacheLimit) + for i := range randomHashes { + rand.Read(randomHashes[i][:]) + } tester.newPeer("valid", hashes, blocks) - tester.newPeer("attack", createHashes(1024*blockCacheLimit, knownHash), nil) + tester.newPeer("attack", randomHashes, nil) // Try and sync with the malicious node and check that it fails if err := tester.sync("attack"); err != errCrossCheckFailed { @@ -531,12 +508,16 @@ func TestMadeupHashChainAttack(t *testing.T) { // one by one prevents reliable block/parent verification. func TestMadeupHashChainDrippingAttack(t *testing.T) { // Create a random chain of hashes to drip - hashes := createHashes(16*blockCacheLimit, knownHash) + randomHashes := make([]common.Hash, 16*blockCacheLimit) + for i := range randomHashes { + rand.Read(randomHashes[i][:]) + } + randomHashes[len(randomHashes)-1] = genesis.Hash() tester := newTester() // Try and sync with the attacker, one hash at a time tester.maxHashFetch = 1 - tester.newPeer("attack", hashes, nil) + tester.newPeer("attack", randomHashes, nil) if err := tester.sync("attack"); err != errStallingPeer { t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errStallingPeer) } @@ -552,9 +533,7 @@ func TestMadeupBlockChainAttack(t *testing.T) { crossCheckCycle = 25 * time.Millisecond // Create a long chain of blocks and simulate an invalid chain by dropping every second - hashes := createHashes(16*blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) - + hashes, blocks := makeChain(16*blockCacheLimit, 0, genesis) gapped := make([]common.Hash, len(hashes)/2) for i := 0; i < len(gapped); i++ { gapped[i] = hashes[2*i] @@ -575,65 +554,26 @@ func TestMadeupBlockChainAttack(t *testing.T) { } } -// Advanced form of the above forged blockchain attack, where not only does the -// attacker make up a valid hashes for random blocks, but also forges the block -// parents to point to existing hashes. -func TestMadeupParentBlockChainAttack(t *testing.T) { - tester := newTester() - - defaultBlockTTL := blockSoftTTL - defaultCrossCheckCycle := crossCheckCycle - - blockSoftTTL = 100 * time.Millisecond - crossCheckCycle = 25 * time.Millisecond - - // Create a long chain of blocks and simulate an invalid chain by dropping every second - hashes := createHashes(16*blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) - tester.newPeer("valid", hashes, blocks) - - for _, block := range blocks { - block.ParentHeaderHash = knownHash // Simulate pointing to already known hash - } - tester.newPeer("attack", hashes, blocks) - - // Try and sync with the malicious node and check that it fails - if err := tester.sync("attack"); err != errCrossCheckFailed { - t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errCrossCheckFailed) - } - // Ensure that a valid chain can still pass sync - blockSoftTTL = defaultBlockTTL - crossCheckCycle = defaultCrossCheckCycle - - if err := tester.sync("valid"); err != nil { - t.Fatalf("failed to synchronise blocks: %v", err) - } -} - -// Tests that if one/multiple malicious peers try to feed a banned blockchain to +// tests that if one/multiple malicious peers try to feed a banned blockchain to // the downloader, it will not keep refetching the same chain indefinitely, but -// gradually block pieces of it, until it's head is also blocked. +// gradually block pieces of it, until its head is also blocked. func TestBannedChainStarvationAttack(t *testing.T) { - // Create the tester and ban the selected hash - tester := newTester() - tester.downloader.banned.Add(bannedHash) + n := 8 * blockCacheLimit + fork := n/2 - 23 + hashes, forkHashes, blocks, forkBlocks := makeChainFork(n, fork, genesis) - // Construct a valid chain, for it and ban the fork - hashes := createHashes(8*blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) + // Create the tester and ban the selected hash. + tester := newTester() + tester.downloader.banned.Add(forkHashes[fork-1]) tester.newPeer("valid", hashes, blocks) - - fork := len(hashes)/2 - 23 - hashes = append(createHashes(4*blockCacheLimit, bannedHash), hashes[fork:]...) - blocks = createBlocksFromHashes(hashes) - tester.newPeer("attack", hashes, blocks) + tester.newPeer("attack", forkHashes, forkBlocks) // Iteratively try to sync, and verify that the banned hash list grows until // the head of the invalid chain is blocked too. for banned := tester.downloader.banned.Size(); ; { // Try to sync with the attacker, check hash chain failure if err := tester.sync("attack"); err != errInvalidChain { - if tester.downloader.banned.Has(hashes[0]) && err == errBannedHead { + if tester.downloader.banned.Has(forkHashes[0]) && err == errBannedHead { break } t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errInvalidChain) @@ -646,7 +586,7 @@ func TestBannedChainStarvationAttack(t *testing.T) { banned = bans } // Check that after banning an entire chain, bad peers get dropped - if err := tester.newPeer("new attacker", hashes, blocks); err != errBannedHead { + if err := tester.newPeer("new attacker", forkHashes, forkBlocks); err != errBannedHead { t.Fatalf("peer registration mismatch: have %v, want %v", err, errBannedHead) } if peer := tester.downloader.peers.Peer("new attacker"); peer != nil { @@ -662,9 +602,14 @@ func TestBannedChainStarvationAttack(t *testing.T) { // gradually banned, it will have an upper limit on the consumed memory and also // the origin bad hashes will not be evacuated. func TestBannedChainMemoryExhaustionAttack(t *testing.T) { - // Create the tester and ban the selected hash + // Construct a banned chain with more chunks than the ban limit + n := 8 * blockCacheLimit + fork := n/2 - 23 + hashes, forkHashes, blocks, forkBlocks := makeChainFork(n, fork, genesis) + + // Create the tester and ban the root hash of the fork. tester := newTester() - tester.downloader.banned.Add(bannedHash) + tester.downloader.banned.Add(forkHashes[fork-1]) // Reduce the test size a bit defaultMaxBlockFetch := MaxBlockFetch @@ -673,15 +618,8 @@ func TestBannedChainMemoryExhaustionAttack(t *testing.T) { MaxBlockFetch = 4 maxBannedHashes = 256 - // Construct a banned chain with more chunks than the ban limit - hashes := createHashes(8*blockCacheLimit, knownHash) - blocks := createBlocksFromHashes(hashes) tester.newPeer("valid", hashes, blocks) - - fork := len(hashes)/2 - 23 - hashes = append(createHashes(maxBannedHashes*MaxBlockFetch, bannedHash), hashes[fork:]...) - blocks = createBlocksFromHashes(hashes) - tester.newPeer("attack", hashes, blocks) + tester.newPeer("attack", forkHashes, forkBlocks) // Iteratively try to sync, and verify that the banned hash list grows until // the head of the invalid chain is blocked too. @@ -690,8 +628,8 @@ func TestBannedChainMemoryExhaustionAttack(t *testing.T) { if err := tester.sync("attack"); err != errInvalidChain { t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errInvalidChain) } - // Short circuit if the entire chain was banned - if tester.downloader.banned.Has(hashes[0]) { + // Short circuit if the entire chain was banned. + if tester.downloader.banned.Has(forkHashes[0]) { break } // Otherwise ensure we never exceed the memory allowance and the hard coded bans are untouched @@ -722,8 +660,7 @@ func TestBannedChainMemoryExhaustionAttack(t *testing.T) { func TestOverlappingDeliveryAttack(t *testing.T) { // Create an arbitrary batch of blocks ( < cache-size not to block) targetBlocks := blockCacheLimit - 23 - hashes := createHashes(targetBlocks, knownHash) - blocks := createBlocksFromHashes(hashes) + hashes, blocks := makeChain(targetBlocks, 0, genesis) // Register an attacker that always returns non-requested blocks too tester := newTester() @@ -775,7 +712,7 @@ func TestHashAttackerDropping(t *testing.T) { for i, tt := range tests { // Register a new peer and ensure it's presence id := fmt.Sprintf("test %d", i) - if err := tester.newPeer(id, []common.Hash{knownHash}, nil); err != nil { + if err := tester.newPeer(id, []common.Hash{genesis.Hash()}, nil); err != nil { t.Fatalf("test %d: failed to register new peer: %v", i, err) } if _, ok := tester.peerHashes[id]; !ok { @@ -784,7 +721,7 @@ func TestHashAttackerDropping(t *testing.T) { // Simulate a synchronisation and check the required result tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.result } - tester.downloader.Synchronise(id, knownHash) + tester.downloader.Synchronise(id, genesis.Hash()) if _, ok := tester.peerHashes[id]; !ok != tt.drop { t.Errorf("test %d: peer drop mismatch for %v: have %v, want %v", i, tt.result, !ok, tt.drop) } @@ -797,7 +734,10 @@ func TestBlockAttackerDropping(t *testing.T) { tests := []struct { failure bool drop bool - }{{true, true}, {false, false}} + }{ + {true, true}, + {false, false}, + } // Run the tests and check disconnection status tester := newTester() @@ -811,9 +751,10 @@ func TestBlockAttackerDropping(t *testing.T) { t.Fatalf("test %d: registered peer not found", i) } // Assemble a good or bad block, depending of the test - raw := createBlock(1, knownHash, common.Hash{}) + raw := core.GenerateChain(genesis, testdb, 1, nil)[0] if tt.failure { - raw = createBlock(1, unknownHash, common.Hash{}) + parent := types.NewBlock(&types.Header{}, nil, nil, nil) + raw = core.GenerateChain(parent, testdb, 1, nil)[0] } block := &Block{OriginPeer: id, RawBlock: raw} |