From be36b32b3e4d999a5504e22c4d69d2c521860c63 Mon Sep 17 00:00:00 2001 From: benjamin202410 Date: Thu, 12 Mar 2026 12:12:49 -0700 Subject: [PATCH] fix(consensus): fix config object not reference to same one and concurrency issues and refactor access pattern (#2146) * bug fix for using same config object * update * change log level to trace on ispochswtich --------- Co-authored-by: liam.lai --- consensus/XDPoS/engines/engine_v2/engine.go | 12 ++++++++---- consensus/XDPoS/engines/engine_v2/epochSwitch.go | 2 +- eth/hooks/engine_v2_hooks.go | 7 +++++-- params/config.go | 15 ++++++++++++--- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 01937fa625..a2da3d8616 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -1,6 +1,7 @@ package engine_v2 import ( + "bytes" "encoding/json" "errors" "fmt" @@ -1115,8 +1116,6 @@ func (x *XDPoS_v2) saveRewardToFile(blockHash common.Hash, blockNumber uint64) { log.Debug("[saveRewardToFile] Saved rewards", "filename", filename) log.Debug("[saveRewardToFile] Saved rewards", "rewards", string(data)) - - return } func deepCloneJSON(original map[string]interface{}) (map[string]interface{}, error) { @@ -1126,10 +1125,15 @@ func deepCloneJSON(original map[string]interface{}) (map[string]interface{}, err } var cloned map[string]interface{} - err = json.Unmarshal(data, &cloned) - if err != nil { + dec := json.NewDecoder(bytes.NewReader(data)) + dec.UseNumber() + if err = dec.Decode(&cloned); err != nil { return nil, fmt.Errorf("failed to unmarshal: %w", err) } return cloned, nil } + +func (x *XDPoS_v2) Config(r uint64) *params.V2Config { + return x.config.V2.Config(r) +} diff --git a/consensus/XDPoS/engines/engine_v2/epochSwitch.go b/consensus/XDPoS/engines/engine_v2/epochSwitch.go index 9bcc061986..e4b222fce1 100644 --- a/consensus/XDPoS/engines/engine_v2/epochSwitch.go +++ b/consensus/XDPoS/engines/engine_v2/epochSwitch.go @@ -164,7 +164,7 @@ func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) { log.Info("[IsEpochSwitch] true, parent equals V2.SwitchBlock", "round", round, "number", header.Number.Uint64(), "hash", header.Hash()) return true, epochNum, nil } - log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash().Hex()) + log.Trace("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash().Hex()) // if isEpochSwitch, add to cache if parentRound < epochStartRound { x.round2epochBlockInfo.Add(round, &types.BlockInfo{ diff --git a/eth/hooks/engine_v2_hooks.go b/eth/hooks/engine_v2_hooks.go index 41a771ba60..194ba818b2 100644 --- a/eth/hooks/engine_v2_hooks.go +++ b/eth/hooks/engine_v2_hooks.go @@ -99,7 +99,8 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf listBlockHash = append(listBlockHash, parentHash) } - currentConfig := chain.Config().XDPoS.V2.Config(uint64(round)) + currentConfig := adaptor.EngineV2.Config(uint64(round)) + // add list not miner to penalties preMasternodes := adaptor.EngineV2.GetMasternodesByHash(chain, currentHash) penalties := []common.Address{} @@ -286,7 +287,9 @@ func AttachConsensusV2Hooks(adaptor *XDPoS.XDPoS, bc *core.BlockChain, chainConf log.Error("[HookReward] Fail to get round", "error", err) return nil, err } - currentConfig := chain.Config().XDPoS.V2.Config(uint64(round)) + + currentConfig := adaptor.EngineV2.Config(uint64(round)) + // Get signers/signing tx count, and burned tokens in one epoch signers, burnedInOneEpoch, err := GetSigningTxCount(adaptor, chain, header, parentState, currentConfig) diff --git a/params/config.go b/params/config.go index 93ddd0f123..c606847c26 100644 --- a/params/config.go +++ b/params/config.go @@ -840,11 +840,14 @@ func (v2 *V2) GetCurrentConfig() *V2Config { } // avoid CurrentConfig is changed by other goroutines - cpyConfig := *v2.CurrentConfig - return &cpyConfig + cfg := *v2.CurrentConfig + return &cfg } func (v2 *V2) Config(round uint64) *V2Config { + v2.lock.RLock() + defer v2.lock.RUnlock() + configRound := round var index uint64 @@ -855,10 +858,16 @@ func (v2 *V2) Config(round uint64) *V2Config { break } } - return v2.AllConfigs[index] + + // avoid config is changed by other goroutines + cfg := *v2.AllConfigs[index] + return &cfg } func (v2 *V2) BuildConfigIndex() { + v2.lock.Lock() + defer v2.lock.Unlock() + list := slices.Collect(maps.Keys(v2.AllConfigs)) // Make it descending order slices.SortFunc(list, func(a, b uint64) int {