Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
lrettig committed Jul 30, 2024
1 parent fc8bc09 commit d52bed7
Show file tree
Hide file tree
Showing 48 changed files with 8,171 additions and 1 deletion.
2 changes: 1 addition & 1 deletion genvm/core/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

// Context serves 2 purposes:
// - maintains changes to the system state, that will be applied only after succeful execution
// - maintains changes to the system state, that will be applied only after successful execution
// - accumulates set of reusable objects and data.
type Context struct {
Registry HandlerRegistry
Expand Down
286 changes: 286 additions & 0 deletions vm/core/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
package core

import (
"bytes"
"fmt"

"github.com/spacemeshos/go-scale"

"github.com/spacemeshos/go-spacemesh/common/types"
)

// Context serves 2 purposes:
// - maintains changes to the system state, that will be applied only after successful execution
// - accumulates set of reusable objects and data.
type Context struct {
Registry HandlerRegistry
Loader AccountLoader

// LayerID of the block.
LayerID LayerID
GenesisID types.Hash20

PrincipalHandler Handler
PrincipalTemplate Template
PrincipalAccount Account

ParseOutput ParseOutput
Gas struct {
BaseGas uint64
FixedGas uint64
}
Header Header
Args scale.Encodable

// consumed is in gas units and will be used
consumed uint64
// fee is in coins units
fee uint64
// an amount transfrered to other accounts
transferred uint64

touched []Address
changed map[Address]*Account
}

// Principal returns address of the account that signed transaction.
func (c *Context) Principal() Address {
return c.PrincipalAccount.Address
}

// Layer returns block layer id.
func (c *Context) Layer() LayerID {
return c.LayerID
}

// GetGenesisID returns genesis id.
func (c *Context) GetGenesisID() Hash20 {
return c.GenesisID
}

// Balance returns the account balance.
func (c *Context) Balance() uint64 { return c.PrincipalAccount.Balance }

// Template of the principal account.
func (c *Context) Template() Template {
return c.PrincipalTemplate
}

// Handler of the principal account.
func (c *Context) Handler() Handler {
return c.PrincipalHandler
}

// Spawn account.
func (c *Context) Spawn(args scale.Encodable) error {
account, err := c.load(ComputePrincipal(c.Header.TemplateAddress, args))
if err != nil {
return err
}
if account.TemplateAddress != nil {
return ErrSpawned
}
handler := c.Registry.Get(c.Header.TemplateAddress)
if handler == nil {
return fmt.Errorf("%w: spawn is called with unknown handler", ErrInternal)
}
buf := bytes.NewBuffer(nil)
instance, err := handler.New(args)
if err != nil {
return fmt.Errorf("%w: %w", ErrMalformed, err)
}
_, err = instance.EncodeScale(scale.NewEncoder(buf))
if err != nil {
return fmt.Errorf("%w: %w", ErrInternal, err)
}
account.State = buf.Bytes()
account.TemplateAddress = &c.Header.TemplateAddress
c.change(account)
return nil
}

// Transfer amount to the address after validation passes.
func (c *Context) Transfer(to Address, amount uint64) error {
return c.transfer(&c.PrincipalAccount, to, amount, c.Header.MaxSpend)
}

func (c *Context) transfer(from *Account, to Address, amount, max uint64) error {
account, err := c.load(to)
if err != nil {
return err
}
if amount > from.Balance {
return ErrNoBalance
}
if c.transferred+amount > max {
return fmt.Errorf("%w: %d", ErrMaxSpend, max)
}
// noop. only gas is consumed
if from.Address == to {
return nil
}

c.transferred += amount
from.Balance -= amount
account.Balance += amount
c.change(account)
return nil
}

// Relay call to the remote account.
func (c *Context) Relay(remoteTemplate, address Address, call func(Host) error) error {
account, err := c.load(address)
if err != nil {
return err
}
if account.TemplateAddress == nil {
return ErrNotSpawned
}
if *account.TemplateAddress != remoteTemplate {
return fmt.Errorf(
"%w: %s != %s",
ErrTemplateMismatch,
remoteTemplate.String(),
account.TemplateAddress.String(),
)
}
handler := c.Registry.Get(remoteTemplate)
if handler == nil {
panic("template of the spawned account should exist in the registry")
}
template, err := handler.Load(account.State)
if err != nil {
return err
}

remote := &RemoteContext{
Context: c,
remote: account,
handler: handler,
template: template,
}
if err := call(remote); err != nil {
return err
}
// ideally such changes would be serialized once for the whole block execution
// but it requires more changes in the cache, so can be done as an optimization
// if it proves meaningful (most likely wont)
buf := bytes.NewBuffer(nil)
encoder := scale.NewEncoder(buf)
if _, err := template.EncodeScale(encoder); err != nil {
return fmt.Errorf("%w: %w", ErrInternal, err)
}
account.State = buf.Bytes()
c.change(account)
return nil
}

// Consume gas from the account after validation passes.
func (c *Context) Consume(gas uint64) (err error) {
amount := gas * c.Header.GasPrice
if amount > c.PrincipalAccount.Balance {
amount = c.PrincipalAccount.Balance
err = ErrOutOfGas
} else if total := c.consumed + gas; total > c.Header.MaxGas {
gas = c.Header.MaxGas - c.consumed
amount = gas * c.Header.GasPrice
err = ErrMaxGas
}
c.consumed += gas
c.fee += amount
c.PrincipalAccount.Balance -= amount
return err
}

// Apply is executed if transaction was consumed.
func (c *Context) Apply(updater AccountUpdater) error {
c.PrincipalAccount.NextNonce = c.Header.Nonce + 1
if err := updater.Update(c.PrincipalAccount); err != nil {
return fmt.Errorf("%w: %w", ErrInternal, err)
}
for _, address := range c.touched {
account := c.changed[address]
if err := updater.Update(*account); err != nil {
return fmt.Errorf("%w: %w", ErrInternal, err)
}
}
return nil
}

// Consumed gas.
func (c *Context) Consumed() uint64 {
return c.consumed
}

// Fee computed from consumed gas.
func (c *Context) Fee() uint64 {
return c.fee
}

// Updated list of addresses.
func (c *Context) Updated() []types.Address {
rst := make([]types.Address, 0, len(c.touched)+1)
rst = append(rst, c.PrincipalAccount.Address)
rst = append(rst, c.touched...)
return rst
}

func (c *Context) load(address types.Address) (*Account, error) {
if address == c.Principal() {
return &c.PrincipalAccount, nil
}
if c.changed == nil {
c.changed = map[Address]*Account{}
}
account, exist := c.changed[address]
if !exist {
loaded, err := c.Loader.Get(address)
if err != nil {
return nil, fmt.Errorf("%w: %w", ErrInternal, err)
}
account = &loaded
}
return account, nil
}

func (c *Context) change(account *Account) {
if account.Address == c.Principal() {
return
}
_, exist := c.changed[account.Address]
if !exist {
c.touched = append(c.touched, account.Address)
}
c.changed[account.Address] = account
}

// RemoteContext ...
type RemoteContext struct {
*Context
remote *Account
handler Handler
template Template
}

// Balance returns the remote account balance.
func (r *RemoteContext) Balance() uint64 {
return r.remote.Balance
}

// Template ...
func (r *RemoteContext) Template() Template {
return r.template
}

// Handler ...
func (r *RemoteContext) Handler() Handler {
return r.handler
}

// Transfer ...
func (r *RemoteContext) Transfer(to Address, amount uint64) error {
if err := r.transfer(r.remote, to, amount, amount); err != nil {
return err
}
return nil
}
Loading

0 comments on commit d52bed7

Please sign in to comment.