Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

duebook package

Overview

Package duebook is a pure scheduling primitive for realms that need to authorize an action now and perform it later. It is to *time* what feeledger is to *money*: it holds no coins, performs no effects, imports no chain APIs, and owns no package-level state. The importing realm owns a *Book, supplies the clock, and performs its own effects.

The one idea

A realm does not ask duebook to execute anything — Gno has no autonomous execution, so "scheduled" always means "someone sends a transaction later". What a realm actually needs is for that later transaction to be authorized exactly once. Claim is that step:

Example
1d, err := book.Claim(id, now)
2if err != nil {
3	panic(err) // not due, expired, cancelled, or already claimed
4}
5// ... the realm performs its own effect here, under its own authority

Claim checks due / not-expired / still-open and CONSUMES the deferral in the same call, before returning. The realm then acts. Replay is not guarded against, it is structurally impossible:

  • IDs are allocated monotonically from an internal counter and are NEVER reused, for the lifetime of the Book;
  • a successful Claim removes the deferral before returning;

so at most one Claim per ID can ever succeed, across all transactions, forever. A second Claim of the same ID returns ErrNotFound whether the first one happened in this transaction or a year ago.

Because the realm performs the effect itself, no closure, callback, or capability ever crosses a realm boundary. duebook cannot be handed code to run, so it cannot be tricked into running the wrong code.

Lifecycle

A deferral is open from Schedule until exactly one of Claim, Cancel, or Expire consumes it. There is no other transition and no way back.

Example
1Schedule ──> open ──┬── Claim   (now >= DueAt, before ExpiresAt)
2                    ├── Cancel  (owner only, any time while open)
3                    └── Expire  (anyone, once now >= ExpiresAt)

Claimability is the half-open interval [DueAt, ExpiresAt): due at DueAt, no longer claimable at ExpiresAt. A deferral scheduled with ttl == 0 never expires and has ExpiresAt == 0.

State growth

Consumed deferrals are removed, not archived — the ID counter, not a tombstone, is what prevents replay, so there is nothing to keep. Open deferrals are capped per Book at construction. Storage is therefore bounded by maxOpen regardless of how many deferrals have ever existed. Audit history belongs in the consuming realm's events.

Consumer contract (the parts the package cannot enforce)

  1. SUPPLY A REAL CLOCK. duebook cannot verify that `now` came from runtime.ChainHeight() or time.Now().Unix(). A realm that lets a caller choose `now` has no delay at all. Pass the chain's clock, never a transaction parameter. This is the single most important obligation and the one most commonly got wrong.
  2. USE ONE CLOCK CONSISTENTLY. Heights and seconds must not be mixed within a Book; delay, ttl and now are all in the caller's chosen unit.
  3. DO NOT EXPORT THE BOOK. A *Book is a mutable handle. Returning one across a realm boundary hands out the right to schedule, cancel and claim. Expose your own crossing functions instead; this package returns Deferral values, never pointers into its state.
  4. AUTHORIZE THE ACTOR. duebook authenticates nothing but ownership on Cancel. Who may Schedule, and who may Claim, are the realm's policy — derive the caller from cur.Previous().Address(), not from an argument.
  5. ACT AFTER A SUCCESSFUL CLAIM, IN THE SAME TRANSACTION. Claim's return value is the authorization. Storing it to act on later reintroduces the replay window this package exists to close.

All failures are returned as errors and leave the Book COMPLETELY UNCHANGED. Must* wrappers are the only functions here that panic.

The Book is address-agnostic: owners are non-empty strings. Realms normally use address.String().

Functions

MustNew

func MustNew(minDelay, maxDelay int64, maxOpen int) *Book

MustNew is New but panics on error.

Params

Command

gnokey query vm/qeval -remote "https://rpc.pearl.testnets.gno.land" -data "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook.MustNew(,,)"

Result

New

func New(minDelay, maxDelay int64, maxOpen int) (*Book, error)

New returns an empty Book.

minDelay is the floor on how far ahead a deferral may be scheduled; 0 permits same-instant scheduling. maxDelay is the ceiling, and doubles as the overflow guard on now+delay. maxOpen caps simultaneously open deferrals and must be in [1, MaxOpenLimit].

Units are the caller's choice — block heights or seconds — but must be used consistently for the life of the Book.

Params

Command

gnokey query vm/qeval -remote "https://rpc.pearl.testnets.gno.land" -data "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/duebook.New(,,)"

Result