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
7 changes: 7 additions & 0 deletions api/v1alpha1/networking_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,10 @@ func (n *NetworkingConfig) HTTPEnabled() bool {
}
return n.TCP == nil
}

// TCPEnabled reports whether L4 NLB per-pod P2P exposure is requested.
// Mirrors HTTPEnabled but does not carry the legacy `networking: {}`
// back-compat — TCP must be explicitly opted into.
func (n *NetworkingConfig) TCPEnabled() bool {
return n != nil && n.TCP != nil
}
20 changes: 12 additions & 8 deletions api/v1alpha1/seinode_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ import (
// +kubebuilder:validation:XValidation:rule="!has(self.replayer) || (has(self.peers) && size(self.peers) > 0)",message="peers is required when replayer mode is set"
type SeiNodeSpec struct {
// ChainID of the chain this node belongs to.
// Constrained to DNS-1123 label characters because the controller composes
// it into P2P endpoint hostnames (e.g. `<node>-p2p.<chainID>.<domain>`) when
// the parent SND opts into TCP networking; the address is a one-way door
// once peers cache it.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
ChainID string `json:"chainId"`

// Image is the seid container image.
Expand Down Expand Up @@ -68,6 +73,13 @@ type SeiNodeSpec struct {
// +optional
Validator *ValidatorSpec `json:"validator,omitempty"`

// ExternalAddress is the routable P2P host:port written into seid's
// `p2p.external_address`. SND-managed nodes get this stamped by the
// SND reconciler when TCP networking is enabled. Standalone SeiNodes
// can set it directly.
// +optional
ExternalAddress string `json:"externalAddress,omitempty"`

// Paused freezes reconciliation. While true, the controller does not
// advance the lifecycle, start plans, or mutate derived resources
// except the owned StatefulSet — which scales to Replicas=0 so pods
Expand Down Expand Up @@ -341,14 +353,6 @@ type SeiNodeStatus struct {
// +optional
ResolvedPeers []string `json:"resolvedPeers,omitempty"`

// ExternalAddress is the routable P2P address for this node — bare
// host:port, no nodeId@ prefix. Populated by the SeiNode controller
// from the per-pod LoadBalancer Service when the parent SND has
// Spec.Networking.TCP set; consumed by the planner to set
// p2p.external_address in CometBFT config.
// +optional
ExternalAddress string `json:"externalAddress,omitempty"`

// StatefulSet references the StatefulSet the controller created for
// this SeiNode. UID is the identity check: an STS with the expected
// name but a different UID is not the one this controller created
Expand Down
34 changes: 33 additions & 1 deletion api/v1alpha1/seinodedeployment_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,13 @@ type UpdateStrategy struct {
// GenesisCeremonyConfig configures genesis ceremony orchestration for a node group.
type GenesisCeremonyConfig struct {
// ChainID for the new network.
// Constrained to DNS-1123 label characters because child SeiNodes
// compose it into P2P endpoint hostnames when the SND has
// `spec.networking.tcp` set; the address is a one-way door once peers
// cache it.
// +kubebuilder:validation:MinLength=1
// +kubebuilder:validation:MaxLength=64
// +kubebuilder:validation:Pattern=`^[a-z0-9][a-z0-9-]*[a-z0-9]$`
// +kubebuilder:validation:Pattern=`^[a-z0-9]([-a-z0-9]*[a-z0-9])?$`
ChainID string `json:"chainId"`

// StakingAmount is the amount each validator self-delegates in its gentx.
Expand Down Expand Up @@ -343,6 +347,17 @@ type NetworkingStatus struct {
// Routes lists the HTTPRoute hostnames managed by this deployment.
// +optional
Routes []RouteStatus `json:"routes,omitempty"`

// P2PEndpoints lists the per-ordinal P2P endpoint
// hostnames stamped by the SND when `spec.networking.tcp` is set.
// Each entry mirrors the value injected into the child SeiNode's
// `spec.externalAddress` (hostname:port). Hostnames are deterministic
// from the SND identity, so this slice is populated without reading
// Service status back.
// +listType=map
// +listMapKey=ordinal
// +optional
P2PEndpoints []P2PEndpoint `json:"p2pEndpoints,omitempty"`
}

// RouteStatus is the observed state of a single HTTPRoute hostname.
Expand All @@ -355,6 +370,23 @@ type RouteStatus struct {
Protocol string `json:"protocol,omitempty"`
}

// P2PEndpoint is the observed state of one child's publishable
// P2P endpoint. Stamped by the SND networking reconciler.
type P2PEndpoint struct {
// Ordinal is the child's replica index within the SND
// (matches `sei.io/nodedeployment-ordinal`).
Ordinal int32 `json:"ordinal"`

// SeiNodeName is the child SeiNode resource name (also the
// per-pod headless Service name).
SeiNodeName string `json:"seiNodeName"`

// Hostname is the vanity host:port advertised to peers via
// `p2p.external_address`. Equals the value injected into the
// child SeiNode's `spec.externalAddress`.
Hostname string `json:"hostname"`
}

// RolloutStatus tracks an in-progress rollout. Presence on the parent
// SeiNodeDeployment is the single source of truth for "rollout in
// progress" — `Status.Rollout != nil` and the `RolloutInProgress`
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

3 changes: 3 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ func main() {
os.Exit(1)
}

p2pEndpointDomain := os.Getenv("SEI_P2P_ENDPOINT_DOMAIN")

//nolint:staticcheck // migrating to events.EventRecorder API is a separate effort
recorder := mgr.GetEventRecorderFor("seinodedeployment-controller")
if err := (&nodedeploymentcontroller.SeiNodeDeploymentReconciler{
Expand All @@ -223,6 +225,7 @@ func main() {
GatewayNamespace: platformCfg.GatewayNamespace,
GatewayDomain: platformCfg.GatewayDomain,
GatewayPublicDomain: platformCfg.GatewayPublicDomain,
P2PEndpointDomain: p2pEndpointDomain,
PlanExecutor: &planner.Executor[*seiv1alpha1.SeiNodeDeployment]{
ConfigFor: func(ctx context.Context, group *seiv1alpha1.SeiNodeDeployment) task.ExecutionConfig {
var assemblerNode *seiv1alpha1.SeiNode
Expand Down
63 changes: 60 additions & 3 deletions config/crd/sei.io_seinodedeployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,15 @@ spec:
type: object
type: array
chainId:
description: ChainID for the new network.
description: |-
ChainID for the new network.
Constrained to DNS-1123 label characters because child SeiNodes
compose it into P2P endpoint hostnames when the SND has
`spec.networking.tcp` set; the address is a one-way door once peers
cache it.
maxLength: 64
minLength: 1
pattern: ^[a-z0-9][a-z0-9-]*[a-z0-9]$
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
maxCeremonyDuration:
description: |-
Expand Down Expand Up @@ -222,8 +227,14 @@ spec:
type: object
type: object
chainId:
description: ChainID of the chain this node belongs to.
description: |-
ChainID of the chain this node belongs to.
Constrained to DNS-1123 label characters because the controller composes
it into P2P endpoint hostnames (e.g. `<node>-p2p.<chainID>.<domain>`) when
the parent SND opts into TCP networking; the address is a one-way door
once peers cache it.
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
dataVolume:
description: |-
Expand Down Expand Up @@ -261,6 +272,13 @@ spec:
x-kubernetes-validations:
- message: import cannot be unset once configured
rule: (!has(oldSelf.import) || has(self.import))
externalAddress:
description: |-
ExternalAddress is the routable P2P host:port written into seid's
`p2p.external_address`. SND-managed nodes get this stamped by the
SND reconciler when TCP networking is enabled. Standalone SeiNodes
can set it directly.
type: string
fullNode:
description: FullNode configures a chain-following full node
(absorbs the "rpc" role).
Expand Down Expand Up @@ -1072,6 +1090,45 @@ spec:
description: NetworkingStatus reports the observed state of networking
resources.
properties:
p2pEndpoints:
description: |-
P2PEndpoints lists the per-ordinal P2P endpoint
hostnames stamped by the SND when `spec.networking.tcp` is set.
Each entry mirrors the value injected into the child SeiNode's
`spec.externalAddress` (hostname:port). Hostnames are deterministic
from the SND identity, so this slice is populated without reading
Service status back.
items:
description: |-
P2PEndpoint is the observed state of one child's publishable
P2P endpoint. Stamped by the SND networking reconciler.
properties:
hostname:
description: |-
Hostname is the vanity host:port advertised to peers via
`p2p.external_address`. Equals the value injected into the
child SeiNode's `spec.externalAddress`.
type: string
ordinal:
description: |-
Ordinal is the child's replica index within the SND
(matches `sei.io/nodedeployment-ordinal`).
format: int32
type: integer
seiNodeName:
description: |-
SeiNodeName is the child SeiNode resource name (also the
per-pod headless Service name).
type: string
required:
- hostname
- ordinal
- seiNodeName
type: object
type: array
x-kubernetes-list-map-keys:
- ordinal
x-kubernetes-list-type: map
routes:
description: Routes lists the HTTPRoute hostnames managed by this
deployment.
Expand Down
23 changes: 14 additions & 9 deletions config/crd/sei.io_seinodes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,14 @@ spec:
type: object
type: object
chainId:
description: ChainID of the chain this node belongs to.
description: |-
ChainID of the chain this node belongs to.
Constrained to DNS-1123 label characters because the controller composes
it into P2P endpoint hostnames (e.g. `<node>-p2p.<chainID>.<domain>`) when
the parent SND opts into TCP networking; the address is a one-way door
once peers cache it.
minLength: 1
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
type: string
dataVolume:
description: |-
Expand Down Expand Up @@ -127,6 +133,13 @@ spec:
x-kubernetes-validations:
- message: import cannot be unset once configured
rule: (!has(oldSelf.import) || has(self.import))
externalAddress:
description: |-
ExternalAddress is the routable P2P host:port written into seid's
`p2p.external_address`. SND-managed nodes get this stamped by the
SND reconciler when TCP networking is enabled. Standalone SeiNodes
can set it directly.
type: string
fullNode:
description: FullNode configures a chain-following full node (absorbs
the "rpc" role).
Expand Down Expand Up @@ -800,14 +813,6 @@ spec:
Parent controllers compare this against spec.image to determine
whether a spec change has been fully actuated.
type: string
externalAddress:
description: |-
ExternalAddress is the routable P2P address for this node — bare
host:port, no nodeId@ prefix. Populated by the SeiNode controller
from the per-pod LoadBalancer Service when the parent SND has
Spec.Networking.TCP set; consumed by the planner to set
p2p.external_address in CometBFT config.
type: string
phase:
description: Phase is the high-level lifecycle state.
enum:
Expand Down
4 changes: 4 additions & 0 deletions internal/controller/nodedeployment/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ type SeiNodeDeploymentReconciler struct {
GatewayDomain string
GatewayPublicDomain string

// P2PEndpointDomain is the DNS zone for per-pod P2P endpoint hostnames,
// from SEI_P2P_ENDPOINT_DOMAIN. Empty disables the P2P endpoint path.
P2PEndpointDomain string

// PlanExecutor drives group-level task plans (e.g. genesis assembly).
PlanExecutor planner.PlanExecutor[*seiv1alpha1.SeiNodeDeployment]
}
Expand Down
Loading
Loading