Skip to content
Open
Changes from all 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
59 changes: 56 additions & 3 deletions docker_cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,65 @@ function reset_genesis() {
npm install

# 3. Clean up and reinstall Foundry framework components (Standard Library & Tests)
# Direct git clone instead of `forge install --no-git` because the latter
# fails inside a parent git repo with "not a git repository: ../../.git/modules/lib/ds-test"
# when forge tries to recursively init forge-std's ds-test submodule.
rm -rf lib/forge-std
forge install --no-git foundry-rs/forge-std@v1.7.3
git clone --depth 1 --branch v1.7.3 https://github.com/foundry-rs/forge-std.git lib/forge-std
cd lib/forge-std/lib
rm -rf ds-test
git clone https://github.com/dapphub/ds-test
}

# Patches genesis to disable fast-finality (Plato/Luban) for private chains with N>4 validators.
#
# Why: BSC's Plato hardfork (BLS fast finality) activates at block 7 in the default
# genesis-template. With >4 validators across a WAN, race conditions at chain start
# cause DoubleSign forks at block 2, and the fast-finality reorg path
# (consensus/parlia/snapshot.go:411) panics on misshapen attestation state.
#
# Empirically: 4 validators → works. 21 validators across 3 GCP regions → consistently
# panics at block 8-9. See https://github.com/bnb-chain/bsc/blob/master/consensus/parlia/snapshot.go
# Mainnet didn't bootstrap with Plato either; Plato activated years into a running chain.
#
# This function pushes Plato/Luban (and dependent Berlin/London/Hertz) past chain
# lifetime, plus disables time-based forks that depend on Plato. The chain runs
# vanilla Parlia (probabilistic finality, ~12-block depth).
#
# Toggle: set DISABLE_FAST_FINALITY=false in .env to keep upstream behavior (default 4-validator setup).
function patch_for_private_chain() {
if [ "${DISABLE_FAST_FINALITY:-true}" != "true" ]; then
echo "patch_for_private_chain: DISABLE_FAST_FINALITY=false, skipping"
return 0
fi

local TPL=${workspace}/genesis/genesis-template.json
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; }
Comment on lines +98 to +111
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.

echo "patch_for_private_chain: pushed Plato/Luban to block 100000000, disabled time-based post-Plato forks"

# Sort validators ascending by consensusAddr in extraData.
# Parlia's snapshot.validators() returns ascending-sorted; if extraData order
# differs, in-turn calculation goes to wrong validator → "unauthorized validator" at block 1.
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
Comment on lines +117 to +121
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.

}

# 4. Generate validator configurations, hardfork times, and the final genesis.json
function prepare_config() {
rm -f ${workspace}/genesis/validators.conf
Expand Down Expand Up @@ -369,8 +421,9 @@ prepare)
echo "Preparing Docker cluster configs..."
create_validator # Step 1: Prepare keys and .local/
prepare_bsc_client # Step 2: Build geth if necessary
reset_genesis # Step 3: Setup genesis deps (Forge, Poetry, etc)
prepare_config # Step 4: Generate genesis.json and node configs
reset_genesis # Step 3: Setup genesis deps (Forge, Poetry, etc)
patch_for_private_chain # Step 3b: Disable fast-finality for >4-validator chains (no-op if DISABLE_FAST_FINALITY=false)
prepare_config # Step 4: Generate genesis.json and node configs
initNetwork # Step 5: Initialize Geth data directories
patch_p2p_network # Step 6: Patch 127.0.0.1 in configs to docker DNS
generate_compose # Step 7: Generate .env.cluster and docker-compose
Expand Down