From 5a26279c1cfca04548e571226320880118278135 Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Mon, 3 Nov 2025 17:21:54 +0800 Subject: [PATCH] engine_v2, params: fix unsynchronized reads of V2.CurrentConfig, close XFN-53 (#1642) --- consensus/XDPoS/engines/engine_v2/engine.go | 12 +++++++----- consensus/XDPoS/engines/engine_v2/timeout.go | 2 +- params/config.go | 16 +++++++++++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/consensus/XDPoS/engines/engine_v2/engine.go b/consensus/XDPoS/engines/engine_v2/engine.go index 260fdd19ff..5725beb9b6 100644 --- a/consensus/XDPoS/engines/engine_v2/engine.go +++ b/consensus/XDPoS/engines/engine_v2/engine.go @@ -150,14 +150,15 @@ func (x *XDPoS_v2) UpdateParams(header *types.Header) { x.config.V2.UpdateConfig(uint64(round)) // Setup timeoutTimer - duration := time.Duration(x.config.V2.CurrentConfig.TimeoutPeriod) * time.Second - err = x.timeoutWorker.SetParams(duration, x.config.V2.CurrentConfig.ExpTimeoutConfig.Base, x.config.V2.CurrentConfig.ExpTimeoutConfig.MaxExponent) + currentConfig := x.config.V2.GetCurrentConfig() + duration := time.Duration(currentConfig.TimeoutPeriod) * time.Second + err = x.timeoutWorker.SetParams(duration, currentConfig.ExpTimeoutConfig.Base, currentConfig.ExpTimeoutConfig.MaxExponent) if err != nil { log.Error("[UpdateParams] set params failed", "err", err) } // avoid deadlock go func() { - x.minePeriodCh <- x.config.V2.CurrentConfig.MinePeriod + x.minePeriodCh <- currentConfig.MinePeriod }() } @@ -261,10 +262,11 @@ func (x *XDPoS_v2) initial(chain consensus.ChainReader, header *types.Header) er } // Initial timeout - log.Warn("[initial] miner wait period", "period", x.config.V2.CurrentConfig.MinePeriod) + currentConfig := x.config.V2.GetCurrentConfig() + log.Warn("[initial] miner wait period", "period", currentConfig.MinePeriod) // avoid deadlock go func() { - x.minePeriodCh <- x.config.V2.CurrentConfig.MinePeriod + x.minePeriodCh <- currentConfig.MinePeriod }() // Kick-off the countdown timer diff --git a/consensus/XDPoS/engines/engine_v2/timeout.go b/consensus/XDPoS/engines/engine_v2/timeout.go index 0a57de31a4..729d8f982d 100644 --- a/consensus/XDPoS/engines/engine_v2/timeout.go +++ b/consensus/XDPoS/engines/engine_v2/timeout.go @@ -324,7 +324,7 @@ func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error { } x.timeoutCount++ - if x.timeoutCount%x.config.V2.CurrentConfig.TimeoutSyncThreshold == 0 { + if x.timeoutCount%x.config.V2.GetCurrentConfig().TimeoutSyncThreshold == 0 { syncInfo := x.getSyncInfo() log.Info("[OnCountdownTimeout] Timeout sync threshold reached, send syncInfo message", "QC round", syncInfo.HighestQuorumCert.ProposedBlockInfo.Round, "QC num", syncInfo.HighestQuorumCert.ProposedBlockInfo.Number, "QC sigs", len(syncInfo.HighestQuorumCert.Signatures), "TC round", syncInfo.HighestTimeoutCert.Round, "TC sigs", len(syncInfo.HighestTimeoutCert.Signatures)) x.broadcastToBftChannel(syncInfo) diff --git a/params/config.go b/params/config.go index 7fc254f38d..a058731699 100644 --- a/params/config.go +++ b/params/config.go @@ -560,7 +560,7 @@ func (v2 *V2) Description(indent int) string { banner += fmt.Sprintf("%s- SwitchEpoch: %v\n", prefix, v2.SwitchEpoch) banner += fmt.Sprintf("%s- SwitchBlock: %v\n", prefix, v2.SwitchBlock) banner += fmt.Sprintf("%s- SkipV2Validation: %v\n", prefix, v2.SkipV2Validation) - banner += fmt.Sprintf("%s- %s", prefix, v2.CurrentConfig.Description("CurrentConfig", indent+2)) + banner += fmt.Sprintf("%s- %s", prefix, v2.GetCurrentConfig().Description("CurrentConfig", indent+2)) return banner } @@ -625,6 +625,20 @@ func (v2 *V2) UpdateConfig(round uint64) { v2.CurrentConfig = v2.AllConfigs[index] } +// GetCurrentConfig returns a opy of the current config, it assumes v2 is not nil +func (v2 *V2) GetCurrentConfig() *V2Config { + v2.lock.RLock() + defer v2.lock.RUnlock() + + if v2.CurrentConfig == nil { + return nil + } + + // avoid CurrentConfig is changed by other goroutines + cpyConfig := *v2.CurrentConfig + return &cpyConfig +} + func (v2 *V2) Config(round uint64) *V2Config { configRound := round var index uint64