Documentation / @etherfold/core / LogFetcher
Class: LogFetcher<ABI> ​
Defined in: packages/core/src/logFetcher.ts:298
The LOG-FETCHER of ADR-0003: the chain-facing half of a split deployment, whose defining property is that it holds no state worth losing.
One operation, fetchAndPush(): work out where to start, fetch a contiguous range of logs, and push it. WHEN that runs is a host's business (a Node loop, a Worker cron), which is why nothing here schedules anything.
What it does not have, and why that is the design ​
No cursor, no unconfirmed window, no reorg logic. The receiver owns all three (ADR-0004), and that is what makes at-least-once on the wire exactly-once in effect: the cursor IS the idempotency key, so a batch re-sent after a lost acknowledgement is refused with a 409 and corrected instead of applied twice. A fetcher that persisted its own cursor and trusted it would have re-introduced the split brain the contract removes: two opinions of one value, one of which is wrong exactly when it matters.
What it DOES keep between cycles is a HINT (cursorHint): the last expectedFromBlock the receiver reported. It is a cache, never an authority. It saves one round-trip per cycle in the ordinary case; it is dropped the moment a push fails, since a failed push may or may not have been applied; and when it is wrong, the 409 that follows is not an error but the normal correction path. Losing it costs one extra request and nothing else, which is the test for whether a piece of state is safe to hold here.
The LEARNED RANGE (learnedRange) is the one thing held here that FAILS that test, and the way it is handled is the answer to it rather than an exception. Losing it costs more than one request: the range fetcher re-pays the walk up from the small starting range, being refused as it goes. It is still only performance, so the response is not to give this class a store -- which would hand it a copy it could be restored from when stale, a lifecycle to own and a place to put the next block number, which is the split brain ADR-0004 removes -- but to move the MEMORY out: it is REPORTED (limits, and a host's status surface) and CONFIGURED BACK IN (fetch.learnedRange), so a deployment that wants a restart to resume where discovery left off keeps that value wherever it already keeps configuration (ADR-0074). Nothing here writes it down, and a run that configures none rediscovers exactly as before.
The one thing it must never get wrong ​
A payload holds EVERY log in [fromBlock, toBlock]. Providers cap eth_getLogs, and a short payload delivered as a complete range is read by the receiver as an absence, an absence is a reorg, and a reorg deletes state. So truncation is expressed by LOWERING toBlock (never by delivering part of a range), a result set landing exactly on the cap is treated as suspect rather than as an answer, and the envelope is checked with the receiver's own assertWellFormed before it is sent.
The half of that a DEPLOYMENT owns is suspectResultCount: a node that caps silently is caught by matching its cap exactly, so a node capping at anything other than the default 10000 must be known about. It is DISCOVERED where the provider reports its cap in a refusal and CONFIGURED where it does not, in that order of precedence -- an operator's stated number always wins over a parsed one. See that option and suspectResultCount below; it is the sharpest edge on this class.
Chain-bound, deliberately ​
Every chain call in the deployment is here, and there are three of them: eth_chainId for identity, eth_blockNumber for the tip and eth_getLogs for the logs. No configuration adds a fourth -- the provider is held behind the engine's declared surface (declaredMethodsOnly, ADR-0073), so a reintroduced per-block read is refused here rather than paid for. The receiver makes NO chain call at all, so the chain identity check is one only this side can perform, and it is made ONCE a cycle, after the fetch and before the push (ADR-0081).
Type Parameters ​
ABI ​
ABI extends Abi
Constructors ​
Constructor ​
new LogFetcher<
ABI>(provider,source,target,config?):LogFetcher<ABI>
Defined in: packages/core/src/logFetcher.ts:325
Parameters ​
provider ​
EIP1193ProviderWithoutEvents
source ​
IndexingSource<ABI>
target ​
config? ​
Returns ​
LogFetcher<ABI>
Properties ​
context ​
readonlycontext:WireContext
Defined in: packages/core/src/logFetcher.ts:300
The {source, config} every batch asserts. Computed exactly as the receiver computes it.
streamConfig ​
readonlystreamConfig:UsedStreamConfig
Defined in: packages/core/src/logFetcher.ts:301
Accessors ​
cursorHint ​
Get Signature ​
get cursorHint():
number|undefined
Defined in: packages/core/src/logFetcher.ts:347
What the receiver last said, or undefined when this fetcher has yet to be told.
Returns ​
number | undefined
learnedRange ​
Get Signature ​
get learnedRange():
LearnedRange
Defined in: packages/core/src/logFetcher.ts:363
How wide a range this provider has been found to answer: the ceiling it has refused at, the widest span it has answered, and what the next request will ask for.
Reported so it can be CONFIGURED BACK IN (fetch.learnedRange) on a later run, which is how a restart resumes where discovery left off without this class persisting anything (ADR-0074). Losing it costs more than one request -- it re-pays the walk up from the small starting range -- which is exactly why it is not kept here: the class docblock's test for state this fetcher may hold is that losing it costs ONE extra request and nothing else.
Returns ​
limits ​
Get Signature ​
get limits():
FetcherLimits
Defined in: packages/core/src/logFetcher.ts:375
What this fetcher believes about its provider, for a host's status surface.
One read rather than two, because the two numbers are read together and mean different things: an operator diagnosing a slow backfill wants the range, one diagnosing a suspected truncation wants the count and its source, and one looking at a status page wants to see that they disagree with the node.
Returns ​
suspectResultCount ​
Get Signature ​
get suspectResultCount():
ResolvedSuspectResultCount
Defined in: packages/core/src/logFetcher.ts:393
The count a result set is being judged against RIGHT NOW, and where it came from -- the sharpest correctness knob in the fetcher, made readable instead of left to be inferred from behaviour.
It is resolved on every fetch rather than fixed at construction, because the middle tier is DISCOVERED: a provider that reports its own cap in a refusal teaches this fetcher the number mid-run, and the very fetch that provoked the refusal is already judged against it. The two ends do not move.
Read it from a host to report what this fetcher believes about your node; read the source to tell an assertion apart from a discovery, which is the difference between "my configuration is wrong" and "my provider says this".
Returns ​
Methods ​
fetchAndPush() ​
fetchAndPush():
Promise<FetchCycleOutcome>
Defined in: packages/core/src/logFetcher.ts:416
One cycle: find the start, fetch a complete range, push it.
Every outcome is a normal one. pushed is the ordinary case, up-to-date means the chain has not produced anything above the cursor yet, and yielded means the cycle was corrected repeatedly without landing anything and stopped trying, which is what redundant fetchers do to each other. Anything that is NOT a normal outcome throws, and the error's own retryable says whether a host should try again later (IngestionUnavailableError, provider trouble) or stop and tell somebody (IngestionRefusedError, WireContextMismatchError, UnexpectedChainError, SuspectedTruncationError, ArchiveRefusedError).
Returns ​
Promise<FetchCycleOutcome>