-
Notifications
You must be signed in to change notification settings - Fork 20.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
les: implement new les fetcher #20692
Conversation
7e079fd
to
fb89cf2
Compare
@zsfelfoldi I think I address all the comments, please take another look |
td *big.Int | ||
) | ||
case resp := <-f.deliverCh: | ||
if req := fetching[resp.reqid]; req != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I realized there is one small thing missing from this fetcher logic that the old one had. If we download a header based on an announcement we should check that the Td
and Number
are actually the same as in the announcement and drop the peer if it does not match. Maybe we could include them in the request
struct and check here upon delivery. The reason is that the monotonic Td
announcement constraint protects the client against the server spamming it with announcements but it only makes sense if we actually check it. I am not sure whether it is a realistic attack vector now but adding the check would always ensure that the client won't accept a lot of bad announcements (because the server cannot forge a lot of headers with valid, monotonically increasing Td
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it's a bit dangerous. The server can send some announces on the fork(but it thinks it's canonical one). Especially there are two blocks have the same td(miner can choose one randomly). Then we can't find the same announces
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I add the check. If the hash is the same and is announced by the server, then check the number and td. Please check it
f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { | ||
removed := p.forwardAnno(number) | ||
for _, anno := range removed { | ||
if header := f.chain.GetHeaderByHash(anno.data.Hash); header != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At first I thought that this check is going to be simpler and we just need to add the check after delivering a header to the block fetcher by f.fetcher.FilterHeaders
. Unfortunately that function returns before actually inserting the header to the chain so we still don't have the Td calculated. I guess this is why you are checking after evicting the announcements from the queue. This might work too but then I think you should also do this check in the other case when you are evicting the announcement because the queue is full (this is actually the more likely case when an attacker is spamming with bad announcements). Then we can be sure that causing the client to request useless headers by fake announcements is always punished. Either the hash is non-existent (in which case the retrieval will fail) or the hash exists but the number/Td does not match (in which case this check is always going to catch it when it gets out of the queue one way or the other).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Think about it. We have two cases to evict announces: (a) local chain has inserted some headers, to evict stale or useless announces (b) the announce queue is full.
In the latter one, we can't do any meaningful check since they are all "future" announces.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not necessarily true if the server is lying about the Td (which is the primary attack vector). If the headers exist but it is a worthless sidechain (or a fork like ETC) and the attacking server just keeps feeding announcements of it with fake high Tds then it is never evicted by forwardAnno
since the Td appearing in the FIFO list is high (higher than the real chain). It will be evicted by addAnno
where there is no check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually it's still ok. Image the client keeps feeding super high td all the time, it will finally trigger a syncing. And the downloader will detect this announcement is invalid and drop this peer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And also it's very hard to differentiate "fake announcement with high td" and "valid announcement because local client is out of sync". The main difference is for the latter one, we can finally sync to this point, but for the former, we will never reach the announcement point(it can be dropped by the downloader)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few details left, see comments.
f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { | ||
removed := p.forwardAnno(number) | ||
for _, anno := range removed { | ||
if header := f.chain.GetHeaderByHash(anno.data.Hash); header != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not necessarily true if the server is lying about the Td (which is the primary attack vector). If the headers exist but it is a worthless sidechain (or a fork like ETC) and the attacking server just keeps feeding announcements of it with fake high Tds then it is never evicted by forwardAnno
since the Td appearing in the FIFO list is high (higher than the real chain). It will be evicted by addAnno
where there is no check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM as far as the 'normal' import goes, haven't looked too deeply at the les-logic
eth/fetcher/block_fetcher.go
Outdated
@@ -330,11 +360,15 @@ func (f *BlockFetcher) loop() { | |||
break | |||
} | |||
// Otherwise if fresh and still unknown, try and import | |||
if number+maxUncleDist < height || f.getBlock(hash) != nil { | |||
if number+maxUncleDist < height || f.light && f.getHeader(hash) != nil || !f.light && f.getBlock(hash) != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add parentheses around the x && y
groups. I know Go's convention is that &&
has a higher precedence than ||
, but I really don't want to do mental gymnastics to figure out what comes before what else :)
eth/fetcher/block_fetcher.go
Outdated
// Pick a random peer to retrieve from, reset all others | ||
announce := announces[rand.Intn(len(announces))] | ||
f.forgetHash(hash) | ||
|
||
// If the block still didn't arrive, queue for fetching | ||
if f.getBlock(hash) == nil { | ||
if f.light && f.getHeader(hash) == nil || !f.light && f.getBlock(hash) == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add parentheses.
eth/fetcher/block_fetcher.go
Outdated
log.Debug("Unknown parent of propagated header", "peer", peer, "number", header.Number, "hash", hash, "parent", header.ParentHash) | ||
return | ||
} | ||
// Quickly validate the header and propagate the block if it passes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This section is a bit weird, we don't have "quick propagation". Perhaps let's change it to a simple if
// Validate the header and if something went wrong, drop the peer
if err := f.verifyHeader(header);; err != nil && err != consensus.ErrFutureBlock {
log.Debug("Propagated header verification failed", "peer", peer, "number", header.Number, "hash", hash, "err", err)
f.dropPeer(peer)
return
}
Linter is red, some build error in the tests. |
@karalabe the lint is super weird. It reports |
Dafuq. It works for me locally too, even the linter is all green locally. |
Perhaps it's a merge issue on travis? Lemme just merge the thing and see if it blows up on the squashed commit. |
* cmd, consensus, eth, les: implement light fetcher * les: address comment * les: address comment * les: address comments * les: check td after delivery * les: add linearExpiredValue for error counter * les: fix import * les: fix dead lock * les: order announces by td * les: encapsulate invalid counter * les: address comment * les: add more checks during the delivery * les: fix log * eth, les: fix lint * eth/fetcher: address comment
This PR refactors the
light/fetcher
which reuses underlyingeth/fetcher
.This PR is based on the #19991, would be nice to merge this one first.