Skip to content

Commit

Permalink
mointor: implement double sign monitor
Browse files Browse the repository at this point in the history
  • Loading branch information
j75689 committed Nov 23, 2022
1 parent 39c2d16 commit 1e88ee8
Show file tree
Hide file tree
Showing 11 changed files with 235 additions and 3 deletions.
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,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 @@ -900,6 +900,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 @@ -1166,6 +1171,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 @@ -1328,6 +1341,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
12 changes: 9 additions & 3 deletions consensus/beacon/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,10 +171,11 @@ func (beacon *Beacon) VerifyUncles(chain consensus.ChainReader, block *types.Blo
// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum consensus engine. The difference between the beacon and classic is
// (a) The following fields are expected to be constants:
// - difficulty is expected to be 0
// - nonce is expected to be 0
// - unclehash is expected to be Hash(emptyHeader)
// - difficulty is expected to be 0
// - nonce is expected to be 0
// - unclehash is expected to be Hash(emptyHeader)
// to be the desired constants
//
// (b) the timestamp is not verified anymore
// (c) the extradata is limited to 32 bytes
func (beacon *Beacon) verifyHeader(chain consensus.ChainHeaderReader, header, parent *types.Header) error {
Expand Down Expand Up @@ -337,6 +338,11 @@ func (beacon *Beacon) Close() error {
return beacon.ethone.Close()
}

// ExtraSeal returns fixed number of extra-data suffix bytes reserved for signer seal
func (beacon *Beacon) ExtraSeal() int {
return 0
}

// IsPoSHeader reports the header belongs to the PoS-stage with some special fields.
// This function is not suitable for a part of APIs like Prepare or CalcDifficulty
// because the header difficulty is not set yet.
Expand Down
5 changes: 5 additions & 0 deletions consensus/clique/clique.go
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,11 @@ func (c *Clique) Delay(chain consensus.ChainReader, header *types.Header) *time.
return nil
}

// ExtraSeal returns fixed number of extra-data suffix bytes reserved for signer seal
func (c *Clique) ExtraSeal() int {
return extraSeal
}

// Seal implements consensus.Engine, attempting to create a sealed block using
// the local signing credentials.
func (c *Clique) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
Expand Down
2 changes: 2 additions & 0 deletions consensus/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ type Engine interface {
// SealHash returns the hash of a block prior to it being sealed.
SealHash(header *types.Header) common.Hash

ExtraSeal() int

// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have.
CalcDifficulty(chain ChainHeaderReader, time uint64, parent *types.Header) *big.Int
Expand Down
5 changes: 5 additions & 0 deletions consensus/ethash/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,11 @@ func (ethash *Ethash) Delay(_ consensus.ChainReader, _ *types.Header) *time.Dura
return nil
}

// ExtraSeal returns fixed number of extra-data suffix bytes reserved for signer seal
func (ethash *Ethash) ExtraSeal() int {
return 0
}

// SealHash returns the hash of a block prior to it being sealed.
func (ethash *Ethash) SealHash(header *types.Header) (hash common.Hash) {
hasher := sha3.NewLegacyKeccak256()
Expand Down
5 changes: 5 additions & 0 deletions consensus/parlia/parlia.go
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,11 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header) *time.
return &delay
}

// ExtraSeal returns fixed number of extra-data suffix bytes reserved for signer seal
func (p *Parlia) ExtraSeal() int {
return extraSeal
}

// Seal implements consensus.Engine, attempting to create a sealed block using
// the local signing credentials.
func (p *Parlia) Seal(chain consensus.ChainHeaderReader, block *types.Block, results chan<- *types.Block, stop <-chan struct{}) error {
Expand Down
39 changes: 39 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 @@ -2566,6 +2577,29 @@ func (bc *BlockChain) trustedDiffLayerLoop() {
}
}

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

go bc.doubleSignMonitor.Start(headerChan)
for {
select {
case event := <-eventChan:
headerChan <- 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 @@ -2972,6 +3006,11 @@ func EnableBlockValidator(chainConfig *params.ChainConfig, engine consensus.Engi
}
}

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

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

import (
"bytes"
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/prque"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/secp256k1"
"github.com/ethereum/go-ethereum/log"
)

const (
MaxCacheHeader = 100
)

func NewDoubleSignMonitor(
extraSeal int,
sealHash func(header *types.Header) (hash common.Hash),
) *DoubleSignMonitor {
return &DoubleSignMonitor{
sealHash: sealHash,
extraSeal: extraSeal,
headerNumbers: prque.New(nil),
headers: make(map[uint64]*types.Header, MaxCacheHeader),
quit: make(chan struct{}),
}
}

type DoubleSignMonitor struct {
extraSeal int
sealHash func(header *types.Header) (hash common.Hash)
headerNumbers *prque.Prque
headers map[uint64]*types.Header
quit chan struct{}
}

func (m *DoubleSignMonitor) getSignature(h *types.Header) ([]byte, error) {
if len(h.Extra) < m.extraSeal {
return nil, errors.New("extra-data 65 byte signature suffix missing")
}
signature := h.Extra[len(h.Extra)-m.extraSeal:]
return signature, nil
}

func (m *DoubleSignMonitor) extractSignerFromHeader(h *types.Header) (signer common.Address, err error) {
signature, err := m.getSignature(h)
if err != nil {
return
}
pubKey, err := secp256k1.RecoverPubkey(m.sealHash(h).Bytes(), signature)
if err != nil {
return
}
copy(signer[:], crypto.Keccak256(pubKey[1:])[12:])
return
}

func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, []byte, []byte, error) {
if h1 == nil || h2 == nil {
return false, nil, nil, nil
}
if h1.Number.Cmp(h2.Number) != 0 {
return false, nil, nil, nil
}
if bytes.Equal(h1.ParentHash[:], h2.ParentHash[:]) {
return false, nil, nil, nil
}
signature1, err := m.getSignature(h1)
if err != nil {
return false, nil, nil, err
}
signature2, err := m.getSignature(h2)
if err != nil {
return false, nil, nil, err
}
if bytes.Equal(signature1, signature2) {
return false, signature1, signature2, nil
}

signer1, err := m.extractSignerFromHeader(h1)
if err != nil {
return false, signature1, signature2, err
}
signer2, err := m.extractSignerFromHeader(h2)
if err != nil {
return false, signature1, signature2, err
}
if !bytes.Equal(signer1.Bytes(), signer2.Bytes()) {
return false, signature1, signature2, nil
}

return true, signature1, signature2, 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, []byte, []byte, 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, nil, nil
}

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

return false, nil, s1, s2, nil
}

func (m *DoubleSignMonitor) Start(ch <-chan *types.Header) {
for {
select {
case h := <-ch:
isDoubleSign, h2, s1, s2, err := m.checkHeader(h)
if err != nil {
log.Error("check double sign header error", "err", err)
continue
}
if isDoubleSign {
// found a double sign header
log.Error("found a double sign header", "number", h.Number.Uint64(),
"first_hash", h.Hash(), "first_miner", h.Coinbase, "first_signature", s1,
"second_hash", h2.Hash(), "second_miner", h2.Coinbase, "second_signature", s2)
}
case <-m.quit:
return
}
}
}

func (m *DoubleSignMonitor) Close() {
close(m.quit)
}
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 1e88ee8

Please sign in to comment.