Skip to content

Commit 8665400

Browse files
committed
fix(params): log v2 allconfigs mismatch context
Add a warning log before returning V2 compatibility errors to expose round-level mismatch context. Include active round, map key presence flags, and consensus field snapshots for stored/new configs.
1 parent 1654aed commit 8665400

3 files changed

Lines changed: 128 additions & 6 deletions

File tree

params/config_compat.go

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"strings"
3333

3434
"github.com/XinFinOrg/XDPoSChain/common"
35+
"github.com/XinFinOrg/XDPoSChain/log"
3536
)
3637

3738
// CheckCompatible checks whether scheduled fork transitions have been imported
@@ -190,18 +191,52 @@ func checkXDCSystemContractCompatible(stored, newcfg *ChainConfig, head *big.Int
190191

191192
// checkV2ScheduleCompatible compares the historically active V2 round configs
192193
// between stored and new chain config.
193-
func checkV2ScheduleCompatible(stored, newcfg *V2, xdposRound *uint64) *ConfigCompatError {
194-
activeRound := v2ActiveCompatRound(stored, newcfg, xdposRound)
195-
for _, round := range v2HistoricalRounds(stored, newcfg, activeRound) {
194+
func checkV2ScheduleCompatible(stored, candidate *V2, xdposRound *uint64) *ConfigCompatError {
195+
activeRound := v2ActiveCompatRound(stored, candidate, xdposRound)
196+
for _, round := range v2HistoricalRounds(stored, candidate, activeRound) {
196197
storedCfg, storedOK := stored.AllConfigs[round]
197-
newCfg, newOK := newcfg.AllConfigs[round]
198-
if !storedOK || !newOK || !v2ConsensusConfigEqual(storedCfg, newCfg) {
199-
return newCompatError(fmt.Sprintf("XDPoS.V2 config at round %d", round), xdposV2CompatBlock(stored), xdposV2CompatBlock(newcfg))
198+
candidateCfg, newOK := candidate.AllConfigs[round]
199+
if !storedOK || !newOK || !v2ConsensusConfigEqual(storedCfg, candidateCfg) {
200+
log.Warn(strings.Repeat("-", 153))
201+
log.Warn("V2 config mismatch", "stored", stored.StableLogValue())
202+
log.Warn(strings.Repeat("-", 153))
203+
log.Warn("V2 config mismatch", "candidate", candidate.StableLogValue())
204+
log.Warn(strings.Repeat("-", 153))
205+
log.Warn("V2 config mismatch",
206+
"round", round,
207+
"activeRound", activeRound,
208+
"storedOK", storedOK,
209+
"newOK", newOK,
210+
"storedCfg", v2ConsensusConfigSnapshot(storedCfg),
211+
"candidateCfg", v2ConsensusConfigSnapshot(candidateCfg),
212+
)
213+
log.Warn(strings.Repeat("-", 153))
214+
return newCompatError(fmt.Sprintf("XDPoS.V2 config at round %d", round), xdposV2CompatBlock(stored), xdposV2CompatBlock(candidate))
200215
}
201216
}
202217
return nil
203218
}
204219

220+
func v2ConsensusConfigSnapshot(cfg *V2Config) map[string]any {
221+
if cfg == nil {
222+
return nil
223+
}
224+
return map[string]any{
225+
"maxMasternodes": cfg.MaxMasternodes,
226+
"maxProtectorNodes": cfg.MaxProtectorNodes,
227+
"maxObverserNodes": cfg.MaxObverserNodes,
228+
"switchRound": cfg.SwitchRound,
229+
"minePeriod": cfg.MinePeriod,
230+
"certThreshold": cfg.CertThreshold,
231+
"masternodeReward": cfg.MasternodeReward,
232+
"protectorReward": cfg.ProtectorReward,
233+
"observerReward": cfg.ObserverReward,
234+
"minimumMinerBlockPerEpoch": cfg.MinimumMinerBlockPerEpoch,
235+
"limitPenaltyEpoch": cfg.LimitPenaltyEpoch,
236+
"minimumSigningTx": cfg.MinimumSigningTx,
237+
}
238+
}
239+
205240
// v2ActiveCompatRound returns the latest round whose config must remain stable
206241
// across restarts.
207242
func v2ActiveCompatRound(stored, newcfg *V2, xdposRound *uint64) uint64 {

params/config_xdpos.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,54 @@ func (v2 *V2) String() string {
521521
return fmt.Sprintf("V2{SwitchEpoch: %v, SwitchBlock: %v, %s}", snapshot.switchEpoch, snapshot.switchBlock, snapshot.currentConfig.String())
522522
}
523523

524+
type stableLogConfigEntry struct {
525+
Round uint64 `json:"round"`
526+
Config *V2Config `json:"config"`
527+
}
528+
529+
type stableLogView struct {
530+
SwitchEpoch uint64 `json:"switchEpoch"`
531+
SwitchBlock string `json:"switchBlock"`
532+
CurrentConfig *V2Config `json:"currentConfig"`
533+
AllConfigs []stableLogConfigEntry `json:"allConfigs"`
534+
ConfigIndex []uint64 `json:"configIndex"`
535+
}
536+
537+
// StableLogValue returns a full, deterministic structured value for startup
538+
// logs. Callers can pass the returned object directly as a field value to
539+
// avoid JSON string escaping in terminal log output.
540+
func (v2 *V2) StableLogValue() stableLogView {
541+
if v2 == nil {
542+
return stableLogView{}
543+
}
544+
545+
snapshot := snapshotV2ReadOnly(v2)
546+
547+
keys := slices.Collect(maps.Keys(snapshot.allConfigs))
548+
slices.Sort(keys)
549+
550+
allConfigs := make([]stableLogConfigEntry, 0, len(keys))
551+
for _, round := range keys {
552+
allConfigs = append(allConfigs, stableLogConfigEntry{
553+
Round: round,
554+
Config: snapshot.allConfigs[round].Clone(),
555+
})
556+
}
557+
558+
var switchBlock string
559+
if snapshot.switchBlock != nil {
560+
switchBlock = snapshot.switchBlock.String()
561+
}
562+
563+
return stableLogView{
564+
SwitchEpoch: snapshot.switchEpoch,
565+
SwitchBlock: switchBlock,
566+
CurrentConfig: snapshot.currentConfig.Clone(),
567+
AllConfigs: allConfigs,
568+
ConfigIndex: append([]uint64(nil), snapshot.configIndex...),
569+
}
570+
}
571+
524572
// Description returns a human-readable description of V2
525573
// NOTE: don't append "\n" to end
526574
func (v2 *V2) Description(indent int) string {

params/config_xdpos_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package params
1919
import (
2020
"encoding/json"
2121
"math/big"
22+
"strings"
2223
"sync"
2324
"testing"
2425

@@ -192,6 +193,44 @@ func TestV2StringConcurrentWithUpdateConfig(t *testing.T) {
192193
wg.Wait()
193194
}
194195

196+
func TestV2StableLogValueDeterministicAndComplete(t *testing.T) {
197+
v2 := &V2{
198+
SwitchEpoch: 63143,
199+
SwitchBlock: big.NewInt(56828700),
200+
CurrentConfig: &V2Config{
201+
MaxMasternodes: 15,
202+
},
203+
AllConfigs: map[uint64]*V2Config{
204+
10: {SwitchRound: 10, MaxMasternodes: 16},
205+
0: {SwitchRound: 0, MaxMasternodes: 15},
206+
},
207+
configIndex: []uint64{10, 0},
208+
}
209+
210+
firstBytes, err := json.Marshal(v2.StableLogValue())
211+
assert.NoError(t, err)
212+
secondBytes, err := json.Marshal(v2.StableLogValue())
213+
assert.NoError(t, err)
214+
215+
first := string(firstBytes)
216+
second := string(secondBytes)
217+
assert.Equal(t, first, second)
218+
219+
assert.Contains(t, first, `"switchEpoch":63143`)
220+
assert.Contains(t, first, `"switchBlock":"56828700"`)
221+
assert.Contains(t, first, `"configIndex":[10,0]`)
222+
assert.Contains(t, first, `"currentConfig"`)
223+
224+
idxRound0 := strings.Index(first, `"round":0`)
225+
idxRound10 := strings.Index(first, `"round":10`)
226+
if idxRound0 == -1 || idxRound10 == -1 {
227+
t.Fatalf("expected allConfigs rounds in stable log output, got %q", first)
228+
}
229+
if idxRound0 > idxRound10 {
230+
t.Fatalf("expected allConfigs rounds sorted ascending, got %q", first)
231+
}
232+
}
233+
195234
func TestBuildConfigIndex(t *testing.T) {
196235
TestXDPoSMockChainConfig.XDPoS.V2.BuildConfigIndex()
197236
index := TestXDPoSMockChainConfig.XDPoS.V2.ConfigIndex()

0 commit comments

Comments
 (0)