diff --git a/chain/block.go b/chain/block.go index 62abb27b4e..4c5f4b5d85 100644 --- a/chain/block.go +++ b/chain/block.go @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/hypersdk/internal/window" "github.com/ava-labs/hypersdk/internal/workers" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/state/layout" "github.com/ava-labs/hypersdk/utils" ) @@ -422,7 +423,7 @@ func (b *StatefulBlock) innerVerify(ctx context.Context, vctx VerifyContext) err } // Fetch parent height key and ensure block height is valid - heightKey := b.vm.StateLayout().HeightKey() + heightKey := HeightKey(layout.HeightPrefix()) parentHeightRaw, err := parentView.GetValue(ctx, heightKey) if err != nil { return err @@ -439,7 +440,7 @@ func (b *StatefulBlock) innerVerify(ctx context.Context, vctx VerifyContext) err // // Parent may not be available (if we preformed state sync), so we // can't rely on being able to fetch it during verification. - timestampKey := b.vm.StateLayout().TimestampKey() + timestampKey := TimestampKey(layout.TimestampPrefix()) parentTimestampRaw, err := parentView.GetValue(ctx, timestampKey) if err != nil { return err @@ -482,7 +483,7 @@ func (b *StatefulBlock) innerVerify(ctx context.Context, vctx VerifyContext) err } // Compute next unit prices to use - feeKey := b.vm.StateLayout().FeeKey() + feeKey := FeeKey(layout.FeePrefix()) feeRaw, err := parentView.GetValue(ctx, feeKey) if err != nil { return err @@ -735,7 +736,7 @@ func (b *StatefulBlock) View(ctx context.Context, verify bool) (state.View, erro if err != nil { return nil, err } - acceptedHeightRaw, err := acceptedState.Get(b.vm.StateLayout().HeightKey()) + acceptedHeightRaw, err := acceptedState.Get(HeightKey(layout.HeightPrefix())) if err != nil { return nil, err } @@ -857,7 +858,8 @@ func (b *StatefulBlock) IsRepeat( } func (b *StatefulBlock) GetVerifyContext(ctx context.Context, blockHeight uint64, parent ids.ID) (VerifyContext, error) { - // If [blockHeight] is 0, we throw an error because there is no pre-genesis verification context. + // If [blockHeight] is 0, we throw an error because there is no pre-genesis + // verification context. if blockHeight == 0 { return nil, errors.New("cannot get context of genesis block") } diff --git a/chain/builder.go b/chain/builder.go index a95992be61..ff1e4090cc 100644 --- a/chain/builder.go +++ b/chain/builder.go @@ -22,6 +22,7 @@ import ( "github.com/ava-labs/hypersdk/internal/fees" "github.com/ava-labs/hypersdk/keys" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/state/layout" "github.com/ava-labs/hypersdk/state/tstate" ) @@ -92,7 +93,7 @@ func BuildBlock( } // Compute next unit prices to use - feeKey := vm.StateLayout().FeeKey() + feeKey := FeeKey(layout.FeePrefix()) feeRaw, err := parentView.GetValue(ctx, feeKey) if err != nil { return nil, err @@ -372,9 +373,9 @@ func BuildBlock( } // Update chain metadata - heightKey := b.vm.StateLayout().HeightKey() + heightKey := HeightKey(layout.HeightPrefix()) heightKeyStr := string(heightKey) - timestampKey := b.vm.StateLayout().TimestampKey() + timestampKey := TimestampKey(layout.TimestampPrefix()) timestampKeyStr := string(timestampKey) feeKeyStr := string(feeKey) diff --git a/chain/dependencies.go b/chain/dependencies.go index 1f1b7b936d..f18bec82f2 100644 --- a/chain/dependencies.go +++ b/chain/dependencies.go @@ -72,7 +72,6 @@ type VM interface { State() (merkledb.MerkleDB, error) BalanceHandler() BalanceHandler - StateLayout() state.Layout Mempool() Mempool IsRepeat(context.Context, []*Transaction, set.Bits, bool) set.Bits diff --git a/examples/morpheusvm/storage/storage.go b/examples/morpheusvm/storage/storage.go index 4ff5234448..441a8e0195 100644 --- a/examples/morpheusvm/storage/storage.go +++ b/examples/morpheusvm/storage/storage.go @@ -14,6 +14,7 @@ import ( "github.com/ava-labs/hypersdk/codec" "github.com/ava-labs/hypersdk/consts" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/state/layout" smath "github.com/ava-labs/avalanchego/utils/math" ) @@ -21,7 +22,7 @@ import ( type ReadState func(context.Context, [][]byte) ([][]byte, []error) var ( - balancePrefix = consts.LowestAvailablePrefix() + balancePrefix = layout.LowestAvailablePrefix() VMSpecificPrefixes = []byte{balancePrefix} ) diff --git a/state/layout.go b/state/layout.go deleted file mode 100644 index 7ada8219b3..0000000000 --- a/state/layout.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package state - -import ( - "bytes" - "errors" - "fmt" - "strings" - - "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/hypersdk/keys" -) - -const ( - heightKeyChunks = 1 - timestampKeyChunks = 1 - feeKeyChunks = 8 // 96 (per dimension) * 5 (num dimensions) - balanceKeyChunks uint16 = 1 -) - -var ( - //TODO fix errors - ErrInvalidKey = errors.New("invalid key") - ErrInvalidKeyPrefix = errors.New("invalid key prefix") - ErrDuplicateKey = errors.New("duplicate key") - ErrConflictingKey = errors.New("conflicting key") - - // heightKeyName = "height" - // timestampKeyName = "timestamp" - // feeKeyName = "fee" -) - -// TODO test user writing to managed key (balance prefix + account collides with -// reserved key) -func NewLayout( - heightPrefix byte, - timestampPrefix byte, - feePrefix byte, - vmSpecificPrefixes []byte, -) (Layout, error) { - - prefixes := []byte{ - heightPrefix, - timestampPrefix, - feePrefix, - } - - prefixes = append(prefixes, vmSpecificPrefixes...) - - verifiedPrefixes := set.Set[string]{} - - for _, k := range prefixes { - keyString := string(k) - - for prefix := range verifiedPrefixes { - if !strings.HasPrefix(prefix, keyString) { - return Layout{}, fmt.Errorf("invalid state key %s: %w", string(k), ErrConflictingKey) - } - } - - verifiedPrefixes.Add(keyString) - } - - return Layout{ - heightKey: keys.EncodeChunks([]byte{heightPrefix}, heightKeyChunks), - timestampKey: keys.EncodeChunks([]byte{timestampPrefix}, timestampKeyChunks), - feeKey: keys.EncodeChunks([]byte{feePrefix}, feeKeyChunks), - }, nil -} - -// Layout defines hypersdk-manged state keys -// TODO unit tests -// TODO rename Schema/Factory/Keys -type Layout struct { - heightKey []byte - timestampKey []byte - feeKey []byte -} - -func (l Layout) HeightKey() []byte { - return l.heightKey -} - -func (l Layout) TimestampKey() []byte { - return l.timestampKey -} - -func (l Layout) FeeKey() []byte { - return l.feeKey -} - -func (l Layout) ConflictingPrefix(prefix byte) bool { - return bytes.HasPrefix(l.heightKey, []byte{prefix}) || - bytes.HasPrefix(l.timestampKey, []byte{prefix}) || - bytes.HasPrefix(l.feeKey, []byte{prefix}) -} diff --git a/state/layout/layout.go b/state/layout/layout.go new file mode 100644 index 0000000000..fecd23fb23 --- /dev/null +++ b/state/layout/layout.go @@ -0,0 +1,66 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package layout + +import ( + "errors" + "fmt" + + "github.com/ava-labs/avalanchego/utils/set" +) + +const ( + defaultHeightStatePrefix byte = 0x0 + defaultTimestampStatePrefix byte = 0x1 + defaultFeeStatePrefix byte = 0x2 + lowestAvailablePrefix byte = 0x3 +) + +var ErrConflictingKey = errors.New("conflicting key") + +func IsValidLayout(vmSpecificPrefixes []byte) error { + prefixes := []byte{ + defaultHeightStatePrefix, + defaultTimestampStatePrefix, + defaultFeeStatePrefix, + } + + prefixes = append(prefixes, vmSpecificPrefixes...) + + verifiedPrefixes := set.Set[string]{} + + for _, k := range prefixes { + keyString := string(k) + + for prefix := range verifiedPrefixes { + if prefix == keyString { + return fmt.Errorf("invalid state key %s: %w", string(k), ErrConflictingKey) + } + } + + verifiedPrefixes.Add(keyString) + } + + return nil +} + +func HeightPrefix() []byte { + return []byte{defaultHeightStatePrefix} +} + +func TimestampPrefix() []byte { + return []byte{defaultTimestampStatePrefix} +} + +func FeePrefix() []byte { + return []byte{defaultFeeStatePrefix} +} + +func ConflictingPrefix(prefix byte) bool { + return defaultHeightStatePrefix == prefix || defaultTimestampStatePrefix == prefix || defaultFeeStatePrefix == prefix +} + +func LowestAvailablePrefix() byte { + return lowestAvailablePrefix +} diff --git a/vm/resolutions.go b/vm/resolutions.go index 28bf54f61c..6fd5458d3c 100644 --- a/vm/resolutions.go +++ b/vm/resolutions.go @@ -25,6 +25,7 @@ import ( "github.com/ava-labs/hypersdk/internal/gossiper" "github.com/ava-labs/hypersdk/internal/workers" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/state/layout" "github.com/ava-labs/hypersdk/state/tstate" internalfees "github.com/ava-labs/hypersdk/internal/fees" @@ -385,10 +386,6 @@ func (vm *VM) BalanceHandler() chain.BalanceHandler { return vm.balanceHandler } -func (vm *VM) StateLayout() state.Layout { - return vm.stateLayout -} - func (vm *VM) RecordRootCalculated(t time.Duration) { vm.metrics.rootCalculated.Observe(float64(t)) } @@ -470,7 +467,7 @@ func (vm *VM) RecordClearedMempool() { } func (vm *VM) UnitPrices(context.Context) (fees.Dimensions, error) { - v, err := vm.stateDB.Get(chain.FeeKey(vm.StateLayout().FeeKey())) + v, err := vm.stateDB.Get(chain.FeeKey(layout.FeePrefix())) if err != nil { return fees.Dimensions{}, err } diff --git a/vm/vm.go b/vm/vm.go index d0fdc7a9a5..65b1e1ce6c 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -42,6 +42,7 @@ import ( "github.com/ava-labs/hypersdk/internal/validators" "github.com/ava-labs/hypersdk/internal/workers" "github.com/ava-labs/hypersdk/state" + "github.com/ava-labs/hypersdk/state/layout" "github.com/ava-labs/hypersdk/storage" "github.com/ava-labs/hypersdk/utils" @@ -61,12 +62,6 @@ const ( MinAcceptedBlockWindow = 1024 ) -const ( - defaultHeightStatePrefix byte = 0x0 - defaultTimestampStatePrefix byte = 0x1 - defaultFeeStatePrefix byte = 0x2 -) - type VM struct { DataDir string v *version.Semantic @@ -95,12 +90,11 @@ type VM struct { stateDB merkledb.MerkleDB vmDB database.Database handlers map[string]http.Handler - balanceHandler chain.BalanceHandler + balanceHandler chain.BalanceHandler actionRegistry chain.ActionRegistry authRegistry chain.AuthRegistry outputRegistry chain.OutputRegistry authEngine map[uint8]AuthEngine - stateLayout state.Layout tracer avatrace.Tracer mempool *mempool.Mempool[*chain.Transaction] @@ -172,21 +166,13 @@ func New( allocatedNamespaces.Add(option.Namespace) } - stateLayout, err := state.NewLayout( - defaultHeightStatePrefix, - defaultTimestampStatePrefix, - defaultFeeStatePrefix, - vmSpecificPrefixes, - ) - - if err != nil { + if err := layout.IsValidLayout(vmSpecificPrefixes); err != nil { return nil, err } return &VM{ v: v, - balanceHandler: balanceHandler, - stateLayout: stateLayout, + balanceHandler: balanceHandler, config: NewConfig(), actionRegistry: actionRegistry, authRegistry: authRegistry, @@ -400,10 +386,10 @@ func (vm *VM) Initialize( // Update chain metadata sps = state.NewSimpleMutable(vm.stateDB) - if err := sps.Insert(ctx, chain.HeightKey(vm.stateLayout.HeightKey()), binary.BigEndian.AppendUint64(nil, 0)); err != nil { + if err := sps.Insert(ctx, chain.HeightKey(layout.HeightPrefix()), binary.BigEndian.AppendUint64(nil, 0)); err != nil { return err } - if err := sps.Insert(ctx, chain.TimestampKey(vm.stateLayout.TimestampKey()), binary.BigEndian.AppendUint64(nil, 0)); err != nil { + if err := sps.Insert(ctx, chain.TimestampKey(layout.HeightPrefix()), binary.BigEndian.AppendUint64(nil, 0)); err != nil { return err } genesisRules := vm.Rules(0) @@ -413,7 +399,7 @@ func (vm *VM) Initialize( feeManager.SetUnitPrice(i, minUnitPrice[i]) snowCtx.Log.Info("set genesis unit price", zap.Int("dimension", int(i)), zap.Uint64("price", feeManager.UnitPrice(i))) } - if err := sps.Insert(ctx, chain.FeeKey(vm.stateLayout.FeeKey()), feeManager.Bytes()); err != nil { + if err := sps.Insert(ctx, chain.FeeKey(layout.FeePrefix()), feeManager.Bytes()); err != nil { return err } @@ -897,7 +883,7 @@ func (vm *VM) Submit( // This will error if a block does not yet have processed state. return []error{err} } - feeRaw, err := view.GetValue(ctx, chain.FeeKey(vm.stateLayout.FeeKey())) + feeRaw, err := view.GetValue(ctx, chain.FeeKey(layout.FeePrefix())) if err != nil { return []error{err} }