From 22d679950cfe5ebd61c514fed4919269bfc86915 Mon Sep 17 00:00:00 2001 From: j75689 Date: Wed, 23 Nov 2022 18:22:15 +0800 Subject: [PATCH 1/8] mointor: implement double sign monitor --- cmd/geth/main.go | 1 + cmd/utils/flags.go | 14 +++ consensus/beacon/consensus.go | 5 + consensus/clique/clique.go | 5 + consensus/consensus.go | 2 + consensus/ethash/consensus.go | 5 + consensus/parlia/parlia.go | 5 + core/blockchain.go | 39 ++++++++ core/monitor/double_sign_mointor.go | 149 ++++++++++++++++++++++++++++ eth/backend.go | 3 + node/config.go | 3 + 11 files changed, 231 insertions(+) create mode 100644 core/monitor/double_sign_mointor.go diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 852266513e..143f8047c6 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -171,6 +171,7 @@ var ( configFileFlag, utils.BlockAmountReserved, utils.CheckSnapshotWithMPT, + utils.EnableDoubleSignMonitorFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 10c1be58b7..802dfd64a4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -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 @@ -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 { @@ -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) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 25ae50ad8d..bd4dc40a7a 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -338,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. diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index a258f1fe5f..80969dfea5 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -595,6 +595,11 @@ func (c *Clique) Delay(chain consensus.ChainReader, header *types.Header, leftOv 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 { diff --git a/consensus/consensus.go b/consensus/consensus.go index 87632a9d0d..3e66313b29 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -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 diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 12a69c127a..36f9ef1915 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -614,6 +614,11 @@ func (ethash *Ethash) Delay(_ consensus.ChainReader, _ *types.Header, _ *time.Du 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() diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 5d28dadd5e..12c9f46563 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -823,6 +823,11 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv 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 { diff --git a/core/blockchain.go b/core/blockchain.go index 96bdac8905..7d42f86464 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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" @@ -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 @@ -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 } @@ -2569,6 +2580,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() @@ -2975,6 +3009,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 diff --git a/core/monitor/double_sign_mointor.go b/core/monitor/double_sign_mointor.go new file mode 100644 index 0000000000..6d72880527 --- /dev/null +++ b/core/monitor/double_sign_mointor.go @@ -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) +} diff --git a/eth/backend.go b/eth/backend.go index e9c077e6a7..fc0ca6534c 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -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)) diff --git a/node/config.go b/node/config.go index 3c8c541420..2e37c23532 100644 --- a/node/config.go +++ b/node/config.go @@ -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 From f7ea0bd298bb50b4ac12214f4a98073b8d35e21d Mon Sep 17 00:00:00 2001 From: j75689 Date: Thu, 24 Nov 2022 10:52:21 +0800 Subject: [PATCH 2/8] monitor: fix the double sign check function --- core/monitor/double_sign_mointor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/monitor/double_sign_mointor.go b/core/monitor/double_sign_mointor.go index 6d72880527..ad08ad0e4b 100644 --- a/core/monitor/double_sign_mointor.go +++ b/core/monitor/double_sign_mointor.go @@ -65,7 +65,7 @@ func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, []b if h1.Number.Cmp(h2.Number) != 0 { return false, nil, nil, nil } - if bytes.Equal(h1.ParentHash[:], h2.ParentHash[:]) { + if !bytes.Equal(h1.ParentHash[:], h2.ParentHash[:]) { return false, nil, nil, nil } signature1, err := m.getSignature(h1) From 969228570e9b49627cdb50f499ce482c2c17e5b9 Mon Sep 17 00:00:00 2001 From: j75689 Date: Fri, 25 Nov 2022 14:56:36 +0800 Subject: [PATCH 3/8] monitor: simplify double-signed check function --- consensus/consensus.go | 1 + core/monitor/double_sign_mointor.go | 27 ++------------------------- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index 3e66313b29..b8b62ed5cd 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -118,6 +118,7 @@ type Engine interface { // SealHash returns the hash of a block prior to it being sealed. SealHash(header *types.Header) common.Hash + // ExtraSeal returns fixed number of extra-data suffix bytes reserved for signer seal ExtraSeal() int // CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty diff --git a/core/monitor/double_sign_mointor.go b/core/monitor/double_sign_mointor.go index ad08ad0e4b..bf0701e76b 100644 --- a/core/monitor/double_sign_mointor.go +++ b/core/monitor/double_sign_mointor.go @@ -7,8 +7,6 @@ import ( "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" ) @@ -45,19 +43,6 @@ func (m *DoubleSignMonitor) getSignature(h *types.Header) ([]byte, error) { 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 @@ -79,16 +64,8 @@ func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, []b 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()) { + // signer is already verified in sync program, we can trust coinbase. + if !bytes.Equal(h1.Coinbase.Bytes(), h2.Coinbase.Bytes()) { return false, signature1, signature2, nil } From 7a705e824fb1316d47e8632dcfc9836fa69a48e7 Mon Sep 17 00:00:00 2001 From: j75689 Date: Mon, 28 Nov 2022 11:25:12 +0800 Subject: [PATCH 4/8] monitor: use hash value comparison instead of signature comparison --- consensus/beacon/consensus.go | 5 --- consensus/clique/clique.go | 5 --- consensus/consensus.go | 3 -- consensus/ethash/consensus.go | 5 --- consensus/parlia/parlia.go | 5 --- core/blockchain.go | 2 +- core/monitor/double_sign_mointor.go | 62 +++++++++-------------------- 7 files changed, 20 insertions(+), 67 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index bd4dc40a7a..25ae50ad8d 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -338,11 +338,6 @@ 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. diff --git a/consensus/clique/clique.go b/consensus/clique/clique.go index 80969dfea5..a258f1fe5f 100644 --- a/consensus/clique/clique.go +++ b/consensus/clique/clique.go @@ -595,11 +595,6 @@ func (c *Clique) Delay(chain consensus.ChainReader, header *types.Header, leftOv 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 { diff --git a/consensus/consensus.go b/consensus/consensus.go index b8b62ed5cd..87632a9d0d 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -118,9 +118,6 @@ type Engine interface { // SealHash returns the hash of a block prior to it being sealed. SealHash(header *types.Header) common.Hash - // ExtraSeal returns fixed number of extra-data suffix bytes reserved for signer seal - 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 diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 36f9ef1915..12a69c127a 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -614,11 +614,6 @@ func (ethash *Ethash) Delay(_ consensus.ChainReader, _ *types.Header, _ *time.Du 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() diff --git a/consensus/parlia/parlia.go b/consensus/parlia/parlia.go index 12c9f46563..5d28dadd5e 100644 --- a/consensus/parlia/parlia.go +++ b/consensus/parlia/parlia.go @@ -823,11 +823,6 @@ func (p *Parlia) Delay(chain consensus.ChainReader, header *types.Header, leftOv 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 { diff --git a/core/blockchain.go b/core/blockchain.go index 7d42f86464..2cc0c0f0b9 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -3010,7 +3010,7 @@ func EnableBlockValidator(chainConfig *params.ChainConfig, engine consensus.Engi } func EnableDoubleSignChecker(bc *BlockChain) (*BlockChain, error) { - bc.doubleSignMonitor = monitor.NewDoubleSignMonitor(bc.engine.ExtraSeal(), bc.engine.SealHash) + bc.doubleSignMonitor = monitor.NewDoubleSignMonitor() return bc, nil } diff --git a/core/monitor/double_sign_mointor.go b/core/monitor/double_sign_mointor.go index bf0701e76b..a8b9c06b0f 100644 --- a/core/monitor/double_sign_mointor.go +++ b/core/monitor/double_sign_mointor.go @@ -2,9 +2,7 @@ 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/log" @@ -14,13 +12,8 @@ const ( MaxCacheHeader = 100 ) -func NewDoubleSignMonitor( - extraSeal int, - sealHash func(header *types.Header) (hash common.Hash), -) *DoubleSignMonitor { +func NewDoubleSignMonitor() *DoubleSignMonitor { return &DoubleSignMonitor{ - sealHash: sealHash, - extraSeal: extraSeal, headerNumbers: prque.New(nil), headers: make(map[uint64]*types.Header, MaxCacheHeader), quit: make(chan struct{}), @@ -28,48 +21,31 @@ func NewDoubleSignMonitor( } 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) isDoubleSignHeaders(h1, h2 *types.Header) (bool, []byte, []byte, error) { +func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, error) { if h1 == nil || h2 == nil { - return false, nil, nil, nil + return false, nil } if h1.Number.Cmp(h2.Number) != 0 { - return false, nil, nil, nil + return false, 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 + return false, nil } - if bytes.Equal(signature1, signature2) { - return false, signature1, signature2, nil + // if the Hash is different the signature should not be equal + if bytes.Equal(h1.Hash().Bytes(), h2.Hash().Bytes()) { + return false, nil } // signer is already verified in sync program, we can trust coinbase. if !bytes.Equal(h1.Coinbase.Bytes(), h2.Coinbase.Bytes()) { - return false, signature1, signature2, nil + return false, nil } - return true, signature1, signature2, nil + return true, nil } func (m *DoubleSignMonitor) deleteOldHeader() { @@ -78,7 +54,7 @@ func (m *DoubleSignMonitor) deleteOldHeader() { delete(m.headers, h.Number.Uint64()) } -func (m *DoubleSignMonitor) checkHeader(h *types.Header) (bool, *types.Header, []byte, []byte, error) { +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 { @@ -86,25 +62,25 @@ func (m *DoubleSignMonitor) checkHeader(h *types.Header) (bool, *types.Header, [ } m.headers[h.Number.Uint64()] = h m.headerNumbers.Push(h, -h.Number.Int64()) - return false, nil, nil, nil, nil + return false, nil, nil } - isDoubleSign, s1, s2, err := m.isDoubleSignHeaders(h, h2) + isDoubleSign, err := m.isDoubleSignHeaders(h, h2) if err != nil { - return false, nil, s1, s2, err + return false, nil, err } if isDoubleSign { - return true, h2, s1, s2, nil + return true, h2, nil } - return false, nil, s1, s2, nil + return false, nil, nil } func (m *DoubleSignMonitor) Start(ch <-chan *types.Header) { for { select { case h := <-ch: - isDoubleSign, h2, s1, s2, err := m.checkHeader(h) + isDoubleSign, h2, err := m.checkHeader(h) if err != nil { log.Error("check double sign header error", "err", err) continue @@ -112,8 +88,8 @@ func (m *DoubleSignMonitor) Start(ch <-chan *types.Header) { 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) + "first_hash", h.Hash(), "first_miner", h.Coinbase, + "second_hash", h2.Hash(), "second_miner", h2.Coinbase) } case <-m.quit: return From a65906419bb2da1ea753a0c8ca43d29f50e2c39c Mon Sep 17 00:00:00 2001 From: j75689 Date: Mon, 28 Nov 2022 11:28:38 +0800 Subject: [PATCH 5/8] revert: undo the changes of beacon.consensus.go --- consensus/beacon/consensus.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 25ae50ad8d..8282ed7cb4 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -171,11 +171,10 @@ 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 { From 2464933c7799d630fc5fa81a96cc554600d459cb Mon Sep 17 00:00:00 2001 From: j75689 Date: Mon, 28 Nov 2022 13:00:10 +0800 Subject: [PATCH 6/8] monitor: fix comments --- core/blockchain.go | 8 +++--- core/monitor/double_sign_mointor.go | 40 ++++++++++------------------- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 2cc0c0f0b9..b119270b99 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2583,20 +2583,18 @@ 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() + if bc.doubleSignMonitor != nil { + bc.doubleSignMonitor.Verify(event.Block.Header()) + } case <-bc.quit: return } diff --git a/core/monitor/double_sign_mointor.go b/core/monitor/double_sign_mointor.go index a8b9c06b0f..7803860c06 100644 --- a/core/monitor/double_sign_mointor.go +++ b/core/monitor/double_sign_mointor.go @@ -16,14 +16,12 @@ func NewDoubleSignMonitor() *DoubleSignMonitor { return &DoubleSignMonitor{ headerNumbers: prque.New(nil), headers: make(map[uint64]*types.Header, MaxCacheHeader), - quit: make(chan struct{}), } } type DoubleSignMonitor struct { headerNumbers *prque.Prque headers map[uint64]*types.Header - quit chan struct{} } func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, error) { @@ -37,11 +35,12 @@ func (m *DoubleSignMonitor) isDoubleSignHeaders(h1, h2 *types.Header) (bool, err return false, nil } // if the Hash is different the signature should not be equal - if bytes.Equal(h1.Hash().Bytes(), h2.Hash().Bytes()) { + 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.Bytes(), h2.Coinbase.Bytes()) { + if !bytes.Equal(h1.Coinbase[:], h2.Coinbase[:]) { return false, nil } @@ -76,27 +75,16 @@ func (m *DoubleSignMonitor) checkHeader(h *types.Header) (bool, *types.Header, e return false, nil, nil } -func (m *DoubleSignMonitor) Start(ch <-chan *types.Header) { - for { - select { - case h := <-ch: - isDoubleSign, h2, 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, - "second_hash", h2.Hash(), "second_miner", h2.Coinbase) - } - case <-m.quit: - return - } +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.Error("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) } -} - -func (m *DoubleSignMonitor) Close() { - close(m.quit) } From 82fc576837f2d1d77fb90a34f65b8c0038c9fd75 Mon Sep 17 00:00:00 2001 From: j75689 Date: Fri, 16 Dec 2022 15:17:41 +0800 Subject: [PATCH 7/8] monitor: log more info of double-signed header --- core/monitor/double_sign_mointor.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/core/monitor/double_sign_mointor.go b/core/monitor/double_sign_mointor.go index 7803860c06..54a18c27bd 100644 --- a/core/monitor/double_sign_mointor.go +++ b/core/monitor/double_sign_mointor.go @@ -3,9 +3,11 @@ 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 ( @@ -83,8 +85,19 @@ func (m *DoubleSignMonitor) Verify(h *types.Header) { } if isDoubleSign { // found a double sign header - log.Error("found a double sign header", "number", h.Number.Uint64(), + 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)) } } From 6c8542ca664897205b924d1641d9ab199994f358 Mon Sep 17 00:00:00 2001 From: j75689 Date: Fri, 3 Feb 2023 10:35:09 +0800 Subject: [PATCH 8/8] docs: fix annotation of verifyHeader --- consensus/beacon/consensus.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go index 8282ed7cb4..25ae50ad8d 100644 --- a/consensus/beacon/consensus.go +++ b/consensus/beacon/consensus.go @@ -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 {