keeper

package
v0.0.48 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 11, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Index

Constants

View Source
const ErrBudgetTooSmall = "callback budget does not cover the declared callback gas limit"

ErrBudgetTooSmall is recorded on a read whose callback budget cannot cover the gas its app declared it needs. Fixed text, set by us from a deterministic comparison — it never comes from a validator and never touches a ballot.

View Source
const MaxExpiriesPerBlock = 50

MaxExpiriesPerBlock bounds how many requests one EndBlocker may retire.

Each expiry is a real EVM call, so an unbounded sweep would let a backlog turn a single block into an arbitrarily expensive one. The bound is a rate limit, not a cap: whatever is left over is picked up next block, and the set is ordered by deadline so the longest-overdue always go first.

View Source
const MaxExpiryAttempts = 3

MaxExpiryAttempts bounds how many times one request's expiry call may fail before the chain stops trying.

Retrying matters because expiry now moves money: UniversalCallback._settle credits the funder's refund, and expireExternalRead is module-gated, so if we stop calling nobody else can. A transient failure that we treated as final would strand the refund permanently.

Bounded because two of the contract's three reverts are permanent — RequestAlreadyFulfilled and InvalidCallbackTarget both mean the request was already settled by the fulfil path, so the money is safe and retrying is pure waste. Three attempts distinguishes the transient case without looping forever.

Variables

This section is empty.

Functions

func NewBallotHooks

func NewBallotHooks(k Keeper) uvalidatortypes.BallotHooks

NewBallotHooks returns the ballot hook implementation for x/ucallback.

func NewEVMHooks

func NewEVMHooks(k Keeper) evmtypes.EvmHooks

NewEVMHooks creates a new instance of EVMHooks.

func NewMsgServerImpl

func NewMsgServerImpl(keeper Keeper) types.MsgServer

NewMsgServerImpl returns an implementation of the module MsgServer interface.

Types

type BallotHooks

type BallotHooks struct {
	// contains filtered or unexported fields
}

BallotHooks reacts to READ_RESULT ballots reaching a terminal state.

Fulfilment is driven from here rather than from the vote that reached quorum, so it happens exactly once regardless of which validator's vote was decisive. Doing it in VoteReadResult would make the deciding validator pay the callback's gas and would tie the EVM call's success to that one transaction.

func (BallotHooks) AfterBallotTerminal

func (h BallotHooks) AfterBallotTerminal(
	ctx sdk.Context,
	ballotID string,
	ballotType uvalidatortypes.BallotObservationType,
	status uvalidatortypes.BallotStatus,
) error

AfterBallotTerminal dispatches on ballot type, ignoring everything that is not a read result — x/uexecutor owns the other kinds.

type EVMHooks

type EVMHooks struct {
	// contains filtered or unexported fields
}

EVMHooks implements the EVM post-processing hooks for x/ucallback.

Runs after every EVM transaction, so the log filter in IngestReadRequests must stay tight: this hook sees traffic for the whole chain and must be a no-op for all of it except UniversalCallback's ReadRequested events.

func (EVMHooks) PostTxProcessing

func (h EVMHooks) PostTxProcessing(
	ctx sdk.Context,
	sender common.Address,
	msg core.Message,
	receipt *ethtypes.Receipt,
) error

PostTxProcessing inspects the receipt and records a UniversalRead for every ReadRequested event the transaction emitted.

Returning an error reverts the whole EVM transaction. That is the behaviour we want here: a ReadRequested log we cannot record is a request the user paid for that no validator would ever serve. Reverting returns their fee instead of stranding it.

type Keeper

type Keeper struct {

	// state management
	Schema collections.Schema
	Params collections.Item[types.Params]

	// UniversalReads is the canonical record for every read request, keyed by
	// requestId. Indexes over it are added alongside the lookups they serve, and
	// are always derived — never a source of truth.
	UniversalReads collections.Map[string, types.UniversalRead]

	// PendingByExpiry holds (expiryHeight, requestId) for reads that have not
	// settled — the module's in-flight set. Ordered composite key so the sweeper
	// can range-scan by height.
	PendingByExpiry collections.KeySet[collections.Pair[uint64, string]]

	// ReadsByTxHash holds (pushTxHash, requestId) so every read emitted by one
	// Push transaction can be listed together.
	ReadsByTxHash collections.KeySet[collections.Pair[string, string]]

	// AbortedReads holds the ids of reads the chain abandoned. Derived from
	// status like the other indexes — see SetUniversalRead.
	AbortedReads collections.KeySet[string]

	// ModuleAccountNonce is the EVM nonce of this module's account. x/ucallback
	// owns it because it owns the account — UniversalCallback admits only this
	// module's address, so nothing else ever sends from it.
	ModuleAccountNonce collections.Item[uint64]
	// contains filtered or unexported fields
}

func NewKeeper

func NewKeeper(
	cdc codec.BinaryCodec,
	storeService storetypes.KVStoreService,
	logger log.Logger,
	authority string,
	uvalidatorKeeper types.UValidatorKeeper,
	evmKeeper types.EVMKeeper,
	accountKeeper types.AccountKeeper,
	bankKeeper types.BankKeeper,
	feemarketKeeper types.FeeMarketKeeper,
) Keeper

NewKeeper creates a new Keeper instance

func (Keeper) CallExpireExternalRead

func (k Keeper) CallExpireExternalRead(
	ctx sdk.Context,
	requestID string,
) (*evmtypes.MsgEthereumTxResponse, error)

CallExpireExternalRead retires a request the contract will no longer accept.

func (Keeper) CallFulfillExternalCallback

func (k Keeper) CallFulfillExternalCallback(
	ctx sdk.Context,
	requestID string,
	result *types.ReadResult,
	callbackGasLimit uint64,
) (*evmtypes.MsgEthereumTxResponse, error)

CallFulfillExternalCallback delivers a finalized observation to the contract, which forwards it to the requesting app's callback.

func (Keeper) CallReportCallbackGas

func (k Keeper) CallReportCallbackGas(
	ctx sdk.Context, requestID string, cost *big.Int,
) (*evmtypes.MsgEthereumTxResponse, error)

CallReportCallbackGas settles an executed request, returning the amount the contract clamped the report to.

func (Keeper) CallbackCost

func (k Keeper) CallbackCost(ctx sdk.Context, gas uint64) (*big.Int, error)

CallbackCost prices a gas figure at the current base fee.

Same valuation x/uexecutor applies to UEA execution, so a read and a payload execution are charged alike.

func (Keeper) CanAffordCallback

func (k Keeper) CanAffordCallback(ctx sdk.Context, req *types.ReadRequest) (bool, error)

CanAffordCallback reports whether the request funded the gas it declared.

All-or-nothing on purpose. Executing a partially funded callback would hand the app less gas than it asked for, which almost certainly runs out anyway — the user then pays for a doomed attempt instead of getting a full refund.

func (Keeper) ExpireRead

func (k Keeper) ExpireRead(ctx sdk.Context, ur types.UniversalRead) error

ExpireRead retires one request: tell the contract, record the attempt, and mark the record terminal once the contract has acknowledged it.

A failed call leaves the request in flight so the next block retries it, up to MaxExpiryAttempts, tracked by ExpiryAttempts.

Deliberately not len(PcTx): that slice holds every EVM attempt on the request, and a fulfilment that failed without settling leaves its entry behind while the read stays in flight. Counting entries would hand exactly those reads a shorter retry budget than a read that reached the sweeper cleanly.

func (*Keeper) ExportGenesis

func (k *Keeper) ExportGenesis(ctx context.Context) *types.GenesisState

ExportGenesis exports the module's state to a genesis state.

func (Keeper) FulfilRead

func (k Keeper) FulfilRead(ctx sdk.Context, ur types.UniversalRead) error

FulfilRead delivers a settled observation to UniversalCallback and records the outcome on the request.

The read is only marked terminal when the contract actually settled it. That distinction matters: on any revert the whole transaction rolls back, so fulfilledRequests stays false, _pending survives and _settle never runs — the funder's deposit is still escrowed. Retiring the record in that state would drop it out of PendingByExpiry, leaving nothing able to release those funds, since expireExternalRead admits only this module.

A reverting app callback is NOT such a case: the contract catches it with .call, so the outer transaction succeeds, the request settles, and we mark it FULFILLED.

func (Keeper) GetModuleAccountNonce

func (k Keeper) GetModuleAccountNonce(ctx context.Context) (uint64, error)

GetModuleAccountNonce returns the module account's current EVM nonce, defaulting to 0 before the first call is made.

func (Keeper) GetModuleAddress

func (k Keeper) GetModuleAddress(ctx context.Context) (common.Address, string)

GetModuleAddress returns the x/ucallback module account's EVM address.

This is the address UniversalCallback's access control admits; the contract rejects a call from anything else. Derived from the module name, so it is fixed for the life of the chain: 0x07a0258D367A4A4cd9d6E4b7eEE8E7eF491CC519.

func (Keeper) GetUniversalRead

func (k Keeper) GetUniversalRead(ctx context.Context, requestID string) (types.UniversalRead, bool)

GetUniversalRead returns the read for requestId, if it exists.

func (Keeper) GetUniversalReadByBallot

func (k Keeper) GetUniversalReadByBallot(ctx context.Context, ballotKey string) (types.UniversalRead, bool)

GetUniversalReadByBallot resolves a ballot key to its read. AfterBallotTerminal hands us only a ballot ID, and ballot IDs are one-way digests over the observation — not reversible — so this scans rather than indexes.

The scan is over PendingByExpiry, not UniversalReads: entries leave that set the moment a read settles, so it holds only in-flight work. This mirrors uexecutor's ballot hook, which walks PendingInbounds for the same reason (x/uexecutor/keeper/ballot_hooks.go:86) — the pending set is small and transient, and this path only runs on terminal transitions.

Returns false if no pending read owns the ballot: it may have already settled by another path, or the ballot may not belong to this module at all.

func (Keeper) HasUniversalRead

func (k Keeper) HasUniversalRead(ctx context.Context, requestID string) bool

HasUniversalRead reports whether a read already exists. Ingest uses this to stay idempotent when the same log is seen twice.

func (Keeper) IncrementModuleAccountNonce

func (k Keeper) IncrementModuleAccountNonce(ctx context.Context) (uint64, error)

IncrementModuleAccountNonce advances the nonce and returns the new value.

func (Keeper) IngestReadRequests

func (k Keeper) IngestReadRequests(ctx context.Context, receipt *evmtypes.MsgEthereumTxResponse) error

IngestReadRequests records a UniversalRead for every ReadRequested event in the receipt.

The two-part filter — log.Address must be UniversalCallback, topic0 must be ReadRequested — is what makes the decoded event trustworthy. Any contract can emit a log with the same topic0; only the system contract's address makes it ours. Dropping the address check would let anyone mint read requests.

func (*Keeper) InitGenesis

func (k *Keeper) InitGenesis(ctx context.Context, data *types.GenesisState) error

InitGenesis initializes the module's state from a genesis state.

Only UniversalReads is imported. The PendingByExpiry, ReadsByTxHash and AbortedReads indexes are rebuilt here from the records themselves, via SetUniversalRead — importing them separately would allow a genesis file to carry indexes that disagree with the records they point at.

func (Keeper) IterateExpiredBy

func (k Keeper) IterateExpiredBy(ctx context.Context, height uint64, fn func(types.UniversalRead) bool) error

IterateExpiredBy calls fn for every unsettled read whose expiry height is at or below height, in ascending height order. The sweeper drives this.

The key codec orders by expiryHeight first, so a plain ascending walk reaches every due entry before any that is not yet due — we break at the first key past height rather than constructing a cross-prefix range.

func (Keeper) IterateReadsByTxHash

func (k Keeper) IterateReadsByTxHash(ctx context.Context, txHash string, fn func(types.UniversalRead) bool) error

IterateReadsByTxHash calls fn for every read requested by the given Push tx. A single transaction can emit several ReadRequested logs; each is its own record, and this is how the batch is reassembled.

func (Keeper) Logger

func (k Keeper) Logger() log.Logger

func (Keeper) SetUniversalRead

func (k Keeper) SetUniversalRead(ctx context.Context, ur types.UniversalRead) error

SetUniversalRead writes a read record.

This is the only sanctioned way to mutate a UniversalRead. Indexes derived from the record are reconciled here, so writing k.UniversalReads directly will leave them stale — in particular the sweeper would keep expiring a read that has already settled.

func (Keeper) SweepExpired

func (k Keeper) SweepExpired(ctx sdk.Context) error

SweepExpired retires every read whose deadline has passed.

This is the only path by which a request expires. Nothing else can trigger it, because a deadline passing is not an event — it is just time going by, so somebody has to look. Fulfilment has an event to hang off (a ballot reaching quorum); expiry does not.

func (Keeper) TakeAndBurn

func (k Keeper) TakeAndBurn(ctx sdk.Context, amount *big.Int) error

TakeAndBurn moves the consumed callback budget out of UniversalCallback and destroys it.

No contract API is involved: a contract's balance is an ordinary bank balance, so the module debits it directly — the same shape as x/uexecutor's DeductAndBurnFees. reportCallbackGas has already released the refund and decremented totalEscrowed, so `amount` is exactly the unattributed slack the contract left behind for us.

func (Keeper) VoteOnReadBallot

func (k Keeper) VoteOnReadBallot(
	ctx context.Context,
	universalValidator sdk.ValAddress,
	requestID string,
	result *types.ReadResult,
	expiryHeight uint64,
) (ballotKey string, isFinalized bool, isNew bool, err error)

VoteOnReadBallot casts one validator's vote on the ballot for (requestID, result) and reports whether that vote carried it to quorum.

Mirrors x/uexecutor's VoteOnOutboundBallot for the threshold and voter set, but not for expiry: the ballot is given the request's own deadline rather than uexecutor's inert 100M blocks, so the two cannot disagree about when the request is over.

func (Keeper) VoteReadResult

func (k Keeper) VoteReadResult(
	ctx context.Context,
	universalValidator sdk.ValAddress,
	requestID string,
	result *types.ReadResult,
) (bool, error)

VoteReadResult records a universal validator's observation of a read request.

Reaching quorum here does NOT fulfil the request — it only settles what was observed. The fulfilment EVM call is driven by the ballot terminal hook (C7), so that it runs exactly once no matter which validator's vote happened to be the deciding one.

type Querier

type Querier struct {
	Keeper
}

func NewQuerier

func NewQuerier(keeper Keeper) Querier

func (Querier) AllAbortedReadRequests

AllAbortedReadRequests implements types.QueryServer.

Paginates the AbortedReads index rather than filtering UniversalReads. Abandoned reads should be rare, so a status filter over the full history could walk every read the chain has ever seen just to fill one page — a soft DoS on a public endpoint. The index holds only the abandoned ones.

func (Querier) AllPendingReadRequests

AllPendingReadRequests implements types.QueryServer.

Paginates the in-flight set (PendingByExpiry), which already excludes settled reads. Requests whose expiry height has passed are filtered out here too, rather than waiting for the sweeper to retire them: a validator that picked one up would spend a destination-chain read on work the contract may no longer accept. That makes the visible set correct regardless of how often the sweeper runs.

func (Querier) Params

func (Querier) ReadsByTx

ReadsByTx implements types.QueryServer.

Returns every read a single Push transaction requested, settled or not. Batches are the reason this exists: one transaction can emit several ReadRequested logs, each becoming an independent record that settles on its own schedule.

Unpaginated by design — the fan-out is bounded by what fits in one transaction.

func (Querier) UniversalRead

UniversalRead implements types.QueryServer.

Serves a read at any point in its lifecycle, settled or not — this is the endpoint for "what happened to my request", so it must not filter the way AllPendingReadRequests does.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL