Skip to content

Commit efe9859

Browse files
committed
feat(nitro): complete Nitro/Orbit chain deployment support
Major improvements to Nitro orchestrator for production deployments: Orchestrator (orchestrator.go): - Enhanced state machine for deployment stages - Better error handling and recovery - Improved progress tracking and logging - Support for Celestia DA configuration Deployer (deployer.go): - Complete L1 contract deployment flow - Chain configuration generation - Genesis file creation - Rollup contract interaction Config (config.go): - Celestia DA provider configuration - POPSigner mTLS endpoint settings - Batch poster and staker address management - Chain parameter validation Bundle (bundler.go, nitro.go): - Docker Compose generation for Nitro + Celestia - Environment template generation - Certificate bundle packaging - README and documentation generation Handler (handler.go): - API endpoints for deployment management - Artifact download endpoints - Status and progress endpoints This enables full Nitro/Orbit chain deployments via Popkins.
1 parent d503e76 commit efe9859

File tree

10 files changed

+851
-215
lines changed

10 files changed

+851
-215
lines changed

control-plane/cmd/server/main.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,23 @@ func main() {
167167
nitroMTLSEndpoint = "https://51.15.112.44:8546"
168168
}
169169

170+
// Initialize Nitro infrastructure repository (stores deployed RollupCreator addresses)
171+
nitroInfraRepo := repository.NewNitroInfrastructureRepository(db.Pool())
172+
170173
nitroOrch := nitro.NewOrchestrator(
171174
bootstrapRepo,
172175
nitroCertProvider,
173176
nitro.OrchestratorConfig{
174177
Logger: logger,
175178
WorkerPath: "internal/bootstrap/nitro/worker",
176179
POPSignerMTLSEndpoint: nitroMTLSEndpoint,
180+
NitroInfraRepo: nitroInfraRepo,
177181
},
178182
)
179-
logger.Info("Nitro orchestrator initialized", slog.String("mtls_endpoint", nitroMTLSEndpoint))
183+
logger.Info("Nitro orchestrator initialized",
184+
slog.String("mtls_endpoint", nitroMTLSEndpoint),
185+
slog.Bool("infra_repo_enabled", true),
186+
)
180187

181188
// Initialize key resolver and API key manager for orchestrator
182189
keyResolver := bootstraporchestrator.NewKeyServiceResolver(keySvc)

control-plane/internal/bootstrap/bundle/bundler.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,17 @@ func (b *Bundler) buildBundleConfig(deployment *repository.Deployment, artifacts
138138
cfg.Artifacts[a.ArtifactType] = a.Content
139139
}
140140

141+
// Extract ready-to-use .env file if present (from env_file artifact)
142+
if envFileData, ok := cfg.Artifacts["env_file"]; ok {
143+
cfg.EnvFile = string(envFileData)
144+
}
145+
141146
// Set default POPSigner endpoints
142147
switch cfg.Stack {
143148
case StackOPStack:
144149
cfg.POPSignerEndpoint = "https://rpc.popsigner.com"
145150
case StackNitro:
146-
cfg.POPSignerMTLSEndpoint = "https://rpc-mtls.popsigner.com"
151+
cfg.POPSignerMTLSEndpoint = "https://rpc-mtls.popsigner.com:8546"
147152
}
148153

149154
return cfg

control-plane/internal/bootstrap/bundle/nitro.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,24 @@ func (b *Bundler) createNitroBundle(cfg *BundleConfig) (*BundleResult, error) {
6161
}
6262
files = append(files, FileEntry{
6363
Path: ".env.example",
64-
Description: "Environment variables template (copy to .env and fill in)",
65-
Required: true,
64+
Description: "Environment variables template (for reference)",
65+
Required: false,
6666
SizeBytes: int64(len(cfg.EnvExample)),
6767
})
6868

69+
// .env (ready-to-use with actual values)
70+
if cfg.EnvFile != "" {
71+
if err := tarW.addFile(baseDir+"/.env", []byte(cfg.EnvFile)); err != nil {
72+
return nil, fmt.Errorf("add .env: %w", err)
73+
}
74+
files = append(files, FileEntry{
75+
Path: ".env",
76+
Description: "Ready-to-use environment file (pre-configured)",
77+
Required: true,
78+
SizeBytes: int64(len(cfg.EnvFile)),
79+
})
80+
}
81+
6982
// ===========================================
7083
// CONFIG DIRECTORY
7184
// ===========================================

control-plane/internal/bootstrap/bundle/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ type BundleConfig struct {
111111
// Generated files (from compose generator)
112112
DockerCompose string
113113
EnvExample string
114+
EnvFile string // Ready-to-use .env file with actual values
114115
}
115116

116117
// BundleResult contains the generated bundle and metadata.

control-plane/internal/bootstrap/nitro/config.go

Lines changed: 160 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ func (g *ArtifactGenerator) GenerateArtifacts(
8383
return err
8484
}
8585

86+
// 6b. Generate ready-to-use .env file
87+
envFile := GenerateEnv(config, result)
88+
if err := g.saveTextArtifact(ctx, deploymentID, "env_file", envFile); err != nil {
89+
return err
90+
}
91+
8692
// 7. Generate README.md
8793
readme := GenerateReadme(config, result)
8894
if err := g.saveTextArtifact(ctx, deploymentID, "readme", readme); err != nil {
@@ -255,7 +261,7 @@ func buildChainConfig(config *DeployConfig) map[string]interface{} {
255261
// true = AnyTrust DAC mode
256262
// For Celestia, we use external-provider flags with DAC=false
257263
"DataAvailabilityCommittee": config.DataAvailability == "anytrust",
258-
"InitialArbOSVersion": 20,
264+
"InitialArbOSVersion": 51, // ArbOS 51 - Nitro consensus-v51
259265
"InitialChainOwner": config.Owner,
260266
"GenesisBlockNum": 0,
261267
},
@@ -614,6 +620,13 @@ services:
614620
- POPSIGNER_API_KEY=${POPSIGNER_CELESTIA_API_KEY}
615621
# Parent chain RPC for Blobstream validation (fraud proofs)
616622
- ETH_RPC_URL=${L1_RPC_URL}
623+
# Healthcheck ensures celestia-das-server is ready before nitro-sequencer starts
624+
healthcheck:
625+
test: ["CMD", "wget", "-q", "--spider", "http://localhost:9876/health"]
626+
interval: 5s
627+
timeout: 3s
628+
retries: 5
629+
start_period: 10s
617630
networks:
618631
- nitro-network
619632
@@ -635,7 +648,7 @@ services:
635648
restart: unless-stopped
636649
depends_on:
637650
celestia-das-server:
638-
condition: service_started
651+
condition: service_healthy # Wait for healthcheck to pass, not just container start
639652
ports:
640653
- "8547:8547" # HTTP RPC
641654
- "8548:8548" # WebSocket RPC
@@ -651,6 +664,9 @@ services:
651664
- L1_RPC_URL=${L1_RPC_URL} # Can be HTTP or WSS (e.g., wss://sepolia.infura.io/ws/v3/KEY)
652665
- L1_BEACON_URL=${L1_BEACON_URL} # Beacon Chain API (HTTP)
653666
- POPSIGNER_MTLS_URL=${POPSIGNER_MTLS_URL}
667+
# External signer addresses (managed by PopSigner)
668+
- BATCH_POSTER_ADDRESS=${BATCH_POSTER_ADDRESS}
669+
- STAKER_ADDRESS=${STAKER_ADDRESS}
654670
command:
655671
# -------------------------------------------------------------------------
656672
# Core Chain Configuration
@@ -699,18 +715,27 @@ services:
699715
# -------------------------------------------------------------------------
700716
- --node.batch-poster.enable=true
701717
- --node.batch-poster.data-poster.external-signer.url=${POPSIGNER_MTLS_URL}
718+
- --node.batch-poster.data-poster.external-signer.address=${BATCH_POSTER_ADDRESS}
702719
- --node.batch-poster.data-poster.external-signer.method=eth_signTransaction
703720
- --node.batch-poster.data-poster.external-signer.client-cert=/certs/client.crt
704721
- --node.batch-poster.data-poster.external-signer.client-private-key=/certs/client.key
722+
- --node.batch-poster.data-poster.external-signer.root-ca=/certs/ca.crt
705723
# -------------------------------------------------------------------------
706-
# Staker/Validator Configuration (uses PopSigner for L1 tx signing)
724+
# Staker/Validator Configuration (BOLD Protocol)
725+
# IMPORTANT: BOLD requires WETH (not native ETH) for staking!
726+
# Before starting, ensure your STAKER_ADDRESS has:
727+
# 1. WETH tokens (wrap ETH via WETH.deposit())
728+
# 2. Approved ChallengeManager to spend WETH
729+
# Stake amount: ~0.1 ETH equivalent per assertion level
707730
# -------------------------------------------------------------------------
708731
- --node.staker.enable=true
709732
- --node.staker.strategy=MakeNodes
710733
- --node.staker.data-poster.external-signer.url=${POPSIGNER_MTLS_URL}
734+
- --node.staker.data-poster.external-signer.address=${STAKER_ADDRESS}
711735
- --node.staker.data-poster.external-signer.method=eth_signTransaction
712736
- --node.staker.data-poster.external-signer.client-cert=/certs/client.crt
713737
- --node.staker.data-poster.external-signer.client-private-key=/certs/client.key
738+
- --node.staker.data-poster.external-signer.root-ca=/certs/ca.crt
714739
# -------------------------------------------------------------------------
715740
# Feed Output (for full nodes to subscribe)
716741
# -------------------------------------------------------------------------
@@ -908,6 +933,24 @@ func GenerateEnvExample(config *DeployConfig, result *DeployResult) string {
908933
rpcExample = "wss://mainnet.infura.io/ws/v3/YOUR_KEY"
909934
}
910935

936+
// Get the batch poster address from config.BatchPosters
937+
batchPosterAddress := ""
938+
if len(config.BatchPosters) > 0 {
939+
batchPosterAddress = config.BatchPosters[0]
940+
}
941+
if batchPosterAddress == "" {
942+
batchPosterAddress = "REPLACE_WITH_YOUR_BATCH_POSTER_ADDRESS"
943+
}
944+
945+
// Get staker address from config.Validators (NOT same as batch poster!)
946+
stakerAddress := ""
947+
if len(config.Validators) > 0 {
948+
stakerAddress = config.Validators[0]
949+
}
950+
if stakerAddress == "" {
951+
stakerAddress = "REPLACE_WITH_YOUR_STAKER_ADDRESS"
952+
}
953+
911954
return fmt.Sprintf(`# =============================================================================
912955
# %s Nitro + Celestia DA Environment Configuration
913956
# Chain ID: %d | Parent Chain: %s (%d)
@@ -938,13 +981,26 @@ L1_BEACON_URL=%s
938981
# =============================================================================
939982
940983
# PopSigner mTLS endpoint for transaction signing
941-
POPSIGNER_MTLS_URL=https://rpc-mtls.popsigner.com
984+
POPSIGNER_MTLS_URL=https://rpc-mtls.popsigner.com:8546
942985
943986
# Note: mTLS certificates are included in ./certs/
944987
# - ./certs/client.crt (auto-generated during deployment)
945988
# - ./certs/client.key (auto-generated during deployment)
946989
# - ./certs/ca.crt (CA certificate for verification)
947990
991+
# =============================================================================
992+
# EXTERNAL SIGNER ADDRESSES
993+
# These are the Ethereum addresses that PopSigner will sign transactions for
994+
# =============================================================================
995+
996+
# Batch Poster Address - the address used to submit batches to L1
997+
# This should be the address associated with your PopSigner key
998+
BATCH_POSTER_ADDRESS=%s
999+
1000+
# Staker Address - the address used for staking/validation (if staker enabled)
1001+
# Can be same as batch poster or a different address
1002+
STAKER_ADDRESS=%s
1003+
9481004
# =============================================================================
9491005
# POPSIGNER FOR CELESTIA - Blob Submission Signing
9501006
# Used for signing Celestia blob transactions (SEPARATE from L1 signer!)
@@ -981,7 +1037,106 @@ NITRO_DAS_IMAGE=ghcr.io/celestiaorg/nitro-das-celestia:v0.7.0
9811037
`, config.ChainName, config.ChainID, parentChainName, config.ParentChainID,
9821038
parentChainName,
9831039
strings.ToLower(parentChainName), strings.ToLower(parentChainName), strings.ToLower(parentChainName),
984-
rpcExample, beaconURL, beaconURL, parentChainName)
1040+
rpcExample, beaconURL, beaconURL, parentChainName,
1041+
batchPosterAddress, stakerAddress)
1042+
}
1043+
1044+
// GenerateEnv creates a ready-to-use .env file with actual values.
1045+
// This removes the need for users to manually copy/edit .env.example.
1046+
func GenerateEnv(config *DeployConfig, result *DeployResult) string {
1047+
// Determine parent chain name and beacon URL
1048+
parentChainName := "Sepolia"
1049+
beaconURL := "https://ethereum-sepolia-beacon-api.publicnode.com"
1050+
if config.ParentChainID == 1 {
1051+
parentChainName = "Mainnet"
1052+
beaconURL = "https://ethereum-mainnet-beacon-api.publicnode.com"
1053+
}
1054+
1055+
// Use actual RPC URL from config, or default to public endpoint
1056+
rpcURL := config.ParentChainRpc
1057+
if rpcURL == "" {
1058+
if config.ParentChainID == 1 {
1059+
rpcURL = "https://ethereum-rpc.publicnode.com"
1060+
} else {
1061+
rpcURL = "https://ethereum-sepolia-rpc.publicnode.com"
1062+
}
1063+
}
1064+
1065+
// Get the batch poster address from config.BatchPosters
1066+
batchPosterAddress := ""
1067+
if len(config.BatchPosters) > 0 {
1068+
batchPosterAddress = config.BatchPosters[0]
1069+
}
1070+
if batchPosterAddress == "" {
1071+
batchPosterAddress = "REPLACE_WITH_YOUR_BATCH_POSTER_ADDRESS"
1072+
}
1073+
1074+
// Get staker address from config.Validators (NOT same as batch poster!)
1075+
stakerAddress := ""
1076+
if len(config.Validators) > 0 {
1077+
stakerAddress = config.Validators[0]
1078+
}
1079+
if stakerAddress == "" {
1080+
stakerAddress = "REPLACE_WITH_YOUR_STAKER_ADDRESS"
1081+
}
1082+
1083+
// Celestia config - these would come from the deployment config if set
1084+
celestiaAPIKey := "REPLACE_WITH_YOUR_CELESTIA_API_KEY"
1085+
celestiaKeyID := "REPLACE_WITH_YOUR_CELESTIA_KEY_ID"
1086+
1087+
return fmt.Sprintf(`# =============================================================================
1088+
# %s Nitro + Celestia DA Environment Configuration
1089+
# Chain ID: %d | Parent Chain: %s (%d)
1090+
# Generated by PopSigner - Ready to use!
1091+
# =============================================================================
1092+
1093+
# =============================================================================
1094+
# PARENT CHAIN (%s) - L1 CONNECTIONS
1095+
# =============================================================================
1096+
1097+
# Main L1 RPC endpoint (configured during deployment)
1098+
L1_RPC_URL=%s
1099+
1100+
# Beacon Chain API for EIP-4844 blob data
1101+
L1_BEACON_URL=%s
1102+
1103+
# =============================================================================
1104+
# POPSIGNER FOR L1 (%s) - Batch Poster & Validator Signing
1105+
# =============================================================================
1106+
1107+
# PopSigner mTLS endpoint for transaction signing
1108+
POPSIGNER_MTLS_URL=https://rpc-mtls.popsigner.com:8546
1109+
1110+
# =============================================================================
1111+
# EXTERNAL SIGNER ADDRESSES (configured during deployment)
1112+
# =============================================================================
1113+
1114+
# Batch Poster Address - the address used to submit batches to L1
1115+
BATCH_POSTER_ADDRESS=%s
1116+
1117+
# Staker Address - the address used for staking/validation
1118+
STAKER_ADDRESS=%s
1119+
1120+
# =============================================================================
1121+
# POPSIGNER FOR CELESTIA - Blob Submission Signing
1122+
# =============================================================================
1123+
1124+
# PopSigner API key for Celestia (get from PopSigner dashboard)
1125+
POPSIGNER_CELESTIA_API_KEY=%s
1126+
1127+
# PopSigner Key ID for your Celestia signing key
1128+
POPSIGNER_CELESTIA_KEY_ID=%s
1129+
1130+
# =============================================================================
1131+
# NITRO IMAGE
1132+
# =============================================================================
1133+
1134+
NITRO_IMAGE=nitro-node-dev:latest
1135+
NITRO_DAS_IMAGE=ghcr.io/celestiaorg/nitro-das-celestia:v0.7.0
1136+
`, config.ChainName, config.ChainID, parentChainName, config.ParentChainID,
1137+
parentChainName, rpcURL, beaconURL, parentChainName,
1138+
batchPosterAddress, stakerAddress,
1139+
celestiaAPIKey, celestiaKeyID)
9851140
}
9861141

9871142
// ============================================================================

0 commit comments

Comments
 (0)