|
|
@@ -10,7 +10,10 @@ import (
|
|
|
"github.com/ethereum/go-ethereum/core/types"
|
|
|
)
|
|
|
|
|
|
-var knownHash = common.Hash{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
|
+var (
|
|
|
+ knownHash = common.Hash{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
|
+ unknownHash = common.Hash{9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}
|
|
|
+)
|
|
|
|
|
|
func createHashes(start, amount int) (hashes []common.Hash) {
|
|
|
hashes = make([]common.Hash, amount+1)
|
|
|
@@ -27,7 +30,7 @@ func createBlock(i int, prevHash, hash common.Hash) *types.Block {
|
|
|
header := &types.Header{Number: big.NewInt(int64(i))}
|
|
|
block := types.NewBlockWithHeader(header)
|
|
|
block.HeaderHash = hash
|
|
|
- block.ParentHeaderHash = knownHash
|
|
|
+ block.ParentHeaderHash = prevHash
|
|
|
return block
|
|
|
}
|
|
|
|
|
|
@@ -42,9 +45,12 @@ func createBlocksFromHashes(hashes []common.Hash) map[common.Hash]*types.Block {
|
|
|
}
|
|
|
|
|
|
type downloadTester struct {
|
|
|
- downloader *Downloader
|
|
|
- hashes []common.Hash
|
|
|
- blocks map[common.Hash]*types.Block
|
|
|
+ downloader *Downloader
|
|
|
+
|
|
|
+ hashes []common.Hash // Chain of hashes simulating
|
|
|
+ blocks map[common.Hash]*types.Block // Blocks associated with the hashes
|
|
|
+ chain []common.Hash // Block-chain being constructed
|
|
|
+
|
|
|
t *testing.T
|
|
|
pcount int
|
|
|
done chan bool
|
|
|
@@ -52,7 +58,15 @@ type downloadTester struct {
|
|
|
}
|
|
|
|
|
|
func newTester(t *testing.T, hashes []common.Hash, blocks map[common.Hash]*types.Block) *downloadTester {
|
|
|
- tester := &downloadTester{t: t, hashes: hashes, blocks: blocks, done: make(chan bool)}
|
|
|
+ tester := &downloadTester{
|
|
|
+ t: t,
|
|
|
+
|
|
|
+ hashes: hashes,
|
|
|
+ blocks: blocks,
|
|
|
+ chain: []common.Hash{knownHash},
|
|
|
+
|
|
|
+ done: make(chan bool),
|
|
|
+ }
|
|
|
downloader := New(tester.hasBlock, tester.getBlock)
|
|
|
tester.downloader = downloader
|
|
|
|
|
|
@@ -64,9 +78,17 @@ func (dl *downloadTester) sync(peerId string, hash common.Hash) error {
|
|
|
return dl.downloader.Synchronise(peerId, hash)
|
|
|
}
|
|
|
|
|
|
+func (dl *downloadTester) insertBlocks(blocks types.Blocks) {
|
|
|
+ for _, block := range blocks {
|
|
|
+ dl.chain = append(dl.chain, block.Hash())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
func (dl *downloadTester) hasBlock(hash common.Hash) bool {
|
|
|
- if knownHash == hash {
|
|
|
- return true
|
|
|
+ for _, h := range dl.chain {
|
|
|
+ if h == hash {
|
|
|
+ return true
|
|
|
+ }
|
|
|
}
|
|
|
return false
|
|
|
}
|
|
|
@@ -175,10 +197,9 @@ func TestTaking(t *testing.T) {
|
|
|
if err != nil {
|
|
|
t.Error("download error", err)
|
|
|
}
|
|
|
-
|
|
|
- bs1 := tester.downloader.TakeBlocks()
|
|
|
- if len(bs1) != 1000 {
|
|
|
- t.Error("expected to take 1000, got", len(bs1))
|
|
|
+ bs := tester.downloader.TakeBlocks()
|
|
|
+ if len(bs) != targetBlocks {
|
|
|
+ t.Error("retrieved block mismatch: have %v, want %v", len(bs), targetBlocks)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -248,17 +269,17 @@ func TestThrottling(t *testing.T) {
|
|
|
done := make(chan struct{})
|
|
|
took := []*types.Block{}
|
|
|
go func() {
|
|
|
- for {
|
|
|
+ for running := true; running; {
|
|
|
select {
|
|
|
case <-done:
|
|
|
- took = append(took, tester.downloader.TakeBlocks()...)
|
|
|
- done <- struct{}{}
|
|
|
- return
|
|
|
+ running = false
|
|
|
default:
|
|
|
- took = append(took, tester.downloader.TakeBlocks()...)
|
|
|
time.Sleep(time.Millisecond)
|
|
|
}
|
|
|
+ // Take a batch of blocks and accumulate
|
|
|
+ took = append(took, tester.downloader.TakeBlocks()...)
|
|
|
}
|
|
|
+ done <- struct{}{}
|
|
|
}()
|
|
|
|
|
|
// Synchronise the two threads and verify
|
|
|
@@ -273,3 +294,43 @@ func TestThrottling(t *testing.T) {
|
|
|
t.Fatalf("downloaded block mismatch: have %v, want %v", len(took), targetBlocks)
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+// Tests that if a peer returns an invalid chain with a block pointing to a non-
|
|
|
+// existing parent, it is correctly detected and handled.
|
|
|
+func TestNonExistingParentAttack(t *testing.T) {
|
|
|
+ // Forge a single-link chain with a forged header
|
|
|
+ hashes := createHashes(0, 1)
|
|
|
+ blocks := createBlocksFromHashes(hashes)
|
|
|
+
|
|
|
+ forged := blocks[hashes[0]]
|
|
|
+ forged.ParentHeaderHash = unknownHash
|
|
|
+
|
|
|
+ // Try and sync with the malicious node and check that it fails
|
|
|
+ tester := newTester(t, hashes, blocks)
|
|
|
+ tester.newPeer("attack", big.NewInt(10000), hashes[0])
|
|
|
+ if err := tester.sync("attack", hashes[0]); err != nil {
|
|
|
+ t.Fatalf("failed to synchronise blocks: %v", err)
|
|
|
+ }
|
|
|
+ bs := tester.downloader.TakeBlocks()
|
|
|
+ if len(bs) != 1 {
|
|
|
+ t.Fatalf("retrieved block mismatch: have %v, want %v", len(bs), 1)
|
|
|
+ }
|
|
|
+ if tester.hasBlock(bs[0].ParentHash()) {
|
|
|
+ t.Fatalf("tester knows about the unknown hash")
|
|
|
+ }
|
|
|
+ tester.downloader.Cancel()
|
|
|
+
|
|
|
+ // Reconstruct a valid chain, and try to synchronize with it
|
|
|
+ forged.ParentHeaderHash = knownHash
|
|
|
+ tester.newPeer("valid", big.NewInt(20000), hashes[0])
|
|
|
+ if err := tester.sync("valid", hashes[0]); err != nil {
|
|
|
+ t.Fatalf("failed to synchronise blocks: %v", err)
|
|
|
+ }
|
|
|
+ bs = tester.downloader.TakeBlocks()
|
|
|
+ if len(bs) != 1 {
|
|
|
+ t.Fatalf("retrieved block mismatch: have %v, want %v", len(bs), 1)
|
|
|
+ }
|
|
|
+ if !tester.hasBlock(bs[0].ParentHash()) {
|
|
|
+ t.Fatalf("tester doesn't know about the origin hash")
|
|
|
+ }
|
|
|
+}
|