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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

instance.gno

4.76 Kb · 187 lines
  1package vmkit
  2
  3import (
  4	"errors"
  5
  6	"gno.land/p/nt/avl/v0"
  7)
  8
  9// ErrNoInstance is returned when an id names nothing.
 10var ErrNoInstance = errors.New("vmkit: no such instance")
 11
 12// ErrBudgetExhausted is returned when an instance has already spent its total
 13// fuel budget, so there is nothing left to step.
 14var ErrBudgetExhausted = errors.New("vmkit: instance fuel budget exhausted")
 15
 16// Instance is one guest program as a realm stores it: the code, the machine
 17// state between slices, and the accounting that survives the transaction.
 18//
 19// It holds a snapshot rather than a [Machine], on purpose. A realm keeps
 20// bytes; the machine is rebuilt for the slice that needs it and thrown away
 21// after. That is what makes the storage cost of a paused program measurable,
 22// which is the whole kill criterion for continuations: if resuming costs more
 23// than re-running, continuations are theater.
 24type Instance struct {
 25	ID    string
 26	Owner address
 27	VM    string // which guest machine the Program is for
 28
 29	Program  []byte
 30	Snapshot []byte
 31
 32	Status Status
 33	Trap   string
 34
 35	// FuelUsed is the total across every slice run so far.
 36	FuelUsed int64
 37	// FuelBudget caps FuelUsed across the instance's whole life, or is
 38	// [Unmetered] for no cap.
 39	FuelBudget int64
 40
 41	// Slices counts how many transactions have stepped this instance.
 42	Slices int64
 43
 44	Output []byte
 45}
 46
 47// NewInstance returns an instance ready for its first slice.
 48func NewInstance(id string, owner address, vm string, program []byte, budget int64) *Instance {
 49	if budget < 0 {
 50		budget = Unmetered
 51	}
 52	prog := make([]byte, len(program))
 53	copy(prog, program)
 54	return &Instance{
 55		ID:         id,
 56		Owner:      owner,
 57		VM:         vm,
 58		Program:    prog,
 59		Status:     Running,
 60		FuelBudget: budget,
 61	}
 62}
 63
 64// Remaining reports the fuel left in the instance's total budget, or
 65// [Unmetered].
 66func (i *Instance) Remaining() int64 {
 67	if i.FuelBudget == Unmetered {
 68		return Unmetered
 69	}
 70	left := i.FuelBudget - i.FuelUsed
 71	if left < 0 {
 72		return 0
 73	}
 74	return left
 75}
 76
 77// Slice returns how much fuel the next call to [Instance.Run] may spend when
 78// the caller asks for `want`: `want` clamped to what the budget still allows.
 79func (i *Instance) Slice(want int64) int64 {
 80	left := i.Remaining()
 81	if left == Unmetered {
 82		return want
 83	}
 84	if want == Unmetered || want > left {
 85		return left
 86	}
 87	return want
 88}
 89
 90// Run steps m for one slice of at most `fuel` units and folds the result back
 91// into the instance: the new snapshot, the status, the fuel spent, and
 92// anything the guest wrote to h.
 93//
 94// m must be a fresh machine loaded from i.Program; Run restores i.Snapshot
 95// into it when there is one, so the caller never has to remember the order.
 96// The instance is left untouched when Restore fails, which means a snapshot
 97// that cannot be decoded costs the caller gas but never corrupts the stored
 98// program.
 99func (i *Instance) Run(m Machine, h Host, fuel int64) error {
100	if i.Status.Done() && i.Status != Running {
101		return ErrBudgetExhausted
102	}
103	if len(i.Snapshot) > 0 {
104		if err := m.Restore(i.Snapshot); err != nil {
105			return err
106		}
107	}
108	slice := i.Slice(fuel)
109	if slice == 0 {
110		i.Status = OutOfFuel
111		return ErrBudgetExhausted
112	}
113
114	used, status := m.Step(h, slice)
115
116	i.FuelUsed += used
117	i.Slices++
118	i.Snapshot = m.Snapshot()
119	i.Status = status
120	i.Trap = TrapReason(m)
121	if status == Running && i.Remaining() == 0 {
122		i.Status = OutOfFuel
123	}
124	return nil
125}
126
127// Store is the avl-backed set of instances a realm owns, keyed by id.
128type Store struct {
129	tree *avl.Tree
130}
131
132// NewStore returns an empty store.
133func NewStore() *Store { return &Store{tree: avl.NewTree()} }
134
135// Set writes an instance, replacing any instance with the same id.
136func (s *Store) Set(i *Instance) {
137	if i == nil {
138		return
139	}
140	s.tree.Set(i.ID, i)
141}
142
143// Get returns the instance with this id, or nil.
144func (s *Store) Get(id string) *Instance {
145	v := s.tree.Get(id)
146	if v == nil {
147		return nil
148	}
149	inst, ok := v.(*Instance)
150	if !ok {
151		return nil
152	}
153	return inst
154}
155
156// Remove deletes an instance and reports whether it existed.
157func (s *Store) Remove(id string) bool {
158	_, removed := s.tree.Remove(id)
159	return removed
160}
161
162// Size returns how many instances are stored.
163func (s *Store) Size() int { return s.tree.Size() }
164
165// Iterate walks every instance in key order, stopping early when fn returns
166// true.
167func (s *Store) Iterate(fn func(*Instance) bool) {
168	s.tree.Iterate("", "", func(_ string, v any) bool {
169		inst, ok := v.(*Instance)
170		if !ok {
171			return false
172		}
173		return fn(inst)
174	})
175}
176
177// ReverseIterate walks every instance in descending key order, which is how a
178// realm renders newest-first when ids are zero-padded and ascending.
179func (s *Store) ReverseIterate(fn func(*Instance) bool) {
180	s.tree.ReverseIterate("", "", func(_ string, v any) bool {
181		inst, ok := v.(*Instance)
182		if !ok {
183			return false
184		}
185		return fn(inst)
186	})
187}