// 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" "github.com/holiman/uint256" ) 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.XDCGenesisGasLimit, 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{ chainConfig: 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()) } } func TestWorkerSetGasTipValidation(t *testing.T) { w := &worker{tip: uint256.NewInt(1)} old := new(uint256.Int).Set(w.tip) tests := []struct { name string tip *big.Int }{ {name: "nil", tip: nil}, {name: "negative", tip: big.NewInt(-1)}, {name: "too high", tip: new(big.Int).Add(maxGasTip, big.NewInt(1))}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { if err := w.setGasTip(tc.tip); err == nil { t.Fatalf("expected error for %s tip", tc.name) } if w.tip.Cmp(old) != 0 { t.Fatalf("tip changed on invalid input: have %v want %v", w.tip, old) } }) } } func TestWorkerSetGasTipCopiesValue(t *testing.T) { w := &worker{} input := big.NewInt(2 * params.GWei) if err := w.setGasTip(input); err != nil { t.Fatalf("setGasTip failed: %v", err) } if w.tip == nil { t.Fatal("worker tip was not set") } input.Add(input, big.NewInt(1)) if w.tip.Cmp(uint256.NewInt(2*params.GWei)) != 0 { t.Fatalf("worker tip mutated via input pointer: have %v", w.tip) } }