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: les/4 protocol extensions #21676

Closed
wants to merge 14 commits into from
Closed

Conversation

zsfelfoldi
Copy link
Contributor

@zsfelfoldi zsfelfoldi commented Oct 8, 2020

This WIP PR implements

  • lespay UDP communication via the new discv5 Talk request
  • a simple DDoS protection mechanism (see Use utils.Limiter for discv5 devp2p#162 )
  • the lespay capacity query (available capacity vs. purchased tokens curve)
  • les/4 protocol changes:
    • serve lespay messages through LES TCP connection
    • allow higher capacity request for priority clients
    • allow inactive (zero capacity) LES connections for a short time; this is necessary for price negotiation and token purchase if the client cannot use UDP
    • replace the ReqID and BufferValue message fields with a future-proof "metainfo channel" that can contain any number of mutually understood meta fields per message
    • add individual request cost and token balance feedback fields to the new metainfo structure (optional)

TO DO:

  • limit frequency of capacity requests

@fjl
Copy link
Contributor

fjl commented Oct 14, 2020

The discv5 implementation is now merged on master, you can rebase on that.

@zsfelfoldi zsfelfoldi force-pushed the les4udp branch 2 times, most recently from 7cf8782 to 33cc672 Compare October 15, 2020 15:03
@zsfelfoldi zsfelfoldi force-pushed the les4udp branch 2 times, most recently from 14b7791 to 6501fd5 Compare October 31, 2020 14:51
@zsfelfoldi zsfelfoldi changed the title les: implement lespay udp capacity query (WIP) les: les/4 protocol extensions (WIP) Nov 6, 2020
@zsfelfoldi zsfelfoldi changed the title les: les/4 protocol extensions (WIP) les: les/4 protocol extensions Nov 12, 2020
@rjl493456442
Copy link
Member

Zsolt! My first suggestion is to separate this PR into a few small ones. I would really like to have the first PR for the UDP limitation!

And the second suggestion is about the UDP limitation. I would say the double selection is a good idea: one for DoS protection, one for DDoS protection. However now it looks like we are making another wheel as the TCP limitation. In les tcp protocol, we limit the request serving by the time cost, response size. And imho it's already really complicated. I really don't want to make the udp stuff that much complicated.

As far as I see, the udp protocol should be much simpler? Can we make the UDP handler kind of not cpu-bounded?
For example, only support some simple query, simple acitivities? So that we can hold the assumption that the serving cost is basically equal? In this way we can delete all the relCost stuff, all the costFilter, all the ... stuff.

@rjl493456442
Copy link
Member

rjl493456442 commented Nov 20, 2020

If I understand correctly, the main purpose of using UDP is: we still want to support some communications between the servers and clients before establishing the TCP connection. (e.g. query the price, buy the tokens). All the complicated communications should be put into the TCP protocol, right? It's unnecessary to put all of them into the UDP.

@rjl493456442
Copy link
Member

rjl493456442 commented Nov 20, 2020

And even more, the functionalities in the UDP level should be limited(imho, query price, buy tokens, query the average performance statistics, like the average latency should be enough). We don't want to expose too much information via the UDP, especially some expensive query APIs.

addressSelect, valueSelect *WeightedRandomSelect
maxValue float64
maxPriorWeight, totalPriorWeight, totalPriorLimit uint64
lastValueSelected bool
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps selectedByValue is a better name?

Copy link
Member

Choose a reason for hiding this comment

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

priorWeight is confusing for me. PriorWeight is a rough estimate of the serving cost of the request., perhaps we can pick a better name?

And also for totalPriorWeight, perhaps we can use cumulativeXXX. Because the requests are accumulated

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I dropped the prior weight vs. variable cost logic in order to make things simpler.

panic("added node queue is already in an address group")
}
l := len(ag.nodes)
if l == 1 {
Copy link
Member

Choose a reason for hiding this comment

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

I don't understand why we need to specialize the single element set?

If there is a single element in the selection set, we can still build the set for it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. The original version was meant to be a performance optimization but I tried and it does not make a big difference (less than 10%).

nq.groupIndex = -1
ag.nodes = ag.nodes[:l]
ag.sumFlatWeight -= nq.flatWeight
if l >= 1 {
Copy link
Member

Choose a reason for hiding this comment

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

Also If we can remove the specialization for the single element set, it will be simplified a lot

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// selectionWeights calculates the selection weights of a node for both the address and
// the value selector. The weight depends on the relative cost of the previous processed
// request.
func selectionWeights(relCost, value float64) (flatWeight, valueWeight uint64) {
Copy link
Member

Choose a reason for hiding this comment

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

As I commented, the flatweight is derived by some means. It's not fair anymore...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not sure if I understand you correctly but the flat weight is divided if there are multiple nodes requesting from the same IP address. This protects against Sybil attacks using many node keys.

return process
}
if value > l.maxValue {
l.maxValue = value
Copy link
Member

Choose a reason for hiding this comment

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

It looks a bit hacky to me. It means the base(maxValue) for all other requests might be different. I can understand in order to normalize it we have to adjust the cap.

Why not just passing a "value" in range [0, 1] from the outside? It likes more like a library.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The value can be an arbitrarily high number and it is a lot easier to use if we don't expect the caller to know the highest possible value in advance. Changing maxValue while running is not really a problem because it only screws up probability ratios for a short time, each node will be calculated with the right max value next time. I think this solution is convenient and has practically zero risk.

l.lock.Unlock()
costCh := make(chan float64)
request.process <- costCh
relCost := <-costCh / float64(l.maxPriorWeight)
Copy link
Member

Choose a reason for hiding this comment

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

It's also hacky. The base for relcost is changing all the time(maxPriorWeight).

Copy link
Member

Choose a reason for hiding this comment

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

Also the <-costCh return is super unclear for the read. What does it mean? What's the relevant value range of the return?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was the "real cost" of the request (range between 0 and priorWeight) but now it is dropped.

request.process <- costCh
relCost := <-costCh / float64(l.maxPriorWeight)
l.lock.Lock()
nq.relCost += relCost
Copy link
Member

Choose a reason for hiding this comment

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

I'm not quite sure, maybe we can use assignment? nq.relCost = relCost? It's always reset before the unlock....

}
list = append(list, dropListItem{
nq: nq,
priority: w / float64(nq.totalPriorWeight),
Copy link
Member

Choose a reason for hiding this comment

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

It's unnecessary to divide the nq.totalPriorWeight right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, it does matter because we iterate on all node queues so it is different each time.

l.totalPriorWeight -= item.nq.totalPriorWeight
// set the last relative cost as if all dropped requests were processed with
// the highest possible cost (which equals their prior weight).
item.nq.relCost += float64(item.nq.totalPriorWeight)
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, it was a mistake, now it is simplified and handled correctly.

@zsfelfoldi
Copy link
Contributor Author

The UDP limiter is now in a separate PR, simplified and issues addressed:
#21930
(the cost filter is not needed anymore)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants