Skip to content

Commit

Permalink
feature: add malicious vote monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
NathanBSC committed May 9, 2023
1 parent b90b1f5 commit 436a579
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 12 deletions.
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ var (
utils.CheckSnapshotWithMPT,
utils.EnableDoubleSignMonitorFlag,
utils.VotingEnabledFlag,
utils.EnableMaliciousVoteMonitorFlag,
utils.BLSPasswordFileFlag,
utils.BLSWalletDirFlag,
utils.VoteJournalDirFlag,
Expand Down
15 changes: 11 additions & 4 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,11 @@ var (
Usage: "Enable voting",
}

EnableMaliciousVoteMonitorFlag = cli.BoolFlag{
Name: "monitor.maliciousvote",
Usage: "Enable malicious vote monitor to check whether any validator violates the voting rules of fast finality",
}

BLSPasswordFileFlag = cli.StringFlag{
Name: "blspassword",
Usage: "File path for the BLS password, which contains the password to unlock BLS wallet for managing votes in fast_finality feature",
Expand Down Expand Up @@ -1159,12 +1164,14 @@ func setLes(ctx *cli.Context, cfg *ethconfig.Config) {
}
}

// setMonitor creates the monitor from the set
// command line flags, returning empty if the monitor is disabled.
func setMonitor(ctx *cli.Context, cfg *node.Config) {
// setMonitors enable monitors from the command line flags.
func setMonitors(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(EnableDoubleSignMonitorFlag.Name) {
cfg.EnableDoubleSignMonitor = true
}
if ctx.GlobalBool(EnableMaliciousVoteMonitorFlag.Name) {
cfg.EnableMaliciousVoteMonitor = true
}
}

// MakeDatabaseHandles raises out the number of allowed file handles per process
Expand Down Expand Up @@ -1329,7 +1336,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
setNodeUserIdent(ctx, cfg)
setDataDir(ctx, cfg)
setSmartCard(ctx, cfg)
setMonitor(ctx, cfg)
setMonitors(ctx, cfg)
setBLSWalletDir(ctx, cfg)
setVoteJournalDir(ctx, cfg)

Expand Down
File renamed without changes.
97 changes: 97 additions & 0 deletions core/monitor/malicious_vote_monitor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package monitor

import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
lru "github.com/hashicorp/golang-lru"
)

// follow define in core/vote
const (
maxSizeOfRecentEntry = 512
maliciousVoteSlashScope = 256
upperLimitOfVoteBlockNumber = 11
)

var (
violateRule1Counter = metrics.NewRegisteredCounter("monitor/maliciousVote/violateRule1", nil)
violateRule2Counter = metrics.NewRegisteredCounter("monitor/maliciousVote/violateRule2", nil)
)

// two purposes
// 1. monitor whether there are bugs in the voting mechanism, so add metrics to observe it.
// 2. do malicious vote slashing. TODO
type MaliciousVoteMonitor struct {
curVotes map[types.BLSPublicKey]*lru.Cache
}

func NewMaliciousVoteMonitor() *MaliciousVoteMonitor {
return &MaliciousVoteMonitor{
curVotes: make(map[types.BLSPublicKey]*lru.Cache, 21), // mainnet config
}
}

func (m *MaliciousVoteMonitor) Verify(newVote *types.VoteEnvelope, pendingBlockNumber uint64) {
// get votes for specified VoteAddress
if _, ok := m.curVotes[newVote.VoteAddress]; !ok {
voteDataBuffer, err := lru.New(maxSizeOfRecentEntry)
if err != nil {
log.Error("MaliciousVoteMonitor new lru failed", "err", err)
return
}
m.curVotes[newVote.VoteAddress] = voteDataBuffer
}
voteDataBuffer := m.curVotes[newVote.VoteAddress]
sourceNumber, targetNumber := newVote.Data.SourceNumber, newVote.Data.TargetNumber

//Rule 1: A validator must not publish two distinct votes for the same height.
if voteDataBuffer.Contains(targetNumber) {
voteData, ok := voteDataBuffer.Get(targetNumber)
if !ok {
log.Error("Failed to get voteData info from LRU cache.")
return
}
log.Warn("violate rule1", "VoteAddress", common.Bytes2Hex(newVote.VoteAddress[:]), "voteExisted", voteData.(*types.VoteData), "newVote", newVote)
violateRule1Counter.Inc(1)
// prepare message for slashing
}

//Rule 2: A validator must not vote within the span of its other votes.
blockNumber := sourceNumber + 1
if blockNumber+maliciousVoteSlashScope < pendingBlockNumber {
blockNumber = pendingBlockNumber - maliciousVoteSlashScope
}
for ; blockNumber < pendingBlockNumber; blockNumber++ {
if voteDataBuffer.Contains(blockNumber) {
voteData, ok := voteDataBuffer.Get(blockNumber)
if !ok {
log.Error("Failed to get voteData info from LRU cache.")
return
}
if voteData.(*types.VoteData).SourceNumber > sourceNumber {
log.Warn("violate rule2", "VoteAddress", common.Bytes2Hex(newVote.VoteAddress[:]), "voteExisted", voteData.(*types.VoteData), "newVote", newVote)
violateRule2Counter.Inc(1)
// prepare message for slashing
}
}
}
for blockNumber := targetNumber + 1; blockNumber <= pendingBlockNumber+upperLimitOfVoteBlockNumber; blockNumber++ {
if voteDataBuffer.Contains(blockNumber) {
voteData, ok := voteDataBuffer.Get(blockNumber)
if !ok {
log.Error("Failed to get voteData info from LRU cache.")
return
}
if voteData.(*types.VoteData).SourceNumber < sourceNumber {
log.Warn("violate rule2", "VoteAddress", common.Bytes2Hex(newVote.VoteAddress[:]), "voteExisted", voteData.(*types.VoteData), "newVote", newVote)
violateRule2Counter.Inc(1)
// no need to prepare message for slashing
}
}
}

// for simplicity, Just override even if the targetNumber has existed.
voteDataBuffer.Add(newVote.Data.TargetNumber, newVote.Data)
}
7 changes: 4 additions & 3 deletions core/vote/vote_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func (voteManager *VoteManager) UnderRules(header *types.Header) (bool, uint64,
continue
}
if voteData.(*types.VoteData).SourceNumber > sourceNumber {
log.Debug(fmt.Sprintf("error: cur vote %d-->%d is within the span of other votes %d-->%d",
log.Debug(fmt.Sprintf("error: cur vote %d-->%d is across the span of other votes %d-->%d",
sourceNumber, targetNumber, voteData.(*types.VoteData).SourceNumber, voteData.(*types.VoteData).TargetNumber))
return false, 0, common.Hash{}
}
Expand All @@ -208,14 +208,15 @@ func (voteManager *VoteManager) UnderRules(header *types.Header) (bool, uint64,
continue
}
if voteData.(*types.VoteData).SourceNumber < sourceNumber {
log.Debug("error: other votes are within span of cur vote")
log.Debug(fmt.Sprintf("error: cur vote %d-->%d is within the span of other votes %d-->%d",
sourceNumber, targetNumber, voteData.(*types.VoteData).SourceNumber, voteData.(*types.VoteData).TargetNumber))
return false, 0, common.Hash{}
}
}
}

// Rule 3: Validators always vote for their canonical chain’s latest block.
// Since the header subscribed to is the canonical chain, so this rule is satisified by default.
// Since the header subscribed to is the canonical chain, so this rule is satisfied by default.
log.Debug("All three rules check passed")
return true, sourceNumber, sourceHash
}
4 changes: 4 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/parlia"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/monitor"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/pruner"
"github.com/ethereum/go-ethereum/core/types"
Expand Down Expand Up @@ -300,6 +301,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
if eth.votePool != nil {
eth.handler.votepool = eth.votePool
if stack.Config().EnableMaliciousVoteMonitor {
eth.handler.maliciousVoteMonitor = monitor.NewMaliciousVoteMonitor()
}
}

eth.miner = miner.New(eth, &config.Miner, chainConfig, eth.EventMux(), eth.engine, eth.isLocalBlock)
Expand Down
33 changes: 28 additions & 5 deletions eth/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/beacon"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/forkid"
"github.com/ethereum/go-ethereum/core/monitor"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
Expand Down Expand Up @@ -133,11 +134,12 @@ type handler struct {
checkpointNumber uint64 // Block number for the sync progress validator to cross reference
checkpointHash common.Hash // Block hash for the sync progress validator to cross reference

database ethdb.Database
txpool txPool
votepool votePool
chain *core.BlockChain
maxPeers int
database ethdb.Database
txpool txPool
votepool votePool
maliciousVoteMonitor *monitor.MaliciousVoteMonitor
chain *core.BlockChain
maxPeers int

downloader *downloader.Downloader
blockFetcher *fetcher.BlockFetcher
Expand Down Expand Up @@ -641,6 +643,11 @@ func (h *handler) Start(maxPeers int) {
h.voteCh = make(chan core.NewVoteEvent, voteChanSize)
h.votesSub = h.votepool.SubscribeNewVoteEvent(h.voteCh)
go h.voteBroadcastLoop()

if h.maliciousVoteMonitor != nil {
h.wg.Add(1)
go h.startMaliciousVoteMonitor()
}
}

// announce local pending transactions again
Expand All @@ -659,6 +666,22 @@ func (h *handler) Start(maxPeers int) {
go h.chainSync.loop()
}

func (h *handler) startMaliciousVoteMonitor() {
defer h.wg.Done()
voteCh := make(chan core.NewVoteEvent, voteChanSize)
votesSub := h.votepool.SubscribeNewVoteEvent(voteCh)
defer votesSub.Unsubscribe()
for {
select {
case event := <-voteCh:
pendingBlockNumber := h.chain.CurrentHeader().Number.Uint64() + 1
h.maliciousVoteMonitor.Verify(event.Vote, pendingBlockNumber)
case <-votesSub.Err():
return
}
}
}

func (h *handler) Stop() {
h.txsSub.Unsubscribe() // quits txBroadcastLoop
h.reannoTxsSub.Unsubscribe() // quits txReannounceLoop
Expand Down
3 changes: 3 additions & 0 deletions node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ type Config struct {
// EnableDoubleSignMonitor is a flag that whether to enable the double signature checker
EnableDoubleSignMonitor bool `toml:",omitempty"`

// EnableMaliciousVoteMonitor is a flag that whether to enable the malicious vote checker
EnableMaliciousVoteMonitor bool `toml:",omitempty"`

// BLSPasswordFile is the file that contains BLS wallet password.
BLSPasswordFile string `toml:",omitempty"`

Expand Down

0 comments on commit 436a579

Please sign in to comment.