Compare commits

..

40 Commits

Author SHA1 Message Date
529a91f0d7 Merge pull request 'show-status: exit code was always 0 — three compounding bugs' (#66) from fix-show-status-exit-code into main 2026-08-28 05:25:11 +00:00
3478d286f6 reload_dshackle.sh: make gateway reconnect conditional on upstream/disabled-method removals
Operator ruling: NEVER force a reconnect on a routine reload/rpc-update run.
Only upstream REMOVALS or new disabled-method ADDITIONS require a gateway
reconnect to be seen by dRPC gateways.

Implement snapshot-based comparison:
- Before SIGHUP, read current advertised set (upstream IDs + disabled methods)
- Compare against previous snapshot (if exists)
- Drop established gRPC connections ONLY IF:
  * an upstream ID disappeared, OR
  * a new disabled-method entry appeared
- Pure additions or no change: reload only, no drop
- First run with no prior snapshot: do NOT drop, just write snapshot
- Always rewrite snapshot after successful reload
- All operations best-effort (failures never fail the script)

Live-measured: upstream/method additions propagate over existing connection
in ~16s (no reconnect needed).

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-27 03:28:32 +00:00
8f8ac427d3 kite: fix staking port mapping to use fixed container port 9651
Fix AVAGO_STAKING_PORT to use fixed port 9651 instead of salted client_port.
Fix port mapping from {{ client_port }}:{{ client_port }} to {{ client_port }}:9651
to ensure container uses fixed P2P port while host binds to salted port.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-27 03:23:29 +00:00
81c9addac9 reload_dshackle.sh: drop established gRPC connections after successful SIGHUP reload
Extend reload_dshackle.sh to drop established gateway gRPC connections on
port 2449 after a successful SIGHUP config reload, forcing the dRPC edge
to reconnect within ~1s and re-read the advertised chain/method list.
Gateways only re-initialize advertisements on reconnection (verified live:
0.03-0.06s serving gap, 33 re-probes in 4s). This allows deploy-time upstream
additions (new chains like mova, new backends like us-50) to be seen by
dRPC immediately rather than waiting for natural reconnection.

The TCP-level drop is best-effort: failure does NOT fail the script.
Operator ruling: this stays a TCP-level drop, NOT a dshackle code change.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-27 03:14:47 +00:00
37a26d8791 mova: add mainnet-v2 geth profile (pruned, archive)
Add MOVA Mainnet v2 node profile with geth client and std_pruned_archive
profiles (pebble/path pruned, leveldb/hash archive). Chain ID 61901.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-27 03:10:45 +00:00
783435dc20 Raise cronos feeHistory cap from 100 to 250
- Add FEEHISTORY_CAP env tunable with default 250
- Update init.sh to use FEEHISTORY_CAP for feehistory-cap in app.toml
- Maintain backward compatibility with JSON_RPC_FEEHISTORY_CAP
- Apply to both mainnet and testnet profiles

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-27 02:46:48 +00:00
48302a4270 abcore: fix version interpolation and state scheme
- Use canonical clients version substitution format:
  ${SERVICE_CLIENT_VERSION:-version} instead of ${SERVICE_GETH_VERSION:-version}
- Change profile from pruned-pebble-path to pruned-pebble-hash
- Remove --state.scheme=path from run command (geth v1.13 cannot write path scheme)
- Remove --state.scheme=path from init.sh geth init command
- Keep --db.engine=pebble and --config=/config/config.toml

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-26 03:35:23 +00:00
d5e3068e7f Fix abcore init: add --db.engine=pebble --state.scheme=path to geth init
Matches the run profile flags to prevent 'incompatible state scheme' crash
on fresh datadir initialization (stored: hash, provided: path).

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-26 03:27:25 +00:00
5182ab6bd4 Fix abcore mainnet: wire client_version to build arg and use equals form for --config flag
- Fix ABCORE_MAINNET_GETH_VERSION default from empty to v1.13.15-abcore-1.2
- Fix command flag from '--config /config/config.toml' to '--config=/config/config.toml'

Both issues caused first-deploy failures on rpc-de-32:
1. Empty version default caused curl exit 22 on binary download
2. Space-separated flag/value caused geth crash-loop with 'flag provided but not defined: -config'

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-26 03:14:14 +00:00
20e2c1a66e Merge pull request 'haqq: fix init.sh addrbook bootstrap bug' (#50) from issue-2601 into main 2026-08-26 02:45:56 +00:00
bedd7a32aa Add abcore mainnet node profile (chain-id 36888)
- Add abcore.Dockerfile for geth-fork binary download from ABFoundationGlobal/abcore
- Add scripts/init.sh for geth initialization with genesis.json and config.toml
- Add generated compose file abcore-mainnet-geth-pruned-pebble-path.yml
- Update compose_registry.json with new entry

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-26 02:44:57 +00:00
206fc78735 Merge branch 'main' into issue-2601 2026-08-26 02:42:30 +00:00
20511cd247 Merge pull request 'abcore mainnet: add geth-fork chain-id 36888' (#56) from issue-3079 into main 2026-08-26 02:35:13 +00:00
42bc7f504e avalanche fuji: enable C-chain state-sync on pruned profile (mirrors #48 for mainnet; full block execution was 55h+ ETA on the de-22 prune-cycle test node) 2026-08-26 01:50:29 +00:00
fed15a5484 Merge pull request 'avalanche: enable state-sync on C-chain pruned profile' (#48) from issue-2307 into main 2026-08-26 01:49:58 +00:00
ba44def37b fix zero-gravity doubled-v: add VERSION normalization to extraction RUN step
The previous commit (7cbd0532) added VERSION normalization to the DOWNLOAD
RUN step but forgot to add it to the EXTRACTION RUN step. Each Dockerfile
RUN is a fresh shell, so the extraction step fails with 'VERSION: unbound
variable' when it tries to use ${VERSION} in the SRC variable.

Fix: add VERSION="${ZERO_GRAVITY_VERSION#v}"; to the extraction RUN step
(line 73) so it defines VERSION before using it.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-25 09:14:25 +00:00
0071f41e4c tac: update mainnet peer list with verified live peers (2026-08-25) 2026-08-25 05:35:26 +00:00
7cbd053236 fix zero-gravity doubled-v bug in Dockerfile asset names
Remove leading 'v' from ZERO_GRAVITY_VERSION values in context.yml
(1.0.6, 3.0.8 instead of v1.0.6, v3.0.8) and add defensive stripping in
zerog.Dockerfile (VERSION=${ZERO_GRAVITY_VERSION#v}) to prevent
asset names like aristotle-vv1.0.6.tar.gz / galileo-vv3.0.8.tar.gz.

The asset filename is always <spec>-v<version>.tar.gz, and the Dockerfile
was adding a 'v' prefix to versions that already had one.

Verified URLs resolve:
- aristotle: https://github.com/0gfoundation/0gchain-Aristotle/releases/download/v1.0.6/aristotle-v1.0.6.tar.gz
- galileo: https://github.com/0gfoundation/0gchain-NG/releases/download/v3.0.8/galileo-v3.0.8.tar.gz

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-25 04:36:36 +00:00
6979290310 tac: fix malformed sed address ranges in init.sh
Fix four sed address ranges that were missing closing slash before comma:
- /^\[rpc\],/^\[/ -> /^\[rpc\]/,/^\[/
- /^\[json-rpc\],/^\[/ -> /^\[json-rpc\]/,/^\[/ (3 occurrences)

The malformed patterns caused sed to crash-loop the TAC container.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-25 04:08:06 +00:00
0e6c2a3bc7 zero-gravity: bump zerog node versions aristotle 1.0.6->v1.0.6, galileo 3.0.8->v3.0.8
Match upstream release tags: aristotle v1.0.6 (0gchain-Aristotle) and
galileo v3.0.8 (0gchain-NG) both carry v-prefix in their release tags.
Required for 0g hardfork; old clients stuck-syncing.

Supersedes stale unmergeable PR ethereum-rpc-docker#62.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-25 04:03:35 +00:00
Mistral Vibe
7cab80da70 avalanche: bump fuji avalanchego version to v1.15.0-fuji
Fuji validators run the v1.15.0-fuji prerelease and refuse v1.14.2 peers
('peer attempting to connect with newer version'), so fuji nodes cannot
bootstrap on the stable version.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-25 03:51:42 +00:00
fc9f82ec48 tac: fix crash-loop by installing GNU sed for sed command compatibility
The TAC mainnet container was crash-looping with 'sed: unsupported command ^'
because busybox sed in Alpine does not support the GNU sed extensions used in
the cometbft-common.sh scripts (address ranges with ^ anchor and {} grouping).

Adding the 'sed' package installs GNU sed which supports these features.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-25 03:40:27 +00:00
b93ad15f52 avalanche: add mainnet offline-prune config matching fuji pattern
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-24 05:31:21 +00:00
cff52429bd tac: add TAC mainnet archive profile with EVM JSON-RPC upstream
- Add archive profile (pruning=nothing) for tacchaind
- Change client_rpc_port from 26657 (CometBFT) to 8545 (EVM JSON-RPC)
- Update Dockerfile to download pre-built binary from GitHub releases v1.6.0
- Verify binary against checksums.txt
- Update init.sh to support PRUNING env var (nothing for archive, default for pruned)
- Update peers from NETWORKS.md
- Add archive snapshot URL note
- Traefik routes to EVM JSON-RPC port 8545 for both pruned and archive

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-08-23 10:55:39 +00:00
434717b194 deploy: regenerate compose from vibe-node main a86fd1cc5923 2026-08-20 17:16:40 +00:00
25b764684d deploy: regenerate compose from vibe-node main 57aeb4d4424f 2026-08-20 04:01:57 +00:00
502a75b2fd show-status: exit code was always 0 — three compounding bugs
show-status.sh could never report failure. The ansible task that wraps it
('Execute show-status.sh and fail on failure') therefore always passed, on every
host, regardless of node state. Three bugs, each masking the next:

1. $? read too late. `code=0` sits between the sync-status.sh call and
   `if [ $? -ne 0 ]`. A plain assignment succeeds and overwrites $? with 0, so
   the condition was ALWAYS false and the else branch always taken.

2. The else branch was inverted. It is the sync-status-SUCCEEDED path, yet it set
   `code=1; any_failure=true` — marking healthy nodes as failures.

3. any_failure could never propagate. check_sync_status runs backgrounded (`&`),
   i.e. in a subshell, so `any_failure=true` inside it is discarded; and the
   `wait "$pid"` loop threw away each job's exit status.

(3) hid (1) and (2): a script that believed every node had failed still exited 0,
so nobody saw it.

Fix: capture rc immediately; restore the intended logic (success => 0, syncing or
lagging => tolerated, anything else => failure); propagate failure in the PARENT
via `wait "$pid" || any_failure=true`, since the subshell cannot.

Verified with a stubbed sync-status.sh:

  scenario              before   after
  all online              0        0
  one syncing             0        0   (tolerated)
  one lagging             0        0   (tolerated)
  one ERROR               0        1
  ALL error               0        1

Behaviour for healthy fleets is unchanged; only genuine failures now surface.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-14 19:56:10 +00:00
b92fb0ecb3 deploy: regenerate compose from vibe-node main a1ebdcbaffd4 2026-08-13 08:08:13 +00:00
92cdb31d17 zero-gravity: init guard must check the CONFIG volume, not a data-volume proxy
aristotle @ rpc-de-32 crash-looped after its build was fixed:

    priv_validator_state.json found in /root/.0g. Continuing!
    Already initialized, continuing!
    panic: ProvideBlsSigner (node-core/components/signer.go:46):
           key file does not exist at path: /root/.0g/config/priv_validator_key.json

$DATA_DIR and $CONFIG_DIR are SEPARATE docker volumes (<node>_zerog and
<node>_config), but the guard tested only $DATA_DIR/priv_validator_state.json —
inferring the state of the config volume from a file on the data volume. Worse, the
copy order wrote that guard file BEFORE the two key files, so an init interrupted
between them left the data volume with the guard and the config volume without the
keys. Every later start then took the 'Already initialized' branch, copied nothing,
and panicked. Unrecoverable by restarting: the node wedges permanently. aristotle
got there via the repeated build failures earlier today.

Fix, two parts:
  - guard on everything 0gchaind needs to boot (both config keys AND the data state
    file), not one proxy file on the wrong volume;
  - write the keys FIRST and the guard file LAST, so an interrupted init re-runs on
    the next start instead of latching into the wedged state.

Tested locally against the real 0gchaind binary (aristotle v1.0.6), sandboxed HOME:

  scenario                     before            after
  fresh volumes                keys created      keys created        (no regression)
  wedged (state, no keys)      keys MISSING      keys created        (unwedges)
  partial (state+node_key)     keys MISSING      keys created
  healthy (all present)        no re-init        no re-init, and a SENTINEL written
                                                 into priv_validator_key.json survives
                                                 -> never clobbers an initialized node

The 'wedged' row reproduces aristotle's production failure exactly.

Note this regenerates node identity on an affected node (node_key.json,
priv_validator_key.json). These are RPC nodes that never sign, so a fresh identity is
harmless; chain data on the _zerog volume is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 19:33:48 +00:00
4a060bee0d deploy: regenerate compose from vibe-node main 3c848087f5c8 2026-08-12 17:37:46 +00:00
4db13b2fc4 deploy: regenerate compose from vibe-node main 0415567d141c 2026-08-12 16:53:00 +00:00
6df9f0f17a zero-gravity: detect tarball layout; galileo 3.0.8 moved binaries to a shared root bin/
Fixes the de-32 build break introduced by vibe-node #2912 (galileo 3.0.3 -> 3.0.8,
merged as bc4cecf). Job ad580587:

    #16 chmod: cannot access '/0g/bin/0gchaind': No such file or directory

The Dockerfile hardcoded 'galileo -> mv <root>/rpc /0g'. That held for 3.0.3, where
every profile dir carried its own copy of the binaries. 3.0.8 DEDUPLICATED them into
a shared <root>/bin/ (168 MB -> 69 MB), leaving rpc/ as configs-only — so the move
produced configs and no binaries, and the build died two layers later on a chmod.

Note 'does <root>/rpc exist' is not a sufficient test: 3.0.8 still ships rpc/, it just
no longer holds bin/. Verified layouts:

  galileo 3.0.3     <root>/{rpc,validator,archive,seed}/bin/ + configs
  galileo 3.0.8     <root>/bin/ shared + <root>/{rpc,...}/ configs
  aristotle 1.0.6   <root>/bin/ + configs, no profile dirs

So: take configs from the profile dir when present, then top the binaries up from the
shared root bin/ if the profile dir did not supply them.

Also asserts both binaries exist immediately after extraction, naming the chain, the
version and the known layouts — so the next upstream reshuffle reports itself at the
point of failure rather than as an anonymous chmod error.

Verified against the real artifacts — all three now yield an identical /0g
(bin/0gchaind 76M, bin/geth 46M, geth-config.toml, geth-genesis.json,
kzg-trusted-setup.json, 0g-home/, rollback_cl.sh), and a synthetic unknown layout
exits 1 with the diagnostic instead of reaching chmod.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 11:09:51 +00:00
561ff43828 deploy: regenerate compose from vibe-node main bc4cecf1a74f 2026-08-12 08:38:04 +00:00
cfac32736e zero-gravity: resolve release tag by probing known forms; fail loudly on 404
0G publishes aristotle and galileo from two different repos, and neither uses a
consistent release-tag format. The asset filename is always <spec>-v<version>.tar.gz;
only the tag varies:

  aristotle -> 0gchain-Aristotle   1.0.4 = "1.0.4"    1.0.6 = "v1.0.6"
  galileo   -> 0gchain-NG          3.0.3 = "v3.0.3"   3.0.7 = "galileo-v3.0.7"   3.0.8 = "v3.0.8"

The hardcoded formats meant aristotle could not go past 1.0.4 and galileo could not
reach 3.0.7. Resolve the tag by trying the known forms in order instead.

Also switch curl -sL to -fsSL. Without -f a 404 HTML body was written into the
.tar.gz and the build failed several layers later at 'tar: not in gzip format',
an error pointing nowhere near the cause. That has now caused three separate
misdiagnoses, including a de-32 build blocker.

Verified end-to-end against the real artifacts: aristotle 1.0.4/1.0.6 and galileo
3.0.3/3.0.7/3.0.8 all resolve; aristotle 3.0.3 and galileo 9.9.9 fail immediately
with an actionable message.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 06:21:38 +00:00
799209b5d2 zero-gravity-aristotle: fix node version to 1.0.4 (was 3.0.3 which doesn't exist for Aristotle repo) 2026-08-10 21:56:44 +00:00
477e969e54 abcore: add mainnet genesis.json and config.toml for chain-id 36888 2026-08-09 00:19:29 +00:00
b0efb236bf Add abcore mainnet genesis.json (chain-id 36888, Newton) 2026-08-08 22:47:51 +00:00
bfe5bdd97d haqq: add required DTEAM and allnodes.me seeds to bootstrap config 2026-08-04 12:04:21 +00:00
223c694085 haqq: fix bootstrap config - correct addrbook filename, add Polkachu seeds, persistent peers, auto-refresh from Polkachu when empty 2026-08-04 10:28:18 +00:00
7a3003cb63 avalanche: enable state-sync on C-chain pruned profile 2026-08-01 00:17:37 +00:00
55 changed files with 1609 additions and 158 deletions

25
abcore/abcore.Dockerfile Normal file
View File

@@ -0,0 +1,25 @@
FROM rockylinux:9
COPY ./scripts/init.sh /usr/local/bin/init.sh
RUN chmod +x /usr/local/bin/init.sh
ARG ABCORE_MAINNET_GETH_VERSION
# Download and verify the geth binary from GitHub releases
RUN set -eu; \
VERSION="${ABCORE_MAINNET_GETH_VERSION}"; \
BINARY="geth-${VERSION}"; \
URL="https://github.com/ABFoundationGlobal/abcore/releases/download/${VERSION}/${BINARY}"; \
SHA256_URL="${URL}.sha256"; \
echo "abcore: downloading geth binary from ${URL}"; \
curl -fsSL "${URL}" -o "/tmp/${BINARY}"; \
echo "abcore: downloading sha256 checksum from ${SHA256_URL}"; \
curl -fsSL "${SHA256_URL}" -o "/tmp/${BINARY}.sha256"; \
cd /tmp; \
sha256sum -c "${BINARY}.sha256" || (echo "abcore: SHA256 verification failed for ${BINARY}" >&2 && exit 1); \
chmod +x "${BINARY}"; \
mkdir -p /abcore/bin; \
mv "${BINARY}" /abcore/bin/geth; \
rm -f "${BINARY}" "${BINARY}.sha256"
ENTRYPOINT [ "init.sh" ]

View File

@@ -0,0 +1,151 @@
---
x-logging-defaults: &logging-defaults
driver: json-file
options:
max-size: "512m"
max-file: "2"
# ABFoundationGlobal/abcore geth-fork client
# Distributed as release binaries from https://github.com/ABFoundationGlobal/abcore/releases
# Version: v1.13.15-abcore-1.2
# Usage:
#
# mkdir rpc && cd rpc
#
# git init
# git remote add origin https://github.com/StakeSquid/ethereum-rpc-docker.git
# git fetch origin vibe
# git checkout origin/vibe
#
# docker run --rm alpine sh -c "printf '0x'; head -c32 /dev/urandom | xxd -p -c 64" > .jwtsecret
#
# env
# ...
# IP=$(curl ipinfo.io/ip)
# DOMAIN=${IP}.traefik.me
# COMPOSE_FILE=base.yml:rpc.yml:abcore/geth/abcore-mainnet-geth-pruned-pebble-hash.yml
#
# docker compose up -d
#
# curl -X POST https://${IP}.traefik.me/abcore-mainnet-geth \
# -H "Content-Type: application/json" \
# --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
services:
abcore-mainnet-geth:
build:
context: ./abcore
dockerfile: abcore.Dockerfile
args:
ABCORE_MAINNET_GETH_VERSION: ${ABCORE_MAINNET_GETH_VERSION:-v1.13.15-abcore-1.2}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle
net.ipv4.tcp_no_metrics_save: 1 # Disable metrics cache
net.ipv4.tcp_rmem: 4096 87380 16777216 # Increase TCP read buffers
net.ipv4.tcp_wmem: 4096 87380 16777216 # Increase TCP write buffers
net.core.somaxconn: 32768 # Higher connection queue
# Memory/Connection Management
# net.core.netdev_max_backlog: 50000 # Increase network buffer
net.ipv4.tcp_max_syn_backlog: 30000 # More SYN requests
net.ipv4.tcp_max_tw_buckets: 2000000 # Allow more TIME_WAIT sockets
ulimits:
nofile: 1048576 # Max open files (for RPC/WS connections)
user: root
ports:
- 13110:13110
- 13110:13110/udp
expose:
- 8545
- 6060
environment:
- CHAIN_NAME=mainnet
entrypoint: [/usr/local/bin/init.sh]
command:
- --config=/config/config.toml
- --datadir=/root/.ethereum
- --db.engine=pebble
- --gcmode=full
- --http
- --http.addr=0.0.0.0
- --http.api=eth,net,web3,debug,admin,txpool,engine
- --http.port=8545
- --http.vhosts=*
- --maxpeers=50
- --metrics
- --metrics.addr=0.0.0.0
- --metrics.port=6060
- --nat=extip:${IP}
- --networkid=36888
- --port=13110
- --rpc.gascap=600000000
- --rpc.txfeecap=0
- --syncmode=full
- --ws
- --ws.addr=0.0.0.0
- --ws.api=eth,net,web3,debug,admin,txpool,engine
- --ws.origins=*
- --ws.port=8545
restart: unless-stopped
stop_grace_period: 5m
networks:
- chains
volumes:
- ${ABCORE_MAINNET_GETH_PRUNED_PEBBLE_HASH_DATA:-abcore-mainnet-geth-pruned-pebble-hash}:/root/.ethereum
- ./abcore/mainnet:/config
- /slowdisk:/slowdisk
logging: *logging-defaults
labels:
- prometheus-scrape.enabled=true
- prometheus-scrape.port=6060
- prometheus-scrape.path=/debug/metrics/prometheus
- traefik.enable=true
- traefik.http.middlewares.abcore-mainnet-geth-pruned-pebble-hash-stripprefix.stripprefix.prefixes=/abcore-mainnet-geth
- traefik.http.services.abcore-mainnet-geth-pruned-pebble-hash.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.abcore-mainnet-geth-pruned-pebble-hash.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.abcore-mainnet-geth-pruned-pebble-hash.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.abcore-mainnet-geth-pruned-pebble-hash.rule=Host(`$DOMAIN`) && (Path(`/abcore-mainnet-geth`) || Path(`/abcore-mainnet-geth/`))}
- ${NO_SSL:+traefik.http.routers.abcore-mainnet-geth-pruned-pebble-hash.rule=Path(`/abcore-mainnet-geth`) || Path(`/abcore-mainnet-geth/`)}
- traefik.http.routers.abcore-mainnet-geth-pruned-pebble-hash.middlewares=abcore-mainnet-geth-pruned-pebble-hash-stripprefix, ipallowlist
volumes:
abcore-mainnet-geth-pruned-pebble-hash:
x-upstreams:
- id: $${ID}
labels:
provider: $${PROVIDER}
connection:
generic:
rpc:
url: $${RPC_URL}
ws:
frameSize: 20Mb
msgSize: 50Mb
url: $${WS_URL}
chain: abcore
method-groups:
enabled:
- debug
- filter
methods:
disabled:
enabled:
- name: txpool_content # TODO: should be disabled for rollup nodes
# standard geth only
- name: debug_getRawBlock
- name: debug_getRawTransaction
- name: debug_getRawReceipts
- name: debug_getRawHeader
- name: debug_getBadBlocks
# non standard geth only slightly dangerous
- name: debug_intermediateRoots
- name: debug_dumpBlock
# standard geth and erigon
- name: debug_accountRange
- name: debug_getModifiedAccountsByNumber
- name: debug_getModifiedAccountsByHash
# non standard geth and erigon
- name: eth_getRawTransactionByHash
- name: eth_getRawTransactionByBlockHashAndIndex
...

View File

@@ -0,0 +1,33 @@
[Eth]
NetworkId = 36888
SyncMode = "snap"
NoPruning = false
NoPrefetch = false
[Eth.Miner]
GasFloor = 0
GasCeil = 63000000
GasPrice = 50000000000000
Recommit = 2000000000
NewPayloadTimeout = 2000000000
[Node]
DataDir = "/root/.ethereum"
HTTPHost = "0.0.0.0"
HTTPPort = 8545
HTTPVirtualHosts = ["*"]
HTTPModules = ["eth", "net", "web3", "debug", "admin", "txpool", "engine"]
WSHost = "0.0.0.0"
WSPort = 8546
WSModules = ["eth", "net", "web3", "debug", "admin", "txpool", "engine"]
WSOrigins = ["*"]
[Node.P2P]
MaxPeers = 50
NoDiscovery = false
DiscoveryV4 = true
ListenAddr = ":33333"
[Metrics]
HTTP = "127.0.0.1"
Port = 6060

22
abcore/scripts/init.sh Normal file
View File

@@ -0,0 +1,22 @@
#!/bin/sh
set -e # Exit on failure
GETH_DATA_DIR="/root/.ethereum"
CONFIG_FILE="/config/config.toml"
GENESIS_FILE="/config/genesis.json"
# Initialize geth from genesis if data directory is empty
if [ -z "$(ls -A "$GETH_DATA_DIR")" ]; then
echo "abcore: data directory is empty, initializing from genesis..."
if [ -f "$GENESIS_FILE" ]; then
/abcore/bin/geth init --datadir "$GETH_DATA_DIR" --db.engine=pebble "$GENESIS_FILE"
else
echo "abcore: genesis file not found at $GENESIS_FILE" >&2
exit 1
fi
else
echo "abcore: data directory not empty, continuing..."
fi
exec /abcore/bin/geth --config "$CONFIG_FILE" --datadir "$GETH_DATA_DIR" "$@"

View File

@@ -1,4 +1,6 @@
{
"rpc-gas-cap": 2500000000,
"eth-rpc-gas-limit": 2500000000
"eth-rpc-gas-limit": 2500000000,
"state-sync-enabled": true,
"state-sync-skip-resume": true
}

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
avalanche-fuji-archive:
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.14.2}
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.15.0-fuji}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
avalanche-fuji:
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.14.2}
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.15.0-fuji}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
avalanche-fuji:
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.14.2}
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.15.0-fuji}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
avalanche-fuji:
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.14.2}
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.15.0-fuji}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
avalanche-fuji:
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.14.2}
image: ${AVALANCHE_GO_IMAGE:-avaplatform/avalanchego}:${AVALANCHE_FUJI_GO_VERSION:-v1.15.0-fuji}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -1,23 +1,6 @@
{
"snowman-api-enabled": false,
"coreth-admin-api-enabled": false,
"net-api-enabled": true,
"rpc-gas-cap": 2500000000,
"rpc-tx-fee-cap": 100,
"eth-rpc-gas-limit": 2500000000,
"eth-api-enabled": true,
"personal-api-enabled": false,
"tx-pool-api-enabled": false,
"debug-api-enabled": false,
"web3-api-enabled": true,
"local-txs-enabled": false,
"pruning-enabled": true,
"api-max-duration": 0,
"api-max-blocks-per-request": 0,
"allow-unfinalized-queries": false,
"log-level": "info",
"state-sync-enabled": false,
"state-sync-skip-resume": true,
"offline-pruning-enabled": true,
"offline-pruning-data-directory": "/root/.avalanchego/offline-pruning"
}

View File

@@ -16,6 +16,6 @@
"api-max-blocks-per-request": 0,
"allow-unfinalized-queries": false,
"log-level": "info",
"state-sync-enabled": false,
"state-sync-enabled": true,
"state-sync-skip-resume": true
}

View File

@@ -1,4 +1,22 @@
[
{
"chain": "mainnet",
"client": "geth",
"compose_file": "abcore/geth/abcore-mainnet-geth-pruned-pebble-hash",
"drpc_chain": "abcore",
"features": [
"hash",
"pebble"
],
"network": "abcore",
"node": null,
"relay": null,
"stack": null,
"type": "pruned",
"volumes": [
"abcore-mainnet-geth-pruned-pebble-hash"
]
},
{
"chain": "mainnet",
"client": "external-node",
@@ -4148,6 +4166,42 @@
"morph-mainnet-l2geth-archive-leveldb-hash-mpt_morph"
]
},
{
"chain": "mainnet-v2",
"client": "geth",
"compose_file": "mova/geth/mova-mainnet-v2-geth-archive-leveldb-hash",
"drpc_chain": "mova-mainnet-v2",
"features": [
"hash",
"leveldb"
],
"network": "mova",
"node": null,
"relay": null,
"stack": null,
"type": "archive",
"volumes": [
"mova-mainnet-v2-geth-archive-leveldb-hash"
]
},
{
"chain": "mainnet-v2",
"client": "geth",
"compose_file": "mova/geth/mova-mainnet-v2-geth-pruned-pebble-path",
"drpc_chain": "mova-mainnet-v2",
"features": [
"path",
"pebble"
],
"network": "mova",
"node": null,
"relay": null,
"stack": null,
"type": "pruned",
"volumes": [
"mova-mainnet-v2-geth-pruned-pebble-path"
]
},
{
"chain": "mainnet",
"client": "erigon",
@@ -9672,6 +9726,22 @@
"starknet-sepolia-pathfinder-pruned"
]
},
{
"chain": "mainnet",
"client": "tacchaind",
"compose_file": "tac/tacchaind/tac-mainnet-tacchaind-archive",
"drpc_chain": "tac",
"features": [],
"network": "tac",
"node": "config",
"relay": null,
"stack": null,
"type": "archive",
"volumes": [
"tac-mainnet-tacchaind-archive",
"tac-mainnet-tacchaind-archive_config"
]
},
{
"chain": "mainnet",
"client": "tacchaind",
@@ -9688,6 +9758,22 @@
"tac-mainnet-tacchaind-pruned_config"
]
},
{
"chain": "spb",
"client": "tacchaind",
"compose_file": "tac/tacchaind/tac-spb-tacchaind-archive",
"drpc_chain": "tac-spb",
"features": [],
"network": "tac",
"node": "config",
"relay": null,
"stack": null,
"type": "archive",
"volumes": [
"tac-spb-tacchaind-archive",
"tac-spb-tacchaind-archive_config"
]
},
{
"chain": "spb",
"client": "tacchaind",

View File

@@ -35,7 +35,7 @@ services:
dockerfile: cometbft.Dockerfile
args:
CL_IMAGE: ${COSMOS_GAIAD_IMAGE:-ghcr.io/cosmos/gaia}
CL_VERSION: ${COSMOS_MAINNET_GAIAD_VERSION:-v27.5.0}
CL_VERSION: ${COSMOS_MAINNET_GAIAD_VERSION:-v27.6.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -61,12 +61,13 @@ services:
- API=eth,txpool,net,debug,web3
- CHAINID=cronosmainnet_25-1
- EVM_MAX_TX_GAS_WANTED=500000
- FEEHISTORY_CAP=250
- GENESIS_URL=https://raw.githubusercontent.com/crypto-org-chain/cronos-mainnet/master/cronosmainnet_25-1/genesis.json
- IP=${IP}
- JSON_RPC_BLOCK_RANGE_CAP=10000
- JSON_RPC_ENABLE=true
- JSON_RPC_ENABLE_INDEXER=true
- JSON_RPC_FEEHISTORY_CAP=100
- JSON_RPC_FEEHISTORY_CAP=250
- JSON_RPC_HTTP_TIMEOUT=30s
- JSON_RPC_LOGS_CAP=10000
- MIN_GAS=5000000000000basecro

View File

@@ -61,12 +61,13 @@ services:
- API=eth,txpool,net,debug,web3
- CHAINID=cronostestnet_338-3
- EVM_MAX_TX_GAS_WANTED=500000
- FEEHISTORY_CAP=250
- GENESIS_URL=https://raw.githubusercontent.com/crypto-org-chain/cronos-testnets/main/cronostestnet_338-3/genesis.json
- IP=${IP}
- JSON_RPC_BLOCK_RANGE_CAP=10000
- JSON_RPC_ENABLE=true
- JSON_RPC_ENABLE_INDEXER=true
- JSON_RPC_FEEHISTORY_CAP=100
- JSON_RPC_FEEHISTORY_CAP=250
- JSON_RPC_HTTP_TIMEOUT=30s
- JSON_RPC_LOGS_CAP=10000
- MIN_GAS=5000000000000basetcro

View File

@@ -18,7 +18,8 @@ PRUNING_KEEP_RECENT="${PRUNING_KEEP_RECENT:-100}"
PRUNING_INTERVAL="${PRUNING_INTERVAL:-19}"
JSON_RPC_ENABLE="${JSON_RPC_ENABLE:-true}"
JSON_RPC_ENABLE_INDEXER="${JSON_RPC_ENABLE_INDEXER:-true}"
JSON_RPC_FEEHISTORY_CAP="${JSON_RPC_FEEHISTORY_CAP:-100}"
FEEHISTORY_CAP="${FEEHISTORY_CAP:-250}"
JSON_RPC_FEEHISTORY_CAP="${JSON_RPC_FEEHISTORY_CAP:-$FEEHISTORY_CAP}"
JSON_RPC_LOGS_CAP="${JSON_RPC_LOGS_CAP:-10000}"
JSON_RPC_BLOCK_RANGE_CAP="${JSON_RPC_BLOCK_RANGE_CAP:-10000}"
JSON_RPC_HTTP_TIMEOUT="${JSON_RPC_HTTP_TIMEOUT:-30s}"
@@ -47,7 +48,7 @@ sed -i "/^\[json-rpc\]/,/^\[/{s|^ws-origins = .*|ws-origins = \"$JSON_RPC_WS_ORI
sed -i "/^\[json-rpc\]/,/^\[/{s|^api = .*|api = \"$API\"|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^enable = .*|enable = $JSON_RPC_ENABLE|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^enable-indexer = .*|enable-indexer = $JSON_RPC_ENABLE_INDEXER|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^feehistory-cap = .*|feehistory-cap = $JSON_RPC_FEEHISTORY_CAP|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^feehistory-cap = .*|feehistory-cap = $FEEHISTORY_CAP|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^logs-cap = .*|logs-cap = $JSON_RPC_LOGS_CAP|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^block-range-cap = .*|block-range-cap = $JSON_RPC_BLOCK_RANGE_CAP|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^http-timeout = .*|http-timeout = \"$JSON_RPC_HTTP_TIMEOUT\"|}" "$CONFIG_DIR/app.toml"

View File

@@ -61,6 +61,7 @@ services:
- --engine.cross-block-cache-size=${ETHEREUM_HOODI_RETH_STATE_CACHE:-4096}
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -62,6 +62,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -61,6 +61,7 @@ services:
- --engine.cross-block-cache-size=${ETHEREUM_MAINNET_RETH_STATE_CACHE:-4096}
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -61,6 +61,7 @@ services:
- --engine.cross-block-cache-size=${ETHEREUM_MAINNET_RETH_STATE_CACHE:-4096}
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -61,6 +61,7 @@ services:
- --engine.cross-block-cache-size=${ETHEREUM_MAINNET_RETH_STATE_CACHE:-4096}
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -65,6 +65,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -87,6 +87,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -62,6 +62,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -62,6 +62,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -62,6 +62,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -61,6 +61,7 @@ services:
- --engine.cross-block-cache-size=${ETHEREUM_SEPOLIA_RETH_STATE_CACHE:-4096}
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -61,6 +61,7 @@ services:
- --engine.cross-block-cache-size=${ETHEREUM_SEPOLIA_RETH_STATE_CACHE:-4096}
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -62,6 +62,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -62,6 +62,7 @@ services:
- --engine.memory-block-buffer-target=0
- --engine.persistence-threshold=0
- --full
- --gpo.ignoreprice=2
- --max-inbound-peers=50
- --max-outbound-peers=50
- --metrics=0.0.0.0:9001

View File

@@ -0,0 +1,223 @@
#!/bin/sh
# cometbft-common.sh — reusable CometBFT-node bootstrap helpers (family C).
#
# Source this from a chain-specific init.sh. It encapsulates the operations every
# CometBFT-consensus node needs (init, fetch config artifacts, patch config.toml /
# app.toml, seed priv_validator_state), extracted verbatim from the proven berachain
# beacon-kit entrypoint so callers inherit known-good behavior.
#
# Each function takes explicit arguments (paths/values) — it is binary-agnostic. The
# caller owns the binary name, the `<binary> init` invocation, the artifact URLs, and
# the final `exec <binary> start ...`. EL-driven chains (beacon-kit, morph) also call
# the JWT / engine-dial helpers; pure-consensus chains (gaiad) skip them.
#
# Conventions: POSIX sh (alpine). Config dir is conventionally $HOME_DIR/config.
# Used by: morph-node, gaiad (cosmos batch), and any future family-C chain.
# beacon-kit (berachain) keeps its own bespoke init.sh on purpose — do not retrofit it.
set -e
ct_log() { echo "[cometbft-init] $*"; }
# Ensure curl exists (alpine base images often omit it). Idempotent.
ct_require_curl() {
if ! command -v curl >/dev/null 2>&1; then
ct_log "installing curl"
apk add --no-cache curl
fi
}
# ct_fetch URL DEST [required]
# Download URL -> DEST. If the 3rd arg is "required", a failure is fatal;
# otherwise a missing/failed fetch is logged and skipped (returns 0).
ct_fetch() {
_url="$1"; _dest="$2"; _req="${3:-optional}"
[ -n "$_url" ] || { [ "$_req" = required ] && { ct_log "FATAL: empty URL for $_dest"; exit 1; }; return 0; }
if curl -fsSL "$_url" -o "$_dest"; then
ct_log "fetched $_url -> $_dest"
else
if [ "$_req" = required ]; then
ct_log "FATAL: failed to fetch required $_url"; exit 1
fi
ct_log "skip: could not fetch optional $_url"
fi
}
# ct_patch_p2p CONFIG_TOML IP P2P_PORT
# Bind p2p to 0.0.0.0:PORT and advertise IP:PORT (only within the [p2p] section).
ct_patch_p2p() {
_cfg="$1"; _ip="$2"; _port="$3"
[ -f "$_cfg" ] || { ct_log "patch_p2p: $_cfg missing, skipping"; return 0; }
_laddr="tcp:\\/\\/0\\.0\\.0\\.0\\:${_port}"
sed -i "/^\[p2p\]/,/^\[/{s|^laddr = .*|laddr = \"$_laddr\"|}" "$_cfg"
sed -i "/^\[p2p\]/,/^\[/{s|^external_address = .*|external_address = \"${_ip}:${_port}\"|}" "$_cfg"
}
# ct_merge_seeds CONFIG_TOML CONFIGURED_SEEDS [SEEDS_URL]
# Merge operator-configured seeds with an optional official seed list (1 entry per
# line, first line skipped like the berachain cl-seeds.txt header), dedupe, write.
ct_merge_seeds() {
_cfg="$1"; _seeds="$2"; _url="$3"
[ -f "$_cfg" ] || return 0
if [ -n "$_url" ]; then
_official=$(curl -f -s "$_url" | tail -n +2 | tr '\n' ',' | sed 's/,$//' || true)
if [ -n "$_official" ]; then
ct_log "merging official seeds from $_url"
_seeds=$(echo "${_seeds},${_official}" | tr ',' '\n' | sed '/^$/d' | sort -u | paste -sd,)
else
ct_log "no official seeds fetched from $_url (continuing with configured)"
fi
fi
if [ -n "$_seeds" ]; then
sed -i "s/^seeds = \".*\"/seeds = \"${_seeds}\"/" "$_cfg"
fi
}
# ct_set_persistent_peers CONFIG_TOML PEERS
# Handles both cometbft-classic `persistent_peers` (underscore) and forks that use
# `persistent-peers` (hyphen, e.g. sei) — patches whichever key is present.
ct_set_persistent_peers() {
_cfg="$1"; _peers="$2"
[ -f "$_cfg" ] || return 0
[ -n "$_peers" ] || return 0
sed -i "s/^persistent_peers = \".*\"/persistent_peers = \"${_peers}\"/" "$_cfg"
sed -i "s/^persistent-peers = \".*\"/persistent-peers = \"${_peers}\"/" "$_cfg"
return 0
}
# ct_set_moniker CONFIG_TOML MONIKER
ct_set_moniker() {
_cfg="$1"; _mon="$2"
[ -f "$_cfg" ] || return 0
[ -n "$_mon" ] && sed -i "s/^moniker = \".*\"/moniker = \"$_mon\"/" "$_cfg"
return 0
}
# ct_set_addrbook CONFIG_DIR ADDRBOOK_URL
# Optional: cosmos chains often seed an addrbook.json for faster peer discovery.
ct_set_addrbook() {
_dir="$1"; _url="$2"
[ -n "$_url" ] || return 0
ct_fetch "$_url" "$_dir/addrbook.json" optional
}
# ct_write_jwt CONFIG_DIR [JWT_SRC]
# EL-driven chains: copy the shared engine JWT (default /jwtsecret) into the config
# dir as jwt.hex so the CL can authenticate to the EL engine API.
ct_write_jwt() {
_dir="$1"; _src="${2:-/jwtsecret}"
[ -f "$_src" ] || { ct_log "write_jwt: $_src missing, skipping"; return 0; }
cat "$_src" > "$_dir/jwt.hex"
}
# ct_set_rpc_dial_url APP_TOML AUTH_RPC
# beacon-kit / app.toml-style EL engine endpoint (e.g. http://<el>:8551).
ct_set_rpc_dial_url() {
_app="$1"; _rpc="$2"
[ -f "$_app" ] || return 0
[ -n "$_rpc" ] && sed -i "s|^rpc-dial-url = \".*\"|rpc-dial-url = \"$_rpc\"|" "$_app"
return 0
}
# ct_seed_priv_validator_state HOME_DIR
# Ensure data/priv_validator_state.json exists (cometbft refuses to start without it
# when one is present in config/). Mirrors the berachain init.sh behavior.
ct_seed_priv_validator_state() {
_home="$1"
if [ -e "$_home/config/priv_validator_state.json" ] && [ ! -e "$_home/data/priv_validator_state.json" ]; then
mkdir -p "$_home/data"
cp "$_home/config/priv_validator_state.json" "$_home/data/priv_validator_state.json"
fi
return 0
}
# ct_apk PKG...
# Install alpine packages idempotently (most cosmos init scripts need curl, some jq).
ct_apk() {
apk add --no-cache "$@"
}
# ct_localize_home CONFIG_DIR
# Rewrite `~/` to `/root/` in config.toml + app.toml. Cosmos `init` writes home-relative
# paths; the container runs as root with a static home, so make paths absolute.
ct_localize_home() {
_dir="$1"
[ -f "$_dir/config.toml" ] && sed -i 's|~/|/root/|g' "$_dir/config.toml"
[ -f "$_dir/app.toml" ] && sed -i 's|~/|/root/|g' "$_dir/app.toml"
return 0
}
# ct_set_min_gas_prices APP_TOML PRICE
# Cosmos chains reject txs (and sometimes refuse to start) with an empty
# minimum-gas-prices. PRICE e.g. "0.01usei", "0.0025uatom", "0.01hqq".
ct_set_min_gas_prices() {
_app="$1"; _price="$2"
[ -f "$_app" ] || return 0
[ -n "$_price" ] || return 0
sed -i "s/minimum-gas-prices = \"\"/minimum-gas-prices = \"${_price}\"/g" "$_app"
return 0
}
# ct_configure_statesync CONFIG_TOML RPC_SERVERS [TRUST_OFFSET]
# Enable cometbft state-sync so a fresh node bootstraps near chainhead instead of
# replaying from genesis — the single biggest lever for "can't keep it at chainhead"
# chains. RPC_SERVERS = comma list of trusted RPC endpoints (>=2 recommended; a single
# endpoint is duplicated). TRUST_OFFSET = blocks below head to trust (default 2000).
# Requires jq + curl. No-op (logged) if head height can't be fetched.
ct_configure_statesync() {
_cfg="$1"; _rpc="$2"; _offset="${3:-2000}"
[ -f "$_cfg" ] || return 0
# NEVER re-arm statesync on a node that already has application state (a restored
# snapshot or a prior sync). Re-statesyncing over it leaves a broken/partial datadir and,
# for wasm chains, drops the wasm files -> startup panic. _cfg is $HOME/config/config.toml,
# so application state lives at $HOME/data/application.db.
_home=$(dirname "$(dirname "$_cfg")")
if [ -e "$_home/data/application.db" ]; then
ct_log "statesync: existing data dir, skipping"
return 0
fi
[ -n "$_rpc" ] || { ct_log "statesync: no RPC servers given, skipping"; return 0; }
_primary=$(echo "$_rpc" | cut -d, -f1)
_latest=$(curl -s "$_primary/block" | jq -r '.result.block.header.height // .block.header.height' 2>/dev/null || true)
if [ -z "$_latest" ] || [ "$_latest" = null ]; then
ct_log "statesync: could not read head height from $_primary, skipping"; return 0
fi
_trust_h=$((_latest - _offset))
_trust_hash=$(curl -s "$_primary/block?height=$_trust_h" | jq -r '.result.block_id.hash // .block_id.hash' 2>/dev/null || true)
[ -n "$_trust_hash" ] && [ "$_trust_hash" != null ] || { ct_log "statesync: no trust hash, skipping"; return 0; }
# second server defaults to the first (cometbft wants >=2 for light-client cross-check)
echo "$_rpc" | grep -q ',' || _rpc="$_rpc,$_rpc"
ct_log "statesync: enable trust_height=$_trust_h trust_hash=$_trust_hash"
# Patch ONLY the [statesync] section. CometBFT config.toml uses underscore keys
# (rpc_servers/trust_height/trust_hash); tolerate hyphen variants with [_-].
sed -i.bak -E "/^\[statesync\]/,/^\[/{
s|^([[:space:]]*enable[[:space:]]*=[[:space:]]*).*|\1true|
s|^([[:space:]]*rpc[_-]servers[[:space:]]*=[[:space:]]*).*|\1\"$_rpc\"|
s|^([[:space:]]*trust[_-]height[[:space:]]*=[[:space:]]*).*|\1$_trust_h|
s|^([[:space:]]*trust[_-]hash[[:space:]]*=[[:space:]]*).*|\1\"$_trust_hash\"|
}" "$_cfg"
return 0
}
# ct_ensure_wasm HOME_DIR WASM_SNAPSHOT_URL
# CosmWasm + IBC 08-wasm bytecode are FILES on disk that state-sync does NOT restore, so
# a state-synced wasm chain panics at startup ("wasmlckeeper failed initialize pinned codes
# / Error opening Wasm file"). Seed them from a wasm-only snapshot (e.g. polkachu
# cosmos_wasmonly.tar.lz4) when the wasm dir is missing/empty. No-op if URL unset or wasm
# already present. Best-effort (logs on failure); the fully robust path for wasm chains is a
# FULL snapshot restore. Requires lz4 + tar (installed here).
ct_ensure_wasm() {
_home="$1"; _url="$2"
[ -n "$_url" ] || return 0
if [ -d "$_home/wasm" ] && [ -n "$(ls -A "$_home/wasm" 2>/dev/null)" ]; then
return 0 # wasm already present
fi
ct_log "wasm: empty, fetching snapshot $_url"
ct_apk lz4 tar
if curl -sL "$_url" | lz4 -dc | tar -xf - -C "$_home"; then
ct_log "wasm: extracted into $_home"
else
ct_log "WARN wasm: fetch/extract failed ($_url)"
fi
return 0
}

95
haqq/scripts/init.sh Normal file → Executable file
View File

@@ -1,64 +1,65 @@
#!/bin/sh
# haqq (haqqd) entrypoint — family C, pure CometBFT (no EL). Thin: sources the
# shared cometbft-common.sh and orchestrates init + statesync bootstrap + start.
# Serves CometBFT RPC :26657 (the dshackle/traefik upstream).
# Genesis replay across ~25 Haqq network upgrades is impractical, so we statesync
# near head with the current binary.
set -e
. /usr/local/bin/cometbft-common.sh
set -e # Exit on failure
HOME_DIR="/root/.haqqd"
CONFIG_DIR="$HOME_DIR/config"
CHAIN_ID="${CHAINID:-haqq_11235-1}"
CHAINNAME="${CHAINNAME:-mainnet}"
GENESIS_URL="${GENESIS_URL:-https://raw.githubusercontent.com/haqq-network/${CHAINNAME}/master/genesis.json}"
ADDRBOOK_URL="${ADDRBOOK_URL:-https://snapshots.polkachu.com/addrbook/haqq/addrbook.json}"
STATESYNC_RPC="${STATESYNC_RPC:-https://haqq-rpc.polkachu.com:443}"
MIN_GAS="${MIN_GAS:-0.01hqq}"
MONIKER="${MONIKER:-rpc-node}"
echo "MONIKER: $MONIKER"
# Polkachu seeds + official Haqq network seeds for reliable peer discovery
SEEDS="${SEEDS:-936e27a05f3cfb090507dd810395cbbd8efbffb0@65.109.115.172:24056,f4675b63b8872fb9216efa99428eb5b1fb297393@65.109.104.118:61256,6c9a6fc0e0d94bf303075e46560832e652cffc14@65.108.71.137:24056,7c153a83@peer.haqq.mainnet.dteam.tech:28656}"
CHAINID=${CHAINID:-haqq_11235-1}
CHAINNAME=${CHAINNAME:-mainnet}
API=${API:-eth,net,web3}
# Optional persistent peers for additional bootstrap reliability
PERSISTENT_PEERS="${PERSISTENT_PEERS:-17e0fd08f04e062d18412808b8bf134e9ad34508@65.109.109.189:10656,e000b992a0066eae9e87c66e0d65229a76647509@198.96.92.242:32656,7c153a83@peer.haqq.mainnet.dteam.tech:28656,235ac65359582357b04754e838496545b3ba6429@haqq-seed-1.allnodes.me:26656,9573a569016378765444095484840d7f543d2800@haqq-seed-2.allnodes.me:26656}"
CONFIG_DIR="/root/.haqqd/config"
ct_apk curl jq
# Create config directory
mkdir -p "$CONFIG_DIR"
P2P_STRING="tcp:\\/\\/0\\.0\\.0\\.0\\:${P2P_PORT:-10465}"
NAT_STRING="${IP}:${P2P_PORT:-10465}"
env
# this goes first because it won't overwrite shit
apk add curl
if [ $? -ne 0 ]; then exit 1; fi
if haqqd init ${MONIKER} --chain-id ${CHAINID} --home /root/.haqqd/; then
# Define variables
GENESIS_URL="https://raw.githubusercontent.com/haqq-network/${CHAINNAME}/master/genesis.json"
ADDRESSBOOK_URL="https://raw.githubusercontent.com/haqq-network/${CHAINNAME}/master/addrbook.json"
# Download config files
curl -sL "$GENESIS_URL" -o "$CONFIG_DIR/genesis.json"
curl -sL "$ADDRESSBOOK_URL" -o "$CONFIG_DIR/addressbook.json"
# somehow it's better to make home static to /root
sed -i 's|~/|/root/|g' "$CONFIG_DIR/config.toml"
sed -i 's|~/|/root/|g' "$CONFIG_DIR/app.toml"
if haqqd init "$MONIKER" --chain-id "$CHAIN_ID" --home "$HOME_DIR" >/dev/null 2>&1; then
ct_log "fresh init; fetching genesis and addrbook"
ct_fetch "$GENESIS_URL" "$CONFIG_DIR/genesis.json" required
ct_set_addrbook "$CONFIG_DIR" "$ADDRBOOK_URL"
ct_localize_home "$CONFIG_DIR"
ct_set_min_gas_prices "$CONFIG_DIR/app.toml" "$MIN_GAS"
else
echo "Already initialized, continuing!" >&2
ct_log "already initialized, continuing"
# Auto-refresh addrbook from Polkachu when empty on startup
if [ ! -s "$CONFIG_DIR/addrbook.json" ]; then
ct_log "addrbook.json is empty, refreshing from Polkachu"
ct_set_addrbook "$CONFIG_DIR" "$ADDRBOOK_URL"
fi
fi
# Serve RPC on all interfaces (dshackle/traefik upstream); default is 127.0.0.1.
sed -i '/^\[rpc\]/,/^\[/{s|^laddr = .*|laddr = "tcp://0.0.0.0:8545"|}' "$CONFIG_DIR/config.toml"
# apply a port change to the config
sed -i "/^\[p2p\]/,/^\[/{s|^laddr = .*|laddr = \"$P2P_STRING\"|}" "$CONFIG_DIR/config.toml"
#sed -i "s/^laddr = \".*\"/laddr = \"$P2P_STRING\"/" "$CONFIG_DIR/config.toml"
sed -i "/^\[p2p\]/,/^\[/{s|^external_address = .*|external_address = \"$NAT_STRING\"|}" "$CONFIG_DIR/config.toml"
#sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.01hqq"/g' $CONFIG_DIR/app.toml
sed -i -e "s/^pruning *=.*/pruning = \"custom\"/" $CONFIG_DIR/app.toml
sed -i -e "s/^pruning-keep-recent *=.*/pruning-keep-recent = \"100\"/" $CONFIG_DIR/app.toml
sed -i -e "s/^pruning-interval *=.*/pruning-interval = \"19\"/" $CONFIG_DIR/app.toml
sed -i -e "s/^indexer *=.*/indexer = \"null\"/" $CONFIG_DIR/config.toml
ct_patch_p2p "$CONFIG_DIR/config.toml" "$IP" "${P2P_PORT:-10465}"
ct_merge_seeds "$CONFIG_DIR/config.toml" "$SEEDS" "$ADDRBOOK_URL"
ct_set_persistent_peers "$CONFIG_DIR/config.toml" "$PERSISTENT_PEERS"
ct_set_moniker "$CONFIG_DIR/config.toml" "$MONIKER"
ct_configure_statesync "$CONFIG_DIR/config.toml" "$STATESYNC_RPC"
ct_seed_priv_validator_state "$HOME_DIR"
# Haqq-specific config: enable API endpoints
sed -i "/^\[json-rpc\]/,/^\[/{s|^address = .*|address = \"0.0.0.0:8545\"|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^ws-address = .*|ws-address = \"0.0.0.0:8546\"|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^metrics-address = .*|metrics-address = \"0.0.0.0:6065\"|}" "$CONFIG_DIR/app.toml"
sed -i "/^\[json-rpc\]/,/^\[/{s|^api = .*|api = \"$API\"|}" "$CONFIG_DIR/app.toml"
# Update moniker if set
if [ -n "$MONIKER" ] && [ -f "$CONFIG_DIR/config.toml" ]; then
sed -i "s/^moniker = \".*\"/moniker = \"$MONIKER\"/" "$CONFIG_DIR/config.toml"
fi
# Custom pruning settings for RPC node
sed -i -e "s/^pruning *=.*/pruning = \"custom\"/" "$CONFIG_DIR/app.toml"
sed -i -e "s/^pruning-keep-recent *=.*/pruning-keep-recent = \"100\"/" "$CONFIG_DIR/app.toml"
sed -i -e "s/^pruning-interval *=.*/pruning-interval = \"19\"/" "$CONFIG_DIR/app.toml"
sed -i -e "s/^indexer *=.*/indexer = \"null\"/" "$CONFIG_DIR/config.toml"
exec haqqd start --chain-id ${CHAINID} $@
exec haqqd start --chain-id "$CHAIN_ID" --home "$HOME_DIR" $@

View File

@@ -46,8 +46,8 @@ services:
nofile: 1048576 # Max open files (for RPC/WS connections)
user: root
ports:
- 10127:10127
- 10127:10127/udp
- 10127:9651
- 10127:9651/udp
expose:
- 9650
environment:
@@ -59,7 +59,7 @@ services:
- AVAGO_NETWORK_ID=mainnet
- AVAGO_PARTIAL_SYNC_PRIMARY_NETWORK=true
- AVAGO_PLUGIN_DIR=/plugins/
- AVAGO_STAKING_PORT=10127
- AVAGO_STAKING_PORT=9651
- AVAGO_TRACK_SUBNETS=21uUaTxVdR3Sp6SJhpcSrdH1g66aFoE8mPQDvwKJCjXNexo5y6
- KITE_CHAIN_ID=3USaEfTcoUhHxpKXvpAG916UKCUEyjrtkg2hBArBG3JyDP7my
- PLUGIN_ID=pJhES6xZkqZxjxMqHiucbpBTTnB97EjL5aTYSynmWBoF26v9e

View File

@@ -54,6 +54,7 @@ services:
- 9545
command:
- --Xbonsai-full-flat-db-enabled=true
- --bonsai-historical-block-limit=4096
- --bootnodes=enode://069800db9e6e0ec9cadca670994ef1aea2cfd3d88133e63ecadbc1cdbd1a5847b09838ee08d8b5f02a9c32ee13abeb4d4104bb5514e5322c9d7ee19f41ff3e51@3.132.73.210:31002,enode://a8e03a71eab12ec4b47bb6e19169d8e4dc7a58373a2476969bbe463f2dded6003037fa4dd5f71e15027f7fc8d7340956fbbefed67ddd116ac19a7f74da034b61@3.132.73.210:31003,enode://97706526cf79df9d930003644f9156805f6c8bd964fc79e083444f7014ce10c9bdd2c5049e63b58040dca1d4c82ebef970822198cf0714de830cff4111534ff1@18.223.198.165:31004,enode://24e1c654a801975a96b7f54ebd7452ab15777fc635c1db25bdbd4425fdb04e7f4768e9e838a87ab724320a765e41631d5d37758c933ad0e8668693558125c8aa@18.223.198.165:31000,enode://27010891d960f73d272a553f72b6336c6698db3ade98d631f09c764e57674a797be5ebc6829ddbb65ab564f439ebc75215d20aa98b6f351d12ea623e7d139ac3@3.132.73.210:31001,enode://228e1b8a4931e46f383e30721dac21fb8fb4e5e1b32c870e13b25478c82db3dc1cd9e7ceb93d302a766466b55638cc9c5cbfc43aa48fa41ced19baf365951f76@3.1.142.64:31002,enode://c22eb0d40fc3ad5ea710aeddea906567778166bfe18c157955e8c39b23a46c45db18a0fa2ba07f2b64c81178a8c796aec2a29151533920ead06fcdfc6d8d03c6@47.128.192.57:31004,enode://8ce733abe39fd7ae0a278b9893f85c1193c611a3886168690dd843435460f22cc4d61f9e8d0ace7f5905836a665319a31cccdaacdada2acc69972c382ecce7db@3.1.142.64:31003,enode://b7c1b2bed65a855f7a2104aac9a14674dfdf018fdac763415b373b29ce18cdb81d36328ba4e5c9f12629f3a50c3e8f9ee048f22dbdbe93a82813da89c6b81334@51.20.235.126:31004,enode://95270e0550848a72fb141cf27f1c4ea10714edde365b411dc0fa06c81c0f282ce155eb9fa472b6b8bb9ee98395eeaf4c5a7b02a01fe58b37ea98ba152eda4c37@13.50.94.193:31000,enode://72013391755f24f08567b932feeeec4c893c06e0b1fb480890c83bf87fd277ad86a5ab9cb586db9ae9970371a2f8cb0c96f6c9f69045abca0fb801db7f047138@51.20.235.126:31001
- --data-path=/opt/besu/database
- --data-storage-format=BONSAI

View File

@@ -54,6 +54,7 @@ services:
- 9545
command:
- --Xbonsai-full-flat-db-enabled=true
- --bonsai-historical-block-limit=4096
- --bootnodes=enode://6f20afbe4397e51b717a7c1ad3095e79aee48c835eebd9237a3e8a16951ade1fe0e66e981e30ea269849fcb6ba03d838da37f524fabd2a557474194a2e2604fa@18.221.100.27:31002,enode://ce1e0d8e0500cb5c0ac56bdcdafb2d6320c3a2c5125b5ccf12f5dfc9b47ee74acbcafc32559017613136c9c36a0ce74ba4f83b7fb8244f099f3b15708d9d3129@3.23.75.47:31000,enode://1b026a5eb0ae74300f58987d235ef0e3a550df963345cb3574be3b0b54378bd11f14dfd515a8976f2c2d2826090e9507b8ccc24f896a9ffffffcabcfd996a733@3.129.120.128:31001
- --data-path=/opt/besu/database
- --data-storage-format=BONSAI

View File

@@ -0,0 +1,139 @@
---
x-logging-defaults: &logging-defaults
driver: json-file
options:
max-size: "512m"
max-file: "2"
# Usage:
#
# mkdir rpc && cd rpc
#
# git init
# git remote add origin https://github.com/StakeSquid/ethereum-rpc-docker.git
# git fetch origin vibe
# git checkout origin/vibe
#
# docker run --rm alpine sh -c "printf '0x'; head -c32 /dev/urandom | xxd -p -c 64" > .jwtsecret
#
# env
# ...
# IP=$(curl ipinfo.io/ip)
# DOMAIN=${IP}.traefik.me
# COMPOSE_FILE=base.yml:rpc.yml:mova/geth/mova-mainnet-v2-geth-archive-leveldb-hash.yml
#
# docker compose up -d
#
# curl -X POST https://${IP}.traefik.me/mova-mainnet-v2-geth-archive \
# -H "Content-Type: application/json" \
# --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
services:
mova-mainnet-v2-geth-archive:
image: ${MOVA_GETH_IMAGE:-ethereum/client-go}:${MOVA_MAINNET_V2_GETH_VERSION:-v1.17.4}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle
net.ipv4.tcp_no_metrics_save: 1 # Disable metrics cache
net.ipv4.tcp_rmem: 4096 87380 16777216 # Increase TCP read buffers
net.ipv4.tcp_wmem: 4096 87380 16777216 # Increase TCP write buffers
net.core.somaxconn: 32768 # Higher connection queue
# Memory/Connection Management
# net.core.netdev_max_backlog: 50000 # Increase network buffer
net.ipv4.tcp_max_syn_backlog: 30000 # More SYN requests
net.ipv4.tcp_max_tw_buckets: 2000000 # Allow more TIME_WAIT sockets
ulimits:
nofile: 1048576 # Max open files (for RPC/WS connections)
user: root
ports:
- 11121:11121
- 11121:11121/udp
expose:
- 8545
- 6060
command:
- --datadir=/root/.ethereum
- --db.engine=leveldb
- --gcmode=archive
- --http
- --http.addr=0.0.0.0
- --http.api=eth,net,web3,debug,admin,txpool,engine
- --http.port=8545
- --http.vhosts=*
- --maxpeers=50
- --metrics
- --metrics.addr=0.0.0.0
- --metrics.port=6060
- --nat=extip:${IP}
- --port=11121
- --rpc.gascap=600000000
- --rpc.txfeecap=0
- --state.scheme=hash
- --syncmode=full
- --ws
- --ws.addr=0.0.0.0
- --ws.api=eth,net,web3,debug,admin,txpool,engine
- --ws.origins=*
- --ws.port=8545
restart: unless-stopped
stop_grace_period: 5m
networks:
- chains
volumes:
- ${MOVA_MAINNET_V2_GETH_ARCHIVE_LEVELDB_HASH_DATA:-mova-mainnet-v2-geth-archive-leveldb-hash}:/root/.ethereum
- /slowdisk:/slowdisk
logging: *logging-defaults
labels:
- prometheus-scrape.enabled=true
- prometheus-scrape.port=6060
- prometheus-scrape.path=/debug/metrics/prometheus
- traefik.enable=true
- traefik.http.middlewares.mova-mainnet-v2-geth-archive-leveldb-hash-stripprefix.stripprefix.prefixes=/mova-mainnet-v2-geth-archive
- traefik.http.services.mova-mainnet-v2-geth-archive-leveldb-hash.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.mova-mainnet-v2-geth-archive-leveldb-hash.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.mova-mainnet-v2-geth-archive-leveldb-hash.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.mova-mainnet-v2-geth-archive-leveldb-hash.rule=Host(`$DOMAIN`) && (Path(`/mova-mainnet-v2-geth-archive`) || Path(`/mova-mainnet-v2-geth-archive/`))}
- ${NO_SSL:+traefik.http.routers.mova-mainnet-v2-geth-archive-leveldb-hash.rule=Path(`/mova-mainnet-v2-geth-archive`) || Path(`/mova-mainnet-v2-geth-archive/`)}
- traefik.http.routers.mova-mainnet-v2-geth-archive-leveldb-hash.middlewares=mova-mainnet-v2-geth-archive-leveldb-hash-stripprefix, ipallowlist
volumes:
mova-mainnet-v2-geth-archive-leveldb-hash:
x-upstreams:
- id: $${ID}
labels:
provider: $${PROVIDER}
connection:
generic:
rpc:
url: $${RPC_URL}
ws:
frameSize: 20Mb
msgSize: 50Mb
url: $${WS_URL}
chain: mova-mainnet-v2
method-groups:
enabled:
- debug
- filter
methods:
disabled:
enabled:
- name: txpool_content # TODO: should be disabled for rollup nodes
# standard geth only
- name: debug_getRawBlock
- name: debug_getRawTransaction
- name: debug_getRawReceipts
- name: debug_getRawHeader
- name: debug_getBadBlocks
# non standard geth only slightly dangerous
- name: debug_intermediateRoots
- name: debug_dumpBlock
# standard geth and erigon
- name: debug_accountRange
- name: debug_getModifiedAccountsByNumber
- name: debug_getModifiedAccountsByHash
# non standard geth and erigon
- name: eth_getRawTransactionByHash
- name: eth_getRawTransactionByBlockHashAndIndex
...

View File

@@ -0,0 +1,141 @@
---
x-logging-defaults: &logging-defaults
driver: json-file
options:
max-size: "512m"
max-file: "2"
# Usage:
#
# mkdir rpc && cd rpc
#
# git init
# git remote add origin https://github.com/StakeSquid/ethereum-rpc-docker.git
# git fetch origin vibe
# git checkout origin/vibe
#
# docker run --rm alpine sh -c "printf '0x'; head -c32 /dev/urandom | xxd -p -c 64" > .jwtsecret
#
# env
# ...
# IP=$(curl ipinfo.io/ip)
# DOMAIN=${IP}.traefik.me
# COMPOSE_FILE=base.yml:rpc.yml:mova/geth/mova-mainnet-v2-geth-pruned-pebble-path.yml
#
# docker compose up -d
#
# curl -X POST https://${IP}.traefik.me/mova-mainnet-v2-geth \
# -H "Content-Type: application/json" \
# --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
services:
mova-mainnet-v2-geth:
image: ${MOVA_GETH_IMAGE:-ethereum/client-go}:${MOVA_MAINNET_V2_GETH_VERSION:-v1.17.4}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle
net.ipv4.tcp_no_metrics_save: 1 # Disable metrics cache
net.ipv4.tcp_rmem: 4096 87380 16777216 # Increase TCP read buffers
net.ipv4.tcp_wmem: 4096 87380 16777216 # Increase TCP write buffers
net.core.somaxconn: 32768 # Higher connection queue
# Memory/Connection Management
# net.core.netdev_max_backlog: 50000 # Increase network buffer
net.ipv4.tcp_max_syn_backlog: 30000 # More SYN requests
net.ipv4.tcp_max_tw_buckets: 2000000 # Allow more TIME_WAIT sockets
ulimits:
nofile: 1048576 # Max open files (for RPC/WS connections)
user: root
ports:
- 11242:11242
- 11242:11242/udp
expose:
- 8545
- 6060
command:
- --datadir=/root/.ethereum
- --db.engine=pebble
- --gcmode=full
- --http
- --http.addr=0.0.0.0
- --http.api=eth,net,web3,debug,admin,txpool,engine
- --http.port=8545
- --http.vhosts=*
- --maxpeers=50
- --metrics
- --metrics.addr=0.0.0.0
- --metrics.port=6060
- --nat=extip:${IP}
- --port=11242
- --rpc.gascap=600000000
- --rpc.txfeecap=0
- --state.scheme=path
- --syncmode=snap
- --ws
- --ws.addr=0.0.0.0
- --ws.api=eth,net,web3,debug,admin,txpool,engine
- --ws.origins=*
- --ws.port=8545
restart: unless-stopped
stop_grace_period: 5m
networks:
- chains
volumes:
- ${MOVA_MAINNET_V2_GETH_PRUNED_PEBBLE_PATH_DATA:-mova-mainnet-v2-geth-pruned-pebble-path}:/root/.ethereum
- /slowdisk:/slowdisk
logging: *logging-defaults
labels:
- prometheus-scrape.enabled=true
- prometheus-scrape.port=6060
- prometheus-scrape.path=/debug/metrics/prometheus
- traefik.enable=true
- traefik.http.middlewares.mova-mainnet-v2-geth-pruned-pebble-path-stripprefix.stripprefix.prefixes=/mova-mainnet-v2-geth
- traefik.http.services.mova-mainnet-v2-geth-pruned-pebble-path.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.mova-mainnet-v2-geth-pruned-pebble-path.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.mova-mainnet-v2-geth-pruned-pebble-path.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.mova-mainnet-v2-geth-pruned-pebble-path.rule=Host(`$DOMAIN`) && (Path(`/mova-mainnet-v2-geth`) || Path(`/mova-mainnet-v2-geth/`))}
- ${NO_SSL:+traefik.http.routers.mova-mainnet-v2-geth-pruned-pebble-path.rule=Path(`/mova-mainnet-v2-geth`) || Path(`/mova-mainnet-v2-geth/`)}
- traefik.http.routers.mova-mainnet-v2-geth-pruned-pebble-path.middlewares=mova-mainnet-v2-geth-pruned-pebble-path-stripprefix, ipallowlist
volumes:
mova-mainnet-v2-geth-pruned-pebble-path:
x-upstreams:
- id: $${ID}
labels:
provider: $${PROVIDER}
connection:
generic:
rpc:
url: $${RPC_URL}
ws:
frameSize: 20Mb
msgSize: 50Mb
url: $${WS_URL}
chain: mova-mainnet-v2
method-groups:
enabled:
- debug
- filter
methods:
disabled:
# not compatible with path state scheme
- name: debug_traceBlockByHash
enabled:
- name: txpool_content # TODO: should be disabled for rollup nodes
# standard geth only
- name: debug_getRawBlock
- name: debug_getRawTransaction
- name: debug_getRawReceipts
- name: debug_getRawHeader
- name: debug_getBadBlocks
# non standard geth only slightly dangerous
- name: debug_intermediateRoots
- name: debug_dumpBlock
# standard geth and erigon
- name: debug_accountRange
- name: debug_getModifiedAccountsByNumber
- name: debug_getModifiedAccountsByHash
# non standard geth and erigon
- name: eth_getRawTransactionByHash
- name: eth_getRawTransactionByBlockHashAndIndex
...

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
polygon-amoy-archive:
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_AMOY_BOR_VERSION:-2.10.0-private}
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_AMOY_BOR_VERSION:-2.10.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
polygon-amoy:
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_AMOY_BOR_VERSION:-2.10.0-private}
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_AMOY_BOR_VERSION:-2.10.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
polygon-mainnet-archive:
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_MAINNET_BOR_VERSION:-2.9.0}
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_MAINNET_BOR_VERSION:-2.10.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -30,7 +30,7 @@ x-logging-defaults: &logging-defaults
services:
polygon-mainnet:
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_MAINNET_BOR_VERSION:-2.9.0}
image: ${POLYGON_BOR_IMAGE:-0xpolygon/bor}:${POLYGON_MAINNET_BOR_VERSION:-2.10.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle

View File

@@ -143,7 +143,8 @@
"urls": [
"https://rpc-amoy.polygon.technology",
"https://polygon-amoy-bor-rpc.publicnode.com",
"https://polygon-amoy.drpc.org"
"https://polygon-amoy.drpc.org",
"https://rpc.satelink.network/rpc/amoy"
]
},
"arbitrum": {
@@ -157,6 +158,7 @@
"https://arb1.arbitrum.io/rpc",
"https://arbitrum-one-rpc.publicnode.com",
"https://rpcfree.com/arbitrum-rpc",
"https://rpc.satelink.network/rpc/arbitrum",
"https://arbitrum.drpc.org"
]
},
@@ -216,6 +218,7 @@
"https://bsc-dataseed3.ninicoin.io",
"https://bsc-dataseed4.ninicoin.io",
"https://bsc-rpc.publicnode.com",
"https://bsc-rpc-public.chainpulse.cc",
"https://bsc.drpc.org"
]
},
@@ -233,6 +236,7 @@
"https://data-seed-prebsc-1-s3.bnbchain.org:8545",
"https://data-seed-prebsc-2-s3.bnbchain.org:8545",
"https://bsc-testnet-rpc.publicnode.com",
"https://bsc-rpc-public.chainpulse.cc/testnet",
"https://bsc-testnet.drpc.org"
]
},
@@ -305,6 +309,7 @@
"https://base-rpc.publicnode.com",
"https://rpcfree.com/base-rpc",
"https://rpc.baseazul.dev",
"https://rpc.satelink.network/rpc/base",
"https://base.drpc.org"
]
},
@@ -3366,6 +3371,16 @@
"https://mova.drpc.org"
]
},
"mova-mainnet-v2": {
"protocol": "eth",
"id": 61901,
"block_time_ms": 2000,
"lagging_lag": 10,
"syncing_lag": 20,
"urls": [
"https://mova-mainnet-v2.drpc.org"
]
},
"mova-testnet": {
"protocol": "eth",
"id": 10323,
@@ -3635,6 +3650,7 @@
"lagging_lag": 10,
"syncing_lag": 20,
"urls": [
"https://rpc.pharos.xyz",
"https://pharos.drpc.org"
]
},
@@ -3720,6 +3736,7 @@
"lagging_lag": 20,
"syncing_lag": 40,
"urls": [
"https://humanity-main.g.alchemy.com/public",
"https://humanity.drpc.org"
]
},
@@ -3818,6 +3835,60 @@
"https://neo-x-testnet.drpc.org"
]
},
"stellar": {
"protocol": "stellar",
"block_time_ms": 5000,
"lagging_lag": 10,
"syncing_lag": 20,
"urls": [
"https://stellar.drpc.org"
]
},
"stellar-testnet": {
"protocol": "stellar",
"block_time_ms": 5000,
"lagging_lag": 10,
"syncing_lag": 20,
"urls": [
"https://stellar-testnet.drpc.org"
]
},
"celestia": {
"protocol": "celestia",
"block_time_ms": 6000,
"lagging_lag": 5,
"syncing_lag": 10,
"urls": [
"https://celestia.drpc.org"
]
},
"celestia-mocha": {
"protocol": "celestia",
"block_time_ms": 6000,
"lagging_lag": 5,
"syncing_lag": 10,
"urls": [
"https://celestia-mocha.drpc.org"
]
},
"sui": {
"protocol": "sui",
"block_time_ms": 250,
"lagging_lag": 20,
"syncing_lag": 40,
"urls": [
"https://sui.drpc.org"
]
},
"sui-testnet": {
"protocol": "sui",
"block_time_ms": 250,
"lagging_lag": 20,
"syncing_lag": 40,
"urls": [
"https://sui-testnet.drpc.org"
]
},
"core-pigeon": {
"id": 1114,
"urls": [

View File

@@ -5,6 +5,137 @@
# survive every configure-drpc regeneration. See apply-dshackle-overrides.py.
[ -f /root/rpc/apply-dshackle-overrides.py ] && python3 /root/rpc/apply-dshackle-overrides.py
# --- Snapshot-based conditional gateway reconnect ---
# Operator ruling (2026-08-27): NEVER force a reconnect on a routine reload/rpc-update run.
# Only upstream REMOVALS or new disabled-method ADDITIONS require a gateway reconnect.
#
# For each dshackle container, we snapshot the advertised set:
# (a) upstream ids present in rendered yaml configs
# (b) disabled-method entries (from methods.disabled across all upstreams)
# into a persisted file: /root/rpc/.dshackle-adv-<container>.snapshot
#
# On each run:
# - Read current config state from disk
# - If previous snapshot exists, compare: drop connections ONLY IF
# * an upstream id disappeared (was in snapshot, not in current), OR
# * a new disabled-method entry appeared (in current, not in snapshot)
# - Pure additions or no change: reload only, no drop
# - First run with no prior snapshot: do NOT drop, just write snapshot after reload
# - Always rewrite snapshot after successful reload (for next run to compare against)
# - All snapshot/compare operations are best-effort; failures never fail the script
# Helper: extract the host config directory path for a dshackle container.
# dshackle containers mount their config at /etc/dshackle; we find the source of that mount.
get_config_dir() {
local cid="$1"
local mount
mount=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination "/etc/dshackle"}}{{.Source}}{{end}}{{end}}' "$cid" 2>/dev/null) || true
[ -n "$mount" ] && echo "$mount" && return 0
# Fallback: try common locations (best-effort)
for d in /root/rpc/main_configs /root/rpc/free_configs; do
[ -d "$d" ] && echo "$d" && return 0
done
return 1
}
# Helper: extract advertised set (upstream ids + disabled methods) from config directory.
# Outputs two lines to stdout:
# upstream_ids (space-separated, sorted)
# disabled_methods (space-separated, sorted)
get_advertised_set() {
local config_dir="$1"
local upstream_ids=""
local disabled_methods=""
[ -d "$config_dir" ] || return 1
# Use python3 to parse YAML (available on host). Best-effort: if python fails, return empty.
python3 -c "
import yaml, glob, sys, os
config_dir = sys.argv[1]
upstream_ids = set()
disabled_methods = set()
for path in glob.glob(os.path.join(config_dir, '*.yaml')):
try:
with open(path) as f:
doc = yaml.safe_load(f)
if not isinstance(doc, dict):
continue
for up in doc.get('upstreams') or []:
if not isinstance(up, dict):
continue
uid = up.get('id')
if uid:
upstream_ids.add(uid)
methods = up.get('methods', {})
if isinstance(methods, dict):
for m in methods.get('disabled') or []:
if isinstance(m, dict):
name = m.get('name')
if name:
disabled_methods.add(name)
elif isinstance(m, str):
disabled_methods.add(m)
except Exception:
pass
print(' '.join(sorted(upstream_ids)))
print(' '.join(sorted(disabled_methods)))
" "$config_dir" 2>/dev/null
return 0
}
# Helper: read snapshot file. Returns upstream_ids and disabled_methods.
read_snapshot() {
local snapshot_file="$1"
[ -f "$snapshot_file" ] || return 1
local line1 line2
line1=$(head -1 "$snapshot_file" 2>/dev/null) || true
line2=$(tail -1 "$snapshot_file" 2>/dev/null) || true
[ -n "$line1" ] && echo "$line1"
[ -n "$line2" ] && echo "$line2"
return 0
}
# Helper: write snapshot file.
write_snapshot() {
local snapshot_file="$1"
local upstream_ids="$2"
local disabled_methods="$3"
echo "$upstream_ids" > "$snapshot_file" 2>/dev/null || true
echo "$disabled_methods" >> "$snapshot_file" 2>/dev/null || true
}
# Helper: check if drop is needed.
# Args: old_upstream_ids, old_disabled_methods, new_upstream_ids, new_disabled_methods
# Returns 0 (true) if drop is needed, 1 (false) otherwise.
drop_needed() {
local old_ids="$1" new_ids="$3"
local old_methods="$2" new_methods="$4"
# Check if any upstream id disappeared (in old, not in new)
for uid in $old_ids; do
[ -z "$uid" ] && continue
# Check if uid is NOT in new_ids
found=0
for nuid in $new_ids; do
[ "$uid" = "$nuid" ] && found=1 && break
done
[ "$found" = 0 ] && return 0 # drop needed
done
# Check if any new disabled method appeared (in new, not in old)
for meth in $new_methods; do
[ -z "$meth" ] && continue
found=0
for ometh in $old_methods; do
[ "$meth" = "$ometh" ] && found=1 && break
done
[ "$found" = 0 ] && return 0 # drop needed
done
return 1 # no drop needed
}
# 1:1 invariant (2026-07-15): refuse to ACTIVATE a dshackle config routing >1 node for the same
# chain — we can't attribute traffic to multiple nodes behind one proxy (no per-upstream request
# metric; conn-seconds biases it), and the attribution model + planner assume 1:1. The validator
@@ -37,6 +168,40 @@ RC=0
for CID in $(docker ps -q -f "name=dshackle"); do
NAME=$(docker inspect -f '{{.Name}}' "$CID" 2>/dev/null | sed 's|^/||')
[ -n "$NAME" ] || NAME="$CID"
# --- Snapshot-based conditional gateway reconnect ---
# Determine config directory and snapshot file for this container.
CONFIG_DIR=$(get_config_dir "$CID") || true
# Sanitize name for use in snapshot filename: replace / with -, remove leading -
SANITIZED_NAME=$(echo "$NAME" | tr '/' '-' | sed 's/^-//')
SNAPSHOT_FILE="/root/rpc/.dshackle-adv-${SANITIZED_NAME}.snapshot"
# Read current advertised set from the (new) on-disk config.
CURRENT_SET=$(get_advertised_set "$CONFIG_DIR") || true
CURRENT_UPSTREAMS=$(echo "$CURRENT_SET" | head -1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
CURRENT_METHODS=$(echo "$CURRENT_SET" | tail -1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Read previous snapshot if it exists.
PREVIOUS_UPSTREAMS=""
PREVIOUS_METHODS=""
HAS_PREVIOUS_SNAPSHOT=0
if [ -f "$SNAPSHOT_FILE" ]; then
PREVIOUS_SET=$(read_snapshot "$SNAPSHOT_FILE") || true
PREVIOUS_UPSTREAMS=$(echo "$PREVIOUS_SET" | head -1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
PREVIOUS_METHODS=$(echo "$PREVIOUS_SET" | tail -1 | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
HAS_PREVIOUS_SNAPSHOT=1
fi
# Decide if we need to drop connections: only if there were removals.
NEED_DROP=0
if [ "$HAS_PREVIOUS_SNAPSHOT" -eq 1 ]; then
# Has previous snapshot: check for removals
if drop_needed "$PREVIOUS_UPSTREAMS" "$PREVIOUS_METHODS" "$CURRENT_UPSTREAMS" "$CURRENT_METHODS"; then
NEED_DROP=1
fi
fi
# If no previous snapshot, NEED_DROP stays 0 (first run: do NOT drop)
T0=$(date +%s)
if ! docker kill --signal=HUP "$CID" >/dev/null 2>&1; then
echo "reload_dshackle: WARNING: failed to SIGHUP $NAME" >&2
@@ -51,7 +216,20 @@ for CID in $(docker ps -q -f "name=dshackle"); do
elif echo "$LOGS" | grep -qF "$DROP_MARK"; then
REASON="concurrent HUP dropped (reload skipped)"
fi
[ -z "$REASON" ] && continue # SIGHUP applied the config — done
[ -z "$REASON" ] && {
# Reload succeeded. Always rewrite snapshot for next run.
write_snapshot "$SNAPSHOT_FILE" "$CURRENT_UPSTREAMS" "$CURRENT_METHODS"
# Drop established gateway gRPC connections ONLY if removals were detected.
if [ "$NEED_DROP" -eq 1 ]; then
PID=$(docker inspect -f '{{.State.Pid}}' "$CID" 2>/dev/null) && \
nsenter -t "$PID" -n ss -K state established '( sport = :2449 )' >/dev/null 2>&1
echo "reload_dshackle: $NAME reload applied, gateway connections dropped (removals detected)" >&2
else
echo "reload_dshackle: $NAME reload applied, no removals — gateway connections kept" >&2
fi
continue
}
echo "reload_dshackle: $NAME reload did NOT apply ($REASON) — restarting to load config fresh" >&2
if ! docker restart "$CID" >/dev/null 2>&1; then
echo "reload_dshackle: ERROR: restart failed for $NAME — config NOT applied, manual intervention" >&2
@@ -64,7 +242,15 @@ for CID in $(docker ps -q -f "name=dshackle"); do
sleep 2
done
if [ -n "$UP" ]; then
# Restart succeeded. Always rewrite snapshot for next run.
write_snapshot "$SNAPSHOT_FILE" "$CURRENT_UPSTREAMS" "$CURRENT_METHODS"
echo "reload_dshackle: $NAME restarted, config applied on clean start" >&2
# On restart, the config is freshly loaded, so we need to drop if removals were detected.
if [ "$NEED_DROP" -eq 1 ]; then
PID=$(docker inspect -f '{{.State.Pid}}' "$CID" 2>/dev/null) && \
nsenter -t "$PID" -n ss -K state established '( sport = :2449 )' >/dev/null 2>&1
echo "reload_dshackle: $NAME restart applied, gateway connections dropped (removals detected)" >&2
fi
else
echo "reload_dshackle: ERROR: $NAME not running after restart — manual intervention" >&2
RC=1

View File

@@ -22,26 +22,25 @@ check_sync_status() {
# Cap the whole per-node branch (belt-and-suspenders over check-health's own cap), so no single
# node can ever block the 'wait' below — that is what wedged the fleet rpc-update for hours.
result=$(timeout "${SYNC_TIMEOUT:-60}" "$BASEPATH/sync-status.sh" "${part%.yml}")
# Capture the status IMMEDIATELY. Any command in between - including a plain
# assignment like `code=0` - overwrites $? with its own (always 0) status.
rc=$?
code=0
if [ $? -ne 0 ]; then
if [[ "$result" == *"syncing"* ]]; then
# Allow exit status 1 if result contains "syncing"
code=0
elif [[ "$result" == *"lagging"* ]]; then
# Allow exit status 1 if result contains "lagging"
if [ "$rc" -ne 0 ]; then
if [[ "$result" == *"syncing"* ]] || [[ "$result" == *"lagging"* ]]; then
# sync-status exits 1 for syncing/lagging; those are expected states,
# not failures.
code=0
else
any_failure=true
code=1
fi
else
code=1
any_failure=true
fi
echo "${part%.yml}: $result"
# NOTE: do NOT set any_failure here. This function runs backgrounded (`&`), so
# it executes in a subshell and any variable it sets is discarded. Failure is
# propagated to the parent through this return code, collected by `wait` below.
return "$code"
}
@@ -74,9 +73,12 @@ for part in "${parts[@]}"; do
fi
done
# Wait for all background processes to finish
# Wait for all background processes to finish. `wait` runs in the PARENT shell, so
# this is where a failing node can actually flip any_failure - the checker itself
# cannot, being a subshell. Previously the status was discarded here, which silently
# neutered the exit code.
for pid in "${pids[@]}"; do
wait "$pid"
wait "$pid" || any_failure=true
done
# Fenced nodes (fleet-state maintenance windows) are dropped from COMPOSE_FILE

15
tac/scripts/init.sh Executable file → Normal file
View File

@@ -1,6 +1,8 @@
#!/bin/sh
# tacchaind entrypoint — TAC (Cosmos-SDK + embedded EVM). Genesis replay is impractical
# (5 gov upgrades), so fresh nodes statesync near head via cometbft-common.sh.
# tacchaind entrypoint — TAC (Cosmos-SDK + embedded EVM). EVM JSON-RPC :8545/:8546
# is the dshackle/traefik upstream; CometBFT :26657 / gRPC :9090 / REST :1317 are NOT
# publicly exposed. Genesis replay is impractical (5 gov upgrades), so fresh nodes
# statesync near head via cometbft-common.sh.
set -e
. /usr/local/bin/cometbft-common.sh
@@ -12,6 +14,8 @@ STATESYNC_RPC="${STATESYNC_RPC:-https://tacchain-rpc.polkachu.com:443}"
MIN_GAS="${MIN_GAS:-25000000000utac}"
API="${API:-eth,net,web3,txpool,debug}"
MONIKER="${MONIKER:-rpc-node}"
# Pruning mode: set to "nothing" for archive nodes, "default" for pruned nodes.
PRUNING="${PRUNING:-default}"
ct_apk curl jq
@@ -24,7 +28,10 @@ else
ct_log "already initialized, continuing"
fi
# Serve CometBFT RPC on all interfaces (dshackle/traefik upstream); default is 127.0.0.1.
# Set pruning mode in app.toml (nothing for archive, default for pruned).
sed -i -e "s/^pruning *=.*/pruning = \"$PRUNING\"/" "$CONFIG_DIR/app.toml"
# Serve CometBFT RPC on all interfaces (internal only); default is 127.0.0.1.
sed -i '/^\[rpc\]/,/^\[/{s|^laddr = .*|laddr = "tcp://0.0.0.0:26657"|}' "$CONFIG_DIR/config.toml"
ct_patch_p2p "$CONFIG_DIR/config.toml" "$IP" "${P2P_PORT:-26656}"
@@ -41,4 +48,4 @@ sed -i "/^\[json-rpc\]/,/^\[/{s|^api = .*|api = \"$API\"|}" "$CONFIG_DIR/app.tom
ct_seed_priv_validator_state "$HOME_DIR"
exec tacchaind start --chain-id="$CHAIN_ID" --pruning=default --json-rpc.enable --home "$HOME_DIR" "$@"
exec tacchaind start --chain-id="$CHAIN_ID" --json-rpc.enable --home "$HOME_DIR" "$@"

View File

@@ -1,38 +1,43 @@
# tacchaind — source build mirroring upstream TacBuild/tacchain Dockerfile (v1.6.0).
# tacchaind — binary download from GitHub releases (v1.6.0).
# Downloads pre-built linux-amd64 binary and verifies against checksums.txt.
ARG VERSION=v1.6.0
FROM golang:1.23.8-alpine3.21 AS go-builder
ARG VERSION
RUN apk add --no-cache \
ca-certificates \
build-base \
git \
libusb-dev \
linux-headers \
eudev-dev
WORKDIR /code
RUN git clone https://github.com/TacBuild/tacchain.git /code && \
cd /code && \
git checkout "${VERSION}" && \
LEDGER_ENABLED=true make build
FROM alpine:3.21
RUN apk upgrade --no-cache && \
apk add --no-cache \
ca-certificates \
libusb
curl \
libusb \
sed
# Download binary and checksums
ARG VERSION
RUN set -eux; \
BINARY_URL="https://github.com/TacBuild/tacchain/releases/download/${VERSION}/tacchaind-linux-amd64" && \
CHECKSUMS_URL="https://github.com/TacBuild/tacchain/releases/download/${VERSION}/checksums.txt" && \
curl -sL -o /tmp/tacchaind "${BINARY_URL}" && \
curl -sL -o /tmp/checksums.txt "${CHECKSUMS_URL}" && \
# Verify the binary against checksums.txt (sha256sum format: <hash> <filename>)
cd /tmp && \
ExpectedHash=$(grep "tacchaind-linux-amd64$" checksums.txt | awk '{print $1}') && \
ActualHash=$(sha256sum tacchaind | awk '{print $1}') && \
if [ "$ExpectedHash" != "$ActualHash" ]; then \
echo "ERROR: checksum mismatch for tacchaind-linux-amd64" >&2; \
echo " Expected: $ExpectedHash" >&2; \
echo " Actual: $ActualHash" >&2; \
exit 1; \
fi && \
chmod +x /tmp/tacchaind && \
mv /tmp/tacchaind /usr/bin/tacchaind && \
rm -f /tmp/checksums.txt
COPY --from=go-builder /code/build/tacchaind /usr/bin/tacchaind
COPY ./scripts/cometbft-common.sh /usr/local/bin/cometbft-common.sh
COPY ./scripts/init.sh /usr/local/bin/init.sh
RUN chmod +x /usr/local/bin/init.sh /usr/local/bin/cometbft-common.sh
WORKDIR /opt
EXPOSE 1317 26656 26657
EXPOSE 1317 26656 26657 8545 8546
ENTRYPOINT ["init.sh"]

View File

@@ -0,0 +1,130 @@
---
x-logging-defaults: &logging-defaults
driver: json-file
options:
max-size: "512m"
max-file: "2"
# Usage:
#
# mkdir rpc && cd rpc
#
# git init
# git remote add origin https://github.com/StakeSquid/ethereum-rpc-docker.git
# git fetch origin vibe
# git checkout origin/vibe
#
# docker run --rm alpine sh -c "printf '0x'; head -c32 /dev/urandom | xxd -p -c 64" > .jwtsecret
#
# env
# ...
# IP=$(curl ipinfo.io/ip)
# DOMAIN=${IP}.traefik.me
# COMPOSE_FILE=base.yml:rpc.yml:tac/tacchaind/tac-mainnet-tacchaind-archive.yml
#
# docker compose up -d
#
# curl -X POST https://${IP}.traefik.me/tac-mainnet-archive \
# -H "Content-Type: application/json" \
# --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
services:
tac-mainnet-archive:
build:
context: ./tac
dockerfile: tacchaind.Dockerfile
args:
VERSION: ${TAC_MAINNET_TACCHAIND_VERSION:-v1.6.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle
net.ipv4.tcp_no_metrics_save: 1 # Disable metrics cache
net.ipv4.tcp_rmem: 4096 87380 16777216 # Increase TCP read buffers
net.ipv4.tcp_wmem: 4096 87380 16777216 # Increase TCP write buffers
net.core.somaxconn: 32768 # Higher connection queue
# Memory/Connection Management
# net.core.netdev_max_backlog: 50000 # Increase network buffer
net.ipv4.tcp_max_syn_backlog: 30000 # More SYN requests
net.ipv4.tcp_max_tw_buckets: 2000000 # Allow more TIME_WAIT sockets
ulimits:
nofile: 1048576 # Max open files (for RPC/WS connections)
user: root
ports:
- 11030:11030
- 11030:11030/udp
expose:
- 8545
- 8546
- 6065
environment:
- API=eth,net,web3,txpool,debug
- CHAIN_ID=tacchain_239-1
- GENESIS_URL=https://raw.githubusercontent.com/TacBuild/tacchain/refs/heads/main/networks/tacchain_239-1/genesis.json
- IP=${IP}
- MIN_GAS=25000000000utac
- MONIKER=d${DOMAIN:-local}
- P2P_PORT=11030
- PERSISTENT_PEERS=68b409519dea2a057d93ab6df436f6d519f13cb8@65.109.61.125:32156,54646e966938882d244a7dfd5aa90deb0cbae102@46.37.123.228:26656,d957eca9e06978956bca65a74d5df13ca858b3ec@209.192.185.53:26656,61bcffa521f20daea439e1560a282a6c6d13382a@136.243.54.160:26656,6224e4d06ace6591af55ffc9e033c1fd8492acf7@40.160.23.137:26656,4594eda1e728c1499801346fd99198e2f7c076dc@3.37.85.69:26656
- PRUNING=nothing
- SEEDS=68b409519dea2a057d93ab6df436f6d519f13cb8@65.109.61.125:32156,54646e966938882d244a7dfd5aa90deb0cbae102@46.37.123.228:26656,d957eca9e06978956bca65a74d5df13ca858b3ec@209.192.185.53:26656,61bcffa521f20daea439e1560a282a6c6d13382a@136.243.54.160:26656,6224e4d06ace6591af55ffc9e033c1fd8492acf7@40.160.23.137:26656,4594eda1e728c1499801346fd99198e2f7c076dc@3.37.85.69:26656
- STATESYNC_RPC=https://tacchain-rpc.polkachu.com:443
restart: unless-stopped
stop_grace_period: 5m
networks:
- chains
volumes:
- ${TAC_MAINNET_TACCHAIND_ARCHIVE_DATA:-tac-mainnet-tacchaind-archive}:/root/.tacchaind/data
- /slowdisk:/slowdisk
- tac-mainnet-tacchaind-archive_config:/root/.tacchaind/config
logging: *logging-defaults
labels:
- prometheus-scrape.enabled=true
- prometheus-scrape.port=6065
- prometheus-scrape.path=/metrics
- traefik.enable=true
- traefik.http.middlewares.tac-mainnet-tacchaind-archive-stripprefix.stripprefix.prefixes=/tac-mainnet-archive
- traefik.http.services.tac-mainnet-tacchaind-archive.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-archive.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-archive.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-archive.rule=Host(`$DOMAIN`) && (Path(`/tac-mainnet-archive`) || Path(`/tac-mainnet-archive/`))}
- ${NO_SSL:+traefik.http.routers.tac-mainnet-tacchaind-archive.rule=Path(`/tac-mainnet-archive`) || Path(`/tac-mainnet-archive/`)}
- traefik.http.routers.tac-mainnet-tacchaind-archive.middlewares=tac-mainnet-tacchaind-archive-stripprefix, ipallowlist
- traefik.http.routers.tac-mainnet-tacchaind-archive.priority=50 # gets any request that is not GET with UPGRADE header
- traefik.http.routers.tac-mainnet-tacchaind-archive-ws.priority=100 # answers GET requests first
- traefik.http.middlewares.tac-mainnet-tacchaind-archive-set-ws-path.replacepath.path=/websocket
- traefik.http.services.tac-mainnet-tacchaind-archive-ws.loadbalancer.server.port=8546
- traefik.http.routers.tac-mainnet-tacchaind-archive-ws.service=tac-mainnet-tacchaind-archive-ws
- traefik.http.routers.tac-mainnet-tacchaind-archive.service=tac-mainnet-tacchaind-archive
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-archive-ws.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-archive-ws.tls.certresolver=myresolver}
# case-insensitive Upgrade: python websocket-client sends "WebSocket" (capital W)
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-archive-ws.rule=Host(`$DOMAIN`) && (Path(`/tac-mainnet-archive`) || Path(`/tac-mainnet-archive/`)) && HeadersRegexp(`Upgrade`, `(?i)websocket`)}
- ${NO_SSL:+traefik.http.routers.tac-mainnet-tacchaind-archive-ws.rule=(Path(`/tac-mainnet-archive`) || Path(`/tac-mainnet-archive/`)) && HeadersRegexp(`Upgrade`, `(?i)websocket`)}
- traefik.http.routers.tac-mainnet-tacchaind-archive-ws.middlewares=tac-mainnet-tacchaind-archive-stripprefix, tac-mainnet-tacchaind-archive-set-ws-path, ipallowlist
volumes:
tac-mainnet-tacchaind-archive:
tac-mainnet-tacchaind-archive_config:
x-upstreams:
- id: $${ID}
labels:
provider: $${PROVIDER}
connection:
generic:
rpc:
url: $${RPC_URL}
ws:
frameSize: 20Mb
msgSize: 50Mb
url: $${WS_URL}
chain: tac
method-groups:
enabled:
- debug
- filter
methods:
disabled:
enabled:
- name: txpool_content # TODO: should be disabled for rollup nodes
...

View File

@@ -53,7 +53,8 @@ services:
- 10283:10283
- 10283:10283/udp
expose:
- 26657
- 8545
- 8546
- 6065
environment:
- API=eth,net,web3,txpool,debug
@@ -63,8 +64,9 @@ services:
- MIN_GAS=25000000000utac
- MONIKER=d${DOMAIN:-local}
- P2P_PORT=10283
- PERSISTENT_PEERS=68b409519dea2a057d93ab6df436f6d519f13cb8@65.109.61.125:32156,186e207d2c95e94a44ff613770aa269dac876013@65.108.201.240:32156,c4410e765acead36188500ed75d5666aa209c93d@65.108.205.121:32156
- SEEDS=68b409519dea2a057d93ab6df436f6d519f13cb8@65.109.61.125:32156
- PERSISTENT_PEERS=68b409519dea2a057d93ab6df436f6d519f13cb8@65.109.61.125:32156,54646e966938882d244a7dfd5aa90deb0cbae102@46.37.123.228:26656,d957eca9e06978956bca65a74d5df13ca858b3ec@209.192.185.53:26656,61bcffa521f20daea439e1560a282a6c6d13382a@136.243.54.160:26656,6224e4d06ace6591af55ffc9e033c1fd8492acf7@40.160.23.137:26656,4594eda1e728c1499801346fd99198e2f7c076dc@3.37.85.69:26656
- PRUNING=default
- SEEDS=68b409519dea2a057d93ab6df436f6d519f13cb8@65.109.61.125:32156,54646e966938882d244a7dfd5aa90deb0cbae102@46.37.123.228:26656,d957eca9e06978956bca65a74d5df13ca858b3ec@209.192.185.53:26656,61bcffa521f20daea439e1560a282a6c6d13382a@136.243.54.160:26656,6224e4d06ace6591af55ffc9e033c1fd8492acf7@40.160.23.137:26656,4594eda1e728c1499801346fd99198e2f7c076dc@3.37.85.69:26656
- STATESYNC_RPC=https://tacchain-rpc.polkachu.com:443
restart: unless-stopped
stop_grace_period: 5m
@@ -81,7 +83,7 @@ services:
- prometheus-scrape.path=/metrics
- traefik.enable=true
- traefik.http.middlewares.tac-mainnet-tacchaind-pruned-stripprefix.stripprefix.prefixes=/tac-mainnet
- traefik.http.services.tac-mainnet-tacchaind-pruned.loadbalancer.server.port=26657
- traefik.http.services.tac-mainnet-tacchaind-pruned.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-pruned.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-pruned.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-pruned.rule=Host(`$DOMAIN`) && (Path(`/tac-mainnet`) || Path(`/tac-mainnet/`))}
@@ -90,7 +92,7 @@ services:
- traefik.http.routers.tac-mainnet-tacchaind-pruned.priority=50 # gets any request that is not GET with UPGRADE header
- traefik.http.routers.tac-mainnet-tacchaind-pruned-ws.priority=100 # answers GET requests first
- traefik.http.middlewares.tac-mainnet-tacchaind-pruned-set-ws-path.replacepath.path=/websocket
- traefik.http.services.tac-mainnet-tacchaind-pruned-ws.loadbalancer.server.port=26657
- traefik.http.services.tac-mainnet-tacchaind-pruned-ws.loadbalancer.server.port=8546
- traefik.http.routers.tac-mainnet-tacchaind-pruned-ws.service=tac-mainnet-tacchaind-pruned-ws
- traefik.http.routers.tac-mainnet-tacchaind-pruned.service=tac-mainnet-tacchaind-pruned
- ${NO_SSL:-traefik.http.routers.tac-mainnet-tacchaind-pruned-ws.entrypoints=websecure}

View File

@@ -0,0 +1,129 @@
---
x-logging-defaults: &logging-defaults
driver: json-file
options:
max-size: "512m"
max-file: "2"
# Usage:
#
# mkdir rpc && cd rpc
#
# git init
# git remote add origin https://github.com/StakeSquid/ethereum-rpc-docker.git
# git fetch origin vibe
# git checkout origin/vibe
#
# docker run --rm alpine sh -c "printf '0x'; head -c32 /dev/urandom | xxd -p -c 64" > .jwtsecret
#
# env
# ...
# IP=$(curl ipinfo.io/ip)
# DOMAIN=${IP}.traefik.me
# COMPOSE_FILE=base.yml:rpc.yml:tac/tacchaind/tac-spb-tacchaind-archive.yml
#
# docker compose up -d
#
# curl -X POST https://${IP}.traefik.me/tac-spb-archive \
# -H "Content-Type: application/json" \
# --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}'
services:
tac-spb-archive:
build:
context: ./tac
dockerfile: tacchaind.Dockerfile
args:
VERSION: ${TAC_SPB_TACCHAIND_VERSION:-v1.6.0}
sysctls:
# TCP Performance
net.ipv4.tcp_slow_start_after_idle: 0 # Disable slow start after idle
net.ipv4.tcp_no_metrics_save: 1 # Disable metrics cache
net.ipv4.tcp_rmem: 4096 87380 16777216 # Increase TCP read buffers
net.ipv4.tcp_wmem: 4096 87380 16777216 # Increase TCP write buffers
net.core.somaxconn: 32768 # Higher connection queue
# Memory/Connection Management
# net.core.netdev_max_backlog: 50000 # Increase network buffer
net.ipv4.tcp_max_syn_backlog: 30000 # More SYN requests
net.ipv4.tcp_max_tw_buckets: 2000000 # Allow more TIME_WAIT sockets
ulimits:
nofile: 1048576 # Max open files (for RPC/WS connections)
user: root
ports:
- 11180:11180
- 11180:11180/udp
expose:
- 8545
- 8546
- 6065
environment:
- API=eth,net,web3,txpool,debug
- CHAIN_ID=tacchain_2391-1
- GENESIS_URL=https://raw.githubusercontent.com/TacBuild/tacchain/refs/heads/main/networks/tacchain_2391-1/genesis.json
- IP=${IP}
- MIN_GAS=25000000000utac
- MONIKER=d${DOMAIN:-local}
- P2P_PORT=11180
- PERSISTENT_PEERS=9c32b3b959a2427bd2aa064f8c9a8efebdad4c23@206.217.210.164:45130,04a2152eed9f73dc44779387a870ea6480c41fe7@206.217.210.164:45140,5aaaf8140262d7416ac53abe4e0bd13b0f582168@23.92.177.41:45110,ddb3e8b8f4d051e914686302dafc2a73adf9b0d2@23.92.177.41:45120
- PRUNING=nothing
- STATESYNC_RPC=https://spb.tendermint.rpc.tac.build
restart: unless-stopped
stop_grace_period: 5m
networks:
- chains
volumes:
- ${TAC_SPB_TACCHAIND_ARCHIVE_DATA:-tac-spb-tacchaind-archive}:/root/.tacchaind/data
- /slowdisk:/slowdisk
- tac-spb-tacchaind-archive_config:/root/.tacchaind/config
logging: *logging-defaults
labels:
- prometheus-scrape.enabled=true
- prometheus-scrape.port=6065
- prometheus-scrape.path=/metrics
- traefik.enable=true
- traefik.http.middlewares.tac-spb-tacchaind-archive-stripprefix.stripprefix.prefixes=/tac-spb-archive
- traefik.http.services.tac-spb-tacchaind-archive.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-archive.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-archive.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-archive.rule=Host(`$DOMAIN`) && (Path(`/tac-spb-archive`) || Path(`/tac-spb-archive/`))}
- ${NO_SSL:+traefik.http.routers.tac-spb-tacchaind-archive.rule=Path(`/tac-spb-archive`) || Path(`/tac-spb-archive/`)}
- traefik.http.routers.tac-spb-tacchaind-archive.middlewares=tac-spb-tacchaind-archive-stripprefix, ipallowlist
- traefik.http.routers.tac-spb-tacchaind-archive.priority=50 # gets any request that is not GET with UPGRADE header
- traefik.http.routers.tac-spb-tacchaind-archive-ws.priority=100 # answers GET requests first
- traefik.http.middlewares.tac-spb-tacchaind-archive-set-ws-path.replacepath.path=/websocket
- traefik.http.services.tac-spb-tacchaind-archive-ws.loadbalancer.server.port=8546
- traefik.http.routers.tac-spb-tacchaind-archive-ws.service=tac-spb-tacchaind-archive-ws
- traefik.http.routers.tac-spb-tacchaind-archive.service=tac-spb-tacchaind-archive
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-archive-ws.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-archive-ws.tls.certresolver=myresolver}
# case-insensitive Upgrade: python websocket-client sends "WebSocket" (capital W)
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-archive-ws.rule=Host(`$DOMAIN`) && (Path(`/tac-spb-archive`) || Path(`/tac-spb-archive/`)) && HeadersRegexp(`Upgrade`, `(?i)websocket`)}
- ${NO_SSL:+traefik.http.routers.tac-spb-tacchaind-archive-ws.rule=(Path(`/tac-spb-archive`) || Path(`/tac-spb-archive/`)) && HeadersRegexp(`Upgrade`, `(?i)websocket`)}
- traefik.http.routers.tac-spb-tacchaind-archive-ws.middlewares=tac-spb-tacchaind-archive-stripprefix, tac-spb-tacchaind-archive-set-ws-path, ipallowlist
volumes:
tac-spb-tacchaind-archive:
tac-spb-tacchaind-archive_config:
x-upstreams:
- id: $${ID}
labels:
provider: $${PROVIDER}
connection:
generic:
rpc:
url: $${RPC_URL}
ws:
frameSize: 20Mb
msgSize: 50Mb
url: $${WS_URL}
chain: tac-spb
method-groups:
enabled:
- debug
- filter
methods:
disabled:
enabled:
- name: txpool_content # TODO: should be disabled for rollup nodes
...

View File

@@ -53,7 +53,8 @@ services:
- 14331:14331
- 14331:14331/udp
expose:
- 26657
- 8545
- 8546
- 6065
environment:
- API=eth,net,web3,txpool,debug
@@ -64,6 +65,7 @@ services:
- MONIKER=d${DOMAIN:-local}
- P2P_PORT=14331
- PERSISTENT_PEERS=9c32b3b959a2427bd2aa064f8c9a8efebdad4c23@206.217.210.164:45130,04a2152eed9f73dc44779387a870ea6480c41fe7@206.217.210.164:45140,5aaaf8140262d7416ac53abe4e0bd13b0f582168@23.92.177.41:45110,ddb3e8b8f4d051e914686302dafc2a73adf9b0d2@23.92.177.41:45120
- PRUNING=default
- STATESYNC_RPC=https://spb.tendermint.rpc.tac.build
restart: unless-stopped
stop_grace_period: 5m
@@ -80,7 +82,7 @@ services:
- prometheus-scrape.path=/metrics
- traefik.enable=true
- traefik.http.middlewares.tac-spb-tacchaind-pruned-stripprefix.stripprefix.prefixes=/tac-spb
- traefik.http.services.tac-spb-tacchaind-pruned.loadbalancer.server.port=26657
- traefik.http.services.tac-spb-tacchaind-pruned.loadbalancer.server.port=8545
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-pruned.entrypoints=websecure}
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-pruned.tls.certresolver=myresolver}
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-pruned.rule=Host(`$DOMAIN`) && (Path(`/tac-spb`) || Path(`/tac-spb/`))}
@@ -89,7 +91,7 @@ services:
- traefik.http.routers.tac-spb-tacchaind-pruned.priority=50 # gets any request that is not GET with UPGRADE header
- traefik.http.routers.tac-spb-tacchaind-pruned-ws.priority=100 # answers GET requests first
- traefik.http.middlewares.tac-spb-tacchaind-pruned-set-ws-path.replacepath.path=/websocket
- traefik.http.services.tac-spb-tacchaind-pruned-ws.loadbalancer.server.port=26657
- traefik.http.services.tac-spb-tacchaind-pruned-ws.loadbalancer.server.port=8546
- traefik.http.routers.tac-spb-tacchaind-pruned-ws.service=tac-spb-tacchaind-pruned-ws
- traefik.http.routers.tac-spb-tacchaind-pruned.service=tac-spb-tacchaind-pruned
- ${NO_SSL:-traefik.http.routers.tac-spb-tacchaind-pruned-ws.entrypoints=websecure}

View File

@@ -35,7 +35,7 @@ services:
context: ./zero-gravity
dockerfile: zerog.Dockerfile
args:
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_ARISTOTLE_ZEROG_VERSION:-3.0.3}
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_ARISTOTLE_ZEROG_VERSION:-1.0.6}
ZERO_GRAVITY_CHAIN_SPEC: aristotle
sysctls:
# TCP Performance
@@ -118,7 +118,7 @@ services:
context: ./zero-gravity
dockerfile: zerog.Dockerfile
args:
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_ARISTOTLE_ZEROG_VERSION:-3.0.3}
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_ARISTOTLE_ZEROG_VERSION:-1.0.6}
ZERO_GRAVITY_CHAIN_SPEC: aristotle
ports:
- 19914:19914

View File

@@ -35,7 +35,7 @@ services:
context: ./zero-gravity
dockerfile: zerog.Dockerfile
args:
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_GALILEO_ZEROG_VERSION:-3.0.3}
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_GALILEO_ZEROG_VERSION:-3.0.8}
ZERO_GRAVITY_CHAIN_SPEC: galileo
sysctls:
# TCP Performance
@@ -62,7 +62,7 @@ services:
- CHAIN_NAME=galileo
entrypoint: [/usr/local/bin/init.sh, geth]
command:
- --bootnodes=enode://de7b86d8ac452b1413983049c20eafa2ea0851a3219c2cc12649b971c1677bd83fe24c5331e078471e52a94d95e8cde84cb9d866574fec957124e57ac6056699@8.218.88.60:30303
- --bootnodes=enode://4c94b9ab893ee17f58505b48b53d6c02d52bd5567e5fc6c028a29ad25ce1d553286a7fe3a6c051ba18c466b196762ab0e92695d6abbaf7f70afcca2e165bca8c@34.82.252.10:30303
- --config /0g/geth-config.toml
- --datadir=/root/.ethereum
- --db.engine=pebble
@@ -118,7 +118,7 @@ services:
context: ./zero-gravity
dockerfile: zerog.Dockerfile
args:
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_GALILEO_ZEROG_VERSION:-3.0.3}
ZERO_GRAVITY_VERSION: ${ZERO_GRAVITY_GALILEO_ZEROG_VERSION:-3.0.8}
ZERO_GRAVITY_CHAIN_SPEC: galileo
ports:
- 16172:16172
@@ -140,7 +140,7 @@ services:
- --chaincfg.node-api.logging
- --home=/root/.0g
- --p2p.external_address=${IP}:16172
- --p2p.seeds=${ZERO_GRAVITY_GALILEO_ZEROG_SEEDS:-85a9b9a1b7fa0969704db2bc37f7c100855a75d9@8.218.88.60:26656}
- --p2p.seeds=${ZERO_GRAVITY_GALILEO_ZEROG_SEEDS:-e0750abc061d276894a593c7b671913c14aa4ecc@34.82.252.10:26656}
- --pruning=default
- --rpc.laddr=tcp://0.0.0.0:16172
restart: unless-stopped

View File

@@ -61,23 +61,41 @@ env
# seems to be the same for all the 0g chains
if [ ! -f "$DATA_DIR/priv_validator_state.json" ]; then
echo "priv_validator_state.json not found in $HOME_DIR. Proceeding with initialization steps..."
# $DATA_DIR and $CONFIG_DIR are SEPARATE docker volumes (<node>_zerog and <node>_config),
# so the presence of a file in one says nothing about the other. Guard on everything
# 0gchaind actually needs to boot, and write the copies so the LAST file written is the
# one that would make a re-run skip.
#
# The previous version guarded solely on $DATA_DIR/priv_validator_state.json and copied it
# BEFORE the two key files. An init interrupted between those copies therefore left the
# data volume with the guard file and the config volume without the keys, so every later
# start took the "Already initialized" branch, copied nothing, and 0gchaind panicked:
#
# panic: error calling provider ProvideBlsSigner (node-core/components/signer.go:46):
# key file does not exist at path: /root/.0g/config/priv_validator_key.json
#
# That state is unrecoverable by restarting - the node wedges permanently. Hit on
# aristotle @ rpc-de-32 on 2026-08-12 after repeated build failures interrupted its init.
if [ ! -f "$CONFIG_DIR/priv_validator_key.json" ] || \
[ ! -f "$CONFIG_DIR/node_key.json" ] || \
[ ! -f "$DATA_DIR/priv_validator_state.json" ]; then
echo "0g: node identity incomplete (config keys and/or priv_validator_state.json). Initializing..."
TMP_DIR=$(mktemp -d)
# You can add any additional initialization logic here if needed
if /0g/bin/0gchaind init ${MONIKER} --chaincfg.chain-spec ${CHAIN_SPEC} --home $TMP_DIR; then
cp -r /0g/0g-home/0gchaind-home/config/* $CONFIG_DIR
cp $TMP_DIR/data/priv_validator_state.json $DATA_DIR
# Keys first, guard file last: if this is interrupted the next start re-runs
# initialization instead of latching into the wedged state described above.
cp $TMP_DIR/config/node_key.json $CONFIG_DIR
cp $TMP_DIR/config/priv_validator_key.json $CONFIG_DIR
cp $TMP_DIR/data/priv_validator_state.json $DATA_DIR
else
echo "Already initialized, continuing!" >&2
fi
rm -rf $TMP_DIR # delete tmp dir
else
echo "priv_validator_state.json found in $HOME_DIR. Continuing!" >&2
echo "Already initialized, continuing!" >&2
echo "0g: node identity complete (config keys + priv_validator_state.json). Continuing!" >&2
fi
exec /0g/bin/0gchaind $@

View File

@@ -8,22 +8,96 @@ RUN chmod +x /usr/local/bin/init.sh
ARG ZERO_GRAVITY_VERSION
ARG ZERO_GRAVITY_CHAIN_SPEC
RUN if [ "${ZERO_GRAVITY_CHAIN_SPEC}" = "aristotle" ]; then \
curl -sL https://github.com/0gfoundation/0gchain-Aristotle/releases/download/${ZERO_GRAVITY_VERSION}/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}.tar.gz -o /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}.tar.gz; \
# 0G publishes each network from a DIFFERENT repository, and the release-tag format is not
# consistent within either of them. The asset filename is always "<spec>-v<version>.tar.gz";
# only the tag varies:
#
# aristotle -> 0gchain-Aristotle 1.0.4 = "1.0.4" 1.0.6 = "v1.0.6"
# galileo -> 0gchain-NG 3.0.3 = "v3.0.3" 3.0.7 = "galileo-v3.0.7" 3.0.8 = "v3.0.8"
#
# So the tag cannot be hardcoded. Resolve it by trying the known forms in order and taking
# the first that actually yields the asset.
#
# curl uses -f (fail on HTTP error). Without it a 404 HTML body is written into the .tar.gz
# and the build dies several layers later at "tar: not in gzip format" -- an error that
# points nowhere near the real cause. That has cost three separate misdiagnoses.
#
# Strip leading 'v' from version if present, to avoid doubled-v (e.g., vv1.0.6 -> v1.0.6)
RUN set -eu; \
VERSION="${ZERO_GRAVITY_VERSION#v}"; \
ASSET="${ZERO_GRAVITY_CHAIN_SPEC}-v${VERSION}.tar.gz"; \
if [ "${ZERO_GRAVITY_CHAIN_SPEC}" = "aristotle" ]; then \
REPO="0gfoundation/0gchain-Aristotle"; \
TAGS="${VERSION} v${VERSION} ${ZERO_GRAVITY_CHAIN_SPEC}-v${VERSION}"; \
else \
curl -sL https://github.com/0gfoundation/0gchain-NG/releases/download/v${ZERO_GRAVITY_VERSION}/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}.tar.gz -o /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}.tar.gz; \
fi
RUN tar -xzf /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}.tar.gz -C /tmp
RUN if [ "${ZERO_GRAVITY_CHAIN_SPEC}" = "galileo" ]; then \
mv /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}/rpc /0g; \
else \
mkdir -p /0g && \
cp -a /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}/* /0g/ 2>/dev/null || true; \
cp -a /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}/.[^.]* /0g/ 2>/dev/null || true; \
rm -rf /tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${ZERO_GRAVITY_VERSION}; \
REPO="0gfoundation/0gchain-NG"; \
TAGS="v${VERSION} ${ZERO_GRAVITY_CHAIN_SPEC}-v${VERSION} ${VERSION}"; \
fi; \
for TAG in ${TAGS}; do \
URL="https://github.com/${REPO}/releases/download/${TAG}/${ASSET}"; \
echo "0g: trying tag '${TAG}' -> ${URL}"; \
if curl -fsSL "${URL}" -o "/tmp/${ASSET}"; then \
echo "0g: resolved ${ZERO_GRAVITY_CHAIN_SPEC} ${VERSION} from tag '${TAG}'"; \
break; \
fi; \
rm -f "/tmp/${ASSET}"; \
done; \
if [ ! -s "/tmp/${ASSET}" ]; then \
echo "0g: FATAL - no release asset '${ASSET}' found in ${REPO} under any of: ${TAGS}" >&2; \
echo "0g: aristotle and galileo are SEPARATE repos on SEPARATE version lines" >&2; \
echo "0g: aristotle = 0gchain-Aristotle (1.0.x), galileo = 0gchain-NG (3.0.x)" >&2; \
echo "0g: check that version '${VERSION}' exists for '${ZERO_GRAVITY_CHAIN_SPEC}'" >&2; \
exit 1; \
fi
RUN chmod +x /0g/bin/0gchaind
RUN chmod +x /0g/bin/geth
# Extract by DETECTING the layout rather than assuming one per chain. 0G has shipped
# three shapes so far and changed between them without notice:
#
# galileo <= 3.0.3 <root>/{rpc,validator,archive,seed}/bin/ + configs (168 MB)
# -> binaries duplicated inside every profile dir
# galileo >= 3.0.8 <root>/bin/ shared, + <root>/{rpc,...}/ configs (69 MB)
# -> binaries DEDUPLICATED to the tarball root; rpc/ is configs-only
# aristotle 1.0.x <root>/bin/ + configs, no profile dirs at all
#
# So "does <root>/rpc exist" is NOT sufficient: 3.0.8 still has rpc/, it just no longer
# holds bin/. Take configs from the profile dir when present, then top the binaries up
# from the shared root bin/ if the profile dir didn't supply them.
#
# The old code hardcoded "galileo -> mv <root>/rpc /0g", so 3.0.8 yielded configs but no
# binaries and the build died two layers later at
# `chmod: cannot access '/0g/bin/0gchaind'` — an error naming neither the chain, the
# version, nor the layout. Assert on the binaries here so the NEXT upstream reshuffle
# reports itself at the point of failure instead.
RUN set -eu; \
VERSION="${ZERO_GRAVITY_VERSION#v}"; \
SRC="/tmp/${ZERO_GRAVITY_CHAIN_SPEC}-v${VERSION}"; \
tar -xzf "${SRC}.tar.gz" -C /tmp; \
mkdir -p /0g; \
if [ -d "${SRC}/rpc" ]; then \
echo "0g: profile dir found -> ${SRC}/rpc"; \
cp -a "${SRC}/rpc/." /0g/; \
else \
echo "0g: no profile dir -> using tarball root ${SRC}"; \
cp -a "${SRC}/." /0g/; \
fi; \
if [ ! -f /0g/bin/0gchaind ] && [ -d "${SRC}/bin" ]; then \
echo "0g: binaries not in the profile dir -> topping up from ${SRC}/bin"; \
cp -a "${SRC}/bin" /0g/; \
fi; \
rm -rf "${SRC}"; \
missing=""; \
for b in 0gchaind geth; do \
[ -f "/0g/bin/${b}" ] || missing="${missing} bin/${b}"; \
done; \
if [ -n "${missing}" ]; then \
echo "0g: FATAL - missing after extraction:${missing}" >&2; \
echo "0g: ${ZERO_GRAVITY_CHAIN_SPEC} v${VERSION} tarball layout not recognised." >&2; \
echo "0g: known layouts are '<root>/rpc/bin/' (per-profile) and '<root>/bin/' (flat)." >&2; \
echo "0g: contents of /0g were:" >&2; \
ls -la /0g >&2 || true; \
exit 1; \
fi; \
chmod +x /0g/bin/0gchaind /0g/bin/geth
ENTRYPOINT [ "init.sh" ]
ENTRYPOINT [ "init.sh" ]