Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
G
Geth-Modification
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
张蕾
Geth-Modification
Commits
84bc93d8
Commit
84bc93d8
authored
May 29, 2015
by
Péter Szilágyi
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
eth/downloader: accumulating hash bans for reconnecting attackers
parent
eedb25b2
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
126 additions
and
5 deletions
+126
-5
downloader.go
eth/downloader/downloader.go
+91
-5
downloader_test.go
eth/downloader/downloader_test.go
+35
-0
No files found.
eth/downloader/downloader.go
View file @
84bc93d8
package
downloader
package
downloader
import
(
import
(
"bytes"
"errors"
"errors"
"fmt"
"math/rand"
"math/rand"
"sync"
"sync"
"sync/atomic"
"sync/atomic"
...
@@ -289,9 +291,15 @@ func (d *Downloader) fetchHashes(p *peer, h common.Hash) error {
...
@@ -289,9 +291,15 @@ func (d *Downloader) fetchHashes(p *peer, h common.Hash) error {
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Peer (%s) responded with empty hash set"
,
active
.
id
)
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Peer (%s) responded with empty hash set"
,
active
.
id
)
return
ErrEmptyHashSet
return
ErrEmptyHashSet
}
}
for
_
,
hash
:=
range
hashPack
.
hashes
{
for
index
,
hash
:=
range
hashPack
.
hashes
{
if
d
.
banned
.
Has
(
hash
)
{
if
d
.
banned
.
Has
(
hash
)
{
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Peer (%s) sent a known invalid chain"
,
active
.
id
)
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Peer (%s) sent a known invalid chain"
,
active
.
id
)
d
.
queue
.
Insert
(
hashPack
.
hashes
[
:
index
+
1
])
if
err
:=
d
.
banBlocks
(
active
.
id
,
hash
);
err
!=
nil
{
fmt
.
Println
(
"ban err"
,
err
)
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Failed to ban batch of blocks: %v"
,
err
)
}
return
ErrInvalidChain
return
ErrInvalidChain
}
}
}
}
...
@@ -399,8 +407,10 @@ func (d *Downloader) fetchBlocks() error {
...
@@ -399,8 +407,10 @@ func (d *Downloader) fetchBlocks() error {
glog
.
V
(
logger
.
Debug
)
.
Infoln
(
"Downloading"
,
d
.
queue
.
Pending
(),
"block(s)"
)
glog
.
V
(
logger
.
Debug
)
.
Infoln
(
"Downloading"
,
d
.
queue
.
Pending
(),
"block(s)"
)
start
:=
time
.
Now
()
start
:=
time
.
Now
()
//
default ticker for re-fetching blocks every now and then
//
Start a ticker to continue throttled downloads and check for bad peers
ticker
:=
time
.
NewTicker
(
20
*
time
.
Millisecond
)
ticker
:=
time
.
NewTicker
(
20
*
time
.
Millisecond
)
defer
ticker
.
Stop
()
out
:
out
:
for
{
for
{
select
{
select
{
...
@@ -413,7 +423,7 @@ out:
...
@@ -413,7 +423,7 @@ out:
block
:=
blockPack
.
blocks
[
0
]
block
:=
blockPack
.
blocks
[
0
]
if
_
,
ok
:=
d
.
checks
[
block
.
Hash
()];
ok
{
if
_
,
ok
:=
d
.
checks
[
block
.
Hash
()];
ok
{
delete
(
d
.
checks
,
block
.
Hash
())
delete
(
d
.
checks
,
block
.
Hash
())
continue
break
}
}
}
}
// If the peer was previously banned and failed to deliver it's pack
// If the peer was previously banned and failed to deliver it's pack
...
@@ -488,7 +498,7 @@ out:
...
@@ -488,7 +498,7 @@ out:
if
d
.
queue
.
Pending
()
>
0
{
if
d
.
queue
.
Pending
()
>
0
{
// Throttle the download if block cache is full and waiting processing
// Throttle the download if block cache is full and waiting processing
if
d
.
queue
.
Throttle
()
{
if
d
.
queue
.
Throttle
()
{
continue
break
}
}
// Send a download request to all idle peers, until throttled
// Send a download request to all idle peers, until throttled
idlePeers
:=
d
.
peers
.
IdlePeers
()
idlePeers
:=
d
.
peers
.
IdlePeers
()
...
@@ -529,10 +539,86 @@ out:
...
@@ -529,10 +539,86 @@ out:
}
}
}
}
glog
.
V
(
logger
.
Detail
)
.
Infoln
(
"Downloaded block(s) in"
,
time
.
Since
(
start
))
glog
.
V
(
logger
.
Detail
)
.
Infoln
(
"Downloaded block(s) in"
,
time
.
Since
(
start
))
return
nil
return
nil
}
}
// banBlocks retrieves a batch of blocks from a peer feeding us invalid hashes,
// and bans the head of the retrieved batch.
//
// This method only fetches one single batch as the goal is not ban an entire
// (potentially long) invalid chain - wasting a lot of time in the meanwhile -,
// but rather to gradually build up a blacklist if the peer keeps reconnecting.
func
(
d
*
Downloader
)
banBlocks
(
peerId
string
,
head
common
.
Hash
)
error
{
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Banning a batch out of %d blocks from %s"
,
d
.
queue
.
Pending
(),
peerId
)
// Ask the peer being banned for a batch of blocks from the banning point
peer
:=
d
.
peers
.
Peer
(
peerId
)
if
peer
==
nil
{
return
nil
}
request
:=
d
.
queue
.
Reserve
(
peer
,
MaxBlockFetch
)
if
request
==
nil
{
return
nil
}
if
err
:=
peer
.
Fetch
(
request
);
err
!=
nil
{
return
err
}
// Wait a bit for the reply to arrive, and ban if done so
timeout
:=
time
.
After
(
blockTTL
)
for
{
select
{
case
<-
d
.
cancelCh
:
return
errCancelBlockFetch
case
<-
timeout
:
return
ErrTimeout
case
blockPack
:=
<-
d
.
blockCh
:
blocks
:=
blockPack
.
blocks
// Short circuit if it's a stale cross check
if
len
(
blocks
)
==
1
{
block
:=
blocks
[
0
]
if
_
,
ok
:=
d
.
checks
[
block
.
Hash
()];
ok
{
delete
(
d
.
checks
,
block
.
Hash
())
break
}
}
// Short circuit if it's not from the peer being banned
if
blockPack
.
peerId
!=
peerId
{
break
}
// Short circuit if no blocks were returned
if
len
(
blocks
)
==
0
{
return
errors
.
New
(
"no blocks returned to ban"
)
}
// Got the batch of invalid blocks, reconstruct their chain order
for
i
:=
0
;
i
<
len
(
blocks
);
i
++
{
for
j
:=
i
+
1
;
j
<
len
(
blocks
);
j
++
{
if
blocks
[
i
]
.
NumberU64
()
>
blocks
[
j
]
.
NumberU64
()
{
blocks
[
i
],
blocks
[
j
]
=
blocks
[
j
],
blocks
[
i
]
}
}
}
// Ensure we're really banning the correct blocks
if
bytes
.
Compare
(
blocks
[
0
]
.
Hash
()
.
Bytes
(),
head
.
Bytes
())
!=
0
{
return
errors
.
New
(
"head block not the banned one"
)
}
index
:=
0
for
_
,
block
:=
range
blocks
[
1
:
]
{
if
bytes
.
Compare
(
block
.
ParentHash
()
.
Bytes
(),
blocks
[
index
]
.
Hash
()
.
Bytes
())
!=
0
{
break
}
index
++
}
d
.
banned
.
Add
(
blocks
[
index
]
.
Hash
())
glog
.
V
(
logger
.
Debug
)
.
Infof
(
"Banned %d blocks from: %s
\n
"
,
index
+
1
,
peerId
)
return
nil
}
}
}
// DeliverBlocks injects a new batch of blocks received from a remote node.
// DeliverBlocks injects a new batch of blocks received from a remote node.
// This is usually invoked through the BlocksMsg by the protocol handler.
// This is usually invoked through the BlocksMsg by the protocol handler.
func
(
d
*
Downloader
)
DeliverBlocks
(
id
string
,
blocks
[]
*
types
.
Block
)
error
{
func
(
d
*
Downloader
)
DeliverBlocks
(
id
string
,
blocks
[]
*
types
.
Block
)
error
{
...
...
eth/downloader/downloader_test.go
View file @
84bc93d8
...
@@ -14,6 +14,7 @@ import (
...
@@ -14,6 +14,7 @@ import (
var
(
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
}
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
}
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
}
bannedHash
=
common
.
Hash
{
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
,
5
}
)
)
func
createHashes
(
start
,
amount
int
)
(
hashes
[]
common
.
Hash
)
{
func
createHashes
(
start
,
amount
int
)
(
hashes
[]
common
.
Hash
)
{
...
@@ -520,3 +521,37 @@ func TestMadeupParentBlockChainAttack(t *testing.T) {
...
@@ -520,3 +521,37 @@ func TestMadeupParentBlockChainAttack(t *testing.T) {
t
.
Fatalf
(
"failed to synchronise blocks: %v"
,
err
)
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
// gradually block pieces of it, until it's head is also blocked.
func
TestBannedChainStarvationAttack
(
t
*
testing
.
T
)
{
// Construct a valid chain, but ban one of the hashes in it
hashes
:=
createHashes
(
0
,
8
*
blockCacheLimit
)
hashes
[
len
(
hashes
)
/
2
+
23
]
=
bannedHash
// weird index to have non multiple of ban chunk size
blocks
:=
createBlocksFromHashes
(
hashes
)
// Create the tester and ban the selected hash
tester
:=
newTester
(
t
,
hashes
,
blocks
)
tester
.
downloader
.
banned
.
Add
(
bannedHash
)
// Iteratively try to sync, and verify that the banned hash list grows until
// the head of the invalid chain is blocked too.
tester
.
newPeer
(
"attack"
,
big
.
NewInt
(
10000
),
hashes
[
0
])
for
banned
:=
tester
.
downloader
.
banned
.
Size
();
;
{
// Try to sync with the attacker, check hash chain failure
if
_
,
err
:=
tester
.
syncTake
(
"attack"
,
hashes
[
0
]);
err
!=
ErrInvalidChain
{
t
.
Fatalf
(
"synchronisation error mismatch: have %v, want %v"
,
err
,
ErrInvalidChain
)
}
// Check that the ban list grew with at least 1 new item, or all banned
bans
:=
tester
.
downloader
.
banned
.
Size
()
if
bans
<
banned
+
1
{
if
tester
.
downloader
.
banned
.
Has
(
hashes
[
0
])
{
break
}
t
.
Fatalf
(
"ban count mismatch: have %v, want %v+"
,
bans
,
banned
+
1
)
}
banned
=
bans
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment