Skip to content
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

Merged
merged 15 commits into from
Jul 28, 2020
Merged

Conversation

rjl493456442
Copy link
Member

@rjl493456442 rjl493456442 commented Feb 19, 2020

This PR refactors the light/fetcher which reuses underlying eth/fetcher.

This PR is based on the #19991, would be nice to merge this one first.

les/fetcher.go Outdated Show resolved Hide resolved
les/fetcher.go Show resolved Hide resolved
les/fetcher.go Outdated Show resolved Hide resolved
les/fetcher.go Outdated Show resolved Hide resolved
les/fetcher.go Outdated Show resolved Hide resolved
les/utils/expiredvalue.go Show resolved Hide resolved
@ericramos1980
Copy link

[email protected]

@rjl493456442
Copy link
Member Author

@zsfelfoldi I think I address all the comments, please take another look

les/fetcher.go Outdated Show resolved Hide resolved
les/utils/expiredvalue.go Outdated Show resolved Hide resolved
les/utils/expiredvalue.go Show resolved Hide resolved
td *big.Int
)
case resp := <-f.deliverCh:
if req := fetching[resp.reqid]; req != nil {
Copy link
Contributor

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).

Copy link
Member Author

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

Copy link
Member Author

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 {
Copy link
Contributor

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).

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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.

Copy link
Member Author

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)

les/fetcher.go Show resolved Hide resolved
les/server_handler.go Outdated Show resolved Hide resolved
Copy link
Contributor

@zsfelfoldi zsfelfoldi left a 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.

les/fetcher.go Show resolved Hide resolved
les/fetcher.go Show resolved Hide resolved
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 {
Copy link
Contributor

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.

les/fetcher.go Outdated Show resolved Hide resolved
Copy link
Contributor

@holiman holiman left a 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

@@ -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 {
Copy link
Member

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 :)

// 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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add parentheses.

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
Copy link
Member

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
}

@karalabe
Copy link
Member

Linter is red, some build error in the tests.

@rjl493456442
Copy link
Member Author

@karalabe the lint is super weird. It reports /home/travis/gopath/src/github.com/ethereum/go-ethereum/les/fetcher_test.go:69:80: too few arguments in call to newClientServerEnv. However I can run all the tests locally.

@karalabe
Copy link
Member

Dafuq. It works for me locally too, even the linter is all green locally.

@karalabe karalabe added this to the 1.9.19 milestone Jul 28, 2020
@karalabe
Copy link
Member

Perhaps it's a merge issue on travis? Lemme just merge the thing and see if it blows up on the squashed commit.

@karalabe karalabe merged commit 28c5a8a into ethereum:master Jul 28, 2020
enriquefynn pushed a commit to enriquefynn/go-ethereum that referenced this pull request Mar 10, 2021
* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants