diff --git a/miner/worker.go b/miner/worker.go index fa10c58dea..6503e6295a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -248,6 +248,15 @@ func (w *worker) start() { for agent := range w.agents { agent.Start() } + + // Verify config and engine + var xdposEngine *XDPoS.XDPoS + if engine, ok := w.engine.(*XDPoS.XDPoS); ok { + xdposEngine = engine + } + if w.config != nil && w.config.XDPoS != nil && xdposEngine == nil { + log.Warn("XDPoS config enabled but consensus engine is not XDPoS") + } } func (w *worker) stop() { @@ -287,8 +296,12 @@ func (w *worker) update() { // timeout waiting for v1 initial value minePeriod := 2 - MinePeriodCh := w.engine.(*XDPoS.XDPoS).MinePeriodCh - NewRoundCh := w.engine.(*XDPoS.XDPoS).NewRoundCh + var minePeriodCh <-chan int + var newRoundCh <-chan types.Round + if xdposEngine, ok := w.engine.(*XDPoS.XDPoS); ok { + minePeriodCh = xdposEngine.MinePeriodCh + newRoundCh = xdposEngine.NewRoundCh + } timeout := time.NewTimer(time.Duration(minePeriod) * time.Second) defer timeout.Stop() @@ -321,7 +334,7 @@ func (w *worker) update() { for { // A real event arrived, process interesting content select { - case v := <-MinePeriodCh: + case v := <-minePeriodCh: log.Info("[worker] update wait period", "period", v) minePeriod = v w.resetCh <- time.Duration(minePeriod) * time.Second @@ -340,7 +353,7 @@ func (w *worker) update() { w.resetCh <- resetTime // Handle new round - case <-NewRoundCh: + case <-newRoundCh: w.commitNewWork() resetTime := getResetTime(w.chain, minePeriod) w.resetCh <- resetTime @@ -664,21 +677,28 @@ func (w *worker) checkPreCommitWithLock() (*types.Block, bool) { // checkPreCommit checks whether a new work commit is needed, // returns the parent block and shouldReturn. func (w *worker) checkPreCommit() (*types.Block, bool) { - c := w.engine.(*XDPoS.XDPoS) + var xdposEngine *XDPoS.XDPoS + if engine, ok := w.engine.(*XDPoS.XDPoS); ok { + xdposEngine = engine + } var parent *types.Block currentHeader := w.chain.CurrentBlock() // Guard against nil header (early startup or uninitialised chain). if currentHeader == nil { return nil, true } - if c != nil { - parent = c.FindParentBlockToAssign(w.chain, currentHeader) + if xdposEngine != nil { + parent = xdposEngine.FindParentBlockToAssign(w.chain, currentHeader) } else { parent = w.chain.GetBlock(currentHeader.Hash(), currentHeader.Number.Uint64()) } if parent == nil { return nil, true } + if w.config.XDPoS != nil && xdposEngine == nil { + log.Debug("XDPoS config enabled but consensus engine is not XDPoS") + return parent, true + } if parent.Hash().Hex() == w.lastParentBlockCommit { return parent, true } @@ -689,8 +709,8 @@ func (w *worker) checkPreCommit() (*types.Block, bool) { // Only try to commit new work if we are mining if atomic.LoadInt32(&w.mining) == 1 { // check if we are right after parent's coinbase in the list - if w.config.XDPoS != nil { - ok, err := c.YourTurn(w.chain, parent.Header(), w.coinbase) + if w.config.XDPoS != nil && xdposEngine != nil { + ok, err := xdposEngine.YourTurn(w.chain, parent.Header(), w.coinbase) if err != nil { log.Warn("Failed when trying to commit new work", "err", err) return parent, true diff --git a/miner/worker_test.go b/miner/worker_test.go new file mode 100644 index 0000000000..a348fccf50 --- /dev/null +++ b/miner/worker_test.go @@ -0,0 +1,132 @@ +// Copyright 2026 The XDPoSChain Authors +// This file is part of the XDPoSChain library. +// +// The XDPoSChain library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The XDPoSChain library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the XDPoSChain library. If not, see . + +package miner + +import ( + "math/big" + "testing" + "time" + + "github.com/XinFinOrg/XDPoSChain/common" + "github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils" + "github.com/XinFinOrg/XDPoSChain/consensus/ethash" + "github.com/XinFinOrg/XDPoSChain/core" + "github.com/XinFinOrg/XDPoSChain/core/rawdb" + "github.com/XinFinOrg/XDPoSChain/core/types" + "github.com/XinFinOrg/XDPoSChain/core/vm" + "github.com/XinFinOrg/XDPoSChain/event" + "github.com/XinFinOrg/XDPoSChain/params" +) + +func newBlockingSubscription() event.Subscription { + return event.NewSubscription(func(unsub <-chan struct{}) error { + <-unsub + return nil + }) +} + +func TestWorkerUpdateNonXDPoSStaysRunning(t *testing.T) { + worker := &worker{ + engine: ethash.NewFaker(), + chainHeadSub: newBlockingSubscription(), + chainSideSub: newBlockingSubscription(), + resetCh: make(chan time.Duration, 1), + } + + done := make(chan struct{}) + started := make(chan struct{}) + go func() { + close(started) + worker.update() + close(done) + }() + select { + case <-started: + // worker.update has started; proceed with timing checks. + case <-time.After(time.Second): + t.Fatal("worker.update did not start in time") + } + + select { + case <-done: + t.Fatal("worker.update returned before unsubscribe") + default: + // Expected: update is still running until subscription error. + } + worker.chainHeadSub.Unsubscribe() + + select { + case <-done: + // Expected: update exits after subscription error. + case <-time.After(time.Second): + t.Fatal("worker.update did not return after unsubscribe") + } +} + +func TestWorkerCheckPreCommitXDPoSMismatch(t *testing.T) { + config := ¶ms.ChainConfig{ + ChainID: big.NewInt(1), + XDPoS: ¶ms.XDPoSConfig{ + V2: ¶ms.V2{ + SwitchBlock: big.NewInt(0), + AllConfigs: map[uint64]*params.V2Config{ + 0: {MinePeriod: 2}, + }, + }, + }, + } + signer := common.HexToAddress("0x0000000000000000000000000000000000000001") + extraData := make([]byte, 0, utils.ExtraVanity+common.AddressLength+utils.ExtraSeal) + extraData = append(extraData, make([]byte, utils.ExtraVanity)...) + extraData = append(extraData, signer.Bytes()...) + extraData = append(extraData, make([]byte, utils.ExtraSeal)...) + genesis := &core.Genesis{ + Config: config, + GasLimit: params.TargetGasLimit, + Difficulty: big.NewInt(1), + Alloc: types.GenesisAlloc{}, + ExtraData: extraData, + } + db := rawdb.NewMemoryDatabase() + if _, err := genesis.Commit(db); err != nil { + t.Fatalf("failed to commit genesis: %v", err) + } + engine := ethash.NewFaker() + chain, err := core.NewBlockChain(db, nil, genesis, engine, vm.Config{}) + if err != nil { + t.Fatalf("failed to create blockchain: %v", err) + } + defer chain.Stop() + + worker := &worker{ + config: config, + engine: engine, + chain: chain, + announceTxs: true, + } + + parent, shouldReturn := worker.checkPreCommitWithLock() + if parent == nil { + t.Fatal("expected parent block, got nil") + } + if !shouldReturn { + t.Fatal("expected checkPreCommitWithLock to skip when XDPoS config is enabled but engine is not XDPoS") + } + if parent.Number().Sign() != 0 { + t.Fatalf("expected genesis parent, got number %v", parent.Number()) + } +}