Skip to content

Commit

Permalink
mointor: implement double sign monitor (bnb-chain#1199)
Browse files Browse the repository at this point in the history
  • Loading branch information
j75689 committed Feb 13, 2023
1 parent 050dd4d commit 153d040
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ var (
configFileFlag,
utils.BlockAmountReserved,
utils.CheckSnapshotWithMPT,
utils.EnableDoubleSignMonitorFlag,
}

rpcFlags = []cli.Flag{
Expand Down
14 changes: 14 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -902,6 +902,11 @@ var (
Name: "check-snapshot-with-mpt",
Usage: "Enable checking between snapshot and MPT ",
}

EnableDoubleSignMonitorFlag = cli.BoolFlag{
Name: "monitor.doublesign",
Usage: "Enable double sign monitor to check whether any validator signs multiple blocks",
}
)

// MakeDataDir retrieves the currently requested data directory, terminating
Expand Down Expand Up @@ -1168,6 +1173,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) {
if ctx.GlobalBool(EnableDoubleSignMonitorFlag.Name) {
cfg.EnableDoubleSignMonitor = true
}
}

// MakeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database.
func MakeDatabaseHandles() int {
Expand Down Expand Up @@ -1330,6 +1343,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) {
setNodeUserIdent(ctx, cfg)
setDataDir(ctx, cfg)
setSmartCard(ctx, cfg)
setMonitor(ctx, cfg)

if ctx.GlobalIsSet(ExternalSignerFlag.Name) {
cfg.ExternalSigner = ctx.GlobalString(ExternalSignerFlag.Name)
Expand Down
37 changes: 37 additions & 0 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/common/mclock"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/monitor"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
Expand Down Expand Up @@ -258,6 +259,9 @@ type BlockChain struct {

shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block.
terminateInsert func(common.Hash, uint64) bool // Testing hook used to terminate ancient receipt chain insertion.

// monitor
doubleSignMonitor *monitor.DoubleSignMonitor
}

// NewBlockChain returns a fully initialised block chain using information
Expand Down Expand Up @@ -504,6 +508,13 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
bc.wg.Add(1)
go bc.rewindInvalidHeaderBlockLoop()
}

if bc.doubleSignMonitor != nil {
bc.wg.Add(1)
go bc.startDoubleSignMonitor()

}

return bc, nil
}

Expand Down Expand Up @@ -2569,6 +2580,27 @@ func (bc *BlockChain) trustedDiffLayerLoop() {
}
}

func (bc *BlockChain) startDoubleSignMonitor() {
eventChan := make(chan ChainHeadEvent, monitor.MaxCacheHeader)
sub := bc.SubscribeChainHeadEvent(eventChan)
defer func() {
sub.Unsubscribe()
close(eventChan)
bc.wg.Done()
}()

for {
select {
case event := <-eventChan:
if bc.doubleSignMonitor != nil {
bc.doubleSignMonitor.Verify(event.Block.Header())
}
case <-bc.quit:
return
}
}
}

func (bc *BlockChain) GetUnTrustedDiffLayer(blockHash common.Hash, pid string) *types.DiffLayer {
bc.diffMux.RLock()
defer bc.diffMux.RUnlock()
Expand Down Expand Up @@ -2975,6 +3007,11 @@ func EnableBlockValidator(chainConfig *params.ChainConfig, engine consensus.Engi
}
}

func EnableDoubleSignChecker(bc *BlockChain) (*BlockChain, error) {
bc.doubleSignMonitor = monitor.NewDoubleSignMonitor()
return bc, nil
}

func (bc *BlockChain) GetVerifyResult(blockNumber uint64, blockHash common.Hash, diffHash common.Hash) *VerifyResult {
var res VerifyResult
res.BlockNumber = blockNumber
Expand Down
103 changes: 103 additions & 0 deletions core/monitor/double_sign_mointor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package monitor

import (
"bytes"

"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
)

const (
MaxCacheHeader = 100
)

func NewDoubleSignMonitor() *DoubleSignMonitor {
return &DoubleSignMonitor{
headerNumbers: prque.New(nil),
headers: make(map[uint64]*types.Header, MaxCacheHeader),
}
}

type DoubleSignMonitor struct {
headerNumbers *prque.Prque
headers map[uint64]*types.Header
}

func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, error) {
if h1 == nil || h2 == nil {
return false, nil
}
if h1.Number.Cmp(h2.Number) != 0 {
return false, nil
}
if !bytes.Equal(h1.ParentHash[:], h2.ParentHash[:]) {
return false, nil
}
// if the Hash is different the signature should not be equal
hash1, hash2 := h1.Hash(), h2.Hash()
if bytes.Equal(hash1[:], hash2[:]) {
return false, nil
}
// signer is already verified in sync program, we can trust coinbase.
if !bytes.Equal(h1.Coinbase[:], h2.Coinbase[:]) {
return false, nil
}

return true, nil
}

func (m *DoubleSignMonitor) deleteOldHeader() {
v, _ := m.headerNumbers.Pop()
h := v.(*types.Header)
delete(m.headers, h.Number.Uint64())
}

func (m *DoubleSignMonitor) checkHeader(h *types.Header) (bool, *types.Header, error) {
h2, exist := m.headers[h.Number.Uint64()]
if !exist {
if m.headerNumbers.Size() > MaxCacheHeader {
m.deleteOldHeader()
}
m.headers[h.Number.Uint64()] = h
m.headerNumbers.Push(h, -h.Number.Int64())
return false, nil, nil
}

isDoubleSign, err := m.isDoubleSignHeaders(h, h2)
if err != nil {
return false, nil, err
}
if isDoubleSign {
return true, h2, nil
}

return false, nil, nil
}

func (m *DoubleSignMonitor) Verify(h *types.Header) {
isDoubleSign, h2, err := m.checkHeader(h)
if err != nil {
log.Error("check double sign header error", "err", err)
return
}
if isDoubleSign {
// found a double sign header
log.Warn("found a double sign header", "number", h.Number.Uint64(),
"first_hash", h.Hash(), "first_miner", h.Coinbase,
"second_hash", h2.Hash(), "second_miner", h2.Coinbase)
h1Bytes, err := rlp.EncodeToBytes(h)
if err != nil {
log.Error("encode header error", "err", err, "hash", h.Hash())
}
h2Bytes, err := rlp.EncodeToBytes(h2)
if err != nil {
log.Error("encode header error", "err", err, "hash", h.Hash())
}
log.Warn("double sign header content",
"header1", hexutil.Encode(h1Bytes),
"header2", hexutil.Encode(h2Bytes))
}
}
3 changes: 3 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
if config.PersistDiff {
bcOps = append(bcOps, core.EnablePersistDiff(config.DiffBlock))
}
if stack.Config().EnableDoubleSignMonitor {
bcOps = append(bcOps, core.EnableDoubleSignChecker)
}

peers := newPeerSet()
bcOps = append(bcOps, core.EnableBlockValidator(chainConfig, eth.engine, config.TriesVerifyMode, peers))
Expand Down
3 changes: 3 additions & 0 deletions node/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ type Config struct {

// AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC.
AllowUnprotectedTxs bool `toml:",omitempty"`

// EnableDoubleSignMonitor is a flag that whether to enable the double signature checker
EnableDoubleSignMonitor bool `toml:",omitempty"`
}

// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
Expand Down

0 comments on commit 153d040

Please sign in to comment.