Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create TypeID for Actions/Auths + delete reflection in Registry #297

Merged
merged 7 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
patrick-ogrady marked this conversation as resolved.
Show resolved Hide resolved
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)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️

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