-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathklc1920_node_state_test.go
More file actions
166 lines (138 loc) · 6.39 KB
/
klc1920_node_state_test.go
File metadata and controls
166 lines (138 loc) · 6.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package sync_test
import (
"sync"
"testing"
"time"
commonMock "github.com/klever-io/klever-go/common/mock"
"github.com/klever-io/klever-go/core"
consensusMock "github.com/klever-io/klever-go/core/consensus/mock"
"github.com/klever-io/klever-go/core/process"
syncpkg "github.com/klever-io/klever-go/core/process/sync"
"github.com/klever-io/klever-go/data"
"github.com/klever-io/klever-go/data/block"
"github.com/stretchr/testify/assert"
)
// klc1920_node_state_test.go covers the new branch in
// baseBootstrap.computeNodeState: when HighestNonceReceived is more than
// BlockFinality blocks ahead of currentBlockNonce, hasLastBlock is forced
// to false so isNodeSynchronized correctly reports the node is behind.
//
// Without this branch, a fallback whose BHReceived path is broken (peer
// churn after election) would have probableHighestNonce == currentBlockNonce
// and falsely declare itself synced — the production failure mode KLC-1920
// and KLC-2389 describe.
type observableStatusHandler struct {
mu sync.Mutex
isSyncing uint64
}
func (o *observableStatusHandler) Increment(_ string) {}
func (o *observableStatusHandler) AddUint64(_ string, _ uint64) {}
func (o *observableStatusHandler) Decrement(_ string) {}
func (o *observableStatusHandler) SetInt64Value(_ string, _ int64) {}
func (o *observableStatusHandler) SetUInt64Value(key string, value uint64) {
if key != core.MetricIsSyncing {
return
}
o.mu.Lock()
o.isSyncing = value
o.mu.Unlock()
}
func (o *observableStatusHandler) SetStringValue(_ string, _ string) {}
func (o *observableStatusHandler) Close() {}
func (o *observableStatusHandler) IsInterfaceNil() bool { return o == nil }
func (o *observableStatusHandler) IsSyncing() uint64 {
o.mu.Lock()
defer o.mu.Unlock()
return o.isSyncing
}
func buildKLC1920Bootstrap(probable, highest, currentBlockNonce uint64) (*syncpkg.BaseBootstrap, *observableStatusHandler) {
forkDetector := &commonMock.ForkDetectorMock{
CheckForkCalled: func() *process.ForkInfo { return &process.ForkInfo{} },
ProbableHighestNonceCalled: func() uint64 { return probable },
HighestNonceReceivedCalled: func() uint64 { return highest },
GetHighestFinalBlockNonceCalled: func() uint64 { return 0 },
}
genesisHeader := &block.Block{Header: &block.BlockHeader{Nonce: 0, Slot: 0}}
currentHeader := &block.Block{Header: &block.BlockHeader{Nonce: currentBlockNonce, Slot: currentBlockNonce}}
chainHandler := &commonMock.BlockChainMock{
GetGenesisHeaderCalled: func() data.HeaderHandler { return genesisHeader },
GetCurrentBlockHeaderCalled: func() data.HeaderHandler { return currentHeader },
}
slotManager := &consensusMock.SlotManagerMock{
SlotIndex: int64(currentBlockNonce + 5),
TimeDurationCalled: func() time.Duration { return 0 },
BeforeGenesisCalled: func() bool { return true }, // suppress requestHeadersIfSyncIsStuck path
}
networkWatcher := &commonMock.MessengerStub{
IsConnectedToTheNetworkCalled: func() bool { return true },
}
statusHandler := &observableStatusHandler{}
boot := syncpkg.NewBaseBootstrapForKLC1920Test(
forkDetector,
chainHandler,
slotManager,
networkWatcher,
statusHandler,
)
return boot, statusHandler
}
// TestKLC1920_ComputeNodeState_GossipAheadForcesNotSynced is the regression
// guard for the synced-state gate. Pre-fix: with probable == current the
// node declared itself synced even when HighestNonceReceived was far ahead.
// Post-fix: any gossip-vs-current gap > BlockFinality forces hasLastBlock=
// false and isNodeSynchronized=false.
func TestKLC1920_ComputeNodeState_GossipAheadForcesNotSynced(t *testing.T) {
t.Parallel()
// Production failure shape: probable matches current (fork detector
// thinks it's caught up) but gossip has reported headers many blocks
// ahead. Use a generous multiple of BlockFinality so we're well past
// the threshold regardless of how it gets tuned later.
const probable = uint64(50)
current := probable
highest := current + uint64(process.BlockFinality)*20
boot, statusHandler := buildKLC1920Bootstrap(probable, highest, current)
boot.ComputeNodeState()
assert.False(t, boot.HasLastBlock(),
"KLC-1920 fix: gossip-ahead gap must force hasLastBlock=false")
assert.False(t, boot.IsNodeSynchronized(),
"KLC-1920 fix: node must not declare synced when gossip is ahead")
assert.Equal(t, uint64(1), statusHandler.IsSyncing(),
"KLC-1920 fix: klv_is_syncing must report 1 — the production-bug metric was 0 (false-synced)")
}
// TestKLC1920_ComputeNodeState_GossipAtBoundaryStaysSynced confirms the gate
// does NOT spuriously fire when gossip is exactly BlockFinality ahead of the
// last committed block — the natural proposal-vs-commit window during normal
// consensus operation.
func TestKLC1920_ComputeNodeState_GossipAtBoundaryStaysSynced(t *testing.T) {
t.Parallel()
// gap == BlockFinality: a BHProposed for nonce N+BlockFinality has been
// seen but not yet committed. This is normal — must NOT trip the gate.
const probable = uint64(50)
current := probable
highest := current + uint64(process.BlockFinality)
boot, statusHandler := buildKLC1920Bootstrap(probable, highest, current)
boot.ComputeNodeState()
assert.True(t, boot.HasLastBlock(),
"normal proposal cycle: gap == BlockFinality must NOT force not-synced")
assert.True(t, boot.IsNodeSynchronized(),
"normal proposal cycle: node remains synced; consensus must not be gated")
assert.Equal(t, uint64(0), statusHandler.IsSyncing(),
"normal proposal cycle: klv_is_syncing stays 0")
}
// TestKLC1920_ComputeNodeState_GossipOneOverBoundaryNotSynced pins down the
// exact `>` boundary: one block past BlockFinality must trip the gate. This
// guards against the check accidentally becoming `>=` in a future refactor.
func TestKLC1920_ComputeNodeState_GossipOneOverBoundaryNotSynced(t *testing.T) {
t.Parallel()
const probable = uint64(50)
current := probable
highest := current + uint64(process.BlockFinality) + 1
boot, statusHandler := buildKLC1920Bootstrap(probable, highest, current)
boot.ComputeNodeState()
assert.False(t, boot.HasLastBlock(),
"boundary: gap == BlockFinality+1 must force not-synced")
assert.False(t, boot.IsNodeSynchronized(),
"boundary: gap == BlockFinality+1 must flip isNodeSynchronized to false")
assert.Equal(t, uint64(1), statusHandler.IsSyncing(),
"boundary: klv_is_syncing == 1 at the first nonce past BlockFinality")
}