Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
25 changes: 25 additions & 0 deletions pkg/pylon/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package pylon

// Storage constants
const (
ExecutionClientStorageSize = "150Gi"
ConsensusClientStorageSize = "100Gi"
StorageClassAWSGP3 = "aws-gp3"
)

// Port constants
const (
ExecutionP2PPort = 30303
ExecutionRPCPort = 8545
ExecutionWSPort = 8546
ExecutionMetricsPort = 9001
ExecutionAuthRPCPort = 8551
ConsensusBeaconAPIPort = 4000
ConsensusMetricsPort = 5054
)

// Image constants
const (
ConsensusClientImage = "sigp/lighthouse:latest"
ImagePullPolicyAlways = "Always"
)
1 change: 1 addition & 0 deletions pkg/pylon/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package pylon
Copy link

Copilot AI Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is empty and can be removed to avoid clutter.

Copilot uses AI. Check for mistakes.
89 changes: 89 additions & 0 deletions pkg/pylon/pylon.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package pylon

import (
"fmt"

"github.com/init4tech/signet-infra-components/pkg/ethereum"
"github.com/init4tech/signet-infra-components/pkg/ethereum/consensus"
"github.com/init4tech/signet-infra-components/pkg/ethereum/execution"
"github.com/pulumi/pulumi-aws/sdk/v6/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func NewPylonComponent(ctx *pulumi.Context, args *PylonComponentArgs, opts ...pulumi.ResourceOption) (*PylonComponent, error) {
component := &PylonComponent{}

err := ctx.RegisterComponentResource("signet:index:Pylon", args.Name, component, opts...)
if err != nil {
return nil, err
}

// Convert public args to internal args
internalArgs := args.toInternal()

stack := ctx.Stack()

// Get the existing Route53 hosted zone for signet.sh
dbProjectName := internalArgs.DbProjectName
dbStackName := fmt.Sprintf("%s/%s", dbProjectName, stack)

// TODO: this should be a stack reference to the pylon db stack- should this be an arg?
// Need to think about how i want to handle the separation of the pylon db and pylon components
thePylonDbStack, err := pulumi.NewStackReference(ctx, dbStackName, nil)
if err != nil {
return nil, err
}

// Get the database cluster endpoint (unused for now but needed for future implementation)
_ = thePylonDbStack.GetStringOutput(pulumi.String("dbClusterEndpoint"))

// Create the S3 bucket for blob storage (unused for now but needed for future implementation)
_, err = s3.NewBucketV2(ctx, "pylon-blob-bucket", &s3.BucketV2Args{
Bucket: internalArgs.PylonBlobBucketName,
})
if err != nil {
return nil, err
}

// Convert environment to internal type for use with ethereum components
internalEnv := args.Env.toInternal()

// Create Ethereum node component
ethereumNodeArgs := &ethereum.EthereumNodeArgs{
Name: args.Name,
Namespace: args.Namespace,
ExecutionClient: &execution.ExecutionClientArgs{
Name: args.Name,
Namespace: args.Namespace,
StorageSize: ExecutionClientStorageSize,
StorageClass: StorageClassAWSGP3,
Image: args.PylonImage,
JWTSecret: args.ExecutionJwt,
P2PPort: ExecutionP2PPort,
RPCPort: ExecutionRPCPort,
WSPort: ExecutionWSPort,
MetricsPort: ExecutionMetricsPort,
AuthRPCPort: ExecutionAuthRPCPort,
DiscoveryPort: ExecutionP2PPort,
ExecutionClientEnv: internalEnv,
},
ConsensusClient: &consensus.ConsensusClientArgs{
Name: args.Name,
Namespace: args.Namespace,
StorageSize: ConsensusClientStorageSize,
StorageClass: StorageClassAWSGP3,
Image: ConsensusClientImage,
ImagePullPolicy: ImagePullPolicyAlways,
BeaconAPIPort: ConsensusBeaconAPIPort,
MetricsPort: ConsensusMetricsPort,
},
}

ethereumNode, err := ethereum.NewEthereumNodeComponent(ctx, ethereumNodeArgs, pulumi.Parent(component))
if err != nil {
return nil, err
}
component.EthereumNode = ethereumNode

return component, nil
}
118 changes: 118 additions & 0 deletions pkg/pylon/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package pylon

import (
"strconv"

"github.com/init4tech/signet-infra-components/pkg/ethereum"
"github.com/init4tech/signet-infra-components/pkg/utils"
v1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

// Public-facing structs with base Go types
type PylonComponentArgs struct {
Namespace string
Name string
DbProjectName string
ExecutionJwt string
PylonImage string
PylonBlobBucketName string
Env PylonEnv
}

// Internal structs with Pulumi types for use within the component
type pylonComponentArgsInternal struct {
Namespace pulumi.StringInput
Name pulumi.StringInput
DbProjectName pulumi.StringInput
ExecutionJwt pulumi.StringInput
PylonImage pulumi.StringInput
PylonBlobBucketName pulumi.StringInput
Env pylonEnvInternal
}

// Public-facing environment struct with base Go types
type PylonEnv struct {
PylonStartBlock int `pulumi:"pylonStartBlock" validate:"required"`
PylonS3Url string `pulumi:"pylonS3Url" validate:"required"`
PylonS3Region string `pulumi:"pylonS3Region" validate:"required"`
PylonSenderAddress string `pulumi:"pylonSenderAddress" validate:"required"`
PylonNetworkSlotDuration int `pulumi:"pylonNetworkSlotDuration" validate:"required"`
PylonNetworkSlotOffset int `pulumi:"pylonNetworkSlotOffset" validate:"required"`
PylonRequestsPerSecond int `pulumi:"pylonRequestsPerSecond" validate:"required"`
PylonRustLog string `pulumi:"pylonRustLog"`
PylonPort int `pulumi:"pylonPort" validate:"required"`
AwsAccessKeyId string `pulumi:"awsAccessKeyId" validate:"required"`
AwsSecretAccessKey string `pulumi:"awsSecretAccessKey" validate:"required"`
AwsRegion string `pulumi:"awsRegion" validate:"required"`
PylonDbUrl string `pulumi:"pylonDbUrl" validate:"required"`
PylonConsensusClientUrl string `pulumi:"pylonConsensusClientUrl" validate:"required"`
PylonBlobscanBaseUrl string `pulumi:"pylonBlobscanBaseUrl" validate:"required"`
PylonNetworkStartTimestamp int `pulumi:"pylonNetworkStartTimestamp" validate:"required"`
}

// Internal environment struct with Pulumi types
type pylonEnvInternal struct {
PylonStartBlock pulumi.StringInput `pulumi:"pylonStartBlock" validate:"required"`
PylonS3Url pulumi.StringInput `pulumi:"pylonS3Url" validate:"required"`
PylonS3Region pulumi.StringInput `pulumi:"pylonS3Region" validate:"required"`
PylonSenderAddress pulumi.StringInput `pulumi:"pylonSenderAddress" validate:"required"`
PylonNetworkSlotDuration pulumi.StringInput `pulumi:"pylonNetworkSlotDuration" validate:"required"`
PylonNetworkSlotOffset pulumi.StringInput `pulumi:"pylonNetworkSlotOffset" validate:"required"`
PylonRequestsPerSecond pulumi.StringInput `pulumi:"pylonRequestsPerSecond" validate:"required"`
PylonRustLog pulumi.StringInput `pulumi:"pylonRustLog"`
PylonPort pulumi.StringInput `pulumi:"pylonPort" validate:"required"`
AwsAccessKeyId pulumi.StringInput `pulumi:"awsAccessKeyId" validate:"required"`
AwsSecretAccessKey pulumi.StringInput `pulumi:"awsSecretAccessKey" validate:"required"`
AwsRegion pulumi.StringInput `pulumi:"awsRegion" validate:"required"`
PylonDbUrl pulumi.StringInput `pulumi:"pylonDbUrl" validate:"required"`
PylonConsensusClientUrl pulumi.StringInput `pulumi:"pylonConsensusClientUrl" validate:"required"`
PylonBlobscanBaseUrl pulumi.StringInput `pulumi:"pylonBlobscanBaseUrl" validate:"required"`
PylonNetworkStartTimestamp pulumi.StringInput `pulumi:"pylonNetworkStartTimestamp" validate:"required"`
}

// Conversion function to convert public args to internal args
func (args PylonComponentArgs) toInternal() pylonComponentArgsInternal {
return pylonComponentArgsInternal{
Namespace: pulumi.String(args.Namespace),
Name: pulumi.String(args.Name),
DbProjectName: pulumi.String(args.DbProjectName),
ExecutionJwt: pulumi.String(args.ExecutionJwt),
PylonImage: pulumi.String(args.PylonImage),
PylonBlobBucketName: pulumi.String(args.PylonBlobBucketName),
Env: args.Env.toInternal(),
}
}

// Conversion function to convert public env to internal env
func (e PylonEnv) toInternal() pylonEnvInternal {
return pylonEnvInternal{
PylonStartBlock: pulumi.String(strconv.Itoa(e.PylonStartBlock)),
PylonS3Url: pulumi.String(e.PylonS3Url),
PylonS3Region: pulumi.String(e.PylonS3Region),
PylonSenderAddress: pulumi.String(e.PylonSenderAddress),
PylonNetworkSlotDuration: pulumi.String(strconv.Itoa(e.PylonNetworkSlotDuration)),
PylonNetworkSlotOffset: pulumi.String(strconv.Itoa(e.PylonNetworkSlotOffset)),
PylonRequestsPerSecond: pulumi.String(strconv.Itoa(e.PylonRequestsPerSecond)),
PylonRustLog: pulumi.String(e.PylonRustLog),
PylonPort: pulumi.String(strconv.Itoa(e.PylonPort)),
AwsAccessKeyId: pulumi.String(e.AwsAccessKeyId),
AwsSecretAccessKey: pulumi.String(e.AwsSecretAccessKey),
AwsRegion: pulumi.String(e.AwsRegion),
PylonDbUrl: pulumi.String(e.PylonDbUrl),
PylonConsensusClientUrl: pulumi.String(e.PylonConsensusClientUrl),
PylonBlobscanBaseUrl: pulumi.String(e.PylonBlobscanBaseUrl),
PylonNetworkStartTimestamp: pulumi.String(strconv.Itoa(e.PylonNetworkStartTimestamp)),
}
}

// GetEnvMap implements the utils.EnvProvider interface for internal env
func (e pylonEnvInternal) GetEnvMap() pulumi.StringMap {
return utils.CreateEnvMap(e)
}

type PylonComponent struct {
pulumi.ResourceState
EthereumNode *ethereum.EthereumNodeComponent
PylonEnvConfigMap *v1.ConfigMap
Comment on lines +116 to +117
Copy link

Copilot AI Jul 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PylonEnvConfigMap field is declared but never initialized or used. Remove it or add logic to create and set this ConfigMap.

Suggested change
EthereumNode *ethereum.EthereumNodeComponent
PylonEnvConfigMap *v1.ConfigMap
EthereumNode *ethereum.EthereumNodeComponent

Copilot uses AI. Check for mistakes.
}
103 changes: 103 additions & 0 deletions pkg/pylon/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package pylon

import (
"fmt"

"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func ValidatePylon(ctx *pulumi.Context, args *PylonComponentArgs) error {
if args.Namespace == "" {
return fmt.Errorf("namespace is required")
}

if args.Name == "" {
return fmt.Errorf("name is required")
}

if args.DbProjectName == "" {
return fmt.Errorf("dbProjectName is required")
}

if args.ExecutionJwt == "" {
return fmt.Errorf("executionJwt is required")
}

if args.PylonImage == "" {
return fmt.Errorf("pylonImage is required")
}

if args.PylonBlobBucketName == "" {
return fmt.Errorf("pylonBlobBucketName is required")
}

if err := validateEnv(args.Env); err != nil {
return err
}

return nil
}

func validateEnv(env PylonEnv) error {
if env.PylonStartBlock == 0 {
return fmt.Errorf("pylonStartBlock is required")
}

if env.PylonSenderAddress == "" {
return fmt.Errorf("pylonSenderAddress is required")
}

if env.PylonS3Url == "" {
return fmt.Errorf("pylonS3Url is required")
}

if env.PylonS3Region == "" {
return fmt.Errorf("pylonS3Region is required")
}

if env.PylonConsensusClientUrl == "" {
return fmt.Errorf("pylonConsensusClientUrl is required")
}

if env.PylonBlobscanBaseUrl == "" {
return fmt.Errorf("pylonBlobscanBaseUrl is required")
}

if env.PylonNetworkStartTimestamp == 0 {
return fmt.Errorf("pylonNetworkStartTimestamp is required")
}

if env.PylonNetworkSlotDuration == 0 {
return fmt.Errorf("pylonNetworkSlotDuration is required")
}

if env.PylonNetworkSlotOffset < 0 {
return fmt.Errorf("pylonNetworkSlotOffset must be a non-negative integer")
}

if env.PylonRequestsPerSecond == 0 {
return fmt.Errorf("pylonRequestsPerSecond is required")
}

if env.PylonPort == 0 {
return fmt.Errorf("pylonPort is required")
}

if env.AwsAccessKeyId == "" {
return fmt.Errorf("awsAccessKeyId is required")
}

if env.AwsSecretAccessKey == "" {
return fmt.Errorf("awsSecretAccessKey is required")
}

if env.AwsRegion == "" {
return fmt.Errorf("awsRegion is required")
}

if env.PylonDbUrl == "" {
return fmt.Errorf("pylonDbUrl is required")
}

return nil
}
Loading