Skip to content

Commit f230b60

Browse files
bdchathamclaude
andauthored
fix: archive nodes bootstrap via block sync, not state sync (#84)
* fix: archive nodes bootstrap via block sync, not state sync The archive planner hardcoded a state-sync SnapshotSource, causing archive nodes to state-sync from a recent height instead of block syncing from genesis. This meant archive nodes had pruned state and produced small (~500MB) snapshots instead of full (~48GB) ones. Removes the hardcoded state-sync source so archive nodes use the genesis/block-sync base progression. Adds test coverage for the archive planner's task progression, peer handling, and snapshot generation overrides. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: update archive test expectations and lint prealloc Update TestBuildPlan_Archive to expect genesis/block-sync progression (no configure-state-sync). Preallocate slice in archive_test.go. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 85d545a commit f230b60

4 files changed

Lines changed: 135 additions & 9 deletions

File tree

api/v1alpha1/archive_types.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package v1alpha1
22

33
// ArchiveSpec configures an archive node (no pruning, full history).
4-
// Archive nodes always bootstrap via Tendermint state sync then block sync
5-
// to retain all historical data from that point forward.
6-
// If SnapshotGeneration is set, the node also acts as a snapshotter.
4+
// Archive nodes bootstrap via block sync from peers to retain all
5+
// historical data. If SnapshotGeneration is set, the node also produces
6+
// Tendermint state-sync snapshots for other nodes to bootstrap from.
77
type ArchiveSpec struct {
88
// SnapshotGeneration configures periodic snapshot creation and optional upload.
99
// +optional

internal/controller/node/plan_execution_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func TestBuildPlan_Archive(t *testing.T) {
320320
p, _ := planner.ForNode(node)
321321
plan := mustBuildPlan(t, p, node)
322322
got := taskTypes(plan)
323-
want := []string{planner.TaskConfigureGenesis, planner.TaskConfigApply, planner.TaskDiscoverPeers, planner.TaskConfigureStateSync, planner.TaskConfigValidate, planner.TaskMarkReady}
323+
want := []string{planner.TaskConfigureGenesis, planner.TaskConfigApply, planner.TaskDiscoverPeers, planner.TaskConfigValidate, planner.TaskMarkReady}
324324
assertProgression(t, got, want)
325325
}
326326

internal/planner/archive.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,12 @@ func (p *archiveNodePlanner) Validate(node *seiv1alpha1.SeiNode) error {
2323
}
2424

2525
func (p *archiveNodePlanner) BuildPlan(node *seiv1alpha1.SeiNode) (*seiv1alpha1.TaskPlan, error) {
26-
return buildBasePlan(node, node.Spec.Peers, p.snapshotSource(), &task.ConfigApplyParams{
26+
return buildBasePlan(node, node.Spec.Peers, nil, &task.ConfigApplyParams{
2727
Mode: string(seiconfig.ModeArchive),
2828
Overrides: mergeOverrides(mergeOverrides(commonOverrides(node), p.controllerOverrides(node)), node.Spec.Overrides),
2929
})
3030
}
3131

32-
func (p *archiveNodePlanner) snapshotSource() *seiv1alpha1.SnapshotSource {
33-
return &seiv1alpha1.SnapshotSource{StateSync: &seiv1alpha1.StateSyncSource{}}
34-
}
35-
3632
func (p *archiveNodePlanner) controllerOverrides(node *seiv1alpha1.SeiNode) map[string]string {
3733
sg := node.Spec.Archive.SnapshotGeneration
3834
if sg == nil {

internal/planner/archive_test.go

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package planner
2+
3+
import (
4+
"slices"
5+
"testing"
6+
7+
seiconfig "github.com/sei-protocol/sei-config"
8+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
9+
10+
seiv1alpha1 "github.com/sei-protocol/sei-k8s-controller/api/v1alpha1"
11+
)
12+
13+
func TestArchivePlanner_BlockSyncProgression(t *testing.T) {
14+
node := &seiv1alpha1.SeiNode{
15+
ObjectMeta: metav1.ObjectMeta{Name: "archive-0", Namespace: "pacific-1"},
16+
Spec: seiv1alpha1.SeiNodeSpec{
17+
ChainID: "pacific-1",
18+
Image: "seid:v6.4.1",
19+
Archive: &seiv1alpha1.ArchiveSpec{},
20+
},
21+
}
22+
23+
p := &archiveNodePlanner{}
24+
plan, err := p.BuildPlan(node)
25+
if err != nil {
26+
t.Fatalf("BuildPlan: %v", err)
27+
}
28+
29+
types := make([]string, 0, len(plan.Tasks))
30+
for _, task := range plan.Tasks {
31+
types = append(types, task.Type)
32+
}
33+
34+
// Archive nodes use the genesis (block sync) progression: no snapshot-restore, no state-sync
35+
if slices.Contains(types, TaskSnapshotRestore) {
36+
t.Error("archive plan should not contain snapshot-restore")
37+
}
38+
if slices.Contains(types, TaskConfigureStateSync) {
39+
t.Error("archive plan should not contain configure-state-sync")
40+
}
41+
42+
// Must contain the base genesis progression
43+
for _, expected := range []string{TaskConfigureGenesis, TaskConfigApply, TaskConfigValidate, TaskMarkReady} {
44+
if !slices.Contains(types, expected) {
45+
t.Errorf("archive plan missing expected task %s, got %v", expected, types)
46+
}
47+
}
48+
}
49+
50+
func TestArchivePlanner_WithPeers(t *testing.T) {
51+
node := &seiv1alpha1.SeiNode{
52+
ObjectMeta: metav1.ObjectMeta{Name: "archive-0", Namespace: "pacific-1"},
53+
Spec: seiv1alpha1.SeiNodeSpec{
54+
ChainID: "pacific-1",
55+
Image: "seid:v6.4.1",
56+
Archive: &seiv1alpha1.ArchiveSpec{},
57+
Peers: []seiv1alpha1.PeerSource{
58+
{Static: &seiv1alpha1.StaticPeerSource{Addresses: []string{"peer1@host:26656"}}},
59+
},
60+
},
61+
}
62+
63+
p := &archiveNodePlanner{}
64+
plan, err := p.BuildPlan(node)
65+
if err != nil {
66+
t.Fatalf("BuildPlan: %v", err)
67+
}
68+
69+
types := make([]string, 0, len(plan.Tasks))
70+
for _, task := range plan.Tasks {
71+
types = append(types, task.Type)
72+
}
73+
74+
if !slices.Contains(types, TaskDiscoverPeers) {
75+
t.Errorf("archive plan with peers should contain discover-peers, got %v", types)
76+
}
77+
}
78+
79+
func TestArchivePlanner_SnapshotGenerationOverrides(t *testing.T) {
80+
node := &seiv1alpha1.SeiNode{
81+
ObjectMeta: metav1.ObjectMeta{Name: "archive-0", Namespace: "pacific-1"},
82+
Spec: seiv1alpha1.SeiNodeSpec{
83+
ChainID: "pacific-1",
84+
Image: "seid:v6.4.1",
85+
Archive: &seiv1alpha1.ArchiveSpec{
86+
SnapshotGeneration: &seiv1alpha1.SnapshotGenerationConfig{
87+
KeepRecent: 5,
88+
},
89+
},
90+
},
91+
}
92+
93+
p := &archiveNodePlanner{}
94+
overrides := p.controllerOverrides(node)
95+
96+
if overrides == nil {
97+
t.Fatal("expected overrides for snapshotGeneration, got nil")
98+
}
99+
if got := overrides[seiconfig.KeySnapshotKeepRecent]; got != "5" {
100+
t.Errorf("snapshot-keep-recent = %q, want %q", got, "5")
101+
}
102+
if _, ok := overrides[seiconfig.KeySnapshotInterval]; !ok {
103+
t.Error("expected snapshot-interval override to be set")
104+
}
105+
}
106+
107+
func TestArchivePlanner_NoSnapshotGenerationOverrides(t *testing.T) {
108+
node := &seiv1alpha1.SeiNode{
109+
ObjectMeta: metav1.ObjectMeta{Name: "archive-0", Namespace: "pacific-1"},
110+
Spec: seiv1alpha1.SeiNodeSpec{
111+
ChainID: "pacific-1",
112+
Image: "seid:v6.4.1",
113+
Archive: &seiv1alpha1.ArchiveSpec{},
114+
},
115+
}
116+
117+
p := &archiveNodePlanner{}
118+
overrides := p.controllerOverrides(node)
119+
120+
if overrides != nil {
121+
t.Errorf("expected nil overrides without snapshotGeneration, got %v", overrides)
122+
}
123+
}
124+
125+
func TestArchivePlanner_Mode(t *testing.T) {
126+
p := &archiveNodePlanner{}
127+
if got := p.Mode(); got != string(seiconfig.ModeArchive) {
128+
t.Errorf("Mode() = %q, want %q", got, seiconfig.ModeArchive)
129+
}
130+
}

0 commit comments

Comments
 (0)