Skip to content

Commit

Permalink
create TypeID for Actions/Auths + delete reflection in Registry (#297)
Browse files Browse the repository at this point in the history
* create TypeID for Actions/Auths + delete reflection in Registry #228

* actions & auth: put ID values explicitly instead of using iota

* function signature delete useless line

* resolve last lint issues

* reword Note

* change register items limit
  • Loading branch information
najeal authored Jul 26, 2023
1 parent bfb2c33 commit fe99c39
Show file tree
Hide file tree
Showing 28 changed files with 174 additions and 137 deletions.
12 changes: 4 additions & 8 deletions chain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,7 @@ func ParseStatefulBlock(
}

if len(source) == 0 {
actionRegistry, authRegistry := vm.Registry()
nsource, err := blk.Marshal(actionRegistry, authRegistry)
nsource, err := blk.Marshal()
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -242,7 +241,7 @@ func (b *StatelessBlock) initializeBuilt(
_, span := b.vm.Tracer().Start(ctx, "StatelessBlock.initializeBuilt")
defer span.End()

blk, err := b.StatefulBlock.Marshal(b.vm.Registry())
blk, err := b.StatefulBlock.Marshal()
if err != nil {
return err
}
Expand Down Expand Up @@ -760,10 +759,7 @@ func (b *StatelessBlock) Results() []*Result {
return b.results
}

func (b *StatefulBlock) Marshal(
actionRegistry ActionRegistry,
authRegistry AuthRegistry,
) ([]byte, error) {
func (b *StatefulBlock) Marshal() ([]byte, error) {
size := consts.IDLen + consts.Uint64Len + consts.Uint64Len +
consts.Uint64Len + window.WindowSliceSize +
consts.IntLen + codec.CummSize(b.Txs) +
Expand All @@ -780,7 +776,7 @@ func (b *StatefulBlock) Marshal(

p.PackInt(len(b.Txs))
for _, tx := range b.Txs {
if err := tx.Marshal(p, actionRegistry, authRegistry); err != nil {
if err := tx.Marshal(p); err != nil {
return nil, err
}
}
Expand Down
2 changes: 2 additions & 0 deletions chain/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ type StateManager interface {
}

type Action interface {
GetTypeID() uint8 // identify uniquely the action
MaxUnits(Rules) uint64 // max units that could be charged via execute
ValidRange(Rules) (start int64, end int64) // -1 means no start/end

Expand Down Expand Up @@ -152,6 +153,7 @@ type Action interface {
}

type Auth interface {
GetTypeID() uint8 // identify uniquely the auth
MaxUnits(Rules) uint64
ValidRange(Rules) (start int64, end int64) // -1 means no start/end

Expand Down
43 changes: 12 additions & 31 deletions chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,16 +58,11 @@ func NewTx(base *Base, wm *warp.Message, act Action) *Transaction {
}
}

func (t *Transaction) Digest(
actionRegistry *codec.TypeParser[Action, *warp.Message, bool],
) ([]byte, error) {
func (t *Transaction) Digest() ([]byte, error) {
if len(t.digest) > 0 {
return t.digest, nil
}
actionByte, _, _, ok := actionRegistry.LookupType(t.Action)
if !ok {
return nil, fmt.Errorf("unknown action type %T", t.Action)
}
actionID := t.Action.GetTypeID()
var warpBytes []byte
if t.WarpMessage != nil {
warpBytes = t.WarpMessage.Bytes()
Expand All @@ -78,7 +73,7 @@ func (t *Transaction) Digest(
p := codec.NewWriter(size, consts.NetworkSizeLimit)
t.Base.Marshal(p)
p.PackBytes(warpBytes)
p.PackByte(actionByte)
p.PackByte(actionID)
t.Action.Marshal(p)
return p.Bytes(), p.Err()
}
Expand All @@ -89,7 +84,7 @@ func (t *Transaction) Sign(
authRegistry AuthRegistry,
) (*Transaction, error) {
// Generate auth
msg, err := t.Digest(actionRegistry)
msg, err := t.Digest()
if err != nil {
return nil, err
}
Expand All @@ -103,7 +98,7 @@ func (t *Transaction) Sign(
// bytes
size := len(msg) + consts.ByteLen + t.Auth.Size()
p := codec.NewWriter(size, consts.NetworkSizeLimit)
if err := t.Marshal(p, actionRegistry, authRegistry); err != nil {
if err := t.Marshal(p); err != nil {
return nil, err
}
if err := p.Err(); err != nil {
Expand Down Expand Up @@ -332,24 +327,14 @@ func (t *Transaction) Payer() string {
return string(t.Auth.Payer())
}

func (t *Transaction) Marshal(
p *codec.Packer,
actionRegistry *codec.TypeParser[Action, *warp.Message, bool],
authRegistry *codec.TypeParser[Auth, *warp.Message, bool],
) error {
func (t *Transaction) Marshal(p *codec.Packer) error {
if len(t.bytes) > 0 {
p.PackFixedBytes(t.bytes)
return p.Err()
}

actionByte, _, _, ok := actionRegistry.LookupType(t.Action)
if !ok {
return fmt.Errorf("unknown action type %T", t.Action)
}
authByte, _, _, ok := authRegistry.LookupType(t.Auth)
if !ok {
return fmt.Errorf("unknown auth type %T", t.Auth)
}
actionID := t.Action.GetTypeID()
authID := t.Auth.GetTypeID()
t.Base.Marshal(p)
var warpBytes []byte
if t.WarpMessage != nil {
Expand All @@ -359,26 +344,22 @@ func (t *Transaction) Marshal(
}
}
p.PackBytes(warpBytes)
p.PackByte(actionByte)
p.PackByte(actionID)
t.Action.Marshal(p)
p.PackByte(authByte)
p.PackByte(authID)
t.Auth.Marshal(p)
return p.Err()
}

func MarshalTxs(
txs []*Transaction,
actionRegistry ActionRegistry,
authRegistry AuthRegistry,
) ([]byte, error) {
func MarshalTxs(txs []*Transaction) ([]byte, error) {
if len(txs) == 0 {
return nil, ErrNoTxs
}
size := consts.IntLen + codec.CummSize(txs)
p := codec.NewWriter(size, consts.NetworkSizeLimit)
p.PackInt(len(txs))
for _, tx := range txs {
if err := tx.Marshal(p, actionRegistry, authRegistry); err != nil {
if err := tx.Marshal(p); err != nil {
return nil, err
}
}
Expand Down
26 changes: 4 additions & 22 deletions codec/type_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
package codec

import (
"fmt"

"github.com/ava-labs/hypersdk/consts"
)

Expand All @@ -18,8 +16,6 @@ type decoder[T any, X any, Y any] struct {
type TypeParser[T any, X any, Y any] struct {
typeToIndex map[string]uint8
indexToDecoder map[uint8]*decoder[T, X, Y]

index uint8
}

// NewTypeParser returns an instance of a Typeparser with generic type [T].
Expand All @@ -33,31 +29,17 @@ func NewTypeParser[T any, X any, Y bool]() *TypeParser[T, X, Y] {
// Register registers a new type into TypeParser [p]. Registers the type by using
// the string representation of [o], and sets the decoder of that index to [f].
// Returns an error if [o] has already been registered or the TypeParser is full.
func (p *TypeParser[T, X, Y]) Register(o T, f func(*Packer, X) (T, error), y Y) error {
if p.index == consts.MaxUint8 {
func (p *TypeParser[T, X, Y]) Register(id uint8, f func(*Packer, X) (T, error), y Y) error {
if len(p.indexToDecoder) == int(consts.MaxUint8)+1 {
return ErrTooManyItems
}
k := fmt.Sprintf("%T", o)
if _, ok := p.typeToIndex[k]; ok {
if _, ok := p.indexToDecoder[id]; ok {
return ErrDuplicateItem
}
p.typeToIndex[k] = p.index
p.indexToDecoder[p.index] = &decoder[T, X, Y]{f, y}
p.index++
p.indexToDecoder[id] = &decoder[T, X, Y]{f, y}
return nil
}

// LookupType returns the index, decoder function and the success of lookup of
// type [o] from Typeparser [p].
func (p *TypeParser[T, X, Y]) LookupType(o T) (uint8, func(*Packer, X) (T, error), Y, bool) {
index, ok := p.typeToIndex[fmt.Sprintf("%T", o)]
if !ok {
return 0, nil, *new(Y), false
}
d := p.indexToDecoder[index]
return index, d.f, d.y, true
}

// LookupIndex returns the decoder function and success of lookup of [index]
// from Typeparser [p].
func (p *TypeParser[T, X, Y]) LookupIndex(index uint8) (func(*Packer, X) (T, error), Y, bool) {
Expand Down
51 changes: 21 additions & 30 deletions codec/type_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,20 @@ type Blah1 struct{}

func (*Blah1) Bark() string { return "blah1" }

func (*Blah1) GetTypeID() uint8 { return 0 }

type Blah2 struct{}

func (*Blah2) Bark() string { return "blah2" }

func (*Blah2) GetTypeID() uint8 { return 1 }

type Blah3 struct{}

func (*Blah3) Bark() string { return "blah3" }

func (*Blah3) GetTypeID() uint8 { return 2 }

func TestTypeParser(t *testing.T) {
tp := NewTypeParser[Blah, any, bool]()

Expand All @@ -35,71 +41,56 @@ func TestTypeParser(t *testing.T) {
require.Nil(f)
require.False(ok)
require.False(b)
index, f, b, ok := tp.LookupType(&Blah1{})
require.Equal(uint8(0), index)
require.Nil(f)
require.False(ok)
require.False(b)
})

t.Run("populated parser", func(t *testing.T) {
require := require.New(t)

blah1 := &Blah1{}
blah2 := &Blah2{}
require.NoError(
tp.Register(
&Blah1{},
blah1.GetTypeID(),
func(p *Packer, a any) (Blah, error) { return nil, errors.New("blah1") },
true,
),
)
require.Equal(uint8(1), tp.index)
require.NoError(
tp.Register(
&Blah2{},
blah2.GetTypeID(),
func(p *Packer, a any) (Blah, error) { return nil, errors.New("blah2") },
false,
),
)
require.Equal(uint8(2), tp.index)

f, b, ok := tp.LookupIndex(0)
f, b, ok := tp.LookupIndex(blah1.GetTypeID())
require.True(ok)
require.True(b)
res, err := f(nil, nil)
require.Nil(res)
require.ErrorContains(err, "blah1")

index, f, b, ok := tp.LookupType(&Blah1{})
require.True(ok)
require.True(b)
require.Equal(uint8(0), index)
res, err = f(nil, nil)
require.Nil(res)
require.ErrorContains(err, "blah1")

f, b, ok = tp.LookupIndex(1)
require.True(ok)
require.False(b)
res, err = f(nil, nil)
require.Nil(res)
require.ErrorContains(err, "blah2")

index, f, b, ok = tp.LookupType(&Blah2{})
f, b, ok = tp.LookupIndex(blah2.GetTypeID())
require.True(ok)
require.False(b)
require.Equal(uint8(1), index)
res, err = f(nil, nil)
require.Nil(res)
require.ErrorContains(err, "blah2")
})

t.Run("duplicate item", func(t *testing.T) {
require := require.New(t)
require.ErrorIs(tp.Register(&Blah1{}, nil, true), ErrDuplicateItem)
require.ErrorIs(tp.Register((&Blah1{}).GetTypeID(), nil, true), ErrDuplicateItem)
})

t.Run("too many items", func(t *testing.T) {
require := require.New(t)
tp.index = consts.MaxUint8 // force max
require.ErrorIs(tp.Register(&Blah3{}, nil, true), ErrTooManyItems)
arrayLength := int(consts.MaxUint8) + 1 - len(tp.indexToDecoder)
for index := range make([]struct{}, arrayLength) {
// 0 and 1 are already existing -> we use index + 2
require.NoError(tp.Register(uint8(index+2), nil, true))
}
// all possible uint8 value should already be store, using any return ErrTooManyItems
require.ErrorIs(tp.Register(uint8(4), nil, true), ErrTooManyItems)
})
}
9 changes: 9 additions & 0 deletions examples/morpheusvm/actions/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package actions

// Note: Registry will error during initialization if a duplicate ID is assigned. We explicitly assign IDs to avoid accidental remapping.
const (
transferID uint8 = 0
)
4 changes: 4 additions & 0 deletions examples/morpheusvm/actions/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ type Transfer struct {
Value uint64 `json:"value"`
}

func (*Transfer) GetTypeID() uint8 {
return transferID
}

func (t *Transfer) StateKeys(rauth chain.Auth, _ ids.ID) [][]byte {
return [][]byte{
storage.PrefixBalanceKey(auth.GetActor(rauth)),
Expand Down
9 changes: 9 additions & 0 deletions examples/morpheusvm/auth/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (C) 2023, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package auth

// Note: Registry will error during initialization if a duplicate ID is assigned. We explicitly assign IDs to avoid accidental remapping.
const (
ed25519ID uint8 = 0
)
4 changes: 4 additions & 0 deletions examples/morpheusvm/auth/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ type ED25519 struct {
Signature crypto.Signature `json:"signature"`
}

func (*ED25519) GetTypeID() uint8 {
return ed25519ID
}

func (*ED25519) MaxUnits(
chain.Rules,
) uint64 {
Expand Down
4 changes: 2 additions & 2 deletions examples/morpheusvm/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ func init() {
errs := &wrappers.Errs{}
errs.Add(
// When registering new actions, ALWAYS make sure to append at the end.
consts.ActionRegistry.Register(&actions.Transfer{}, actions.UnmarshalTransfer, false),
consts.ActionRegistry.Register((&actions.Transfer{}).GetTypeID(), actions.UnmarshalTransfer, false),

// When registering new auth, ALWAYS make sure to append at the end.
consts.AuthRegistry.Register(&auth.ED25519{}, auth.UnmarshalED25519, false),
consts.AuthRegistry.Register((&auth.ED25519{}).GetTypeID(), auth.UnmarshalED25519, false),
)
if errs.Errored() {
panic(errs.Err)
Expand Down
Loading

0 comments on commit fe99c39

Please sign in to comment.