From d270504c9acb9be758a36eca342becc9ea56669f Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Thu, 19 Feb 2026 02:22:52 +0300 Subject: [PATCH] fix: handle pruned RPC state in SubQuery node Substrate nodes prune historical state by default (~256 blocks). When SubQuery restarts and tries to fetch runtime version for old blocks, it crashes with "State already discarded". This patch adds a fallback to current runtime version when historical state is unavailable, preventing crash loops on pruned RPC endpoints. Also removes --pull from deploy workflow to avoid unnecessary full rebuilds on every push. --- .github/workflows/deploy-vps.yml | 2 +- docker/Dockerfile.node | 5 +++ docker/patches/pruned-state-fallback.js | 54 +++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 docker/patches/pruned-state-fallback.js diff --git a/.github/workflows/deploy-vps.yml b/.github/workflows/deploy-vps.yml index bbb2e10..734864e 100644 --- a/.github/workflows/deploy-vps.yml +++ b/.github/workflows/deploy-vps.yml @@ -15,5 +15,5 @@ jobs: script: | cd /opt/subquery git pull origin main - docker compose -f docker-compose.prod.yml build --pull + docker compose -f docker-compose.prod.yml build docker compose -f docker-compose.prod.yml up -d diff --git a/docker/Dockerfile.node b/docker/Dockerfile.node index a2df3d1..1b01fbf 100644 --- a/docker/Dockerfile.node +++ b/docker/Dockerfile.node @@ -41,4 +41,9 @@ RUN node -e " \ fs.writeFileSync(p, JSON.stringify(pkg, null, 2)); \ " +# Patch: handle pruned blockchain state gracefully. +# Without this, SubQuery crashes when RPC nodes have pruned old block state. +COPY docker/patches/pruned-state-fallback.js /tmp/pruned-state-fallback.js +RUN node /tmp/pruned-state-fallback.js && rm /tmp/pruned-state-fallback.js + ENTRYPOINT ["/app/node_modules/.bin/subql-node"] diff --git a/docker/patches/pruned-state-fallback.js b/docker/patches/pruned-state-fallback.js new file mode 100644 index 0000000..72aa73b --- /dev/null +++ b/docker/patches/pruned-state-fallback.js @@ -0,0 +1,54 @@ +/** + * Patch @subql/node to handle pruned blockchain state. + * + * Problem: Substrate nodes prune historical state by default (~256 blocks). + * When SubQuery restarts and tries to fetch runtime version for old blocks, + * state_getRuntimeVersion fails with "State already discarded" and the node + * enters a crash loop. + * + * Fix: When getRuntimeVersion(blockHash) fails due to pruned state, + * fall back to getRuntimeVersion() (current runtime version). + * This is safe for chains with infrequent spec upgrades. + */ +const fs = require("fs"); + +// --- Patch 1: base-runtime.service.js --- +// Handles getSpecFromApi() and getRuntimeVersion() which call +// this.api.rpc.state.getRuntimeVersion(parentBlockHash) +const runtimeFile = + "/app/node_modules/@subql/node/dist/indexer/runtime/base-runtime.service.js"; +let runtimeCode = fs.readFileSync(runtimeFile, "utf8"); + +const runtimeMatches = ( + runtimeCode.match( + /await this\.api\.rpc\.state\.getRuntimeVersion\(\w+\)/g + ) || [] +).length; + +runtimeCode = runtimeCode.replace( + /await this\.api\.rpc\.state\.getRuntimeVersion\((\w+)\)/g, + "await this.api.rpc.state.getRuntimeVersion($1).catch(() => this.api.rpc.state.getRuntimeVersion())" +); +fs.writeFileSync(runtimeFile, runtimeCode); +console.log(`Patched base-runtime.service.js (${runtimeMatches} call sites)`); + +// --- Patch 2: utils/substrate.js --- +// Handles fetchRuntimeVersionRange() which calls +// api.rpc.state.getRuntimeVersion(hash).catch((e) => { throw ... }) +const utilsFile = + "/app/node_modules/@subql/node/dist/utils/substrate.js"; +let utilsCode = fs.readFileSync(utilsFile, "utf8"); + +const utilsBefore = utilsCode.includes("getRuntimeVersion(hash).catch((e)"); + +// Insert a fallback .catch before the existing error handler: +// Original: getRuntimeVersion(hash).catch((e) => { throw... }) +// Patched: getRuntimeVersion(hash).catch(() => getRuntimeVersion()).catch((e) => { throw... }) +utilsCode = utilsCode.replace( + /api\.rpc\.state\.getRuntimeVersion\(hash\)\.catch\(\(e\)/g, + "api.rpc.state.getRuntimeVersion(hash).catch(() => api.rpc.state.getRuntimeVersion()).catch((e)" +); +fs.writeFileSync(utilsFile, utilsCode); +console.log(`Patched utils/substrate.js (had target: ${utilsBefore})`); + +console.log("All pruned-state patches applied.");