Documentation
¶
Index ¶
- Constants
- func NewBallotHooks(k Keeper) uvalidatortypes.BallotHooks
- func NewEVMHooks(k Keeper) evmtypes.EvmHooks
- func NewMsgServerImpl(keeper Keeper) types.MsgServer
- type BallotHooks
- type EVMHooks
- type Keeper
- func (k Keeper) CallExpireExternalRead(ctx sdk.Context, requestID string) (*evmtypes.MsgEthereumTxResponse, error)
- func (k Keeper) CallFulfillExternalCallback(ctx sdk.Context, requestID string, result *types.ReadResult) (*evmtypes.MsgEthereumTxResponse, error)
- func (k Keeper) CallReportCallbackGas(ctx sdk.Context, requestID string, cost *big.Int) (*evmtypes.MsgEthereumTxResponse, error)
- func (k Keeper) CallbackCost(ctx sdk.Context, gas uint64) (*big.Int, error)
- func (k Keeper) CanAffordCallback(ctx sdk.Context, req *types.ReadRequest) (bool, error)
- func (k Keeper) ExpireRead(ctx sdk.Context, ur types.UniversalRead) error
- func (k *Keeper) ExportGenesis(ctx context.Context) *types.GenesisState
- func (k Keeper) FulfilRead(ctx sdk.Context, ur types.UniversalRead) error
- func (k Keeper) GetModuleAccountNonce(ctx context.Context) (uint64, error)
- func (k Keeper) GetModuleAddress(ctx context.Context) (common.Address, string)
- func (k Keeper) GetUniversalRead(ctx context.Context, requestID string) (types.UniversalRead, bool)
- func (k Keeper) GetUniversalReadByBallot(ctx context.Context, ballotKey string) (types.UniversalRead, bool)
- func (k Keeper) HasUniversalRead(ctx context.Context, requestID string) bool
- func (k Keeper) IncrementModuleAccountNonce(ctx context.Context) (uint64, error)
- func (k Keeper) IngestReadRequests(ctx context.Context, receipt *evmtypes.MsgEthereumTxResponse) error
- func (k *Keeper) InitGenesis(ctx context.Context, data *types.GenesisState) error
- func (k Keeper) IterateExpiredBy(ctx context.Context, height uint64, fn func(types.UniversalRead) bool) error
- func (k Keeper) IterateReadsByTxHash(ctx context.Context, txHash string, fn func(types.UniversalRead) bool) error
- func (k Keeper) Logger() log.Logger
- func (k Keeper) SetUniversalRead(ctx context.Context, ur types.UniversalRead) error
- func (k Keeper) SweepExpired(ctx sdk.Context) error
- func (k Keeper) TakeAndBurn(ctx sdk.Context, amount *big.Int) error
- func (k Keeper) VoteOnReadBallot(ctx context.Context, universalValidator sdk.ValAddress, requestID string, ...) (ballotKey string, isFinalized bool, isNew bool, err error)
- func (k Keeper) VoteReadResult(ctx context.Context, universalValidator sdk.ValAddress, requestID string, ...) (bool, error)
- type Querier
- func (k Querier) AllAbortedReadRequests(goCtx context.Context, req *types.QueryAllAbortedReadRequestsRequest) (*types.QueryAllAbortedReadRequestsResponse, error)
- func (k Querier) AllPendingReadRequests(goCtx context.Context, req *types.QueryAllPendingReadRequestsRequest) (*types.QueryAllPendingReadRequestsResponse, error)
- func (k Querier) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error)
- func (k Querier) ReadsByTx(goCtx context.Context, req *types.QueryReadsByTxRequest) (*types.QueryReadsByTxResponse, error)
- func (k Querier) UniversalRead(goCtx context.Context, req *types.QueryUniversalReadRequest) (*types.QueryUniversalReadResponse, error)
Constants ¶
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.
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.
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 ¶
NewEVMHooks creates a new instance of EVMHooks.
func NewMsgServerImpl ¶
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, ) (*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 ¶
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 ¶
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 ¶
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 ¶
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 ¶
GetModuleAccountNonce returns the module account's current EVM nonce, defaulting to 0 before the first call is made.
func (Keeper) GetModuleAddress ¶
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 ¶
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 ¶
HasUniversalRead reports whether a read already exists. Ingest uses this to stay idempotent when the same log is seen twice.
func (Keeper) IncrementModuleAccountNonce ¶
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 ¶
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) SetUniversalRead ¶
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 ¶
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 ¶
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 (Querier) AllAbortedReadRequests ¶
func (k Querier) AllAbortedReadRequests(goCtx context.Context, req *types.QueryAllAbortedReadRequestsRequest) (*types.QueryAllAbortedReadRequestsResponse, error)
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 ¶
func (k Querier) AllPendingReadRequests(goCtx context.Context, req *types.QueryAllPendingReadRequestsRequest) (*types.QueryAllPendingReadRequestsResponse, error)
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 (k Querier) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error)
func (Querier) ReadsByTx ¶
func (k Querier) ReadsByTx(goCtx context.Context, req *types.QueryReadsByTxRequest) (*types.QueryReadsByTxResponse, error)
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 ¶
func (k Querier) UniversalRead(goCtx context.Context, req *types.QueryUniversalReadRequest) (*types.QueryUniversalReadResponse, error)
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.