Skip to content

Commit 12a44e2

Browse files
joshuacolvin0claude
andcommitted
Improve error handling, robustness, and resource cleanup across testnode
- Add try/finally around all WebSocket provider lifecycles to prevent connection leaks on errors (ethcommands.ts) - Replace fragile `tail | tr` output parsing with capture_output and capture_last_field helpers that fail on empty results (test-node.bash) - Replace `sleep 45` with deterministic wait_for_chain_progress that confirms block production before proceeding (test-node.bash) - Add timeouts to previously-infinite polling loops in bridgeFunds, bridgeNativeToken, and waitForSync (ethcommands.ts) - Add Docker healthchecks for geth, sequencer, redis, postgres and use service_healthy conditions in depends_on for proper startup ordering (docker-compose.yaml) - Track background PIDs and clean up on exit via trap (test-node.bash) - Add Redis connection cleanup via finally blocks (redis.ts) - Separate l3node-data volume from validator-data (docker-compose.yaml) - Replace sed-based JSON rewriting with jq for timeboost config - Move S3 credential warning to point of use instead of module level - Add .gitignore, enable set -euo pipefail, fix --dev-contracts shift bug, fix yargs l3 boolean type, validate flag dependencies post-parse Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent e1d439b commit 12a44e2

8 files changed

Lines changed: 580 additions & 333 deletions

File tree

.gitignore

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Dependencies
2+
node_modules/
3+
4+
# Environment
5+
.env
6+
.env.local
7+
.env.*.local
8+
9+
# Logs
10+
*.log
11+
12+
# Local overrides
13+
docker-compose.override.yaml
14+
docker-compose.override.yml
15+
16+
# OS files
17+
.DS_Store
18+
Thumbs.db
19+
20+
# IDE
21+
.idea/
22+
.vscode/
23+
*.swp
24+
*.swo
25+
*~

docker-compose.yaml

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ services:
3232
image: postgres:13.6
3333
restart: unless-stopped
3434
container_name: 'postgres'
35+
healthcheck:
36+
test: ["CMD-SHELL", "pg_isready -U postgres"]
37+
interval: 5s
38+
timeout: 3s
39+
retries: 5
3540
environment:
3641
POSTGRES_PASSWORD: ''
3742
POSTGRES_USER: 'postgres'
@@ -43,11 +48,24 @@ services:
4348

4449
redis:
4550
image: redis:6.2.6
51+
restart: on-failure:3
4652
ports:
4753
- "127.0.0.1:6379:6379"
54+
healthcheck:
55+
test: ["CMD", "redis-cli", "ping"]
56+
interval: 5s
57+
timeout: 3s
58+
retries: 5
4859

4960
geth:
5061
image: ethereum/client-go:v1.16.4
62+
restart: on-failure:3
63+
healthcheck:
64+
test: ["CMD-SHELL", "wget -qO- http://localhost:8545 --post-data='{\"jsonrpc\":\"2.0\",\"method\":\"eth_syncing\",\"params\":[],\"id\":1}' --header='Content-Type: application/json' || exit 1"]
65+
interval: 10s
66+
timeout: 5s
67+
retries: 5
68+
start_period: 10s
5169
ports:
5270
- "127.0.0.1:8545:8545"
5371
- "127.0.0.1:8551:8551"
@@ -157,7 +175,14 @@ services:
157175
sequencer:
158176
pid: host # allow debugging
159177
image: nitro-node-dev-testnode
178+
restart: on-failure:3
160179
entrypoint: /usr/local/bin/nitro
180+
healthcheck:
181+
test: ["CMD-SHELL", "wget -qO- http://localhost:8547/health || exit 1"]
182+
interval: 10s
183+
timeout: 5s
184+
retries: 5
185+
start_period: 15s
161186
ports:
162187
- "127.0.0.1:8547:8547"
163188
- "127.0.0.1:8548:8548"
@@ -177,7 +202,8 @@ services:
177202
- --graphql.vhosts=*
178203
- --graphql.corsdomain=*
179204
depends_on:
180-
- geth
205+
geth:
206+
condition: service_healthy
181207

182208
sequencer_b:
183209
pid: host # allow debugging
@@ -236,6 +262,7 @@ services:
236262
staker-unsafe:
237263
pid: host # allow debugging
238264
image: nitro-node-dev-testnode
265+
restart: on-failure:3
239266
entrypoint: /usr/local/bin/nitro
240267
ports:
241268
- "127.0.0.1:8047:8547"
@@ -246,13 +273,17 @@ services:
246273
- "config:/config"
247274
command: --conf.file /config/unsafe_staker_config.json
248275
depends_on:
249-
- sequencer
250-
- redis
251-
- validation_node
276+
sequencer:
277+
condition: service_healthy
278+
redis:
279+
condition: service_healthy
280+
validation_node:
281+
condition: service_started
252282

253283
poster:
254284
pid: host # allow debugging
255285
image: nitro-node-dev-testnode
286+
restart: on-failure:3
256287
entrypoint: /usr/local/bin/nitro
257288
ports:
258289
- "127.0.0.1:8147:8547"
@@ -263,8 +294,10 @@ services:
263294
- "config:/config"
264295
command: --conf.file /config/poster_config.json
265296
depends_on:
266-
- geth
267-
- redis
297+
geth:
298+
condition: service_healthy
299+
redis:
300+
condition: service_healthy
268301

269302
poster_b:
270303
pid: host # allow debugging
@@ -301,6 +334,7 @@ services:
301334
validator:
302335
pid: host # allow debugging
303336
image: nitro-node-dev-testnode
337+
restart: on-failure:3
304338
entrypoint: /usr/local/bin/nitro
305339
ports:
306340
- "127.0.0.1:8247:8547"
@@ -311,18 +345,21 @@ services:
311345
- "config:/config"
312346
command: --conf.file /config/validator_config.json --http.port 8547 --http.api net,web3,arb,debug --ws.port 8548
313347
depends_on:
314-
- sequencer
315-
- validation_node
348+
sequencer:
349+
condition: service_healthy
350+
validation_node:
351+
condition: service_started
316352

317353
l3node:
318354
pid: host # allow debugging
319355
image: nitro-node-dev-testnode
356+
restart: on-failure:3
320357
entrypoint: /usr/local/bin/nitro
321358
ports:
322359
- "127.0.0.1:3347:3347"
323360
- "127.0.0.1:3348:3348"
324361
volumes:
325-
- "validator-data:/home/user/.arbitrum/local/nitro"
362+
- "l3node-data:/home/user/.arbitrum/local/nitro"
326363
- "l1keystore:/home/user/l1keystore"
327364
- "config:/config"
328365
command: --conf.file /config/l3node_config.json --http.port 3347 --http.api net,web3,arb,debug,eth --ws.port 3348
@@ -521,6 +558,7 @@ volumes:
521558
seqdata_d:
522559
unsafestaker-data:
523560
validator-data:
561+
l3node-data:
524562
poster-data:
525563
poster-data-b:
526564
poster-data-c:

scripts/config.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ import { S3Client, PutObjectCommand, CreateBucketCommand, HeadBucketCommand } fr
77

88
const path = require("path");
99

10+
function warnIfPartialS3Credentials() {
11+
if ((process.env.AWS_ACCESS_KEY_ID && !process.env.AWS_SECRET_ACCESS_KEY) ||
12+
(!process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY)) {
13+
console.warn("Warning: Only one of AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY is set; both must be provided for custom S3 credentials (missing value will default to 'minioadmin')");
14+
}
15+
}
16+
1017
const S3_CONFIG = {
11-
endpoint: "http://minio:9000",
12-
region: "us-east-1",
18+
endpoint: process.env.S3_ENDPOINT || "http://minio:9000",
19+
region: process.env.AWS_REGION || "us-east-1",
1320
credentials: {
14-
accessKeyId: "minioadmin",
15-
secretAccessKey: "minioadmin",
21+
accessKeyId: process.env.AWS_ACCESS_KEY_ID || "minioadmin",
22+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY || "minioadmin",
1623
},
1724
forcePathStyle: true,
1825
};
@@ -189,13 +196,21 @@ function writeGethGenesisConfig(argv: any) {
189196

190197
type ChainInfo = {
191198
[key: string]: any;
192-
};
199+
}[];
193200

194-
// Define a function to return ChainInfo
195201
function getChainInfo(): ChainInfo {
196202
const filePath = path.join(consts.configpath, "l2_chain_info.json");
197-
const fileContents = fs.readFileSync(filePath).toString();
198-
const chainInfo: ChainInfo = JSON.parse(fileContents);
203+
let chainInfo: ChainInfo;
204+
try {
205+
const fileContents = fs.readFileSync(filePath).toString();
206+
chainInfo = JSON.parse(fileContents);
207+
} catch (e: any) {
208+
const action = (e instanceof SyntaxError) ? 'parse' : 'read';
209+
throw new Error(`Failed to ${action} chain info from ${filePath}: ${e.message}`);
210+
}
211+
if (!chainInfo || !Array.isArray(chainInfo) || chainInfo.length === 0) {
212+
throw new Error(`Invalid chain info: expected non-empty array in ${filePath}`);
213+
}
199214
return chainInfo;
200215
}
201216

@@ -406,7 +421,7 @@ function writeConfigs(argv: any) {
406421
sequencerConfig.node["delayed-sequencer"].enable = true
407422
if (argv.timeboost) {
408423
sequencerConfig.execution.sequencer.timeboost = {
409-
"enable": false, // Create it false initially, turn it on with sed in test-node.bash after contract setup.
424+
"enable": false, // Created false initially; enabled via jq in test-node.bash after auction contract deployment.
410425
"redis-url": argv.redisUrl
411426
};
412427
}
@@ -449,7 +464,7 @@ function writeConfigs(argv: any) {
449464
l3Config.node["batch-poster"]["redis-url"] = ""
450465
fs.writeFileSync(path.join(consts.configpath, "l3node_config.json"), JSON.stringify(l3Config))
451466

452-
let validationNodeConfig = JSON.parse(JSON.stringify({
467+
const validationNodeConfig = {
453468
"persistent": {
454469
"chain": "local"
455470
},
@@ -467,7 +482,7 @@ function writeConfigs(argv: any) {
467482
"jwtsecret": valJwtSecret,
468483
"addr": "0.0.0.0",
469484
},
470-
}))
485+
}
471486
fs.writeFileSync(path.join(consts.configpath, "validation_node_config.json"), JSON.stringify(validationNodeConfig))
472487
}
473488

@@ -882,6 +897,7 @@ export const initTxFilteringMinioCommand = {
882897
fs.writeFileSync(path.join(consts.configpath, "initial_address_hashes.json"), JSON.stringify(initialAddressList, null, 2));
883898
fs.writeFileSync(path.join(consts.configpath, "tx_filtering_salt.hex"), salt);
884899

900+
warnIfPartialS3Credentials();
885901
const s3Client = new S3Client(S3_CONFIG);
886902

887903
try {

0 commit comments

Comments
 (0)