-
Notifications
You must be signed in to change notification settings - Fork 20.4k
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
Conversation
234b976
to
0e9a56e
Compare
The discv5 implementation is now merged on master, you can rebase on that. |
7cf8782
to
33cc672
Compare
14b7791
to
6501fd5
Compare
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? |
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. |
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 |
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.
Perhaps selectedByValue
is a better name?
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.
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
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 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 { |
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 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
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.
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 { |
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.
Also If we can remove the specialization for the single element set, it will be simplified a lot
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.
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) { |
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.
As I commented, the flatweight is derived by some means. It's not fair anymore...
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 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 |
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.
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.
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.
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) |
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.
It's also hacky. The base for relcost
is changing all the time(maxPriorWeight).
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.
Also the <-costCh
return is super unclear for the read. What does it mean? What's the relevant value range of the return?
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.
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 |
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'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), |
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.
It's unnecessary to divide the nq.totalPriorWeight
right?
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.
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) |
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.
Why to add the totalPriorWeight
directly? In another place https://github.com/ethereum/go-ethereum/pull/21676/files#diff-05f453344a1370c706aca53fb8864362a541daeca84a5c82aecf9344c7af20acR326 it's divided by the base
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.
Right, it was a mistake, now it is simplified and handled correctly.
The UDP limiter is now in a separate PR, simplified and issues addressed: |
This WIP PR implements
ReqID
andBufferValue
message fields with a future-proof "metainfo channel" that can contain any number of mutually understood meta fields per messageTO DO: