Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions api/v1alpha1/common_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,20 +143,25 @@ type TendermintSnapshotGenerationConfig struct {
// override, prefix) without a breaking change.
type TendermintSnapshotPublishConfig struct{}

// ResultExportConfig enables export of block execution results to S3.
// The sidecar queries the local RPC endpoint for block results and uploads
// them in compressed NDJSON pages to the platform S3 bucket, keyed by the
// node's chain ID.
//
// When CanonicalRPC is set, the sidecar additionally compares local results
// against the canonical chain and the task completes when app-hash divergence
// is detected (monitor mode). Without CanonicalRPC, results are exported
// periodically on a cron schedule (scheduled mode).
// ResultExportConfig configures the node to export block-execution results.
// One or more use-case sub-structs may be enabled. An empty ResultExportConfig
// (no sub-struct set) is rejected by the planner as a likely user typo.
type ResultExportConfig struct {
// ShadowResult configures the node to generate and export shadow-result
// pages, comparing local block execution results against a canonical
// chain via app-hash divergence detection.
// +optional
ShadowResult *ShadowResultConfig `json:"shadowResult,omitempty"`
}

// ShadowResultConfig configures shadow-result generation. The sidecar queries
// the local RPC endpoint for block_results, uploads them in compressed NDJSON
// pages to the platform result-export bucket, and compares each page's app-hash
// against the canonical chain. The export task completes when divergence is
// detected.
type ShadowResultConfig struct {
// CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
// to compare block execution results against. When set, the sidecar
// runs in comparison mode and the task completes when app-hash
// divergence is detected.
// to compare block-execution results against.
// +kubebuilder:validation:MinLength=1
CanonicalRPC string `json:"canonicalRpc"`
}
Expand Down
7 changes: 3 additions & 4 deletions api/v1alpha1/replayer_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ type ReplayerSpec struct {
// Snapshot identifies the snapshot to restore from before replay begins.
Snapshot SnapshotSource `json:"snapshot"`

// ResultExport configures periodic export of block execution results to S3.
// The sidecar queries the local RPC for block_results and uploads compressed
// NDJSON pages on a schedule. Useful for shadow replayers that need their
// execution results compared against the canonical chain.
// ResultExport configures block-execution result export. Select one or more
// sub-structs (e.g., shadowResult) to enable an export mode. Useful for
// shadow replayers that compare execution results against the canonical chain.
// +optional
ResultExport *ResultExportConfig `json:"resultExport,omitempty"`
}
22 changes: 21 additions & 1 deletion api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 17 additions & 13 deletions config/crd/sei.io_seinodedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -457,21 +457,25 @@ spec:
properties:
resultExport:
description: |-
ResultExport configures periodic export of block execution results to S3.
The sidecar queries the local RPC for block_results and uploads compressed
NDJSON pages on a schedule. Useful for shadow replayers that need their
execution results compared against the canonical chain.
ResultExport configures block-execution result export. Select one or more
sub-structs (e.g., shadowResult) to enable an export mode. Useful for
shadow replayers that compare execution results against the canonical chain.
properties:
canonicalRpc:
shadowResult:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block execution results against. When set, the sidecar
runs in comparison mode and the task completes when app-hash
divergence is detected.
minLength: 1
type: string
required:
- canonicalRpc
ShadowResult configures the node to generate and export shadow-result
pages, comparing local block execution results against a canonical
chain via app-hash divergence detection.
properties:
canonicalRpc:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block-execution results against.
minLength: 1
type: string
required:
- canonicalRpc
type: object
type: object
snapshot:
description: Snapshot identifies the snapshot to restore
Expand Down
30 changes: 17 additions & 13 deletions config/crd/sei.io_seinodes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -283,21 +283,25 @@ spec:
properties:
resultExport:
description: |-
ResultExport configures periodic export of block execution results to S3.
The sidecar queries the local RPC for block_results and uploads compressed
NDJSON pages on a schedule. Useful for shadow replayers that need their
execution results compared against the canonical chain.
ResultExport configures block-execution result export. Select one or more
sub-structs (e.g., shadowResult) to enable an export mode. Useful for
shadow replayers that compare execution results against the canonical chain.
properties:
canonicalRpc:
shadowResult:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block execution results against. When set, the sidecar
runs in comparison mode and the task completes when app-hash
divergence is detected.
minLength: 1
type: string
required:
- canonicalRpc
ShadowResult configures the node to generate and export shadow-result
pages, comparing local block execution results against a canonical
chain via app-hash divergence detection.
properties:
canonicalRpc:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block-execution results against.
minLength: 1
type: string
required:
- canonicalRpc
type: object
type: object
snapshot:
description: Snapshot identifies the snapshot to restore from
Expand Down
12 changes: 12 additions & 0 deletions internal/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,18 @@ func validateSnapshotGeneration(sg *seiv1alpha1.SnapshotGenerationConfig) error
return nil
}

// validateResultExport returns errors without a mode prefix; callers
// wrap with their own (e.g., fmt.Errorf("replayer: %w", err)).
func validateResultExport(re *seiv1alpha1.ResultExportConfig) error {
if re == nil {
return nil
}
if re.ShadowResult == nil {
return fmt.Errorf("resultExport is set but has no sub-struct (e.g., shadowResult); omit it to disable result export")
}
return nil
}

func hasS3Snapshot(snap *seiv1alpha1.SnapshotSource) bool {
return snap != nil && snap.S3 != nil
}
Expand Down
3 changes: 3 additions & 0 deletions internal/planner/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ func (p *replayerPlanner) Validate(node *seiv1alpha1.SeiNode) error {
if len(node.Spec.Peers) == 0 {
return fmt.Errorf("replayer requires at least one peer source for block sync")
}
if err := validateResultExport(node.Spec.Replayer.ResultExport); err != nil {
return fmt.Errorf("replayer: %w", err)
}
return nil
}

Expand Down
70 changes: 70 additions & 0 deletions internal/planner/replay_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package planner

import (
"strings"
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

seiv1alpha1 "github.com/sei-protocol/sei-k8s-controller/api/v1alpha1"
)

func TestReplayerPlanner_Validate_ResultExport(t *testing.T) {
cases := []struct {
name string
re *seiv1alpha1.ResultExportConfig
wantErr string
}{
{
name: "nil is fine",
re: nil,
},
{
name: "shadowResult set is fine",
re: &seiv1alpha1.ResultExportConfig{
ShadowResult: &seiv1alpha1.ShadowResultConfig{
CanonicalRPC: "http://rpc.pacific-1.example.com:26657",
},
},
},
{
name: "empty resultExport is rejected",
re: &seiv1alpha1.ResultExportConfig{},
wantErr: "replayer: resultExport is set but has no sub-struct",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
node := &seiv1alpha1.SeiNode{
ObjectMeta: metav1.ObjectMeta{Name: "replayer-0", Namespace: "pacific-1"},
Spec: seiv1alpha1.SeiNodeSpec{
ChainID: "pacific-1",
Image: "seid:v6.4.1",
Peers: []seiv1alpha1.PeerSource{
{Static: &seiv1alpha1.StaticPeerSource{Addresses: []string{"peer1@host:26656"}}},
},
Replayer: &seiv1alpha1.ReplayerSpec{
Snapshot: seiv1alpha1.SnapshotSource{
S3: &seiv1alpha1.S3SnapshotSource{TargetHeight: 100000000},
},
ResultExport: tc.re,
},
},
}
err := (&replayerPlanner{}).Validate(node)
if tc.wantErr == "" {
if err != nil {
t.Fatalf("Validate: unexpected error: %v", err)
}
return
}
if err == nil {
t.Fatalf("Validate: expected error containing %q, got nil", tc.wantErr)
}
if !strings.Contains(err.Error(), tc.wantErr) {
t.Fatalf("Validate: error = %q, want containing %q", err.Error(), tc.wantErr)
}
})
}
}
3 changes: 2 additions & 1 deletion manifests/samples/seinode/pacific-1-shadow-replayer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ spec:
bootstrapImage: "ghcr.io/sei-protocol/sei:v6.3.0"
trustPeriod: "9999h0m0s"
resultExport:
canonicalRpc: "http://rpc.pacific-1.prod.platform.sei.io:26657"
shadowResult:
canonicalRpc: "http://rpc.pacific-1.prod.platform.sei.io:26657"
30 changes: 17 additions & 13 deletions manifests/sei.io_seinodedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -457,21 +457,25 @@ spec:
properties:
resultExport:
description: |-
ResultExport configures periodic export of block execution results to S3.
The sidecar queries the local RPC for block_results and uploads compressed
NDJSON pages on a schedule. Useful for shadow replayers that need their
execution results compared against the canonical chain.
ResultExport configures block-execution result export. Select one or more
sub-structs (e.g., shadowResult) to enable an export mode. Useful for
shadow replayers that compare execution results against the canonical chain.
properties:
canonicalRpc:
shadowResult:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block execution results against. When set, the sidecar
runs in comparison mode and the task completes when app-hash
divergence is detected.
minLength: 1
type: string
required:
- canonicalRpc
ShadowResult configures the node to generate and export shadow-result
pages, comparing local block execution results against a canonical
chain via app-hash divergence detection.
properties:
canonicalRpc:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block-execution results against.
minLength: 1
type: string
required:
- canonicalRpc
type: object
type: object
snapshot:
description: Snapshot identifies the snapshot to restore
Expand Down
30 changes: 17 additions & 13 deletions manifests/sei.io_seinodes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -283,21 +283,25 @@ spec:
properties:
resultExport:
description: |-
ResultExport configures periodic export of block execution results to S3.
The sidecar queries the local RPC for block_results and uploads compressed
NDJSON pages on a schedule. Useful for shadow replayers that need their
execution results compared against the canonical chain.
ResultExport configures block-execution result export. Select one or more
sub-structs (e.g., shadowResult) to enable an export mode. Useful for
shadow replayers that compare execution results against the canonical chain.
properties:
canonicalRpc:
shadowResult:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block execution results against. When set, the sidecar
runs in comparison mode and the task completes when app-hash
divergence is detected.
minLength: 1
type: string
required:
- canonicalRpc
ShadowResult configures the node to generate and export shadow-result
pages, comparing local block execution results against a canonical
chain via app-hash divergence detection.
properties:
canonicalRpc:
description: |-
CanonicalRPC is the HTTP RPC endpoint of the canonical chain node
to compare block-execution results against.
minLength: 1
type: string
required:
- canonicalRpc
type: object
type: object
snapshot:
description: Snapshot identifies the snapshot to restore from
Expand Down
Loading