Repository navigation
Update ghcr.io/streamingfast/firehose-ethereum Docker tag to v2.24.1 - #50
Merged
kw1knode merged 4 commits intoOct 23, 2026
Merged
Conversation
Contributor
Author
Edited/Blocked NotificationRenovate will not automatically rebase this PR, because it does not recognize the last commit author and assumes somebody else may have edited the PR. You can manually request rebase by checking the rebase/retry box above. |
renovate
Bot
deleted the
renovate/ghcr.io-streamingfast-firehose-ethereum-2.x
branch
October 23, 2026 06:24
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
v2.17.3→v2.24.1Release Notes
streamingfast/firehose-ethereum (ghcr.io/streamingfast/firehose-ethereum)
v2.24.1Compare Source
Changed
With continuous authentication (
?continuous=trueon thegrpcauth plugin), each continuousAuthenticateresponse now refreshes the stream's trusted headers. A stream whose first authentication returned no headers (for example an authenticator that failed open) is metered with its identity from the next continuous call on, instead of without it for its whole lifetime.Substreams: Tier2 skips loading dependency stores and compiling WASM modules for a segment once its cached block index proves every module excludes all of the segment's blocks. It still writes the same (empty) output files as before.
Bump
firehose-coreto v1.21.1,dauthto 75940c1 andsubstreamsto 5ed941a.v2.24.0Compare Source
Added
Store URLs, such as
--common-merged-blocks-store-url, acceptcompression_configto tune how files are written, matching the store's compression: a zstd level with an optional window in MiB (best,better/32), or a gzip level from1to9. For examplegs://bucket/merged-blocks?compression_config=best/32. Files written with any setting are read back without configuration. An invalid value makes opening the store fail.On zstd stores,
compression_configalso takes decoder settings, comma separated after the level or alone:lowmem=falsegives each decoder a history buffer of twice the window, andpool=<name>reuses decoders across reads and across every store using that name, in place of thedefaultpool.pool=nonegives each file a decoder of its own. For example--common-merged-blocks-store-url=gs://bucket/merged-blocks?compression_config=best/32,lowmem=false,pool=blocksand--substreams-state-store-url=gs://bucket/states?compression_config=better/16,pool=cache. Substreams tier2 gets them through the URLs tier1 sends. Reading 100 MiBbest/32merged blocks files 10 at a time, addinglowmem=falseto thedefaultpool used 70% less CPU and 57% more peak heap.lowmem=falseonly helps files larger than their window plus about 1 MiB.Call.input_truncatedandCall.return_data_truncatedinsf.ethereum.type.v2. They flag the calls whose input was cut to its 4-byte selector, or whose return data was left out, by a tracer that limits how much call input and return data a transaction or block records. A trace without such limits never sets them.New
fireeth tools convert-to-v5 <src> <dest> <start> <stop>, which rewrites merged-blocks files of version 2, 3 or 4 as version 5 blocks. For each block it:Call.keccak_preimagesonly the preimages of at most 256 bytes that explain a storage slot written by the same transaction or system call, like the tracers do;begin_ordinalof 0 (the root call of every transaction in version 3 and below), and to the genesis root call recorded with anend_ordinalof 0;DELEGATEcalls of version 2 blocks to the address of the closest ancestor that is not aDELEGATEcall;TransactionTrace.return_data, and sets the input of a rootCREATEcall to the transaction's input;Log.indexto the position of the log among all the logs of its transaction, logs of reverted calls included;Call.input_truncatedandCall.return_data_truncated.With
--target-bundle-size, it writes merged-blocks files of a different number of blocks than the source files (--merged-blocks-bundle-size), likeresize-merged-blocks, in the same pass.With
--rpc-endpoint, it also fetchesBlock.withdrawalson the blocks whose headerwithdrawals_rootannounces withdrawals that the block does not hold. Chains and block ranges without withdrawals make no RPC call.When the source store ends before
<stop>, it checks for the next source file every 5 seconds until it reaches<stop>, so it can run ahead of the chain and catch up as new files are merged.The known issues of version 3 that the block alone cannot fix are left as they are, so the output differs from tracer-produced version 5 blocks on those fields. See the command's help for the list.
On Google Cloud Storage, each merged-blocks file it writes carries the same annotation the merger writes (data size, block count and first block time), read by
firecore tools stats-merged-blocks. The otherfireeth toolscommands that rewrite merged-blocks files write it too.Substreams:
--substreams-tier1-allow-pruned-chainlets tier1 serve modules (mappers and stores) whose initial block is below--common-first-streamable-block, after that block was raised (the chain was pruned). They run from the first streamable block on, and their stores resume from the snapshots taken before the pruning. A request is refused when a store has no snapshot at the first streamable block to resume from, or when a store has no initial block: every store must set one, at or above the first streamable block. Tier2 must run this version before the flag is set on tier1.Substreams
eth_callandeth_getBalancecalls are now sent in the metering events asexternal_calls_eth_callandexternal_calls_eth_getBalance. A batch counts for as many calls as it contains, and retries are not counted.fireeth tools resize-merged-blocks --fix-misplaced-blocksrewrites a store where merged-blocks files hold blocks outside of their range, as the empty files of a gap in block numbers can (ex: near-testnet around block5346171). Each block is written to the file of its own number, with its parent number set to the last block written. A block at or below the last block written is skipped, and one that does not have it as parent stops the process with an error. Without the flag, such a store fails withcontains block ..., beyond the configured bundle size.fireeth tools substreams prune-states,prune-outputsandpurge:--delete-rate(default500) caps deletions per second,0removes the cap.Fixed
Firehose and substreams-tier1 now return the sessions they still hold to the session server before exiting, whatever
--common-system-shutdown-signal-delayis set to. Sessions of requests cut by the shutdown used to be released in the background while the process exited, so they stayed counted against the organization until they expired on the session server, and a client reconnecting right away could be refused withConcurrent stream limit exceeded. This applies to session plugins implementingClose(ctx) error, which thetgm://plugin does.Substreams: Fix tier1 requests rejected before their body is read (authentication, compression enforcement) sometimes failing at a load balancer with HTTP 502 or
INTERNALinstead of returning their error.fireeth tools compare-blocks-rpc/compare-oneblock-rpcno longer report false differences forheader.slot_number(EIP-7843) and the EIP-4844 blob fields (blob_gas,blob_gas_fee_cap,blob_hashes, receiptblob_gas_used/blob_gas_price) on Amsterdam-fork blocks. The RPC side of the comparison silently dropped these fields when rebuilding a block frometh_getBlockByNumber/eth_getTransactionReceipt, becauseeth-go's RPC types had no fields to decode them into; bumpedeth-goto pick up the fix and mapped the new fields inblock.RpcToEthBlock.fireeth tools compare-blocks-rpc/compare-oneblock-rpcno longer report false differences onset_code_authorizations(EIP-7702) for transactions carrying aSetCode(type 4) authorization list.eth-go's RPCTransactiontype had no field to decodeauthorizationList; bumpedeth-goto add it and mapped the tuples inblock.RpcToEthBlock, recoveringauthorityfrom the tuple's signature the same way the Firehose tracer does.discardedstays Firehose-only (it depends on execution-time account state the RPC doesn't expose) and is excluded from the comparison, same asCalls/ReturnData.Substreams: Fix a tier1 panic (
INTERNAL: runtime error: invalid memory address or nil pointer dereference) on a production-mode request starting past the chain's final block, when that final block falls within the first segment of the request's stores.Firehose, substreams-tier1 and the relayer no longer exit with
received 5 consecutive unlinkable blocksorcannot link block after reconnectionwhen they start while the merger is deleting the one-block files of a bundle it just merged.fireeth tools compare-blocks-rpcno longer panics with a nil pointer dereference when a Firehose transaction trace has no receipt (observed on reth for an Amsterdam transaction that runs out of gas before its root frame). A missing receipt is now reported as a diff instead of crashing the tool.BlockHeader.block_access_list_hashandblock_access_list_rlp(added to the proto in v2.23.0) were never actually present in the generated Go code, sincetypes/pbwasn't regenerated after the proto change. This made everycompare-blocks-rpc/compare-oneblock-rpccomparison on a block carrying either field fail with an empty, uninformative diff. Bumpedfirehose-ethereum/typesto pick up the regenerated code and compareblock_access_list_hashagainst RPC'seth_getBlockByNumber;block_access_list_rlpstays Firehose-only and is excluded from the comparison, same asCalls/ReturnData.fireeth tools compare-blocks-rpc/compare-block-store-rpcno longer report awithdrawalsdiff on every block carrying EIP-4895 validator withdrawals.block.RpcToEthBlockonly filledHeader.withdrawals_rootand never populatedBlock.withdrawalsfrom the RPC block, while the Firehose side always has them, so the comparison flagged a difference on every such block.index-builderno longer silently skips a block's index write on a transient index store error, nor restarts indexing from the configured start block afterwards. Bumpedbstreamandfirehose-coresoBlockIndexer.Addreturns the write error instead of only logging it, andEthCombinedIndexer.ProcessBlocknow propagates it. (firehose-core#288)Substreams: Fix requests failing with
snapshot "…" does not existafter--common-first-streamable-blockwas raised, for stores without an initial block. Their snapshots named after the previous first streamable block are now ignored and the store is rebuilt.Substreams: Fix dev-mode requests reading blocks below the first streamable block when that block is not a multiple of the segment size.
Substreams: Fix requests with a cursor being refused with
invalid start block …, must be >= …when their start block is below the first streamable block. The block the cursor resolves to is what is checked now.Substreams: Fix the live back filler (started after a quickload) stopping for the rest of the request when it rewound below the first streamable block, or to a snapshot named after another initial block.
index-buildernow serves its health check, over gRPC and HTTP/healthz, on--index-builder-grpc-listen-addr, which it never listened on.Startup no longer lowers the open-files limit (
RLIMIT_NOFILE) to 24,576 when it can't raise it to 1,000,000 and the current limit is already higher, as in containers withoutCAP_SYS_RESOURCE(k0s: 999,999, containerd 2.x: 524,288). It now keeps the current limit.Substreams: Fix wasm calls that fail because the host ran out of file descriptors or memory (for example
Too many open fileswhile creating an instance) being reported as deterministic errors, which returnedInvalidArgumentand were cached for 1h. They now returnInternal, and the shared cache runs the call again instead of handing the same error to every stream on that block.The tools writing merged-blocks files (
resize-merged-blocks,download-from-firehoseand others) now fail instead of writing a block to a file whose range does not hold it. The previous bundle's last block carried into the files of a gap is still written.Merger no longer moves canonical one-block files to the forked-blocks store when readers write the same block with a different LIB, as happens on Polygon PoS / Amoy. Only blocks with another ID at the same height are moved.
The reader now always clamps a decoded block's
lib_numdown to its own block number when the node/plugin reportslib_numgreater than the block number (invalid;lib_numequal to the block number is still valid), logs an error and incrementsreader_node_invalid_libnum_clamped_count, instead of letting the bad value reach the relayer where it moved LIB past head and silently stalled it forever.zstd stores (merged blocks, one-block files, substreams states) now close the object's HTTP stream or file when a reader is closed before the end of the file. Streams stopping mid-bundle, at their stop block or when the client goes away, used to leave it open.
fireeth tools substreams prune-states,prune-outputsandpurgeno longer fail deletions on GCS with429 rateLimitExceeded("request distribution is too uneven across the key-ranges" or "request-rate on the bucket has ramped up too fast"). They now delete in random order instead of listing order, which sent every concurrent deletion to the same part of the bucket, are capped by the new--delete-rateflag, and retry a failed deletion 5 times over several seconds instead of twice over 50ms.Changed
Substreams: production-mode requests disconnected for being more than 2 segments behind the last final block now get
back-processing ended too far behind the last final block of the chain, please reconnect to process the gap in parallelorstream fell too far behind the last final block of the chain, please reconnect to process the gap in parallelinstead ofendpoint is shutting down, please reconnect. Both are stillUnavailable.Apps running in the same process now share one session pool, created from
--common-session-plugin, instead of each creating their own. With thelocal://plugin, firehose and substreams-tier1 running together now count against the samemax_sessionsandmax_sessions_per_organizationlimits.Bump
firehose-networkstov0.3.0.Subscribers to live blocks (relayer, firehose and substreams subscriptions to live) are no longer disconnected as soon as 100 blocks are waiting for them. On fast chains, a live source pausing for a few seconds and then sending its backlog at once filled those 100 slots before a subscriber could send the first block, disconnecting every subscriber at the same time. A subscriber is now disconnected when it has not caught up for
--common-live-subscriber-catch-up-timeout(default30s), which catches one that is stuck or slower than the chain, or when--common-live-subscriber-max-buffered-blocks(default10000) blocks are waiting for it.The relayer, firehose and substreams-tier1 no longer look up one-block files, nor log
block not linkable after one-block lookup, when a live source sends a block they already have below LIB. With several--relayer-source, this happened every few seconds.Substreams: tier2 logs
refusing Substreams ProcessRange requestand the gRPCfinished streaming call with code ResourceExhaustedline atDebuginstead ofInfo. Thesubstreams_tier2_rejected_request_countermetric still counts the refusals by reason.Substreams: tier1 handles Ethereum partial blocks about 20x faster, with almost no allocations. It reads the transaction traces straight from the encoded block and drops the ones already sent by copying bytes, instead of decoding and re-encoding the whole block for every partial.
Merged blocks are read without copying their payload, which halves the memory allocated per merged-blocks file read (25.8 MiB instead of 50.8 MiB for 100 blocks of 256 KiB).
Go 1.27 or newer is now required to build.
fireeth tools resize-merged-blocksnow accepts equal--source-bundle-sizeand--target-bundle-sizeto rewrite merged-blocks files with different settings (ex: compression), after a confirmation prompt (--forceskips it). It also clears the deprecatedpayload_kindandpayload_bufferfields from every block written; the block data stays inpayload.firehoseandsubstreams-tier1launched in the same process now share one forkable hub instead of each holding its own copy of the live blocks; on Solana that saves about 0.5 GiB per process. Firehose still streams complete blocks only.The firehose hub keeps as many final blocks as the substreams-tier1 hub: two merged-blocks files worth, at least 200, instead of at least 500. Clients resuming between 200 and 500 blocks below the last irreversible block now read those blocks from merged-blocks files.
zstd stores (merged blocks, one-block files, substreams states) now read with decoders kept in a process-wide
defaultpool whencompression_confignames no pool, and those decoders work on the reading goroutine instead of 4 background ones. Reading 100 MiBbest/32merged blocks files 10 at a time, that used 6% less CPU and 53% less peak heap; on 2 MiB files, 42% less CPU.compression_config=pool=nonebrings back a decoder per file.Firehose, substreams-tier1 and the relayer now detect a relayer or reader that disappears without closing the connection (for example a host reboot) in about 40s instead of 5 minutes. They send gRPC keepalive pings after
30swithout data and drop the connection after10swithout an answer.fireeth tools pollerprints eachFIRE BLOCKline 11-15x faster and no longer allocates several times the block size to do it: the line is built once in a reused buffer, with SIMD base64 encoding.Bump
firehose-coreto v1.21.0,bstreamto 71724f9 andsubstreamsto d18ca66.Removed
--firehose-discard-partial-blocksflag. Firehose clients only receive complete blocks, and the hub firehose builds when it runs without substreams-tier1 never receives partial blocks from the relayer, so the flag no longer did anything. Operators setting it must drop it, as an unknown flag stops the process from starting.Deprecated
SOURCE_CHAN_SIZEenvironment variable, replaced by--common-live-subscriber-max-buffered-blocks. It is still used when the flag is not set, with a warning at startup.Security
Bump
golang.org/x/nettov0.61.0to fix CVE-2026-78663 (critical), CVE-2026-78669, CVE-2026-78660 and CVE-2026-78659 (high).Bump OpenTelemetry to
v1.47.0to fix known vulnerabilities.v2.23.0Compare Source
Added
New
FIREETH_COMPARE_IGNORE_KECCAK_PREIMAGES_ABOVE_256(aliasFIREETH_TOOLS_COMPARE_IGNORE_KECCAK_PREIMAGES_ABOVE_256), which dropsCall.keccak_preimagesentries whose preimage exceeds 256 bytes from both blocks before comparing. It lets a node running a tracer that caps the map be compared against one that still emits the large entries, without hiding every other preimage the wayFIREETH_COMPARE_IGNORE_KECCAKdoes. Applies tofireeth tools compare-blocksand to a reader node in--reader-node-test-mode.New
fireeth tools remove-large-keccak-preimages <src> <dest> <start> <stop>, which rewrites merged-blocks files dropping everyCall.keccak_preimagesentry whose preimage exceeds 256 bytes. It brings a range written before the tracer cap in line with one written after it. Preimages carry no ordinal, so nothing else in the block moves and the block version is unchanged.New
BlockHeader.block_access_list_hashandBlockHeader.block_access_list_rlpfields for EIP-7928 block access lists, scheduled for the Amsterdam hard fork. Both are ignored in legacy headers; only the hash is committed to by the header itself.block_access_list_rlpis experimental, populated for measurement purposes only, and must not be depended on until StreamingFast declares it stable.Added Morph specific fields to the Ethereum block model. All of them are unset on every other chain.
New
TransactionTrace.TypevaluesTRX_TYPE_MORPHfor MorphTx (0x7f) andTRX_TYPE_MORPH_L1_MESSAGEfor L1 message transactions (0x7e). The L1 message gets its own value because it shares its type byte with Optimism's deposit transaction but has different semantics.New
TransactionTrace.morph_tx_configholding the MorphTx fieldsversion,fee_token_id,fee_limit,referenceandmemo.New
TransactionTrace.morph_l1_message_configholding the L1 messagequeue_index.New
TransactionReceipt.morph_receipt_configholdingl1_fee,fee_rateandtoken_scale.New
BlockHeader.morph_next_l1_msg_indexrecording the queue index at which the next block resumes processing L1 messages.--relayer-sourceaccepts aretry_interval=<duration>query parameter (e.g.my.source:12345?retry_interval=120s) setting the minimum time between two connection attempts to that source. Use it for a rescuer or fallback endpoint that is expected to be down most of the time, so the relayer does not dial it (and log the failure) every 5s.retry_intervalmust be at least5s, and since sources are checked every 5s it is rounded up to the next 5s increment (e.g.12sbehaves as15s). Without it, a source is retried every 5s.gRPC clients receive responses up to 2 GiB instead of 1 GiB, and servers send responses up to 2 GiB (bumped
dgrpc), so a block up to 2 GiB goes from the reader through the relayer to Firehose and Substreams. Request limits are unchanged. External clients need their own receive limit raised to get blocks that big.New
--substreams-tier1-cpu-eviction-orderflag (defaultdev,prod-cached,prod-catchup) listing the request classes the CPU eviction may cancel, least important first. A class left out is never cancelled, so live production requests are no longer cancelled unlessprod-liveis added to the order. See thesubstreamsbump below for the newprod-cachedclass.New
--substreams-tier1-max-request-durationflag (default0, no limit). When set, a Substreams request that has run for that long is ended gracefully: its stores are quick-saved (with--substreams-tier1-quicksave-store) and the client is told to reconnect. Set it a bit under the stream duration limit of the load balancer serving requests.New
--substreams-tier1-squasher-pluginflag sofireeth startcan pointsubstreams-tier1at a remote store-merge (squasher) process. Empty orlocal://keeps in-process squashing. Production usesgrpcs://host?secret=<token>: TLS, port defaults to 443,secretis sent as theauthorizationheader. Plaintext local/dev usesgrpc://host:port. Extra TLS/plaintext toggles stay on the DSN query string (insecure=true,plaintext=false).Reader: the base64 payload of a
FIRE BLOCKline (Firehose protocol 3.x) is decoded while the line is read, instead of after the whole line was copied into a string, using thefirehose-coreline splitter, which decodes withgithub.com/emmansun/base64likefireethdid before. Reading a block needs about 1.5 times its line size in memory instead of about 3 times. With--reader-node-debug-firehose-logs, aFIRE BLOCKline is logged asFIRE BLOCK <header> <payload: N bytes decoded>instead of its full base64 text.Changed
Documented in
Call.keccak_preimagesthat only preimages of 256 bytes or less are recorded. The map is there so a consumer can walk a storage slot back to the expression that produced it, and Solidity's slot derivations are small: 32 bytes for a dynamic array or a longbytes/string, 64 bytes for a mapping with a value-type key, and 32 bytes plus the key for amapping(string => V). 256 bytes covers all of those, with room for a 224-byte dynamic key. A larger preimage is a contract hashing its own data, and is dropped rather than truncated, since a truncated preimage no longer hashes back to its key.--reader-node-line-buffer-sizeis now the normal size of the line buffer (default 100 MiB, at least 32 KiB). The buffer grows past it in pieces of that size for a longer line, up to2861913428bytes: the longest line whose block, with 1 MiB for the message around it, fits in a 2 GiB response. Each time 50 blocks in a row used less than half of it, it shrinks by half, keeping whole pieces and never going below the normal size. A grown buffer stays allocated until it shrinks back.--reader-node-line-buffer-sizeoutside of32768to2861913428bytes is now refused at startup instead of being used as is. A reader configured above that range does not start until the value is lowered.Bumped
firehose-coreto v1.20.1, which needs thegithub.com/ShinyTrinkets/overseerreplace directive moved togithub.com/streamingfast/overseer v0.2.1-0.20260917150444-9ebead8ffdef.Bumped
bstreamto v0.0.2-0.20260921191230-ea57fcbd4fbb for the per-source retry interval backingretry_intervalon--relayer-source.Bumped
substreamsto v1.23.0:Server: production requests that fall far behind the chain are disconnected so they reconnect and back-process the gap in parallel. The main case is a long back-processing: when it finishes, the request would otherwise process everything the chain produced in the meantime linearly on tier1, which can take hours. A production request is disconnected when it is more than 2 segments behind the last final block (rounded down to a segment), checked when back-processing finishes and at every segment boundary while streaming final blocks. It gets the same
Unavailable"endpoint is shutting down, please reconnect" error as a tier1 restart, so clients reconnecting from their cursor pick up where they left off. Set theSUBSTREAMS_MAX_LINEAR_HANDOFF_LAG_SEGMENTSenvironment variable on tier1 to change the number of segments.Server: the CPU eviction order is configurable. Classes are cancelled in the configured order, highest burn first within a class and oldest first on a tie. A new
prod-cachedclass covers production requests that have not processed a block on tier1 yet, only streaming outputs cached by tier2. They run no wasm on tier1, so--substreams-tier1-cpu-eviction-min-burn-coresdoes not apply to them (--substreams-tier1-cpu-eviction-min-agestill does), and since their CPU cost is unknown, a round of eviction stops right after cancelling one; the next round, after--substreams-tier1-cpu-eviction-cooldown, measures what it freed. Thesubstreams_tier1_evicted_requests_countermetric gains theprod-cachedclass.Server: per-store lines are logged at
Debuginstead ofInfo:using mmap KV store,using in-memory KV store,flushing store at boundary,merged partial into full store,deleting partial store.squashing time metricsstays atInfo.Server: the tier1 block hub logs
processing blockatDebug, except for one line every 10 seconds kept atInfoto show progress.linking live block using one blocksmoves up toInfo, so a one-block store lookup after an unlinkable live block shows in production logs.Server: tier1 can merge stores on a remote squasher selected by
--substreams-tier1-squasher-plugin. Empty orlocal://stays in-process. Each remote run is one RPC per store module; if the remote is unreachable that run is squashed locally and later runs stay local until one succeeds. A live remote that returns an application error still fails the request.Security
google.golang.org/grpctov1.85.0-dev.0.20260825072537-93e31b48545e, the commit that fixes CVE-2026-84445.v1.84.0is still in the affected range; this pin comes in withsubstreamsv1.23.0.v2.22.0Compare Source
Added
New
--substreams-tier1-cpu-eviction-*flags lettingsubstreams-tier1shed requests when its own cgroup reports the CPU saturated.--substreams-tier1-cpu-eviction-mode(defaultoff, so nothing changes until you set it) selects what it does:observelogs the requests it would cancel without cancelling any,dev-onlycancels development-mode requests,fullcancels production ones as well. See the bump above for what a round of eviction does.The rest are tunables, each defaulting to the value the eviction was designed around:
--substreams-tier1-cpu-eviction-threshold0.90--substreams-tier1-cpu-eviction-sustain15s--substreams-tier1-cpu-eviction-recover-threshold0.75--substreams-tier1-cpu-eviction-recover-sustain30s--substreams-tier1-cpu-eviction-target-ratio0.75--substreams-tier1-cpu-eviction-interval5s--substreams-tier1-cpu-eviction-cooldown15s--substreams-tier1-cpu-eviction-drain-delay8s--substreams-tier1-cpu-eviction-min-age90s--substreams-tier1-cpu-eviction-min-burn-cores0.05--substreams-tier1-cpu-eviction-quota-cores-override0cpu.max--substreams-tier1-cpu-eviction-nominal-capacity0substreams_tier1_effective_active_requestsmetric and nothing else.0takes--substreams-tier1-active-requests-soft-limitThresholds and ratios are fractions of the CPU budget. That budget is the CPU limit the cgroup carries, so an instance running under cgroup v2 with no limit set —
cpu.maxreadingmax— has nothing to compare usage to and logs a warning at startup, leaving the eviction off;--substreams-tier1-cpu-eviction-quota-cores-overridenames the budget yourself in that case, and should stay at or under whatever limit the kernel does enforce, since above it the instance is throttled before the eviction ever fires. Reading the cgroup CPU files failing outright, cgroup v1 included, also logs a warning and leaves the eviction off. Every one of these warnings is only emitted when the mode is notoff.Added
fireeth tools annotate-merged-blocks <gs-store-url>, which writes thedatasize,itemcountandtimestampmetadata entries above on merged-blocks files written before the merger did it. Files a previous run already annotated are skipped straight from the listing, so a rerun over a mostly-done store costs one listing and nothing else.--parallelism(32 by default) sets how many files are read at once;--start-block,--stop-block,--overwriteand--dry-runare supported. Google Cloud Storage only.Added
fireeth tools stats-merged-blocks <gs-store-url>, which reports a merged-blocks store's total compressed and uncompressed size, block count, compression ratio and bytes per block, broken down by month. Nothing is downloaded: every number comes from those three metadata entries, which come back with the listing, so the whole report costs one listing however large the range is. Google Cloud Storage only.Added
fireeth tools last-oneblock <oneblocks-store>which prints the highest block number found among the store's one-block files, as a bare number on stdout, exiting non-zero when the store cannot be listed, holds no one-block file, or a filename does not parse as one.Added
fireeth tools substreams purge <state-url>which deletes substreams module caches that have not been used recently, reading thelast_used*.zstmarkers thatsubstreams-tier1refreshes on every request it serves. Retention can be per billing plan (--retention default=30d,pro=30d,scaling=14d,free=3d), a module folder being kept as soon as one of its markers is within its own plan's retention. Direct tier1 layouts (<state-url>/<hash>,<state-url>/<tag>/<hash>) and shared network roots (<state-url>/<network>/substreams-states/<tag>/<hash>) are both recognized. Supports--dry-run,--scan-only,--keepglob patterns,--read-marker-contentsfor stores whose objects were copied (which resets last-write times), and--daemon --interval 12hfor unattended purging. See thefirehose-corechangelog for the full details.Added
fireeth tools substreams prune-states <store-url> --keep-every <blocks> --truncate-below-block <block>to delete intermediate store snapshots (<end>-<start>.kvfiles) of modules still in use, keeping the first snapshot of each--keep-every-aligned window. Only snapshots whose end block is at or below--truncate-below-blockare thinned, the most recent snapshot of each module is always kept, and--minimum-ageadditionally spares recently written ones.substreams-tier1rebuilds stores from the last remaining snapshot before the requested block (see the bump above), so pruning trades disk space for reprocessing time. Supports--dry-run.Added
fireeth tools substreams prune-outputs <store-url> --network <network> --truncate-below-block <block> --minimum-age <age>to delete execution output files (<start>-<end>.outputunder a module'soutputs/folder) that are BOTH fully below--truncate-below-blockAND last modified longer than--minimum-ageago, the modification time coming straight out of the listing. Module folders carrying an spkg (directly-queried output modules) are kept untouched unless--output-module-minimum-ageis set. Supports--dry-runand--force.Changed
Bumped
bstreamto enable parallel one-blocks downloading upon bootstrap or reconnect (very useful on fast chains)Bumped
golang.org/x/cryptotov0.56.0, clearing CVE-2026-78662 and CVE-2026-56855 (both HIGH), which the Docker Scout scan of the published image fails on.Bumped
google.golang.org/grpcto v1.83.2, which fixes CVE-2026-84445.Bumped
substreamsto v1.22.1-0.20260903162505-4035f21109ec:Server:
substreams-tier1scheduling no longer slows down as a large backprocessing range progresses. Picking the next tier2 job walked every segment between the squasher and the job frontier on every call, re-checking dependencies that could not have changed, so a run over N segments cost O(N²) in scheduling. The scheduler now keeps, per stage, the lowest segment that may still be pending and the highest segment completed so far, and only looks at the handful of segments those point at. On a 3-stage graph with 4 workers and a squasher three times slower than the jobs, scheduling 8000 segments went from 1.18s to 2.7ms, and now grows linearly with the range. Job order is unchanged.Server:
substreams-tier1now downloads the next cached execution output files while it streams the current one to a production-mode client. Before, each 1000-block segment was opened, decompressed and sent before the next one was even requested from the object store, so every segment paid a full store round trip on the critical path. Prefetching is bounded per request, with no flag to set: at most 4 segments ahead, holding at most 64 MiB of decompressed data. No size is ever asked of the store, the decompressed size of the last downloaded segment being the estimate for the next ones, and a file bigger than the whole budget turns prefetching off for the rest of the request. Missing files are left to the existing retry loop.Server:
substreams-tier1now sends each batch of cached execution output on its own goroutine, so decoding the next batch overlaps with compressing and writing the current one. At most one batch is being built while one is sent, and a segment is only reported done once every batch is out, so message order is unchanged. Decoding a batch also copies one less time, the item now aliasing the buffer it was read into.Server:
substreams-tier1writes thesubstreams.spkgandlast_usedcache markers in the background instead of before the pipeline starts, so those object store round trips no longer delay the first block sent. They run detached from the request with a 30 second timeout: the request neither starts nor exits waiting for them, and a client that disconnects early still leaves its usage marker behind.Bumped
substreamsto v1.22.1-0.20260908190533-360e41fbc42f:CLI: a manifest can now import
sf/substreams/sink/sql/schema/v1/schema.protowithout vendoring a copy of it. The file is a system protobuf, butprotoparseneeds the source on disk to honour its extensions, so an import previously failed withno such file. It is now served from an embedded copy, the same waysf/substreams/options.protoalready was.Server: fixed partial-blocks (flashblocks) streams on a tier1 that is shutting down. The stream now ends with
Unavailablelike a full-block stream does, so the client reconnects elsewhere. It used to stay open but silent, then send an undo signal at each block boundary naming a block the client had never received. An undo signal is also no longer sent for partial-block state whose outputs were never sent.Server:
substreams_tier1_effective_active_requestscould read belowsubstreams_active_requests, the metric it is meant to replace as the horizontal autoscaler input. Requests still setting up were counted by one and not the other, so a tier1 instance with requests queued in setup looked emptier to the autoscaler than it was.Bumped
substreamsto v1.22.1-0.20260911133942-1b7d09c2de7b:Server:
substreams-tier1squashes store partials in runs of up to 1000 segments or 30 s of work per command instead of one segment per command, which capped squashing at about 15 segments per minute on busy backprocessing requests. Segments whose partials are all empty get a copy of the previous full store instead of a merge, server-side on object stores that support it.Server:
substreams-tier1no longer closes the squasher's cached stores while a squash is still running. When the scheduler stopped early (a tier2 job failed, or the instance was shutting down), the in-flight merge could panic the process or write an empty full store to storage.Server:
substreams-tier1asks the relayer for every block from its own LIB when it connects or reconnects, instead of the last 2 blocks, so a gap left by a disconnect is filled from the relayer's memory rather than from the one-block store.Bumped
dstoreto v0.2.4-0.20260911133316-3b0685e87595: S3CopyObjectis done server-side (multipart above 5 GiB) instead of downloading and uploading the object back, falling back to the old behaviour on backends answeringNotImplementedorMethodNotAllowed.Bumped
firehose-coreto v1.18.1-0.20260911145235-fa9c1e143386 (untagged, released asv1.19.0):Merger: no longer spins when a walk over one-block files keeps hitting the unlinkable-blocks limit. A merger stuck behind a gap in one-block files re-walked and logged
too many unlinkable blocks, continuing to next loopabout ten times per second; it now waits--merger-time-between-store-lookupslike any other iteration, and the line is logged atWarn.Merger: the unlinkable-blocks limit (
bundleSize*4) can be overridden with theMERGER_MAX_UNLINKABLE_BLOCKSenv var, for deployments where the default is too tight (e.g. several one-block-file writers per chain).Block poller:
no clients have been working for over 1 minute, still retryingis only logged when fetches have actually been failing for over a minute, and the per-block lines (about to fetch block,processing block,saved cursor, ...) are logged atDebuginstead ofInfo.Firehose: the
Blockshandler logs anincoming firehose Blocks requestline when a request starts, and thefirehose process completedline gainsduration,time_to_first_dataandfirst_sent_block. Both lines carrytrace_idand are emitted for every outcome, early rejections and client disconnects included.reader-node-firehoseno longer re-emitsSTEP_UNDOresponses from its upstream endpoint as new blocks; it logs and skips them.Tools:
fireeth tools compare-blocks --fieldsprints the protobuf field paths that differ for each mismatched block, andfireeth tools stats-merged-blocksgains--jsonand shows the first block of each month.Bumped
firehose-coreto v1.18.1-0.20260902155646-475a571f0fe2 andsubstreamsto v1.22.1-0.20260902153244-5658911b40ce:Server: store snapshots (fullKV files) can now be pruned to save disk space:
substreams-tier1no longer assumes that a fullKV at blockximplies that every earlier fullKV still exists. At request start it walks backwards from the first segment needing work, in growing listing windows, until it finds the last block where every store module still has a snapshot, and rebuilds the stores from there. Only snapshots actually seen are reused, and a job is only scheduled once the previous segment of every lower stage is done, so a pruned file is never read. The newfireeth tools substreams prune-statescommand (below) is what does the pruning.Server: a store snapshot deleted while a request that still needs it is running now fails that request with
FailedPrecondition: ... does not exist (it may have been pruned); restart the requestinstead of retrying the tier2 job forever. Likewise, a mapper output file deleted after its job completed is re-produced after 30 seconds instead of being waited on forever, and a scheduler deadlock on partials left by an interrupted run (a unit shadowed under one merging a partial from disk) is fixed.Server: the initial store lookup no longer falls back to listing a store's whole history when its last snapshot is far behind: it lists at most a handful of bounded windows.
Server: progress messages are sent far less often, the cadence widening with the age of the request — every second for the first minute, every 10 seconds up to 5 minutes, every 30 seconds up to 10 minutes, then every minute — including in the linear phase, which previously sent one every 200ms. Progress messages count as egress like any other response, so a long-running or live request paid for a steady stream of them.
Request.progress_messages_interval_msis now honoured (it was validated and then ignored): setting it pins the cadence for the whole request, the 500ms minimum unchanged.Server:
substreams-tier1now names the usage marker it writes in every module cache folder after the request's plan tier:last_used_<plan>(lowercase, e.g.last_used_pro), still plainlast_usedwhen unauthenticated.fireeth tools substreams purgereads the plan back from that name to apply a retention per plan.Server:
substreams-tier1can now evict requests when its CPU is saturated, which the new--substreams-tier1-cpu-eviction-*flags below turn on. When its own cgroup reports CPU usage above 90% of quota for 15 seconds, the instance advertises itself as unready to the load balancer, refuses new requests, waits 8 seconds for the balancer to stop routing to it, then cancels enough of the heaviest requests withUnavailableto bring usage back under 75% of quota, so their clients reconnect elsewhere. Development-mode requests are cancelled first, then production requests on live blocks, then production requests still catching up from files — a live request burning two cores keeps burning them for as long as it stays connected, while a catching-up one is spending them on work that ends, and cancelling it throws away the segment progress it would have to redo. Newsubstreams_tier1_cpu_*gauges and asubstreams_tier1_evicted_requests_counterreport what it sees and does, inobservemode included.Server: new
substreams_tier1_effective_active_requestsgauge, meant to replacesubstreams_active_requestsas the horizontal autoscaler input on tier1: the higher of the plain active-request count and the number of requests the CPU budget is being spent at. An instance full of expensive requests, or one holding its CPU down by cancelling requests, then reports itself at capacity and the autoscaler adds instances instead of leaving the count looking healthy.Server: the jobs of a tier1 request now reach the tier2 fleet in the order the client will read their output, instead of racing each other for whatever instance has room. A job asks a per-request launch queue for its turn before every request it sends to a tier2, first attempt and retries alike, and leaves the queue as soon as a tier2 takes it. The queue is ordered by lowest segment first and, within a segment, by highest stage, and only its first 20% may dial at all — at least two jobs, at most 15 — the ones behind sending nothing until a job ahead gets in and moves the window up. A job with nothing queued ahead of it dials immediately, so a fleet with room is paced no differently than before. Without this, the segment the client reads first was no more likely to land than one it would only read minutes later, and a whole request could idle behind a single unlucky low segment. Tunable with
SUBSTREAMS_WORKER_LAUNCH_WINDOW_PERCENT(default20) andSUBSTREAMS_WORKER_LAUNCH_WINDOW_MAX(default15).Server: a tier2 job that fails is no longer retried on a growing 1s-to-5s backoff of its own; every reason to dial again goes through that same queue, so retries keep the request's reading order. A job that could not get in at all (
ResourceExhausted: service currently overloaded, a refused connection,Unavailable: no healthy upstream) waits 100ms, and a job that got in and then failed waits 5 seconds once — if a tier2 then turns it away for capacity, it is back on the 100ms delay with its failure already counted. The counters that end a hopeless request are unchanged: five failures, or two execution timeouts, and neither is charged for a job that never got in. Tunable withSUBSTREAMS_WORKER_OVERLOADED_RETRY_DELAY(default100ms, was300ms),SUBSTREAMS_WORKER_FAILED_RETRY_DELAY(default5s) andSUBSTREAMS_WORKER_OVERLOADED_RETRY_JITTER(default100ms, added to a wait so jobs turned away at the same instant do not redial in lockstep).Server: jobs are now scheduled up to twice the request's worker count ahead of the blocks the client is reading, instead of 1.5 times, so a client that reads slowly still keeps the workers busy.
Server:
substreams-tier2logs far less per segment. A large backfill fans out into tens of thousands ofProcessRangecalls, each of which was writing about tenInfolines with no steady-state diagnostic value, enough to raise an instance's log volume by an order of magnitude. The duplicate auth-info log in the response handler is gone, the store-size and exec-output-file-open logs are nowDebug, and so are the four shutdown-lifecycle logs of the per-requestdmeteringevent emitter, which is opened and torn down on everyProcessRangecall. The benignhttp2: server: error reading preface ...: connection reset by peerlogged whenever a client drops a connection mid-handshake against the plaintext/h2c tier2 port is suppressed, like the existing TLS-handshake ones.The merger now records what a merged-blocks file holds on the object itself, as three custom metadata entries written as it uploads each bundle:
datasize, the file's size once decompressed,itemcount, the number of blocks it holds, andtimestamp, the time of its first block. A listing then tells what a file holds without reading it. Google Cloud Storage only: no other backend keeps custom object metadata a listing reads back, so elsewhere nothing is written and nothing changes. An annotation that cannot be written never fails the merge.fireeth tools substreams prune-statesandprune-outputsdelete much faster: deletions now run on their own--delete-parallelism(250 by default) instead of sharing the listing's--parallelism(16 and 64), each attempt is bounded at 5s instead of 30s, and a failed deletion is retried once after 50ms instead of four times over 7.5s. A deletion that still fails is reported as before and picked up by the next run.fireeth tools substreams purge,prune-statesandprune-outputsall skip any module folder carrying aDO_NOT_PRUNEfile at its root, next to thelast_used*.zstmarkers, and report how many folders that spared.fireeth tools substreams logs connectionnow takes its time range via a--sinceflag instead of a positional[<date-range>]argument, matchinglogs connections. Both commands'--sincenow accepts the same grammar aslogs reexec's date-range argument: a relative duration (30m,2h,1d,1w,"1 day ago", …), a single timestamp, or a<start>/<end>range.Dependencies:
google.golang.org/grpcmoves from v1.83.0 to v1.83.1, which clears GHSA-vp52-pcj8-j9qc, reported as HIGH: a peer could exhaust server heap by fragmenting HTTP/2 DATA frames.Removed
--reader-node-firehose-compressionflag. It has never had any effect: the connection to the upstream endpoint always uses zstd. Operators setting it must drop it, as an unknown flag stops the process from starting.v2.21.0Compare Source
Added
Added Ethereum Hoodi testnet (
hoodi,eip155:560048) StreamingFast Firehose and Substreams endpoints (hoodi.eth.streamingfast.io:443).New
substreams/spkgs/eth-methods-v0.1.0.spkg, a diagnostic Substreams package exercising therpc::eth_callandrpc::eth_get_balanceWASM extensions against the endpoints an instance was started with. Each extension gets its own module,map_eth_callandmap_eth_get_balance, so a setup configuring only one of--substreams-rpc-endpoints/--substreams-rpc-get-balance-endpointscan still be tested,map_eth_methodsruns both. Those modules also get one WASM binary each,map_transfersandmap_eth_methodssharing a third importing neither extension: a module resolves its imports when it is instantiated rather than when it calls them, so a single binary mentioning both extensions is refused wholesale (unknown import: 'rpc::eth_get_balance' has not been defined) by an instance registering only one of them, whatever the module being run. Every block performs at least one single-request invocation and one batched one per method, tunable through thecall_ratio,balance_ratio,call_batch_sizeandbalance_batch_sizeparameters. Call targets and queried accounts are resolved out of each block, so the package runs unchanged against any EVM chain;--networkpins a per chain contract formainnet,base,arbitrum-one,optimism,matic,bsc,sepoliaandhoodi, and thecall_toparameter pins an arbitrary one. On a chain quiet enough that a block carries no transfer, or no transaction at all, which regularly happens on a testnet, the target resolution degrades in steps and each report says throughstatus,target_sourceandhintswhat it could and could not do, so a quiet chain never reads as a failing endpoint. The packed artifact is committed and is what tools and operators should use, sources live undersubstreams/modules/eth_methods.Block comparison: new
FIREETH_COMPARE_IGNORE_WITHDRAWALS=truewhich removes every trace of beacon chain withdrawals from both sides before comparing: thConfiguration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.