Skip to content

Commit 67da2af

Browse files
test(consensus): integration-test IsBehind across peer topologies
Covers the live wiring that the pure-helper tests don't: IsBehind driving a real (non-started) Switch's peer set through collectPeerHeights into evaluateBehind, plus the sole-validator and debounce paths, end to end. Builds a Reactor with mock peers at chosen heights, a round state at a chosen height, and catchUpDebounce=0 so the result is the raw decision with no timing. Cases mirror the operational topologies: - majority of peers ahead -> behind (the partition / fell-behind case), - synced multi-peer network -> not behind (no false positive on a healthy multi-node network), - lone peer ahead -> not behind (cannot corroborate), - no peers and not the sole validator -> behind (isolated node), - no peers but the sole validator -> not behind (finalizes alone). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c5e8b23 commit 67da2af

1 file changed

Lines changed: 53 additions & 0 deletions

File tree

consensus/reactor_catchup_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import (
55
"time"
66

77
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
89

10+
cfg "github.com/cometbft/cometbft/config"
11+
cstypes "github.com/cometbft/cometbft/consensus/types"
912
"github.com/cometbft/cometbft/crypto"
1013
"github.com/cometbft/cometbft/p2p"
1114
p2pmock "github.com/cometbft/cometbft/p2p/mock"
@@ -114,6 +117,56 @@ func TestReactorCatchupConfig(t *testing.T) {
114117
assert.Equal(t, 3*time.Second, conR.catchUpDebounce)
115118
}
116119

120+
// newCatchupReactor wires a Reactor with a non-started Switch carrying mock peers
121+
// at the given heights, a round state at myHeight, and (optionally) a single-validator
122+
// set that this node belongs to. catchUpDebounce is 0 so IsBehind reflects the raw
123+
// decision immediately, keeping the test free of timing.
124+
func newCatchupReactor(t *testing.T, myHeight int64, sole bool, peerHeights ...int64) *Reactor {
125+
t.Helper()
126+
cs := &State{}
127+
if sole {
128+
val, _ := types.RandValidator(false, 10)
129+
cs.Validators = types.NewValidatorSet([]*types.Validator{val})
130+
cs.privValidatorPubKey = val.PubKey
131+
}
132+
conR := &Reactor{
133+
conS: cs,
134+
rs: &cstypes.RoundState{Height: myHeight},
135+
catchUpLagThreshold: 5,
136+
catchUpDebounce: 0,
137+
}
138+
conR.BaseReactor = *p2p.NewBaseReactor("Consensus", conR)
139+
140+
sw := p2p.NewSwitch(cfg.DefaultP2PConfig(), nil)
141+
peerSet := sw.Peers().(*p2p.PeerSet)
142+
for _, h := range peerHeights {
143+
require.NoError(t, peerSet.Add(peerWithHeight(h)))
144+
}
145+
conR.SetSwitch(sw)
146+
return conR
147+
}
148+
149+
// TestIsBehindIntegration exercises the full IsBehind path (Switch peers ->
150+
// collectPeerHeights -> evaluateBehind, plus the sole-validator and debounce
151+
// wiring) rather than the pure decision helpers in isolation.
152+
func TestIsBehindIntegration(t *testing.T) {
153+
t.Run("majority of peers ahead reports behind", func(t *testing.T) {
154+
assert.True(t, newCatchupReactor(t, 100, false, 110, 110, 110).IsBehind())
155+
})
156+
t.Run("synced multi-peer network is not behind", func(t *testing.T) {
157+
assert.False(t, newCatchupReactor(t, 100, false, 100, 100, 100).IsBehind())
158+
})
159+
t.Run("lone peer ahead cannot corroborate", func(t *testing.T) {
160+
assert.False(t, newCatchupReactor(t, 100, false, 110).IsBehind())
161+
})
162+
t.Run("no peers and not sole validator is behind", func(t *testing.T) {
163+
assert.True(t, newCatchupReactor(t, 100, false).IsBehind())
164+
})
165+
t.Run("no peers but sole validator is not behind", func(t *testing.T) {
166+
assert.False(t, newCatchupReactor(t, 100, true).IsBehind())
167+
})
168+
}
169+
117170
func TestApplyDebounce(t *testing.T) {
118171
debounce := 10 * time.Second
119172
t0 := time.Now()

0 commit comments

Comments
 (0)