fix: defend ddos voting attack with other mini fix according to audit (#1741)
This commit is contained in:
parent
288804ced0
commit
3fd5b0c149
@ -67,7 +67,7 @@ const (
|
||||
|
||||
systemRewardPercent = 4 // it means 1/2^4 = 1/16 percentage of gas fee incoming will be distributed to system
|
||||
|
||||
collectAdditionalVotesRewardRatio = float64(1) // ratio of additional reward for collecting more votes than needed
|
||||
collectAdditionalVotesRewardRatio = 100 // ratio of additional reward for collecting more votes than needed, the denominator is 100
|
||||
)
|
||||
|
||||
var (
|
||||
@ -1027,7 +1027,7 @@ func (p *Parlia) distributeFinalityReward(chain consensus.ChainHeaderReader, sta
|
||||
}
|
||||
quorum := cmath.CeilDiv(len(snap.Validators)*2, 3)
|
||||
if validVoteCount > quorum {
|
||||
accumulatedWeights[head.Coinbase] += uint64(float64(validVoteCount-quorum) * collectAdditionalVotesRewardRatio)
|
||||
accumulatedWeights[head.Coinbase] += uint64((validVoteCount - quorum) * collectAdditionalVotesRewardRatio / 100)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1788,7 +1788,7 @@ func encodeSigHeader(w io.Writer, header *types.Header, chainId *big.Int) {
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-65], // this will panic if extra is too short, should check before calling encodeSigHeader
|
||||
header.Extra[:len(header.Extra)-extraSeal], // this will panic if extra is too short, should check before calling encodeSigHeader
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
|
@ -944,6 +944,8 @@ func (h *handler) voteBroadcastLoop() {
|
||||
for {
|
||||
select {
|
||||
case event := <-h.voteCh:
|
||||
// The timeliness of votes is very important,
|
||||
// so one vote will be sent instantly without waiting for other votes for batch sending by design.
|
||||
h.BroadcastVote(event.Vote)
|
||||
case <-h.votesSub.Err():
|
||||
return
|
||||
|
@ -61,9 +61,15 @@ func (h *bscHandler) Handle(peer *bsc.Peer, packet bsc.Packet) error {
|
||||
// handleVotesBroadcast is invoked from a peer's message handler when it transmits a
|
||||
// votes broadcast for the local node to process.
|
||||
func (h *bscHandler) handleVotesBroadcast(peer *bsc.Peer, votes []*types.VoteEnvelope) error {
|
||||
// Try to put votes into votepool
|
||||
for _, vote := range votes {
|
||||
h.votepool.PutVote(vote)
|
||||
}
|
||||
if peer.IsOverLimitAfterReceiving() {
|
||||
peer.Log().Warn("peer sending votes too much, votes dropped; it may be a ddos attack, please check!")
|
||||
return nil
|
||||
}
|
||||
// Here we only put the first vote, to avoid ddos attack by sending a large batch of votes.
|
||||
// This won't abandon any valid vote, because one vote is sent every time referring to func voteBroadcastLoop
|
||||
if len(votes) > 0 {
|
||||
h.votepool.PutVote(votes[0])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package bsc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
mapset "github.com/deckarep/golang-set"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
@ -16,6 +18,15 @@ const (
|
||||
|
||||
// voteBufferSize is the maximum number of batch votes can be hold before sending
|
||||
voteBufferSize = 21 * 2
|
||||
|
||||
// used to avoid of DDOS attack
|
||||
// It's the max number of received votes per second from one peer
|
||||
// 21 validators exist now, so 21 votes will be produced every one block interval
|
||||
// so the limit is 7 = 21/3, here set it to 10 with a buffer.
|
||||
receiveRateLimitPerSecond = 10
|
||||
|
||||
// the time span of one period
|
||||
secondsPerPeriod = float64(10)
|
||||
)
|
||||
|
||||
// max is a helper function which returns the larger of the two given integers.
|
||||
@ -31,6 +42,8 @@ type Peer struct {
|
||||
id string // Unique ID for the peer, cached
|
||||
knownVotes *knownCache // Set of vote hashes known to be known by this peer
|
||||
voteBroadcast chan []*types.VoteEnvelope // Channel used to queue votes propagation requests
|
||||
periodBegin time.Time // Begin time of the latest period for votes counting
|
||||
periodCounter uint // Votes number in the latest period
|
||||
|
||||
*p2p.Peer // The embedded P2P package peer
|
||||
rw p2p.MsgReadWriter // Input/output streams for bsc
|
||||
@ -47,6 +60,8 @@ func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
|
||||
id: id,
|
||||
knownVotes: newKnownCache(maxKnownVotes),
|
||||
voteBroadcast: make(chan []*types.VoteEnvelope, voteBufferSize),
|
||||
periodBegin: time.Now(),
|
||||
periodCounter: 0,
|
||||
Peer: p,
|
||||
rw: rw,
|
||||
version: version,
|
||||
@ -114,6 +129,18 @@ func (p *Peer) AsyncSendVotes(votes []*types.VoteEnvelope) {
|
||||
}
|
||||
}
|
||||
|
||||
// Step into the next period when secondsPerPeriod seconds passed,
|
||||
// Otherwise, check whether the number of received votes extra (secondsPerPeriod * receiveRateLimitPerSecond)
|
||||
func (p *Peer) IsOverLimitAfterReceiving() bool {
|
||||
if timeInterval := time.Since(p.periodBegin).Seconds(); timeInterval >= secondsPerPeriod {
|
||||
p.periodBegin = time.Now()
|
||||
p.periodCounter = 0
|
||||
return false
|
||||
}
|
||||
p.periodCounter += 1
|
||||
return p.periodCounter > uint(secondsPerPeriod*receiveRateLimitPerSecond)
|
||||
}
|
||||
|
||||
// broadcastVotes is a write loop that schedules votes broadcasts
|
||||
// to the remote peer. The goal is to have an async writer that does not lock up
|
||||
// node internals and at the same time rate limits queued data.
|
||||
|
Loading…
Reference in New Issue
Block a user