Skip to content
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

mointor: implement double sign monitor #1199

Merged
merged 8 commits into from
Feb 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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