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).
This commit is contained in:
parent
ceaf1c080b
commit
e0e5f74776
@ -1,7 +1,7 @@
|
|||||||
package downloader
|
package downloader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"crypto/rand"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -12,61 +12,47 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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}
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
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}
|
genesis = core.GenesisBlockForTesting(testdb, common.Address{}, big.NewInt(0))
|
||||||
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)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// idCounter is used by the createHashes method the generate deterministic but unique hashes
|
// makeChain creates a chain of n blocks starting at and including
|
||||||
var idCounter = int64(2) // #1 is the genesis block
|
// 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) {
|
||||||
// createHashes generates a batch of hashes rooted at a specific point in the chain.
|
blocks := core.GenerateChain(parent, testdb, n, func(i int, gen *core.BlockGen) {
|
||||||
func createHashes(amount int, root common.Hash) (hashes []common.Hash) {
|
gen.SetCoinbase(common.Address{seed})
|
||||||
hashes = make([]common.Hash, amount+1)
|
})
|
||||||
hashes[len(hashes)-1] = root
|
hashes := make([]common.Hash, n+1)
|
||||||
|
hashes[len(hashes)-1] = parent.Hash()
|
||||||
for i := 0; i < len(hashes)-1; i++ {
|
blockm := make(map[common.Hash]*types.Block, n+1)
|
||||||
binary.BigEndian.PutUint64(hashes[i][:8], uint64(idCounter))
|
blockm[parent.Hash()] = parent
|
||||||
idCounter++
|
for i, b := range blocks {
|
||||||
|
hashes[len(hashes)-i-2] = b.Hash()
|
||||||
|
blockm[b.Hash()] = b
|
||||||
}
|
}
|
||||||
return
|
return hashes, blockm
|
||||||
}
|
}
|
||||||
|
|
||||||
// createBlock assembles a new block at the given chain height.
|
// makeChainFork creates two chains of length n, such that h1[:f] and
|
||||||
func createBlock(i int, parent, hash common.Hash) *types.Block {
|
// h2[:f] are different but have a common suffix of length n-f.
|
||||||
header := &types.Header{
|
func makeChainFork(n, f int, parent *types.Block) (h1, h2 []common.Hash, b1, b2 map[common.Hash]*types.Block) {
|
||||||
Hash: hash,
|
// Create the common suffix.
|
||||||
Number: big.NewInt(int64(i))
|
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
|
||||||
}
|
}
|
||||||
block := types.NewBlockWithHeader(header)
|
return h1, h2, b1, b2
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// downloadTester is a test simulator for mocking out local block chain.
|
// 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.
|
// newTester creates a new downloader test mocker.
|
||||||
func newTester() *downloadTester {
|
func newTester() *downloadTester {
|
||||||
tester := &downloadTester{
|
tester := &downloadTester{
|
||||||
ownHashes: []common.Hash{knownHash},
|
ownHashes: []common.Hash{genesis.Hash()},
|
||||||
ownBlocks: map[common.Hash]*types.Block{knownHash: genesis},
|
ownBlocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
||||||
peerHashes: make(map[string][]common.Hash),
|
peerHashes: make(map[string][]common.Hash),
|
||||||
peerBlocks: make(map[string]map[common.Hash]*types.Block),
|
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)
|
// Assign the owned hashes and blocks to the peer (deep copy)
|
||||||
dl.peerHashes[id] = make([]common.Hash, len(hashes))
|
dl.peerHashes[id] = make([]common.Hash, len(hashes))
|
||||||
copy(dl.peerHashes[id], hashes)
|
copy(dl.peerHashes[id], hashes)
|
||||||
|
|
||||||
dl.peerBlocks[id] = make(map[common.Hash]*types.Block)
|
dl.peerBlocks[id] = make(map[common.Hash]*types.Block)
|
||||||
for hash, block := range blocks {
|
for hash, block := range blocks {
|
||||||
dl.peerBlocks[id][hash] = copyBlock(block)
|
dl.peerBlocks[id][hash] = block
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@ -213,8 +198,7 @@ func (dl *downloadTester) peerGetBlocksFn(id string, delay time.Duration) func([
|
|||||||
func TestSynchronisation(t *testing.T) {
|
func TestSynchronisation(t *testing.T) {
|
||||||
// Create a small enough block chain to download and the tester
|
// Create a small enough block chain to download and the tester
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheLimit - 15
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
tester.newPeer("peer", hashes, blocks)
|
tester.newPeer("peer", hashes, blocks)
|
||||||
@ -245,8 +229,7 @@ func TestInactiveDownloader(t *testing.T) {
|
|||||||
func TestCancel(t *testing.T) {
|
func TestCancel(t *testing.T) {
|
||||||
// Create a small enough block chain to download and the tester
|
// Create a small enough block chain to download and the tester
|
||||||
targetBlocks := blockCacheLimit - 15
|
targetBlocks := blockCacheLimit - 15
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
tester.newPeer("peer", hashes, blocks)
|
tester.newPeer("peer", hashes, blocks)
|
||||||
@ -273,8 +256,7 @@ func TestCancel(t *testing.T) {
|
|||||||
func TestThrottling(t *testing.T) {
|
func TestThrottling(t *testing.T) {
|
||||||
// Create a long block chain to download and the tester
|
// Create a long block chain to download and the tester
|
||||||
targetBlocks := 8 * blockCacheLimit
|
targetBlocks := 8 * blockCacheLimit
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
tester.newPeer("peer", hashes, blocks)
|
tester.newPeer("peer", hashes, blocks)
|
||||||
@ -330,9 +312,7 @@ func TestMultiSynchronisation(t *testing.T) {
|
|||||||
// Create various peers with various parts of the chain
|
// Create various peers with various parts of the chain
|
||||||
targetPeers := 16
|
targetPeers := 16
|
||||||
targetBlocks := targetPeers*blockCacheLimit - 15
|
targetBlocks := targetPeers*blockCacheLimit - 15
|
||||||
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
for i := 0; i < targetPeers; i++ {
|
for i := 0; i < targetPeers; i++ {
|
||||||
@ -365,9 +345,7 @@ func TestSlowSynchronisation(t *testing.T) {
|
|||||||
targetCycles := 2
|
targetCycles := 2
|
||||||
targetBlocks := targetCycles*blockCacheLimit - 15
|
targetBlocks := targetCycles*blockCacheLimit - 15
|
||||||
targetIODelay := time.Second
|
targetIODelay := time.Second
|
||||||
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester.newSlowPeer("fast", hashes, blocks, 0)
|
tester.newSlowPeer("fast", hashes, blocks, 0)
|
||||||
tester.newSlowPeer("slow", hashes, blocks, targetIODelay)
|
tester.newSlowPeer("slow", hashes, blocks, targetIODelay)
|
||||||
@ -392,14 +370,12 @@ func TestSlowSynchronisation(t *testing.T) {
|
|||||||
func TestNonExistingParentAttack(t *testing.T) {
|
func TestNonExistingParentAttack(t *testing.T) {
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
|
|
||||||
// Forge a single-link chain with a forged header
|
hashes, blocks := makeChain(1, 0, genesis)
|
||||||
hashes := createHashes(1, knownHash)
|
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("valid", hashes, blocks)
|
tester.newPeer("valid", hashes, blocks)
|
||||||
|
|
||||||
hashes = createHashes(1, knownHash)
|
wrongblock := types.NewBlock(&types.Header{}, nil, nil, nil)
|
||||||
blocks = createBlocksFromHashes(hashes)
|
wrongblock.Td = blocks[hashes[0]].Td
|
||||||
blocks[hashes[0]].ParentHeaderHash = unknownHash
|
hashes, blocks = makeChain(1, 0, wrongblock)
|
||||||
tester.newPeer("attack", hashes, blocks)
|
tester.newPeer("attack", hashes, blocks)
|
||||||
|
|
||||||
// Try and sync with the malicious node and check that it fails
|
// 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()
|
tester := newTester()
|
||||||
|
|
||||||
// Create a valid chain, but drop the last link
|
// Create a valid chain, but drop the last link
|
||||||
hashes := createHashes(blockCacheLimit, knownHash)
|
hashes, blocks := makeChain(blockCacheLimit, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("valid", hashes, blocks)
|
tester.newPeer("valid", hashes, blocks)
|
||||||
tester.newPeer("attack", hashes[:len(hashes)-1], blocks)
|
tester.newPeer("attack", hashes[:len(hashes)-1], blocks)
|
||||||
|
|
||||||
@ -455,11 +430,10 @@ func TestNonExistingBlockAttack(t *testing.T) {
|
|||||||
tester := newTester()
|
tester := newTester()
|
||||||
|
|
||||||
// Create a valid chain, but forge the last link
|
// Create a valid chain, but forge the last link
|
||||||
hashes := createHashes(blockCacheLimit, knownHash)
|
hashes, blocks := makeChain(blockCacheLimit, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("valid", hashes, blocks)
|
tester.newPeer("valid", hashes, blocks)
|
||||||
|
|
||||||
hashes[len(hashes)/2] = unknownHash
|
hashes[len(hashes)/2] = common.Hash{}
|
||||||
tester.newPeer("attack", hashes, blocks)
|
tester.newPeer("attack", hashes, blocks)
|
||||||
|
|
||||||
// Try and sync with the malicious node and check that it fails
|
// Try and sync with the malicious node and check that it fails
|
||||||
@ -478,8 +452,7 @@ func TestInvalidHashOrderAttack(t *testing.T) {
|
|||||||
tester := newTester()
|
tester := newTester()
|
||||||
|
|
||||||
// Create a valid long chain, but reverse some hashes within
|
// Create a valid long chain, but reverse some hashes within
|
||||||
hashes := createHashes(4*blockCacheLimit, knownHash)
|
hashes, blocks := makeChain(4*blockCacheLimit, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("valid", hashes, blocks)
|
tester.newPeer("valid", hashes, blocks)
|
||||||
|
|
||||||
chunk1 := make([]common.Hash, blockCacheLimit)
|
chunk1 := make([]common.Hash, blockCacheLimit)
|
||||||
@ -509,11 +482,15 @@ func TestMadeupHashChainAttack(t *testing.T) {
|
|||||||
crossCheckCycle = 25 * time.Millisecond
|
crossCheckCycle = 25 * time.Millisecond
|
||||||
|
|
||||||
// Create a long chain of hashes without backing blocks
|
// Create a long chain of hashes without backing blocks
|
||||||
hashes := createHashes(4*blockCacheLimit, knownHash)
|
hashes, blocks := makeChain(4*blockCacheLimit, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
randomHashes := make([]common.Hash, 1024*blockCacheLimit)
|
||||||
|
for i := range randomHashes {
|
||||||
|
rand.Read(randomHashes[i][:])
|
||||||
|
}
|
||||||
|
|
||||||
tester.newPeer("valid", hashes, blocks)
|
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
|
// Try and sync with the malicious node and check that it fails
|
||||||
if err := tester.sync("attack"); err != errCrossCheckFailed {
|
if err := tester.sync("attack"); err != errCrossCheckFailed {
|
||||||
@ -531,12 +508,16 @@ func TestMadeupHashChainAttack(t *testing.T) {
|
|||||||
// one by one prevents reliable block/parent verification.
|
// one by one prevents reliable block/parent verification.
|
||||||
func TestMadeupHashChainDrippingAttack(t *testing.T) {
|
func TestMadeupHashChainDrippingAttack(t *testing.T) {
|
||||||
// Create a random chain of hashes to drip
|
// 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()
|
tester := newTester()
|
||||||
|
|
||||||
// Try and sync with the attacker, one hash at a time
|
// Try and sync with the attacker, one hash at a time
|
||||||
tester.maxHashFetch = 1
|
tester.maxHashFetch = 1
|
||||||
tester.newPeer("attack", hashes, nil)
|
tester.newPeer("attack", randomHashes, nil)
|
||||||
if err := tester.sync("attack"); err != errStallingPeer {
|
if err := tester.sync("attack"); err != errStallingPeer {
|
||||||
t.Fatalf("synchronisation error mismatch: have %v, want %v", 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
|
crossCheckCycle = 25 * time.Millisecond
|
||||||
|
|
||||||
// Create a long chain of blocks and simulate an invalid chain by dropping every second
|
// Create a long chain of blocks and simulate an invalid chain by dropping every second
|
||||||
hashes := createHashes(16*blockCacheLimit, knownHash)
|
hashes, blocks := makeChain(16*blockCacheLimit, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
gapped := make([]common.Hash, len(hashes)/2)
|
gapped := make([]common.Hash, len(hashes)/2)
|
||||||
for i := 0; i < len(gapped); i++ {
|
for i := 0; i < len(gapped); i++ {
|
||||||
gapped[i] = hashes[2*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
|
// tests that if one/multiple malicious peers try to feed a banned blockchain to
|
||||||
// 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
|
|
||||||
// the downloader, it will not keep refetching the same chain indefinitely, but
|
// 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) {
|
func TestBannedChainStarvationAttack(t *testing.T) {
|
||||||
// Create the tester and ban the selected hash
|
n := 8 * blockCacheLimit
|
||||||
|
fork := n/2 - 23
|
||||||
|
hashes, forkHashes, blocks, forkBlocks := makeChainFork(n, fork, genesis)
|
||||||
|
|
||||||
|
// Create the tester and ban the selected hash.
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
tester.downloader.banned.Add(bannedHash)
|
tester.downloader.banned.Add(forkHashes[fork-1])
|
||||||
|
|
||||||
// Construct a valid chain, for it and ban the fork
|
|
||||||
hashes := createHashes(8*blockCacheLimit, knownHash)
|
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("valid", hashes, blocks)
|
tester.newPeer("valid", hashes, blocks)
|
||||||
|
tester.newPeer("attack", forkHashes, forkBlocks)
|
||||||
fork := len(hashes)/2 - 23
|
|
||||||
hashes = append(createHashes(4*blockCacheLimit, bannedHash), hashes[fork:]...)
|
|
||||||
blocks = createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("attack", hashes, blocks)
|
|
||||||
|
|
||||||
// Iteratively try to sync, and verify that the banned hash list grows until
|
// Iteratively try to sync, and verify that the banned hash list grows until
|
||||||
// the head of the invalid chain is blocked too.
|
// the head of the invalid chain is blocked too.
|
||||||
for banned := tester.downloader.banned.Size(); ; {
|
for banned := tester.downloader.banned.Size(); ; {
|
||||||
// Try to sync with the attacker, check hash chain failure
|
// Try to sync with the attacker, check hash chain failure
|
||||||
if err := tester.sync("attack"); err != errInvalidChain {
|
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
|
break
|
||||||
}
|
}
|
||||||
t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errInvalidChain)
|
t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errInvalidChain)
|
||||||
@ -646,7 +586,7 @@ func TestBannedChainStarvationAttack(t *testing.T) {
|
|||||||
banned = bans
|
banned = bans
|
||||||
}
|
}
|
||||||
// Check that after banning an entire chain, bad peers get dropped
|
// 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)
|
t.Fatalf("peer registration mismatch: have %v, want %v", err, errBannedHead)
|
||||||
}
|
}
|
||||||
if peer := tester.downloader.peers.Peer("new attacker"); peer != nil {
|
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
|
// gradually banned, it will have an upper limit on the consumed memory and also
|
||||||
// the origin bad hashes will not be evacuated.
|
// the origin bad hashes will not be evacuated.
|
||||||
func TestBannedChainMemoryExhaustionAttack(t *testing.T) {
|
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 := newTester()
|
||||||
tester.downloader.banned.Add(bannedHash)
|
tester.downloader.banned.Add(forkHashes[fork-1])
|
||||||
|
|
||||||
// Reduce the test size a bit
|
// Reduce the test size a bit
|
||||||
defaultMaxBlockFetch := MaxBlockFetch
|
defaultMaxBlockFetch := MaxBlockFetch
|
||||||
@ -673,15 +618,8 @@ func TestBannedChainMemoryExhaustionAttack(t *testing.T) {
|
|||||||
MaxBlockFetch = 4
|
MaxBlockFetch = 4
|
||||||
maxBannedHashes = 256
|
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)
|
tester.newPeer("valid", hashes, blocks)
|
||||||
|
tester.newPeer("attack", forkHashes, forkBlocks)
|
||||||
fork := len(hashes)/2 - 23
|
|
||||||
hashes = append(createHashes(maxBannedHashes*MaxBlockFetch, bannedHash), hashes[fork:]...)
|
|
||||||
blocks = createBlocksFromHashes(hashes)
|
|
||||||
tester.newPeer("attack", hashes, blocks)
|
|
||||||
|
|
||||||
// Iteratively try to sync, and verify that the banned hash list grows until
|
// Iteratively try to sync, and verify that the banned hash list grows until
|
||||||
// the head of the invalid chain is blocked too.
|
// 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 {
|
if err := tester.sync("attack"); err != errInvalidChain {
|
||||||
t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errInvalidChain)
|
t.Fatalf("synchronisation error mismatch: have %v, want %v", err, errInvalidChain)
|
||||||
}
|
}
|
||||||
// Short circuit if the entire chain was banned
|
// Short circuit if the entire chain was banned.
|
||||||
if tester.downloader.banned.Has(hashes[0]) {
|
if tester.downloader.banned.Has(forkHashes[0]) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
// Otherwise ensure we never exceed the memory allowance and the hard coded bans are untouched
|
// 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) {
|
func TestOverlappingDeliveryAttack(t *testing.T) {
|
||||||
// Create an arbitrary batch of blocks ( < cache-size not to block)
|
// Create an arbitrary batch of blocks ( < cache-size not to block)
|
||||||
targetBlocks := blockCacheLimit - 23
|
targetBlocks := blockCacheLimit - 23
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
// Register an attacker that always returns non-requested blocks too
|
// Register an attacker that always returns non-requested blocks too
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
@ -775,7 +712,7 @@ func TestHashAttackerDropping(t *testing.T) {
|
|||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
// Register a new peer and ensure it's presence
|
// Register a new peer and ensure it's presence
|
||||||
id := fmt.Sprintf("test %d", i)
|
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)
|
t.Fatalf("test %d: failed to register new peer: %v", i, err)
|
||||||
}
|
}
|
||||||
if _, ok := tester.peerHashes[id]; !ok {
|
if _, ok := tester.peerHashes[id]; !ok {
|
||||||
@ -784,7 +721,7 @@ func TestHashAttackerDropping(t *testing.T) {
|
|||||||
// Simulate a synchronisation and check the required result
|
// Simulate a synchronisation and check the required result
|
||||||
tester.downloader.synchroniseMock = func(string, common.Hash) error { return tt.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 {
|
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)
|
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 {
|
tests := []struct {
|
||||||
failure bool
|
failure bool
|
||||||
drop bool
|
drop bool
|
||||||
}{{true, true}, {false, false}}
|
}{
|
||||||
|
{true, true},
|
||||||
|
{false, false},
|
||||||
|
}
|
||||||
|
|
||||||
// Run the tests and check disconnection status
|
// Run the tests and check disconnection status
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
@ -811,9 +751,10 @@ func TestBlockAttackerDropping(t *testing.T) {
|
|||||||
t.Fatalf("test %d: registered peer not found", i)
|
t.Fatalf("test %d: registered peer not found", i)
|
||||||
}
|
}
|
||||||
// Assemble a good or bad block, depending of the test
|
// 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 {
|
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}
|
block := &Block{OriginPeer: id, RawBlock: raw}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package fetcher
|
package fetcher
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
@ -10,58 +9,32 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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}
|
testdb, _ = ethdb.NewMemDatabase()
|
||||||
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}
|
genesis = core.GenesisBlockForTesting(testdb, common.Address{}, big.NewInt(0))
|
||||||
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}
|
unknownBlock = types.NewBlock(&types.Header{}, nil, nil, nil)
|
||||||
|
|
||||||
genesis = createBlock(1, common.Hash{}, knownHash)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// idCounter is used by the createHashes method the generate deterministic but unique hashes
|
// makeChain creates a chain of n blocks starting at and including parent.
|
||||||
var idCounter = int64(2) // #1 is the genesis block
|
// the returned hash chain is ordered head->parent.
|
||||||
|
func makeChain(n int, seed byte, parent *types.Block) ([]common.Hash, map[common.Hash]*types.Block) {
|
||||||
// createHashes generates a batch of hashes rooted at a specific point in the chain.
|
blocks := core.GenerateChain(parent, testdb, n, func(i int, gen *core.BlockGen) {
|
||||||
func createHashes(amount int, root common.Hash) (hashes []common.Hash) {
|
gen.SetCoinbase(common.Address{seed})
|
||||||
hashes = make([]common.Hash, amount+1)
|
})
|
||||||
hashes[len(hashes)-1] = root
|
hashes := make([]common.Hash, n+1)
|
||||||
|
hashes[len(hashes)-1] = parent.Hash()
|
||||||
for i := 0; i < len(hashes)-1; i++ {
|
blockm := make(map[common.Hash]*types.Block, n+1)
|
||||||
binary.BigEndian.PutUint64(hashes[i][:8], uint64(idCounter))
|
blockm[parent.Hash()] = parent
|
||||||
idCounter++
|
for i, b := range blocks {
|
||||||
|
hashes[len(hashes)-i-2] = b.Hash()
|
||||||
|
blockm[b.Hash()] = b
|
||||||
}
|
}
|
||||||
return
|
return hashes, blockm
|
||||||
}
|
|
||||||
|
|
||||||
// createBlock assembles a new block at the given chain height.
|
|
||||||
func createBlock(i int, parent, hash common.Hash) *types.Block {
|
|
||||||
header := &types.Header{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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetcherTester is a test simulator for mocking out local block chain.
|
// fetcherTester is a test simulator for mocking out local block chain.
|
||||||
@ -77,8 +50,8 @@ type fetcherTester struct {
|
|||||||
// newTester creates a new fetcher test mocker.
|
// newTester creates a new fetcher test mocker.
|
||||||
func newTester() *fetcherTester {
|
func newTester() *fetcherTester {
|
||||||
tester := &fetcherTester{
|
tester := &fetcherTester{
|
||||||
hashes: []common.Hash{knownHash},
|
hashes: []common.Hash{genesis.Hash()},
|
||||||
blocks: map[common.Hash]*types.Block{knownHash: genesis},
|
blocks: map[common.Hash]*types.Block{genesis.Hash(): genesis},
|
||||||
}
|
}
|
||||||
tester.fetcher = New(tester.getBlock, tester.verifyBlock, tester.broadcastBlock, tester.chainHeight, tester.insertChain, tester.dropPeer)
|
tester.fetcher = New(tester.getBlock, tester.verifyBlock, tester.broadcastBlock, tester.chainHeight, tester.insertChain, tester.dropPeer)
|
||||||
tester.fetcher.Start()
|
tester.fetcher.Start()
|
||||||
@ -138,10 +111,9 @@ func (f *fetcherTester) dropPeer(peer string) {
|
|||||||
|
|
||||||
// peerFetcher retrieves a fetcher associated with a simulated peer.
|
// peerFetcher retrieves a fetcher associated with a simulated peer.
|
||||||
func (f *fetcherTester) makeFetcher(blocks map[common.Hash]*types.Block) blockRequesterFn {
|
func (f *fetcherTester) makeFetcher(blocks map[common.Hash]*types.Block) blockRequesterFn {
|
||||||
// Copy all the blocks to ensure they are not tampered with
|
|
||||||
closure := make(map[common.Hash]*types.Block)
|
closure := make(map[common.Hash]*types.Block)
|
||||||
for hash, block := range blocks {
|
for hash, block := range blocks {
|
||||||
closure[hash] = copyBlock(block)
|
closure[hash] = block
|
||||||
}
|
}
|
||||||
// Create a function that returns blocks from the closure
|
// Create a function that returns blocks from the closure
|
||||||
return func(hashes []common.Hash) error {
|
return func(hashes []common.Hash) error {
|
||||||
@ -195,8 +167,7 @@ func verifyImportDone(t *testing.T, imported chan *types.Block) {
|
|||||||
func TestSequentialAnnouncements(t *testing.T) {
|
func TestSequentialAnnouncements(t *testing.T) {
|
||||||
// Create a chain of blocks to import
|
// Create a chain of blocks to import
|
||||||
targetBlocks := 4 * hashLimit
|
targetBlocks := 4 * hashLimit
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
fetcher := tester.makeFetcher(blocks)
|
fetcher := tester.makeFetcher(blocks)
|
||||||
@ -217,8 +188,7 @@ func TestSequentialAnnouncements(t *testing.T) {
|
|||||||
func TestConcurrentAnnouncements(t *testing.T) {
|
func TestConcurrentAnnouncements(t *testing.T) {
|
||||||
// Create a chain of blocks to import
|
// Create a chain of blocks to import
|
||||||
targetBlocks := 4 * hashLimit
|
targetBlocks := 4 * hashLimit
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
// Assemble a tester with a built in counter for the requests
|
// Assemble a tester with a built in counter for the requests
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
@ -253,8 +223,7 @@ func TestConcurrentAnnouncements(t *testing.T) {
|
|||||||
func TestOverlappingAnnouncements(t *testing.T) {
|
func TestOverlappingAnnouncements(t *testing.T) {
|
||||||
// Create a chain of blocks to import
|
// Create a chain of blocks to import
|
||||||
targetBlocks := 4 * hashLimit
|
targetBlocks := 4 * hashLimit
|
||||||
hashes := createHashes(targetBlocks, knownHash)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
fetcher := tester.makeFetcher(blocks)
|
fetcher := tester.makeFetcher(blocks)
|
||||||
@ -280,8 +249,7 @@ func TestOverlappingAnnouncements(t *testing.T) {
|
|||||||
// Tests that announces already being retrieved will not be duplicated.
|
// Tests that announces already being retrieved will not be duplicated.
|
||||||
func TestPendingDeduplication(t *testing.T) {
|
func TestPendingDeduplication(t *testing.T) {
|
||||||
// Create a hash and corresponding block
|
// Create a hash and corresponding block
|
||||||
hashes := createHashes(1, knownHash)
|
hashes, blocks := makeChain(1, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
// Assemble a tester with a built in counter and delayed fetcher
|
// Assemble a tester with a built in counter and delayed fetcher
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
@ -319,9 +287,9 @@ func TestPendingDeduplication(t *testing.T) {
|
|||||||
// imported when all the gaps are filled in.
|
// imported when all the gaps are filled in.
|
||||||
func TestRandomArrivalImport(t *testing.T) {
|
func TestRandomArrivalImport(t *testing.T) {
|
||||||
// Create a chain of blocks to import, and choose one to delay
|
// Create a chain of blocks to import, and choose one to delay
|
||||||
hashes := createHashes(maxQueueDist, knownHash)
|
targetBlocks := maxQueueDist
|
||||||
blocks := createBlocksFromHashes(hashes)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
skip := maxQueueDist / 2
|
skip := targetBlocks / 2
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
fetcher := tester.makeFetcher(blocks)
|
fetcher := tester.makeFetcher(blocks)
|
||||||
@ -345,9 +313,9 @@ func TestRandomArrivalImport(t *testing.T) {
|
|||||||
// are correctly schedule, filling and import queue gaps.
|
// are correctly schedule, filling and import queue gaps.
|
||||||
func TestQueueGapFill(t *testing.T) {
|
func TestQueueGapFill(t *testing.T) {
|
||||||
// Create a chain of blocks to import, and choose one to not announce at all
|
// Create a chain of blocks to import, and choose one to not announce at all
|
||||||
hashes := createHashes(maxQueueDist, knownHash)
|
targetBlocks := maxQueueDist
|
||||||
blocks := createBlocksFromHashes(hashes)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
skip := maxQueueDist / 2
|
skip := targetBlocks / 2
|
||||||
|
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
fetcher := tester.makeFetcher(blocks)
|
fetcher := tester.makeFetcher(blocks)
|
||||||
@ -371,8 +339,7 @@ func TestQueueGapFill(t *testing.T) {
|
|||||||
// announces, etc) do not get scheduled for import multiple times.
|
// announces, etc) do not get scheduled for import multiple times.
|
||||||
func TestImportDeduplication(t *testing.T) {
|
func TestImportDeduplication(t *testing.T) {
|
||||||
// Create two blocks to import (one for duplication, the other for stalling)
|
// Create two blocks to import (one for duplication, the other for stalling)
|
||||||
hashes := createHashes(2, knownHash)
|
hashes, blocks := makeChain(2, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
// Create the tester and wrap the importer with a counter
|
// Create the tester and wrap the importer with a counter
|
||||||
tester := newTester()
|
tester := newTester()
|
||||||
@ -410,9 +377,7 @@ func TestImportDeduplication(t *testing.T) {
|
|||||||
// discarded no prevent wasting resources on useless blocks from faulty peers.
|
// discarded no prevent wasting resources on useless blocks from faulty peers.
|
||||||
func TestDistantDiscarding(t *testing.T) {
|
func TestDistantDiscarding(t *testing.T) {
|
||||||
// Create a long chain to import
|
// Create a long chain to import
|
||||||
hashes := createHashes(3*maxQueueDist, knownHash)
|
hashes, blocks := makeChain(3*maxQueueDist, 0, genesis)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
|
|
||||||
head := hashes[len(hashes)/2]
|
head := hashes[len(hashes)/2]
|
||||||
|
|
||||||
// Create a tester and simulate a head block being the middle of the above chain
|
// Create a tester and simulate a head block being the middle of the above chain
|
||||||
@ -445,11 +410,11 @@ func TestHashMemoryExhaustionAttack(t *testing.T) {
|
|||||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||||
|
|
||||||
// Create a valid chain and an infinite junk chain
|
// Create a valid chain and an infinite junk chain
|
||||||
hashes := createHashes(hashLimit+2*maxQueueDist, knownHash)
|
targetBlocks := hashLimit + 2*maxQueueDist
|
||||||
blocks := createBlocksFromHashes(hashes)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
valid := tester.makeFetcher(blocks)
|
valid := tester.makeFetcher(blocks)
|
||||||
|
|
||||||
attack := createHashes(hashLimit+2*maxQueueDist, unknownHash)
|
attack, _ := makeChain(targetBlocks, 0, unknownBlock)
|
||||||
attacker := tester.makeFetcher(nil)
|
attacker := tester.makeFetcher(nil)
|
||||||
|
|
||||||
// Feed the tester a huge hashset from the attacker, and a limited from the valid peer
|
// Feed the tester a huge hashset from the attacker, and a limited from the valid peer
|
||||||
@ -484,13 +449,11 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) {
|
|||||||
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
tester.fetcher.importedHook = func(block *types.Block) { imported <- block }
|
||||||
|
|
||||||
// Create a valid chain and a batch of dangling (but in range) blocks
|
// Create a valid chain and a batch of dangling (but in range) blocks
|
||||||
hashes := createHashes(blockLimit+2*maxQueueDist, knownHash)
|
targetBlocks := hashLimit + 2*maxQueueDist
|
||||||
blocks := createBlocksFromHashes(hashes)
|
hashes, blocks := makeChain(targetBlocks, 0, genesis)
|
||||||
|
|
||||||
attack := make(map[common.Hash]*types.Block)
|
attack := make(map[common.Hash]*types.Block)
|
||||||
for len(attack) < blockLimit+2*maxQueueDist {
|
for i := byte(0); len(attack) < blockLimit+2*maxQueueDist; i++ {
|
||||||
hashes := createHashes(maxQueueDist-1, unknownHash)
|
hashes, blocks := makeChain(maxQueueDist-1, i, unknownBlock)
|
||||||
blocks := createBlocksFromHashes(hashes)
|
|
||||||
for _, hash := range hashes[:maxQueueDist-2] {
|
for _, hash := range hashes[:maxQueueDist-2] {
|
||||||
attack[hash] = blocks[hash]
|
attack[hash] = blocks[hash]
|
||||||
}
|
}
|
||||||
@ -499,7 +462,7 @@ func TestBlockMemoryExhaustionAttack(t *testing.T) {
|
|||||||
for _, block := range attack {
|
for _, block := range attack {
|
||||||
tester.fetcher.Enqueue("attacker", block)
|
tester.fetcher.Enqueue("attacker", block)
|
||||||
}
|
}
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(200 * time.Millisecond)
|
||||||
if queued := tester.fetcher.queue.Size(); queued != blockLimit {
|
if queued := tester.fetcher.queue.Size(); queued != blockLimit {
|
||||||
t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit)
|
t.Fatalf("queued block count mismatch: have %d, want %d", queued, blockLimit)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user