Skip to content

Commit

Permalink
fix: defend ddos voting attack with other mini fix according to audit
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanBSC committed Jul 10, 2023
1 parent dda8e8d commit fdc7dba
Show file tree
Hide file tree
Showing 3 changed files with 36 additions and 14 deletions.
6 changes: 3 additions & 3 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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,
})
Expand Down
4 changes: 4 additions & 0 deletions eth/handler_bsc.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ 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 {
if receiveLimit := peer.ReceiveLimit(); len(votes) > receiveLimit {
peer.Log().Warn("too many votes:%d, dropped:%d, may be a ddos attack", len(votes), len(votes)-receiveLimit)
votes = votes[:receiveLimit]
}
// Try to put votes into votepool
for _, vote := range votes {
h.votepool.PutVote(vote)
Expand Down
40 changes: 29 additions & 11 deletions eth/protocols/bsc/peer.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package bsc

import (
"math"
"time"

mapset "github.com/deckarep/golang-set"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -16,6 +19,12 @@ 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
)

// max is a helper function which returns the larger of the two given integers.
Expand All @@ -28,9 +37,10 @@ func max(a, b int) int {

// Peer is a collection of relevant information we have about a `bsc` peer.
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
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
lastArrivalTime time.Time // Arrival time of previous batch of votes

*p2p.Peer // The embedded P2P package peer
rw p2p.MsgReadWriter // Input/output streams for bsc
Expand All @@ -44,14 +54,15 @@ type Peer struct {
func NewPeer(version uint, p *p2p.Peer, rw p2p.MsgReadWriter) *Peer {
id := p.ID().String()
peer := &Peer{
id: id,
knownVotes: newKnownCache(maxKnownVotes),
voteBroadcast: make(chan []*types.VoteEnvelope, voteBufferSize),
Peer: p,
rw: rw,
version: version,
logger: log.New("peer", id[:8]),
term: make(chan struct{}),
id: id,
knownVotes: newKnownCache(maxKnownVotes),
voteBroadcast: make(chan []*types.VoteEnvelope, voteBufferSize),
lastArrivalTime: time.Now(),
Peer: p,
rw: rw,
version: version,
logger: log.New("peer", id[:8]),
term: make(chan struct{}),
}
go peer.broadcastVotes()
return peer
Expand Down Expand Up @@ -114,6 +125,13 @@ func (p *Peer) AsyncSendVotes(votes []*types.VoteEnvelope) {
}
}

func (p *Peer) ReceiveLimit() int {
// the interval betweem two batch of votes is less than 9 seconds (3 block intervals) usually
timeInterval := int(math.Min(time.Since(p.lastArrivalTime).Seconds()+1, 9))
p.lastArrivalTime = time.Now()
return timeInterval * 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.
Expand Down

0 comments on commit fdc7dba

Please sign in to comment.