Skip to content

Commit 0364820

Browse files
rswansonCopilot
andauthored
feat: ethereum package (#16)
* feat: ethereum package to standardize ethereum deployments * fix: use pulumi input types for the args * fix: consensus client implementation * feat: ethereum node component * fix: add generics for better devex (#18) * fix: Update pkg/ethereum/consensus/component.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix: el env vars * fix: arg types --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d985354 commit 0364820

File tree

14 files changed

+1247
-16
lines changed

14 files changed

+1247
-16
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
package consensus
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/init4tech/signet-infra-components/pkg/utils"
7+
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
8+
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
9+
metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
10+
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
11+
)
12+
13+
// NewConsensusClient creates a new consensus client component
14+
func NewConsensusClient(ctx *pulumi.Context, args *ConsensusClientArgs, opts ...pulumi.ResourceOption) (*ConsensusClientComponent, error) {
15+
if err := args.Validate(); err != nil {
16+
return nil, fmt.Errorf("invalid consensus client args: %w", err)
17+
}
18+
19+
component := &ConsensusClientComponent{}
20+
21+
var name string
22+
pulumi.All(args.Name).ApplyT(func(values []interface{}) error {
23+
name = values[0].(string)
24+
return nil
25+
})
26+
27+
err := ctx.RegisterComponentResource("signet:consensus:ConsensusClient", name, component)
28+
if err != nil {
29+
return nil, fmt.Errorf("failed to register component resource: %w", err)
30+
}
31+
32+
// Create PVC for data storage
33+
pvcName := fmt.Sprintf("%s-data", name)
34+
component.PVC, err = corev1.NewPersistentVolumeClaim(ctx, pvcName, &corev1.PersistentVolumeClaimArgs{
35+
Metadata: &metav1.ObjectMetaArgs{
36+
Name: pulumi.String(pvcName),
37+
Namespace: args.Namespace,
38+
Labels: utils.CreateResourceLabels(name, pvcName, name, nil),
39+
},
40+
Spec: &corev1.PersistentVolumeClaimSpecArgs{
41+
AccessModes: pulumi.StringArray{
42+
pulumi.String("ReadWriteOnce"),
43+
},
44+
Resources: &corev1.VolumeResourceRequirementsArgs{
45+
Requests: pulumi.StringMap{
46+
"storage": args.StorageSize,
47+
},
48+
},
49+
StorageClassName: args.StorageClass,
50+
},
51+
}, pulumi.Parent(component))
52+
if err != nil {
53+
return nil, fmt.Errorf("failed to create PVC: %w", err)
54+
}
55+
56+
// Create JWT secret
57+
jwtSecretName := fmt.Sprintf("%s-jwt", name)
58+
component.JWTSecret, err = corev1.NewSecret(ctx, jwtSecretName, &corev1.SecretArgs{
59+
StringData: pulumi.StringMap{
60+
"jwt.hex": args.JWTSecret,
61+
},
62+
Metadata: &metav1.ObjectMetaArgs{
63+
Name: pulumi.String(jwtSecretName),
64+
Namespace: args.Namespace,
65+
Labels: utils.CreateResourceLabels(name, jwtSecretName, name, nil),
66+
},
67+
}, pulumi.Parent(component))
68+
if err != nil {
69+
return nil, fmt.Errorf("failed to create JWT secret: %w", err)
70+
}
71+
72+
// Create P2P service
73+
p2pServiceName := fmt.Sprintf("%s-p2p", name)
74+
component.P2PService, err = corev1.NewService(ctx, p2pServiceName, &corev1.ServiceArgs{
75+
Metadata: &metav1.ObjectMetaArgs{
76+
Name: pulumi.String(p2pServiceName),
77+
Namespace: args.Namespace,
78+
Labels: utils.CreateResourceLabels(name, p2pServiceName, name, nil),
79+
},
80+
Spec: &corev1.ServiceSpecArgs{
81+
Selector: pulumi.StringMap{
82+
"app": pulumi.String(name),
83+
},
84+
Ports: corev1.ServicePortArray{
85+
corev1.ServicePortArgs{
86+
Name: pulumi.String("p2p"),
87+
Port: args.P2PPort,
88+
TargetPort: args.P2PPort,
89+
Protocol: pulumi.String("TCP"),
90+
},
91+
},
92+
},
93+
}, pulumi.Parent(component))
94+
if err != nil {
95+
return nil, fmt.Errorf("failed to create P2P service: %w", err)
96+
}
97+
98+
// Create Beacon API service
99+
beaconAPIServiceName := fmt.Sprintf("%s-beacon-api", name)
100+
component.BeaconAPIService, err = corev1.NewService(ctx, beaconAPIServiceName, &corev1.ServiceArgs{
101+
Metadata: &metav1.ObjectMetaArgs{
102+
Name: pulumi.String(beaconAPIServiceName),
103+
Namespace: args.Namespace,
104+
Labels: utils.CreateResourceLabels(name, beaconAPIServiceName, name, nil),
105+
},
106+
Spec: &corev1.ServiceSpecArgs{
107+
Selector: pulumi.StringMap{
108+
"app": pulumi.String(name),
109+
},
110+
Ports: corev1.ServicePortArray{
111+
corev1.ServicePortArgs{
112+
Name: pulumi.String("beacon-api"),
113+
Port: args.BeaconAPIPort,
114+
TargetPort: args.BeaconAPIPort,
115+
},
116+
corev1.ServicePortArgs{
117+
Name: pulumi.String("metrics"),
118+
Port: args.MetricsPort,
119+
TargetPort: args.MetricsPort,
120+
},
121+
},
122+
},
123+
}, pulumi.Parent(component))
124+
if err != nil {
125+
return nil, fmt.Errorf("failed to create Beacon API service: %w", err)
126+
}
127+
128+
// Create StatefulSet
129+
statefulSetName := name
130+
component.StatefulSet, err = appsv1.NewStatefulSet(ctx, statefulSetName, &appsv1.StatefulSetArgs{
131+
Metadata: &metav1.ObjectMetaArgs{
132+
Name: pulumi.String(statefulSetName),
133+
Namespace: args.Namespace,
134+
Labels: utils.CreateResourceLabels(name, statefulSetName, name, nil),
135+
},
136+
Spec: &appsv1.StatefulSetSpecArgs{
137+
Replicas: pulumi.Int(1),
138+
Selector: &metav1.LabelSelectorArgs{
139+
MatchLabels: pulumi.StringMap{
140+
"app": pulumi.String(name),
141+
},
142+
},
143+
Template: &corev1.PodTemplateSpecArgs{
144+
Metadata: &metav1.ObjectMetaArgs{
145+
Labels: pulumi.StringMap{
146+
"app": pulumi.String(name),
147+
},
148+
},
149+
Spec: &corev1.PodSpecArgs{
150+
Containers: corev1.ContainerArray{
151+
corev1.ContainerArgs{
152+
Name: pulumi.String("consensus"),
153+
Image: args.Image,
154+
ImagePullPolicy: args.ImagePullPolicy,
155+
Command: createConsensusClientCommand(args),
156+
Ports: corev1.ContainerPortArray{
157+
corev1.ContainerPortArgs{
158+
Name: pulumi.String("p2p"),
159+
ContainerPort: args.P2PPort,
160+
Protocol: pulumi.String("TCP"),
161+
},
162+
corev1.ContainerPortArgs{
163+
Name: pulumi.String("beacon-api"),
164+
ContainerPort: args.BeaconAPIPort,
165+
},
166+
corev1.ContainerPortArgs{
167+
Name: pulumi.String("metrics"),
168+
ContainerPort: args.MetricsPort,
169+
},
170+
},
171+
VolumeMounts: corev1.VolumeMountArray{
172+
corev1.VolumeMountArgs{
173+
Name: pulumi.String("data"),
174+
MountPath: pulumi.String("/data"),
175+
},
176+
corev1.VolumeMountArgs{
177+
Name: pulumi.String("jwt"),
178+
MountPath: pulumi.String("/etc/execution/jwt"),
179+
},
180+
},
181+
Resources: nil,
182+
},
183+
},
184+
Volumes: corev1.VolumeArray{
185+
corev1.VolumeArgs{
186+
Name: pulumi.String("data"),
187+
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSourceArgs{
188+
ClaimName: pulumi.String(pvcName),
189+
},
190+
},
191+
corev1.VolumeArgs{
192+
Name: pulumi.String("jwt"),
193+
Secret: &corev1.SecretVolumeSourceArgs{
194+
SecretName: pulumi.String(jwtSecretName),
195+
},
196+
},
197+
},
198+
NodeSelector: args.NodeSelector,
199+
Tolerations: args.Tolerations,
200+
},
201+
},
202+
},
203+
}, pulumi.Parent(component))
204+
if err != nil {
205+
return nil, fmt.Errorf("failed to create StatefulSet: %w", err)
206+
}
207+
208+
component.Name = args.Name.ToStringOutput()
209+
component.Namespace = args.Namespace.ToStringOutput()
210+
return component, nil
211+
}
212+
213+
// createConsensusClientCommand creates the command array for the consensus client
214+
func createConsensusClientCommand(args *ConsensusClientArgs) pulumi.StringArray {
215+
cmd := pulumi.StringArray{
216+
pulumi.String("--datadir=/data"),
217+
pulumi.Sprintf("--execution-jwt=/etc/execution/jwt/jwt.hex"),
218+
pulumi.Sprintf("--execution-endpoint=%s", args.ExecutionClientEndpoint),
219+
pulumi.Sprintf("--port=%d", args.P2PPort),
220+
pulumi.Sprintf("--metrics-port=%d", args.MetricsPort),
221+
pulumi.Sprintf("--http-port=%d", args.BeaconAPIPort),
222+
pulumi.String("--http-address=0.0.0.0"),
223+
pulumi.String("--metrics-address=0.0.0.0"),
224+
pulumi.String("--validator-monitor-auto"),
225+
pulumi.String("--suggested-fee-recipient=0x0000000000000000000000000000000000000000"),
226+
}
227+
228+
// Add bootnodes
229+
if args.Bootnodes != nil {
230+
for _, bootnode := range args.Bootnodes {
231+
cmd = append(cmd, pulumi.Sprintf("--boot-nodes=%s", bootnode))
232+
}
233+
}
234+
235+
// Add additional args
236+
if args.AdditionalArgs != nil {
237+
for _, arg := range args.AdditionalArgs {
238+
cmd = append(cmd, arg)
239+
}
240+
}
241+
242+
return cmd
243+
}

pkg/ethereum/consensus/types.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package consensus
2+
3+
import (
4+
appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
5+
corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
6+
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
7+
)
8+
9+
// ConsensusClientArgs represents the arguments for creating a consensus client
10+
type ConsensusClientArgs struct {
11+
Name pulumi.StringInput
12+
Namespace pulumi.StringInput
13+
StorageSize pulumi.StringInput
14+
StorageClass pulumi.StringInput
15+
Image pulumi.StringInput
16+
ImagePullPolicy pulumi.StringInput
17+
JWTSecret pulumi.StringInput
18+
NodeSelector pulumi.StringMap
19+
Tolerations corev1.TolerationArray
20+
P2PPort pulumi.IntInput
21+
BeaconAPIPort pulumi.IntInput
22+
MetricsPort pulumi.IntInput
23+
ExecutionClientEndpoint pulumi.StringInput
24+
Bootnodes pulumi.StringArray
25+
AdditionalArgs pulumi.StringArray
26+
}
27+
28+
// ConsensusClientComponent represents a consensus client deployment
29+
type ConsensusClientComponent struct {
30+
pulumi.ResourceState
31+
32+
// Name is the base name for all resources
33+
Name pulumi.StringOutput
34+
// Namespace is the Kubernetes namespace
35+
Namespace pulumi.StringOutput
36+
// PVC is the persistent volume claim
37+
PVC *corev1.PersistentVolumeClaim
38+
// JWTSecret is the JWT secret
39+
JWTSecret *corev1.Secret
40+
// P2PService is the P2P service
41+
P2PService *corev1.Service
42+
// BeaconAPIService is the beacon API service
43+
BeaconAPIService *corev1.Service
44+
// StatefulSet is the stateful set
45+
StatefulSet *appsv1.StatefulSet
46+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package consensus
2+
3+
import (
4+
"fmt"
5+
)
6+
7+
// Validate validates the consensus client arguments
8+
func (args *ConsensusClientArgs) Validate() error {
9+
if args.Name == nil {
10+
return fmt.Errorf("name is required")
11+
}
12+
if args.Namespace == nil {
13+
return fmt.Errorf("namespace is required")
14+
}
15+
if args.StorageSize == nil {
16+
return fmt.Errorf("storageSize is required")
17+
}
18+
if args.StorageClass == nil {
19+
return fmt.Errorf("storageClass is required")
20+
}
21+
if args.Image == nil {
22+
return fmt.Errorf("image is required")
23+
}
24+
if args.ImagePullPolicy == nil {
25+
return fmt.Errorf("imagePullPolicy is required")
26+
}
27+
if args.JWTSecret == nil {
28+
return fmt.Errorf("jwtSecret is required")
29+
}
30+
if args.P2PPort == nil {
31+
return fmt.Errorf("p2pPort is required")
32+
}
33+
if args.BeaconAPIPort == nil {
34+
return fmt.Errorf("beaconAPIPort is required")
35+
}
36+
if args.MetricsPort == nil {
37+
return fmt.Errorf("metricsPort is required")
38+
}
39+
if args.ExecutionClientEndpoint == nil {
40+
return fmt.Errorf("executionClientEndpoint is required")
41+
}
42+
return nil
43+
}

0 commit comments

Comments
 (0)