Documentation
¶
Index ¶
- Constants
- Variables
- type ChainReader
- func (c *ChainReader) Head() *pending.PreConfirmed
- func (c *ChainReader) Length() int
- func (c *ChainReader) NewestFirst() iter.Seq[*pending.PreConfirmed]
- func (c *ChainReader) OldestFirst() iter.Seq[*pending.PreConfirmed]
- func (c *ChainReader) OldestFirstWithBloom() iter.Seq[*core.WithBloom[*pending.PreConfirmed]]
- func (c *ChainReader) PreConfirmedStateAt(blockNumber uint64, bcReader blockchain.Reader) (core.StateReader, blockchain.StateCloser, error)
- func (c *ChainReader) PreConfirmedStateBeforeIndexAt(blockNumber uint64, index uint, bcReader blockchain.Reader) (core.StateReader, blockchain.StateCloser, error)
- func (c *ChainReader) ReceiptByHash(hash *felt.Felt) (*core.TransactionReceipt, uint64, error)
- func (c *ChainReader) TransactionByHash(hash *felt.Felt) (core.Transaction, error)
- type ChainStorage
- type DataSource
- type EventListener
- type Option
- type Poller
- type RequestSync
- func (rs *RequestSync) ListenRequests() <-chan chan struct{}
- func (rs *RequestSync) Request()
- func (rs *RequestSync) SelfRequest() chan struct{}
- func (rs *RequestSync) SignalFailure(respCh chan struct{})
- func (rs *RequestSync) SignalSuccess(respCh chan struct{})
- func (rs *RequestSync) Start()
- func (rs *RequestSync) Stop()
- type SelectiveListener
- type Subscription
Constants ¶
const ( UpdateNoChange = "no_change" UpdateDelta = "delta" UpdateFull = "full" )
Update kinds EventListener.OnPollSucceeded reports.
const ( FailureNotFound = "not_found" FailureRateLimited = "rate_limited" FailureError = "error" )
Reasons EventListener.OnPollFailed reports.
const ( // DefaultPollInterval is how often the poller ticks unless overridden. Measured on mainnet // (2026-10-07), the 1s tick with the stale-after and on-demand wait below costs about 40% // fewer gateway requests than 500ms when idle and answers fresher at every load level, // because a read that finds the chain stale triggers a poll and waits for it. DefaultPollInterval = time.Second // DefaultStaleAfter is how long after a successful poll the pre-confirmed chain is served as // is before a read triggers a new poll, unless overridden. 250ms keeps a busy node at about // 2.8 gateway requests/s; 100ms or 0 polled often enough to be throttled (HTTP 429). DefaultStaleAfter = 250 * time.Millisecond // DefaultOnDemandWait is how long a read waits for the poll it triggered before answering // with the stored chain, unless overridden. 300ms covers nearly every poll (150-250ms end to // end); 150ms timed out on about one poll in five. DefaultOnDemandWait = 300 * time.Millisecond )
Variables ¶
var ErrBaseTxCountMismatch = errors.New("pre-confirmed base transaction count mismatch")
ErrBaseTxCountMismatch is returned when a Delta update's baseTxCount hint doesn't match the targeted slot's current tx count. Defensive against any non-poller writer (or future race) that could drift the slot between the wire send and the storage apply.
Functions ¶
This section is empty.
Types ¶
type ChainReader ¶
type ChainReader struct {
// contains filtered or unexported fields
}
ChainReader is an immutable snapshot of a contiguous run of pre-confirmed blocks, ordered newest-first via parent pointers. Iteration must respect Length — head-aligned views (see ChainStorage.SnapshotForBlock) may stop before the underlying linked list's nil terminator.
func NewChain ¶
func NewChain(entries ...*pending.PreConfirmed) (ChainReader, error)
NewChain builds a ChainReader from non-nil pre-confirmed entries given in oldest-first order with contiguous block numbers. No args returns the zero-value ChainReader. If an entry has block number 0 behaviour is undefined.
func (*ChainReader) Head ¶
func (c *ChainReader) Head() *pending.PreConfirmed
Head returns the most recent pre-confirmed in the view, or nil if empty.
func (*ChainReader) Length ¶
func (c *ChainReader) Length() int
Length is the number of entries in this chain view.
func (*ChainReader) NewestFirst ¶
func (c *ChainReader) NewestFirst() iter.Seq[*pending.PreConfirmed]
NewestFirst yields entries from the most recent down to head+1, bounded by Length.
func (*ChainReader) OldestFirst ¶
func (c *ChainReader) OldestFirst() iter.Seq[*pending.PreConfirmed]
OldestFirst yields entries from head+1 up to the most recent, bounded by Length.
func (*ChainReader) OldestFirstWithBloom ¶ added in v0.17.0
func (c *ChainReader) OldestFirstWithBloom() iter.Seq[*core.WithBloom[*pending.PreConfirmed]]
OldestFirstWithBloom yields entries paired with their event bloom, oldest-first, bounded by Length. The event filter uses the bloom to skip receipt scans.
func (*ChainReader) PreConfirmedStateAt ¶
func (c *ChainReader) PreConfirmedStateAt( blockNumber uint64, bcReader blockchain.Reader, ) (core.StateReader, blockchain.StateCloser, error)
PreConfirmedStateAt returns the chain's view of state at blockNumber. The chain owns base resolution: it opens the canonical state immediately below its own oldest slot (derived from tip - length + 1).
Returns [pending.ErrPreConfirmedNotFound] if blockNumber falls outside the chain.
func (*ChainReader) PreConfirmedStateBeforeIndexAt ¶
func (c *ChainReader) PreConfirmedStateBeforeIndexAt( blockNumber uint64, index uint, bcReader blockchain.Reader, ) (core.StateReader, blockchain.StateCloser, error)
PreConfirmedStateBeforeIndexAt returns the chain's view of state immediately before transaction `index` at blockNumber. See PreConfirmedStateAt for the base- resolution contract; here the chain additionally layers the target slot's per-transaction diffs up to (but not including) `index`. Returns pending.ErrPreConfirmedNotFound if blockNumber isn't in the chain, or pending.ErrTransactionIndexOutOfBounds if `index` exceeds the target's transaction count.
func (*ChainReader) ReceiptByHash ¶
func (c *ChainReader) ReceiptByHash( hash *felt.Felt, ) (*core.TransactionReceipt, uint64, error)
ReceiptByHash scans every chain entry's Block.Receipts. Returns the receipt and the number of the block it lives in. ErrTransactionReceiptNotFound when missing.
func (*ChainReader) TransactionByHash ¶
func (c *ChainReader) TransactionByHash(hash *felt.Felt) (core.Transaction, error)
TransactionByHash scans every chain entry's Block.Transactions.
Returns pending.ErrTransactionNotFound when missing.
type ChainStorage ¶
type ChainStorage struct {
// contains filtered or unexported fields
}
ChainStorage holds a contiguous run of pre-confirmed blocks above the canonical head. Readers obtain a head-aligned view via [SnapshotForBlock]. Single writer (polling loop) with many concurrent readers; reads are lock-free via atomic.Pointer.
func NewChainStorage ¶
func NewChainStorage() *ChainStorage
func (*ChainStorage) AdvanceTo ¶
func (s *ChainStorage) AdvanceTo(oldestPreConf uint64) bool
AdvanceTo realigns the chain to a new canonical head, given as oldestPreConf — the slot the chain's oldest entry must occupy after the realignment, i.e. the first pre-confirmed slot above that head (head number+1). Three outcomes:
- oldestPreConf == the chain's current oldest: already aligned, no-op.
- oldestPreConf > mostRecent (head advanced past everything we stored) OR oldestPreConf < the current oldest (head reverted below us — every entry's parent now references a discarded block): drop the whole chain. The next poll bootstraps fresh against the new head.
- current oldest < oldestPreConf <= mostRecent: rebuild from the new oldest slot up so the surviving nodes nil-terminate cleanly and the dropped tail is GC-able.
Pre-pop readers retain their *ChainReader and walk the original (still intact) nodes; the new chain references only fresh nodes.
Single-writer: like ApplyUpdate, this assumes the pre-confirmed poller goroutine is the only writer.
func (*ChainStorage) ApplyUpdate ¶
func (s *ChainStorage) ApplyUpdate( update starknet.PreConfirmedUpdate, blockNumber uint64, baseTxCount uint64, oldestPreConf uint64, newClasses map[felt.Felt]core.ClassDefinition, ) (*core.WithBloom[*pending.PreConfirmed], error)
ApplyUpdate atomically evolves the stored chain from a wire-side update. blockNumber is the height targeted by the update; baseTxCount is the knownTransactionCount the poller sent (consulted only for the Delta case as a defensive race-check against the targeted slot). oldestPreConf is the slot the chain's oldest entry is expected to occupy: the first pre-confirmed slot above the canonical head, head number+1. Returns the affected entry, or nil when the update was a no-op (NoChange, preserved, rejected at cap, etc.).
On CAS failure the chain changed between Load and CompareAndSwap; we return an error instead of retrying.
A NoChange still registers newClasses on the targeted (tip) slot when non-empty: a re-poll that reported no content change may still carry declared classes we only just fetched for that block.
func (*ChainStorage) SnapshotForBlock ¶ added in v0.16.6
func (s *ChainStorage) SnapshotForBlock(blockNumber uint64) ChainReader
SnapshotForBlock returns a head-aligned view of the pre-confirmed chain based on the input `blockNumber`. If the `blockNumber` sits outside the pre-confirmed chain range, an empty chain is returned. Otherwise, a new chain is returned in the [blockNumber, chain.tip()] range.
The total size of the pre-confirmed chain is uncapped and it can exceed core.BlockHashLag, causing execution simulations on longer chain to fail due to this.
type DataSource ¶
type DataSource interface {
PreConfirmedBlockLatest(
ctx context.Context,
identifier string,
txCount uint64,
) (starknet.PreConfirmedUpdate, uint64, error)
PreConfirmedBlockByNumber(
ctx context.Context,
blockNumber uint64,
identifier string,
txCount uint64,
) (starknet.PreConfirmedUpdate, error)
Class(ctx context.Context, classHash *felt.Felt) (core.ClassDefinition, error)
}
DataSource is the narrow surface the Poller needs from the wire side.
type EventListener ¶ added in v0.17.0
type EventListener interface {
// OnPollSucceeded reports a poll that applied the latest update, its kind and how long the
// poll took.
OnPollSucceeded(update string, took time.Duration)
// OnPollFailed reports a poll that did not apply the latest update.
OnPollFailed(reason string)
// OnBackfill reports a poll starting to backfill the gap below the latest, with how many
// blocks it holds: from the stored tip, or from above the head when nothing is stored.
OnBackfill(gap uint64)
}
EventListener is told how each pre-confirmed poll went. A tick skipped while the node is not at the tip reports nothing.
type Option ¶ added in v0.17.0
type Option func(*options)
Option is a functional option for configuring a Poller.
func WithListener ¶ added in v0.17.0
func WithListener(listener EventListener) Option
WithListener sets the listener every poll is reported to. By default polls are reported to a listener that drops them.
func WithOnDemandWait ¶ added in v0.17.0
WithOnDemandWait overrides DefaultOnDemandWait; zero makes reads never wait.
func WithPollInterval ¶ added in v0.17.0
WithPollInterval overrides DefaultPollInterval; zero disables polling.
func WithStaleAfter ¶ added in v0.17.0
WithStaleAfter overrides DefaultStaleAfter; zero makes every read poll.
type Poller ¶
type Poller struct {
// contains filtered or unexported fields
}
Poller drives the pre-confirmed chain from a single goroutine.
One tick reads as: poll the server's latest pre-confirmed, backfill any gap below it, then apply the latest. backfill runs only when the tip jumped ahead; it re-polls the old mostRecent with delta hints and the intermediate slots up to latest-1 as full blocks (capturing each slot's declared classes) and applies each. Same-height polls (latest matches our mostRecent) skip backfill and land in apply as delta / preserve / replace.
func NewPoller ¶
func NewPoller( dataSource DataSource, blockchain *blockchain.Blockchain, highestBlockHeader *atomic.Pointer[core.Header], logger log.StructuredLogger, opts ...Option, ) *Poller
NewPoller builds a poller over dataSource. It ticks every DefaultPollInterval and serves Poller.RequestPreConfirmedChain reads from the stored chain, polling first when that chain is older than DefaultStaleAfter and waiting at most DefaultOnDemandWait for the poll. Every poll is reported to the listener. highestBlockHeader gates polling to when the node is at the tip. See Option for overriding the defaults.
func (*Poller) ReadPreConfirmedChain ¶ added in v0.17.0
func (p *Poller) ReadPreConfirmedChain() (ChainReader, error)
ReadPreConfirmedChain returns the stored pre-confirmed chain as it is, without polling and waiting for fresher data. See Poller.RequestPreConfirmedChain.
func (*Poller) RequestPreConfirmedChain ¶ added in v0.17.0
func (p *Poller) RequestPreConfirmedChain() (ChainReader, error)
RequestPreConfirmedChain returns the pre-confirmed chain. If current chain is older than [RequestSync.dataFreshnessTime], a new poll will return and it will block until the work is done. In case the works take longer than [RequestSync.requestWaitTime], it will stop waiting for the poll result (without interrupting) and return the current available chain. Use Poller.ReadPreConfirmedChain to read the stored chain without waiting.
func (*Poller) Run ¶
Run polls the sequencer every interval and builds the pre-confirmed chain from it. If the blockchain is empty (pre-genesis) then it will wait until the genesis block is synced. It only stops on context cancellation.
func (*Poller) Subscribe ¶ added in v0.16.7
func (p *Poller) Subscribe() Subscription
type RequestSync ¶ added in v0.17.0
type RequestSync struct {
// contains filtered or unexported fields
}
Dedicated type to handle multiple goroutines requesting from a single working go routine. Usage:
Main worker Go routine should make sure to call RequestSync.Start before accepting incoming requests (via RequestSync.ListenRequests). RequestSync.Stop should be explicitly called when work is done, so pending and follow up requests return right away instead of waiting out requestWaitTime. Finally, during work, (un)successful completion must be signaled accordingly with RequestSync.SignalSuccess and RequestSync.SignalFailure. If the worker wants to start work without any incoming requests, they can call RequestSync.SelfRequest and proceed to use RequestSync.SignalSuccess or RequestSync.SignalFailure as if a normal request was sent.
Requester Go routines should limit themselves to only RequestSync.Request method which will block until the request has been completed (independent of success or failure) or requestWaitTime has passed.
func NewRequestSync ¶ added in v0.17.0
func NewRequestSync(dataFreshnessTime, requestWaitTime time.Duration) *RequestSync
NewRequestSync creates a new RequestSync that is meant to be shared by many requesting Go routines and one main working go routine. You can set `dataFreshnessTime` which is the amount of time that will pass from the last successful request before triggering work again, and `requestWaitTime` which is the maximum amount of time a request waits for that work to be done. It is safe for concurrent use. See RequestSync type definition for more information into its usage.
func (*RequestSync) ListenRequests ¶ added in v0.17.0
func (rs *RequestSync) ListenRequests() <-chan chan struct{}
ListenRequests returns a channel to whom notify requests to
func (*RequestSync) Request ¶ added in v0.17.0
func (rs *RequestSync) Request()
Request asks the worker to do its work and waits for it to be done, successfully or not, for at most requestWaitTime. Once that time has passed it returns while the work carries on. If there is an ongoing request already, it joins it. It returns right away if the RequestSync is not running or the data is still fresh, and early if the RequestSync stops.
func (*RequestSync) SelfRequest ¶ added in v0.17.0
func (rs *RequestSync) SelfRequest() chan struct{}
SelfRequest is similar to RequestSync.Request and is expected for the working Go routine to call it if it's going to do work on its own that would be triggered by a call to RequestSync.Request. In a similar fashion as to receiving a request. The Go Routine is expected to signal success or failure once the self requested work is satisfied. If a request is already waiting to be picked up, the self requested work takes it over. It must not be called while the worker still has a request or self request to signal, since RequestSync.Stop would no longer release the requests waiting on that one.
func (*RequestSync) SignalFailure ¶ added in v0.17.0
func (rs *RequestSync) SignalFailure(respCh chan struct{})
SignalFailure notifies to a waiting go routine that its request is done.
func (*RequestSync) SignalSuccess ¶ added in v0.17.0
func (rs *RequestSync) SignalSuccess(respCh chan struct{})
SignalSuccess notifies to a waiting Go routine that its request is done and updates the last successful time.
func (*RequestSync) Start ¶ added in v0.17.0
func (rs *RequestSync) Start()
func (*RequestSync) Stop ¶ added in v0.17.0
func (rs *RequestSync) Stop()
type SelectiveListener ¶ added in v0.17.0
type SelectiveListener struct {
OnPollSucceededCb func(update string, took time.Duration)
OnPollFailedCb func(reason string)
OnBackfillCb func(gap uint64)
}
func (*SelectiveListener) OnBackfill ¶ added in v0.17.0
func (l *SelectiveListener) OnBackfill(gap uint64)
func (*SelectiveListener) OnPollFailed ¶ added in v0.17.0
func (l *SelectiveListener) OnPollFailed(reason string)
func (*SelectiveListener) OnPollSucceeded ¶ added in v0.17.0
func (l *SelectiveListener) OnPollSucceeded(update string, took time.Duration)
type Subscription ¶ added in v0.16.7
type Subscription struct {
*feed.Subscription[*core.WithBloom[*pending.PreConfirmed]]
}
This is a work-around. mockgen chokes when the instantiated generic type is in the interface.