Skip to content

docker_cluster.sh: support N>4 validators on private chains#7

Open
Taranpreet26311 wants to merge 1 commit intomainfrom
private-chain-21-validators
Open

docker_cluster.sh: support N>4 validators on private chains#7
Taranpreet26311 wants to merge 1 commit intomainfrom
private-chain-21-validators

Conversation

@Taranpreet26311
Copy link
Copy Markdown

@Taranpreet26311 Taranpreet26311 commented May 5, 2026

Summary

Adds patch_for_private_chain() to docker_cluster.sh that:

  • Pushes Plato/Luban to block 100M (effectively disables fast-finality on private chains)
  • Pushes Berlin/London/Hertz to maintain fork ordering
  • Sets bohrTime/pascalTime/lorentzTime/maxwellTime/fermiTime to null
  • Sorts validators by consensusAddr ascending in validators.template

Also replaces flaky forge install --no-git with a direct git clone of forge-std.

Why

The default node-deploy setup is tested at 4 validators. For private chains with N > 4 validators across a WAN (e.g. 21 validators across multiple GCP regions), the chain consistently panics at block 8-9.

The cascade

  1. With many validators starting near-simultaneously, network RTT (100-300ms cross-region) + Parlia's out-of-turn delay produces DoubleSign at block 2 — multiple validators each produce a competing block 2.
  2. Fast finality (Plato, activates at block 7 in the default genesis-template) reads attestations on imports.
  3. The reorg path (ReorgNeededWithFastFinalityparlia/snapshot.go:411) panics on voteAddrs[idx] index out-of-range when the snapshot apply path encounters DoubleSigned headers.
  4. Validators crash-loop, chain stuck at block 9.

The validator sort fix

Parlia's snapshot.validators() returns the validator set sorted-ascending. The original validators.template's extraDataSerialize emitted them in insertion order. Result: in-turn calculation picks a different validator than the one whose key is in keystore[0] → block 1 fails with unauthorized validator. This was only visible after pushing Luban back, because pre-Luban Parlia uses extraData order more directly.

The forge install fix

forge install --no-git foundry-rs/forge-std@v1.7.3 fails inside a parent directory that's a git repo (the genesis submodule's .git is a redirect file pointing into the parent's .git/modules/). Forge's recursive ds-test init can't resolve the path. Direct git clone works reliably.

Test plan

  • Run bash docker_cluster.sh prepare with DISABLE_FAST_FINALITY=true (default) and 21 validators
  • Verify generated genesis.json has lubanBlock: 100000000 and platoBlock: 100000000
  • Start 21 validators in tight sync (within ~5s); confirm chain advances past block 9
  • Run with DISABLE_FAST_FINALITY=false and 4 validators; confirm upstream behavior is preserved (lubanBlock: 6, fast finality active)

Notes

  • Default for DISABLE_FAST_FINALITY is true since the canonical >4-validator path is via StakeHub registration on a running chain (mainnet pattern). Setting this to false preserves the upstream 4-validator-with-fast-finality config.
  • Mainnet itself didn't bootstrap 21 validators with Plato — Plato activated on a chain that had been running for years. Disabling Plato on private chains with N > 4 validators reflects this; it's not a "downgrade", it's the right config for testnets that aren't bootstrapped from a long-lived chain.
  • The contract patches in BSCValidatorSet.sol (turnLength = 16, if (block.number < 2000) return;) remain unchanged.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores
    • Improved private chain initialization: Enhanced dependency installation and genesis configuration patching. Fork block numbers and timestamps now automatically adjust based on finality settings for optimized local testing. The configuration preparation sequence now includes genesis patching before generation, ensuring correct setup ordering and consistency across environments.

Add patch_for_private_chain() that pushes Plato/Luban to block 100M and
disables post-Plato time-based forks for genesis. Required for private
chains with more validators than the default 4-validator setup, where
fast-finality (BEP-126) panics during reorg at parlia/snapshot.go:411
when DoubleSign forks occur at block 2 due to multi-validator startup
race conditions on a WAN.

Also sort validators by consensusAddr ascending in validators.template
so genesis extraData matches Parlia's snapshot.validators() ordering
(otherwise block 1 sealing fails with "unauthorized validator" because
in-turn calculation uses sorted-ascending while extraData was unsorted).

Replace forge install --no-git with direct git clone of forge-std,
because forge install fails when the parent directory is itself a git
repo (submodule path lookup fails for forge-std's ds-test).

Toggle: DISABLE_FAST_FINALITY=true (default) applies the patches.
Set DISABLE_FAST_FINALITY=false in .env to preserve upstream 4-validator
behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

This pull request modifies the docker_cluster.sh script to change how Foundry dependencies are installed and adds genesis configuration patching functionality. The reset_genesis function now uses direct git clone commands instead of forge install to obtain forge-std (v1.7.3) and ds-test. A new patch_for_private_chain function is introduced that conditionally modifies genesis templates and validator scripts based on the DISABLE_FAST_FINALITY environment variable, applying fork block adjustments and validator sorting logic. The prepare command sequence is updated to invoke this new patching function between reset_genesis and prepare_config.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Reasoning: Single-file change with moderate complexity. Requires careful review of sed pattern correctness, conditional branching logic based on environment variables, proper directory removal/cloning sequencing, and idempotency safeguards (e.g., "SORT_PATCHED" guard). The changes introduce new functional logic but remain contained within shell scripting operations without complex multi-component interactions.

🚥 Pre-merge checks | ✅ 8 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title does not follow the required format: missing the commit-style type prefix (feat/fix/refactor/docs/test/chore) before the domain. Reformat the title as 'feat(docker_cluster.sh): support N>4 validators on private chains' or use an appropriate type (fix/chore) if the change is a bug fix or maintenance task.
Security Considerations ⚠️ Warning Unquoted variables $TPL (lines 98-111) and $VT (lines 118-119) in sed/grep commands create command injection risk via word-splitting. Hardcoded test credentials (KEYPASS) in .env. Quote variables: $TPL→"$TPL", $VT→"$VT" in sed/grep commands. Secure or document KEYPASS in .env as dev-only.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Go Build And Test Rationale ✅ Passed PR modifies only docker_cluster.sh (bash script). No Go packages were affected; check is not applicable.
Concurrency Safety ✅ Passed PR modifies only bash script. No Go concurrency code changed. Script edits JSON configs via sed. No goroutines, channels, or racy patterns introduced.
Public Api Changes ✅ Passed docker_cluster.sh is a new file, not a modification of existing public APIs. No breaking changes to exported types/functions. New DISABLE_FAST_FINALITY env var is additive, not breaking.
Rust Best Practices ✅ Passed PR modifies only shell scripts and configuration files. No Rust code exists in repository. Rust best practices check is not applicable to shell/config-only changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch private-chain-21-validators

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docker_cluster.sh`:
- Around line 117-121: The script currently uses an unquoted $VT and a fragile
sed insertion into validators.template targeting the literal "function
extraDataSerialize(validators) {"; fix by quoting the variable references (use
"$VT") and replace the brittle sed-based injection with a more robust patch
strategy: either apply a separate patch file (e.g., validators.template.patch)
and use patch or git apply, or perform the transformation with a small Node or
jq script that locates the extraDataSerialize function (by name) and inserts the
"// SORT_PATCHED" + sort line into its body; ensure you still check for the "//
SORT_PATCHED" marker before patching so the idempotent check using "$VT" and the
marker remains.
- Around line 98-111: The sed/grep commands use an unquoted variable $TPL
(risking globbing/word-splitting) and only verify the "lubanBlock" replacement,
allowing other sed failures to go unnoticed; update all sed and grep invocations
to double-quote "$TPL" and add at least one more verification grep (e.g., check
for '"platoBlock": 100000000') after the sed sequence so failures in sed lines
for "platoBlock" (and similar keys like "berlinBlock", "londonBlock", etc.) are
detected and cause a non-zero exit; locate the sed calls and the existing grep
check in the docker_cluster.sh patch_for_private_chain section and apply these
changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: getoptimum/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b62211b9-f835-4ef5-b882-ff9e095a6b91

📥 Commits

Reviewing files that changed from the base of the PR and between e5ca839 and b4e50ea.

📒 Files selected for processing (1)
  • docker_cluster.sh

Comment thread docker_cluster.sh
Comment on lines +98 to +111
sed -i 's/"lubanBlock": 6,/"lubanBlock": 100000000,/' $TPL
sed -i 's/"platoBlock": 7,/"platoBlock": 100000000,/' $TPL
sed -i 's/"berlinBlock": 8,/"berlinBlock": 100000001,/' $TPL
sed -i 's/"londonBlock": 8,/"londonBlock": 100000001,/' $TPL
sed -i 's/"hertzBlock": 8,/"hertzBlock": 100000002,/' $TPL
sed -i 's/"hertzfixBlock": 8,/"hertzfixBlock": 100000002,/' $TPL
sed -i 's/"bohrTime": 0,/"bohrTime": null,/' $TPL
sed -i 's/"pascalTime": 0,/"pascalTime": null,/' $TPL
sed -i 's/"pragueTime": 0,/"pragueTime": null,/' $TPL
sed -i 's/"lorentzTime": 0,/"lorentzTime": null,/' $TPL
sed -i 's/"maxwellTime": 0,/"maxwellTime": null,/' $TPL
sed -i 's/"fermiTime": 0,/"fermiTime": null,/' $TPL
sed -i 's/"haberFixTime": 0,/"haberFixTime": null,/' $TPL
grep -q '"lubanBlock": 100000000' $TPL || { echo "patch_for_private_chain: luban patch failed" >&2; exit 1; }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Quote variables and verify more patches to catch silent failures.

Two issues:

  1. $TPL is unquoted in all sed/grep commands. Per shellcheck SC2086, double-quote to prevent globbing/word splitting.

  2. Only lubanBlock is verified (line 111). If any other sed pattern doesn't match (e.g., different whitespace in template), the patch silently fails. At minimum, verify platoBlock since it's the primary fast-finality gate.

🛠️ Proposed fix
-    sed -i 's/"lubanBlock": 6,/"lubanBlock": 100000000,/'                $TPL
-    sed -i 's/"platoBlock": 7,/"platoBlock": 100000000,/'               $TPL
-    sed -i 's/"berlinBlock": 8,/"berlinBlock": 100000001,/'             $TPL
-    sed -i 's/"londonBlock": 8,/"londonBlock": 100000001,/'             $TPL
-    sed -i 's/"hertzBlock": 8,/"hertzBlock": 100000002,/'               $TPL
-    sed -i 's/"hertzfixBlock": 8,/"hertzfixBlock": 100000002,/'         $TPL
-    sed -i 's/"bohrTime": 0,/"bohrTime": null,/'                       $TPL
-    sed -i 's/"pascalTime": 0,/"pascalTime": null,/'                   $TPL
-    sed -i 's/"pragueTime": 0,/"pragueTime": null,/'                   $TPL
-    sed -i 's/"lorentzTime": 0,/"lorentzTime": null,/'                 $TPL
-    sed -i 's/"maxwellTime": 0,/"maxwellTime": null,/'                 $TPL
-    sed -i 's/"fermiTime": 0,/"fermiTime": null,/'                     $TPL
-    sed -i 's/"haberFixTime": 0,/"haberFixTime": null,/'               $TPL
-    grep -q '"lubanBlock": 100000000' $TPL || { echo "patch_for_private_chain: luban patch failed" >&2; exit 1; }
+    sed -i 's/"lubanBlock": 6,/"lubanBlock": 100000000,/'                "$TPL"
+    sed -i 's/"platoBlock": 7,/"platoBlock": 100000000,/'               "$TPL"
+    sed -i 's/"berlinBlock": 8,/"berlinBlock": 100000001,/'             "$TPL"
+    sed -i 's/"londonBlock": 8,/"londonBlock": 100000001,/'             "$TPL"
+    sed -i 's/"hertzBlock": 8,/"hertzBlock": 100000002,/'               "$TPL"
+    sed -i 's/"hertzfixBlock": 8,/"hertzfixBlock": 100000002,/'         "$TPL"
+    sed -i 's/"bohrTime": 0,/"bohrTime": null,/'                       "$TPL"
+    sed -i 's/"pascalTime": 0,/"pascalTime": null,/'                   "$TPL"
+    sed -i 's/"pragueTime": 0,/"pragueTime": null,/'                   "$TPL"
+    sed -i 's/"lorentzTime": 0,/"lorentzTime": null,/'                 "$TPL"
+    sed -i 's/"maxwellTime": 0,/"maxwellTime": null,/'                 "$TPL"
+    sed -i 's/"fermiTime": 0,/"fermiTime": null,/'                     "$TPL"
+    sed -i 's/"haberFixTime": 0,/"haberFixTime": null,/'               "$TPL"
+    grep -q '"lubanBlock": 100000000' "$TPL" || { echo "patch_for_private_chain: luban patch failed" >&2; exit 1; }
+    grep -q '"platoBlock": 100000000' "$TPL" || { echo "patch_for_private_chain: plato patch failed" >&2; exit 1; }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sed -i 's/"lubanBlock": 6,/"lubanBlock": 100000000,/' $TPL
sed -i 's/"platoBlock": 7,/"platoBlock": 100000000,/' $TPL
sed -i 's/"berlinBlock": 8,/"berlinBlock": 100000001,/' $TPL
sed -i 's/"londonBlock": 8,/"londonBlock": 100000001,/' $TPL
sed -i 's/"hertzBlock": 8,/"hertzBlock": 100000002,/' $TPL
sed -i 's/"hertzfixBlock": 8,/"hertzfixBlock": 100000002,/' $TPL
sed -i 's/"bohrTime": 0,/"bohrTime": null,/' $TPL
sed -i 's/"pascalTime": 0,/"pascalTime": null,/' $TPL
sed -i 's/"pragueTime": 0,/"pragueTime": null,/' $TPL
sed -i 's/"lorentzTime": 0,/"lorentzTime": null,/' $TPL
sed -i 's/"maxwellTime": 0,/"maxwellTime": null,/' $TPL
sed -i 's/"fermiTime": 0,/"fermiTime": null,/' $TPL
sed -i 's/"haberFixTime": 0,/"haberFixTime": null,/' $TPL
grep -q '"lubanBlock": 100000000' $TPL || { echo "patch_for_private_chain: luban patch failed" >&2; exit 1; }
sed -i 's/"lubanBlock": 6,/"lubanBlock": 100000000,/' "$TPL"
sed -i 's/"platoBlock": 7,/"platoBlock": 100000000,/' "$TPL"
sed -i 's/"berlinBlock": 8,/"berlinBlock": 100000001,/' "$TPL"
sed -i 's/"londonBlock": 8,/"londonBlock": 100000001,/' "$TPL"
sed -i 's/"hertzBlock": 8,/"hertzBlock": 100000002,/' "$TPL"
sed -i 's/"hertzfixBlock": 8,/"hertzfixBlock": 100000002,/' "$TPL"
sed -i 's/"bohrTime": 0,/"bohrTime": null,/' "$TPL"
sed -i 's/"pascalTime": 0,/"pascalTime": null,/' "$TPL"
sed -i 's/"pragueTime": 0,/"pragueTime": null,/' "$TPL"
sed -i 's/"lorentzTime": 0,/"lorentzTime": null,/' "$TPL"
sed -i 's/"maxwellTime": 0,/"maxwellTime": null,/' "$TPL"
sed -i 's/"fermiTime": 0,/"fermiTime": null,/' "$TPL"
sed -i 's/"haberFixTime": 0,/"haberFixTime": null,/' "$TPL"
grep -q '"lubanBlock": 100000000' "$TPL" || { echo "patch_for_private_chain: luban patch failed" >&2; exit 1; }
grep -q '"platoBlock": 100000000' "$TPL" || { echo "patch_for_private_chain: plato patch failed" >&2; exit 1; }
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 98-98: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 99-99: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 100-100: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 101-101: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 102-102: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 103-103: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 104-104: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 105-105: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 106-106: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 107-107: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 108-108: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 109-109: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 110-110: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 111-111: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker_cluster.sh` around lines 98 - 111, The sed/grep commands use an
unquoted variable $TPL (risking globbing/word-splitting) and only verify the
"lubanBlock" replacement, allowing other sed failures to go unnoticed; update
all sed and grep invocations to double-quote "$TPL" and add at least one more
verification grep (e.g., check for '"platoBlock": 100000000') after the sed
sequence so failures in sed lines for "platoBlock" (and similar keys like
"berlinBlock", "londonBlock", etc.) are detected and cause a non-zero exit;
locate the sed calls and the existing grep check in the docker_cluster.sh
patch_for_private_chain section and apply these changes.

Comment thread docker_cluster.sh
Comment on lines +117 to +121
local VT=${workspace}/genesis/scripts/validators.template
if ! grep -q "// SORT_PATCHED" $VT; then
sed -i 's|function extraDataSerialize(validators) {|function extraDataSerialize(validators) {\n // SORT_PATCHED\n validators = validators.slice().sort((a,b) => a.consensusAddr.toLowerCase().localeCompare(b.consensusAddr.toLowerCase()));|' $VT
echo "patch_for_private_chain: validators.template now sorts by consensusAddr"
fi
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Quote $VT and consider more robust patching approach.

Same SC2086 issue: $VT unquoted on lines 118-119.

The sed-based JavaScript injection on line 119 is fragile—it depends on exact function signature formatting. If the template changes (e.g., different whitespace, renamed function), the patch silently fails and validators remain unsorted, causing "unauthorized validator" at block 1.

Consider using a dedicated patch file or jq/node-based approach for more robust patching.

♻️ Minimal fix: quote variables
-    if ! grep -q "// SORT_PATCHED" $VT; then
-        sed -i 's|function extraDataSerialize(validators) {|function extraDataSerialize(validators) {\n  // SORT_PATCHED\n  validators = validators.slice().sort((a,b) => a.consensusAddr.toLowerCase().localeCompare(b.consensusAddr.toLowerCase()));|' $VT
+    if ! grep -q "// SORT_PATCHED" "$VT"; then
+        sed -i 's|function extraDataSerialize(validators) {|function extraDataSerialize(validators) {\n  // SORT_PATCHED\n  validators = validators.slice().sort((a,b) => a.consensusAddr.toLowerCase().localeCompare(b.consensusAddr.toLowerCase()));|' "$VT"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
local VT=${workspace}/genesis/scripts/validators.template
if ! grep -q "// SORT_PATCHED" $VT; then
sed -i 's|function extraDataSerialize(validators) {|function extraDataSerialize(validators) {\n // SORT_PATCHED\n validators = validators.slice().sort((a,b) => a.consensusAddr.toLowerCase().localeCompare(b.consensusAddr.toLowerCase()));|' $VT
echo "patch_for_private_chain: validators.template now sorts by consensusAddr"
fi
local VT=${workspace}/genesis/scripts/validators.template
if ! grep -q "// SORT_PATCHED" "$VT"; then
sed -i 's|function extraDataSerialize(validators) {|function extraDataSerialize(validators) {\n // SORT_PATCHED\n validators = validators.slice().sort((a,b) => a.consensusAddr.toLowerCase().localeCompare(b.consensusAddr.toLowerCase()));|' "$VT"
echo "patch_for_private_chain: validators.template now sorts by consensusAddr"
fi
🧰 Tools
🪛 Shellcheck (0.11.0)

[info] 118-118: Double quote to prevent globbing and word splitting.

(SC2086)


[info] 119-119: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker_cluster.sh` around lines 117 - 121, The script currently uses an
unquoted $VT and a fragile sed insertion into validators.template targeting the
literal "function extraDataSerialize(validators) {"; fix by quoting the variable
references (use "$VT") and replace the brittle sed-based injection with a more
robust patch strategy: either apply a separate patch file (e.g.,
validators.template.patch) and use patch or git apply, or perform the
transformation with a small Node or jq script that locates the
extraDataSerialize function (by name) and inserts the "// SORT_PATCHED" + sort
line into its body; ensure you still check for the "// SORT_PATCHED" marker
before patching so the idempotent check using "$VT" and the marker remains.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant