Skip to content

feat: ethereum package #16

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 11, 2025
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
243 changes: 243 additions & 0 deletions pkg/ethereum/consensus/component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
package consensus

import (
"fmt"

"github.com/init4tech/signet-infra-components/pkg/utils"
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// NewConsensusClient creates a new consensus client component
func NewConsensusClient(ctx *pulumi.Context, args *ConsensusClientArgs, opts ...pulumi.ResourceOption) (*ConsensusClientComponent, error) {
if err := args.Validate(); err != nil {
return nil, fmt.Errorf("invalid consensus client args: %w", err)
}

component := &ConsensusClientComponent{}

var name string
pulumi.All(args.Name).ApplyT(func(values []interface{}) error {
name = values[0].(string)
return nil
})

err := ctx.RegisterComponentResource("signet:consensus:ConsensusClient", name, component)
if err != nil {
return nil, fmt.Errorf("failed to register component resource: %w", err)
}

// Create PVC for data storage
pvcName := fmt.Sprintf("%s-data", name)
component.PVC, err = corev1.NewPersistentVolumeClaim(ctx, pvcName, &corev1.PersistentVolumeClaimArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(pvcName),
Namespace: args.Namespace,
Labels: utils.CreateResourceLabels(name, pvcName, name, nil),
},
Spec: &corev1.PersistentVolumeClaimSpecArgs{
AccessModes: pulumi.StringArray{
pulumi.String("ReadWriteOnce"),
},
Resources: &corev1.VolumeResourceRequirementsArgs{
Requests: pulumi.StringMap{
"storage": args.StorageSize,
},
},
StorageClassName: args.StorageClass,
},
}, pulumi.Parent(component))
if err != nil {
return nil, fmt.Errorf("failed to create PVC: %w", err)
}

// Create JWT secret
jwtSecretName := fmt.Sprintf("%s-jwt", name)
component.JWTSecret, err = corev1.NewSecret(ctx, jwtSecretName, &corev1.SecretArgs{
StringData: pulumi.StringMap{
"jwt.hex": args.JWTSecret,
},
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(jwtSecretName),
Namespace: args.Namespace,
Labels: utils.CreateResourceLabels(name, jwtSecretName, name, nil),
},
}, pulumi.Parent(component))
if err != nil {
return nil, fmt.Errorf("failed to create JWT secret: %w", err)
}

// Create P2P service
p2pServiceName := fmt.Sprintf("%s-p2p", name)
component.P2PService, err = corev1.NewService(ctx, p2pServiceName, &corev1.ServiceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(p2pServiceName),
Namespace: args.Namespace,
Labels: utils.CreateResourceLabels(name, p2pServiceName, name, nil),
},
Spec: &corev1.ServiceSpecArgs{
Selector: pulumi.StringMap{
"app": pulumi.String(name),
},
Ports: corev1.ServicePortArray{
corev1.ServicePortArgs{
Name: pulumi.String("p2p"),
Port: args.P2PPort,
TargetPort: args.P2PPort,
Protocol: pulumi.String("TCP"),
},
},
},
}, pulumi.Parent(component))
if err != nil {
return nil, fmt.Errorf("failed to create P2P service: %w", err)
}

// Create Beacon API service
beaconAPIServiceName := fmt.Sprintf("%s-beacon-api", name)
component.BeaconAPIService, err = corev1.NewService(ctx, beaconAPIServiceName, &corev1.ServiceArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(beaconAPIServiceName),
Namespace: args.Namespace,
Labels: utils.CreateResourceLabels(name, beaconAPIServiceName, name, nil),
},
Spec: &corev1.ServiceSpecArgs{
Selector: pulumi.StringMap{
"app": pulumi.String(name),
},
Ports: corev1.ServicePortArray{
corev1.ServicePortArgs{
Name: pulumi.String("beacon-api"),
Port: args.BeaconAPIPort,
TargetPort: args.BeaconAPIPort,
},
corev1.ServicePortArgs{
Name: pulumi.String("metrics"),
Port: args.MetricsPort,
TargetPort: args.MetricsPort,
},
},
},
}, pulumi.Parent(component))
if err != nil {
return nil, fmt.Errorf("failed to create Beacon API service: %w", err)
}

// Create StatefulSet
statefulSetName := name
component.StatefulSet, err = appsv1.NewStatefulSet(ctx, statefulSetName, &appsv1.StatefulSetArgs{
Metadata: &metav1.ObjectMetaArgs{
Name: pulumi.String(statefulSetName),
Namespace: args.Namespace,
Labels: utils.CreateResourceLabels(name, statefulSetName, name, nil),
},
Spec: &appsv1.StatefulSetSpecArgs{
Replicas: pulumi.Int(1),
Selector: &metav1.LabelSelectorArgs{
MatchLabels: pulumi.StringMap{
"app": pulumi.String(name),
},
},
Template: &corev1.PodTemplateSpecArgs{
Metadata: &metav1.ObjectMetaArgs{
Labels: pulumi.StringMap{
"app": pulumi.String(name),
},
},
Spec: &corev1.PodSpecArgs{
Containers: corev1.ContainerArray{
corev1.ContainerArgs{
Name: pulumi.String("consensus"),
Image: args.Image,
ImagePullPolicy: args.ImagePullPolicy,
Command: createConsensusClientCommand(args),
Ports: corev1.ContainerPortArray{
corev1.ContainerPortArgs{
Name: pulumi.String("p2p"),
ContainerPort: args.P2PPort,
Protocol: pulumi.String("TCP"),
},
corev1.ContainerPortArgs{
Name: pulumi.String("beacon-api"),
ContainerPort: args.BeaconAPIPort,
},
corev1.ContainerPortArgs{
Name: pulumi.String("metrics"),
ContainerPort: args.MetricsPort,
},
},
VolumeMounts: corev1.VolumeMountArray{
corev1.VolumeMountArgs{
Name: pulumi.String("data"),
MountPath: pulumi.String("/data"),
},
corev1.VolumeMountArgs{
Name: pulumi.String("jwt"),
MountPath: pulumi.String("/etc/execution/jwt"),
},
},
Resources: nil,
},
},
Volumes: corev1.VolumeArray{
corev1.VolumeArgs{
Name: pulumi.String("data"),
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSourceArgs{
ClaimName: pulumi.String(pvcName),
},
},
corev1.VolumeArgs{
Name: pulumi.String("jwt"),
Secret: &corev1.SecretVolumeSourceArgs{
SecretName: pulumi.String(jwtSecretName),
},
},
},
NodeSelector: args.NodeSelector,
Tolerations: args.Tolerations,
},
},
},
}, pulumi.Parent(component))
if err != nil {
return nil, fmt.Errorf("failed to create StatefulSet: %w", err)
}

component.Name = args.Name.ToStringOutput()
component.Namespace = args.Namespace.ToStringOutput()
return component, nil
}

// createConsensusClientCommand creates the command array for the consensus client
func createConsensusClientCommand(args *ConsensusClientArgs) pulumi.StringArray {
cmd := pulumi.StringArray{
pulumi.String("--datadir=/data"),
pulumi.Sprintf("--execution-jwt=/etc/execution/jwt/jwt.hex"),
pulumi.Sprintf("--execution-endpoint=%s", args.ExecutionClientEndpoint),
pulumi.Sprintf("--port=%d", args.P2PPort),
pulumi.Sprintf("--metrics-port=%d", args.MetricsPort),
pulumi.Sprintf("--http-port=%d", args.BeaconAPIPort),
pulumi.String("--http-address=0.0.0.0"),
pulumi.String("--metrics-address=0.0.0.0"),
pulumi.String("--validator-monitor-auto"),
pulumi.String("--suggested-fee-recipient=0x0000000000000000000000000000000000000000"),
}

// Add bootnodes
if args.Bootnodes != nil {
for _, bootnode := range args.Bootnodes {
cmd = append(cmd, pulumi.Sprintf("--boot-nodes=%s", bootnode))
}
}

// Add additional args
if args.AdditionalArgs != nil {
for _, arg := range args.AdditionalArgs {
cmd = append(cmd, arg)
}
}

return cmd
}
46 changes: 46 additions & 0 deletions pkg/ethereum/consensus/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package consensus

import (
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// ConsensusClientArgs represents the arguments for creating a consensus client
type ConsensusClientArgs struct {
Name pulumi.StringInput
Namespace pulumi.StringInput
StorageSize pulumi.StringInput
StorageClass pulumi.StringInput
Image pulumi.StringInput
ImagePullPolicy pulumi.StringInput
JWTSecret pulumi.StringInput
NodeSelector pulumi.StringMap
Tolerations corev1.TolerationArray
P2PPort pulumi.IntInput
BeaconAPIPort pulumi.IntInput
MetricsPort pulumi.IntInput
ExecutionClientEndpoint pulumi.StringInput
Bootnodes pulumi.StringArray
AdditionalArgs pulumi.StringArray
}

// ConsensusClientComponent represents a consensus client deployment
type ConsensusClientComponent struct {
pulumi.ResourceState

// Name is the base name for all resources
Name pulumi.StringOutput
// Namespace is the Kubernetes namespace
Namespace pulumi.StringOutput
// PVC is the persistent volume claim
PVC *corev1.PersistentVolumeClaim
// JWTSecret is the JWT secret
JWTSecret *corev1.Secret
// P2PService is the P2P service
P2PService *corev1.Service
// BeaconAPIService is the beacon API service
BeaconAPIService *corev1.Service
// StatefulSet is the stateful set
StatefulSet *appsv1.StatefulSet
}
43 changes: 43 additions & 0 deletions pkg/ethereum/consensus/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package consensus

import (
"fmt"
)

// Validate validates the consensus client arguments
func (args *ConsensusClientArgs) Validate() error {
if args.Name == nil {
return fmt.Errorf("name is required")
}
if args.Namespace == nil {
return fmt.Errorf("namespace is required")
}
if args.StorageSize == nil {
return fmt.Errorf("storageSize is required")
}
if args.StorageClass == nil {
return fmt.Errorf("storageClass is required")
}
if args.Image == nil {
return fmt.Errorf("image is required")
}
if args.ImagePullPolicy == nil {
return fmt.Errorf("imagePullPolicy is required")
}
if args.JWTSecret == nil {
return fmt.Errorf("jwtSecret is required")
}
if args.P2PPort == nil {
return fmt.Errorf("p2pPort is required")
}
if args.BeaconAPIPort == nil {
return fmt.Errorf("beaconAPIPort is required")
}
if args.MetricsPort == nil {
return fmt.Errorf("metricsPort is required")
}
if args.ExecutionClientEndpoint == nil {
return fmt.Errorf("executionClientEndpoint is required")
}
return nil
}
Loading
Loading