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

Tiered Storage #1832

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
extract refund manager from scope
  • Loading branch information
RodrigoVillar committed Dec 13, 2024
commit 02a6b4e4b82fabb5f971369f849d4914f9d77a8f
9 changes: 5 additions & 4 deletions chain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,12 @@ func (c *Builder) BuildBlock(ctx context.Context, parentView state.View, parent
}

// Execute block
scope, err := scope.NewTieredScope(stateKeys, storage, c.config.Epsilon, height)
unsuffixedStorage, hotkeys, err := extractSuffixes(storage, height, c.config.Epsilon)
if err != nil {
c.log.Warn("unable to create scope", zap.Error(err))
c.log.Warn("failed to extract suffixes", zap.Error(err))
return err
}
tsv := ts.NewView(scope)
tsv := ts.NewView(scope.NewDefaultScope(stateKeys, unsuffixedStorage))
if err := tx.PreExecute(ctx, feeManager, c.balanceHandler, r, tsv, nextTime); err != nil {
// We don't need to rollback [tsv] here because it will never
// be committed.
Expand All @@ -305,11 +305,12 @@ func (c *Builder) BuildBlock(ctx context.Context, parentView state.View, parent
}
return nil
}
refundManager := fees.NewHotKeysRefundManager(hotkeys)
result, err := tx.Execute(
ctx,
feeManager,
c.balanceHandler,
scope,
refundManager,
r,
tsv,
nextTime,
Expand Down
4 changes: 4 additions & 0 deletions chain/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ var (
ErrFailedToComputeRefund = errors.New("failed to compute refund")
ErrRefundFailed = errors.New("failed to refund")

// Suffixes
ErrValueTooShortForSuffix = errors.New("value too short for suffix")
ErrFailedToParseMaxChunks = errors.New("failed to parse max chunks")

// Misc
ErrNotImplemented = errors.New("not implemented")
ErrBlockNotProcessed = errors.New("block is not processed")
Expand Down
11 changes: 7 additions & 4 deletions chain/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,21 +378,24 @@ func (p *Processor) executeTxs(
}

// Execute transaction
//
// It is critical we explicitly set the scope before each transaction is
// processed
scope, err := scope.NewTieredScope(stateKeys, storage, p.config.Epsilon, b.Height())
untranslatedStorage, hotKeys, err := extractSuffixes(storage, b.Hght, p.config.Epsilon)
if err != nil {
return err
}
tsv := ts.NewView(scope)
tsv := ts.NewView(
scope.NewDefaultScope(stateKeys, untranslatedStorage),
)

// Ensure we have enough funds to pay fees
if err := tx.PreExecute(ctx, feeManager, p.balanceHandler, r, tsv, t); err != nil {
return err
}

result, err := tx.Execute(ctx, feeManager, p.balanceHandler, scope, r, tsv, t)
refundManager := fees.NewHotKeysRefundManager(hotKeys)

result, err := tx.Execute(ctx, feeManager, p.balanceHandler, refundManager, r, tsv, t)
if err != nil {
return err
}
Expand Down
5 changes: 2 additions & 3 deletions chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"github.com/ava-labs/hypersdk/internal/mempool"
"github.com/ava-labs/hypersdk/keys"
"github.com/ava-labs/hypersdk/state"
"github.com/ava-labs/hypersdk/state/scope"
"github.com/ava-labs/hypersdk/state/tstate"
"github.com/ava-labs/hypersdk/utils"

Expand Down Expand Up @@ -313,7 +312,7 @@ func (t *Transaction) Execute(
ctx context.Context,
feeManager *internalfees.Manager,
bh BalanceHandler,
scope scope.Scope,
rm internalfees.RefundManager,
r Rules,
ts *tstate.TStateView,
timestamp int64,
Expand Down Expand Up @@ -366,7 +365,7 @@ func (t *Transaction) Execute(
}

// We refund here
refund, err := scope.Refund(r)
refund, err := rm.Compute(r)
if err != nil {
return nil, ErrRefundDimensions
}
Expand Down
32 changes: 32 additions & 0 deletions chain/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
package chain

import (
"encoding/binary"

"github.com/ava-labs/avalanchego/ids"

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

Expand All @@ -16,3 +19,32 @@ func CreateActionID(txID ids.ID, i uint8) ids.ID {
actionBytes[ids.IDLen] = i
return utils.ToID(actionBytes)
}

func extractSuffixes(storage map[string][]byte, blockHeight uint64, epsilon uint64) (
map[string][]byte,
map[string]uint16,
error,
) {
unsuffixedStorage := make(map[string][]byte)
hotkeys := make(map[string]uint16)

for k, v := range storage {
if len(v) < consts.Uint64Len {
return nil, nil, ErrValueTooShortForSuffix
}
lastTouched := binary.BigEndian.Uint64(v[len(v)-consts.Uint64Len:])

// If hot key
if blockHeight-lastTouched <= epsilon {
maxChunks, ok := keys.MaxChunks([]byte(k))
if !ok {
return nil, nil, ErrFailedToParseMaxChunks
}
hotkeys[k] = maxChunks
}

unsuffixedStorage[k] = v[:len(v)-consts.Uint64Len]
}

return unsuffixedStorage, hotkeys, nil
}
51 changes: 51 additions & 0 deletions internal/fees/refund_manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2024, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package fees

import (
"github.com/ava-labs/hypersdk/fees"
"github.com/ava-labs/hypersdk/internal/math"
)

var (
_ RefundManager = (*HotKeysRefundManager)(nil)
_ RefundManager = (*NoOpRefundManager)(nil)
)

type RefundRules interface {
GetStorageKeyReadRefundUnits() uint64
GetStorageValueReadRefundUnits() uint64
}

type RefundManager interface {
Compute(r RefundRules) (fees.Dimensions, error)
}

type HotKeysRefundManager struct {
hotKeys map[string]uint16
}

func NewHotKeysRefundManager(hotKeys map[string]uint16) *HotKeysRefundManager {
return &HotKeysRefundManager{hotKeys: hotKeys}
}

func (m *HotKeysRefundManager) Compute(r RefundRules) (fees.Dimensions, error) {
readsRefundOp := math.NewUint64Operator(0)
for _, v := range m.hotKeys {
readsRefundOp.Add(r.GetStorageKeyReadRefundUnits())
readsRefundOp.MulAdd(uint64(v), r.GetStorageValueReadRefundUnits())
}
readRefunds, err := readsRefundOp.Value()
if err != nil {
return fees.Dimensions{}, err
}

return fees.Dimensions{0, 0, readRefunds, 0, 0}, nil
}

type NoOpRefundManager struct{}

func (*NoOpRefundManager) Compute(_ RefundRules) (fees.Dimensions, error) {
return fees.Dimensions{}, nil
}
9 changes: 0 additions & 9 deletions state/scope/dependencies.go

This file was deleted.

93 changes: 0 additions & 93 deletions state/scope/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,16 @@ package scope

import (
"context"
"encoding/binary"
"errors"
"fmt"

"github.com/ava-labs/avalanchego/database"

"github.com/ava-labs/hypersdk/consts"
"github.com/ava-labs/hypersdk/fees"
"github.com/ava-labs/hypersdk/internal/math"
"github.com/ava-labs/hypersdk/keys"
"github.com/ava-labs/hypersdk/state"
)

var (
_ Scope = (*DefaultScope)(nil)
_ Scope = (*SimulatedScope)(nil)
_ Scope = (*TieredScope)(nil)
)

var ErrValueTooShortToBeSuffixed = errors.New("value is too short to be suffixed")
Expand All @@ -30,83 +23,6 @@ type Scope interface {
Has(key []byte, perm state.Permissions) bool
GetValue(ctx context.Context, key []byte) ([]byte, error)
Len() int
Refund(RefundRules) (fees.Dimensions, error)
}

type TieredScope struct {
keys state.Keys
storage map[string][]byte
epsilon uint64
blockHeight uint64

hotKeys map[string]uint16
}

func (d *TieredScope) GetValue(_ context.Context, key []byte) ([]byte, error) {
if v, has := d.storage[string(key)]; has {
return v, nil
}
return nil, database.ErrNotFound
}

func (d *TieredScope) Has(key []byte, perm state.Permissions) bool {
return d.keys[string(key)].Has(perm)
}

func (d *TieredScope) Len() int {
return len(d.keys)
}

func NewTieredScope(
ks state.Keys,
storage map[string][]byte,
epsilon uint64,
blockHeight uint64,
) (*TieredScope, error) {
st := make(map[string][]byte, len(storage))
hotKeys := make(map[string]uint16)

for k, v := range storage {
if len(v) < consts.Uint64Len {
return nil, ErrValueTooShortToBeSuffixed
}

lastTouched := binary.BigEndian.Uint64(v[len(v)-consts.Uint64Len:])

// If hot key
if blockHeight-lastTouched <= epsilon {
maxChunks, ok := keys.MaxChunks([]byte(k))
if !ok {
return nil, fmt.Errorf("failed to parse max chunks for key %s", k)
}
hotKeys[k] = maxChunks
}

v = v[:len(v)-consts.Uint64Len]
st[k] = v
}

return &TieredScope{
keys: ks,
storage: st,
epsilon: epsilon,
blockHeight: blockHeight,
hotKeys: hotKeys,
}, nil
}

func (d *TieredScope) Refund(r RefundRules) (fees.Dimensions, error) {
readsRefundOp := math.NewUint64Operator(0)
for _, v := range d.hotKeys {
readsRefundOp.Add(r.GetStorageKeyReadRefundUnits())
readsRefundOp.MulAdd(uint64(v), r.GetStorageValueReadRefundUnits())
}
readRefunds, err := readsRefundOp.Value()
if err != nil {
return fees.Dimensions{}, err
}

return fees.Dimensions{0, 0, readRefunds, 0, 0}, nil
}

type DefaultScope struct {
Expand Down Expand Up @@ -139,11 +55,6 @@ func (d *DefaultScope) Len() int {
return len(d.keys)
}

func (*DefaultScope) Refund(_ RefundRules) (fees.Dimensions, error) {
return fees.Dimensions{}, nil
}

// TODO: is this even scope?
type SimulatedScope struct {
keys state.Keys
im state.Immutable
Expand All @@ -156,10 +67,6 @@ func NewSimulatedScope(keys state.Keys, im state.Immutable) *SimulatedScope {
}
}

func (*SimulatedScope) Refund(_ RefundRules) (fees.Dimensions, error) {
return fees.Dimensions{}, nil
}

func (d *SimulatedScope) GetValue(ctx context.Context, key []byte) ([]byte, error) {
return d.im.GetValue(ctx, key)
}
Expand Down
Loading