Skip to content

Commit

Permalink
Remove StateKeysMaxChunks() (#1607)
Browse files Browse the repository at this point in the history
  • Loading branch information
RodrigoVillar authored Sep 30, 2024
1 parent 3e358c1 commit 592cd7a
Show file tree
Hide file tree
Showing 16 changed files with 53 additions and 86 deletions.
2 changes: 1 addition & 1 deletion api/jsonrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (j *JSONRPCServer) Execute(
now := time.Now().UnixMilli()

// Get expected state keys
stateKeysWithPermissions := action.StateKeys(args.Actor, ids.Empty)
stateKeysWithPermissions := action.StateKeys(args.Actor)

// flatten the map to a slice of keys
storageKeysToRead := make([][]byte, 0)
Expand Down
4 changes: 4 additions & 0 deletions auth/bls.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ func (*BLSFactory) MaxUnits() (uint64, uint64) {
return BLSSize, BLSComputeUnits
}

func (b *BLSFactory) Address() codec.Address {
return NewBLSAddress(bls.PublicFromPrivateKey(b.priv))
}

func NewBLSAddress(pk *bls.PublicKey) codec.Address {
return codec.CreateAddress(BLSID, utils.ToID(bls.PublicKeyToBytes(pk)))
}
4 changes: 4 additions & 0 deletions auth/ed25519.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ func (*ED25519Factory) MaxUnits() (uint64, uint64) {
return ED25519Size, ED25519ComputeUnits
}

func (d *ED25519Factory) Address() codec.Address {
return NewED25519Address(d.priv.PublicKey())
}

type ED25519AuthEngine struct{}

func (*ED25519AuthEngine) GetBatchVerifier(cores int, count int) chain.AuthBatchVerifier {
Expand Down
4 changes: 4 additions & 0 deletions auth/secp256r1.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ func (*SECP256R1Factory) MaxUnits() (uint64, uint64) {
return SECP256R1Size, SECP256R1ComputeUnits
}

func (d *SECP256R1Factory) Address() codec.Address {
return NewSECP256R1Address(d.priv.PublicKey())
}

func NewSECP256R1Address(pk secp256r1.PublicKey) codec.Address {
return codec.CreateAddress(SECP256R1ID, utils.ToID(pk[:]))
}
8 changes: 2 additions & 6 deletions chain/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,6 @@ type Action interface {
// whether the [Action] can be included in a given block and to compute the required fee to execute.
ComputeUnits(Rules) uint64

// StateKeysMaxChunks is used to estimate the fee a transaction should pay. It includes the max
// chunks each state key could use without requiring the state keys to actually be provided (may
// not be known until execution).
StateKeysMaxChunks() []uint16

// StateKeys is a full enumeration of all database keys that could be touched during execution
// of an [Action]. This is used to prefetch state and will be used to parallelize execution (making
// an execution tree is trivial).
Expand All @@ -229,7 +224,7 @@ type Action interface {
// key (formatted as a big-endian uint16). This is used to automatically calculate storage usage.
//
// If any key is removed and then re-created, this will count as a creation instead of a modification.
StateKeys(actor codec.Address, actionID ids.ID) state.Keys
StateKeys(actor codec.Address) state.Keys

// Execute actually runs the [Action]. Any state changes that the [Action] performs should
// be done here.
Expand Down Expand Up @@ -290,4 +285,5 @@ type AuthFactory interface {
// Sign is used by helpers, auth object should store internally to be ready for marshaling
Sign(msg []byte) (Auth, error)
MaxUnits() (bandwidth uint64, compute uint64)
Address() codec.Address
}
11 changes: 8 additions & 3 deletions chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) {
stateKeys := make(state.Keys)

// Verify the formatting of state keys passed by the controller
for i, action := range t.Actions {
for k, v := range action.StateKeys(t.Auth.Actor(), CreateActionID(t.ID(), uint8(i))) {
for _, action := range t.Actions {
for k, v := range action.StateKeys(t.Auth.Actor()) {
if !stateKeys.Add(k, v) {
return nil, ErrInvalidKeyValue
}
Expand Down Expand Up @@ -213,8 +213,13 @@ func EstimateUnits(r Rules, actions []Action, authFactory AuthFactory) (fees.Dim
return fees.Dimensions{}, err
}

actor := authFactory.Address()
stateKeys := action.StateKeys(actor)
actionStateKeysMaxChunks, ok := stateKeys.ChunkSizes()
if !ok {
return fees.Dimensions{}, ErrInvalidKeyValue
}
bandwidth += consts.ByteLen + uint64(actionSize)
actionStateKeysMaxChunks := action.StateKeysMaxChunks()
stateKeysMaxChunks = append(stateKeysMaxChunks, actionStateKeysMaxChunks...)
computeOp.Add(action.ComputeUnits(r))
}
Expand Down
6 changes: 1 addition & 5 deletions chain/transaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ func (*abstractMockAction) Execute(_ context.Context, _ chain.Rules, _ state.Mut
panic("unimplemented")
}

func (*abstractMockAction) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
panic("unimplemented")
}

func (*abstractMockAction) StateKeysMaxChunks() []uint16 {
func (*abstractMockAction) StateKeys(_ codec.Address) state.Keys {
panic("unimplemented")
}

Expand Down
21 changes: 4 additions & 17 deletions docs/explanation/MorpheusVM.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ Actions implement the following interface:
```golang
ComputeUnits(Rules) uint64
StateKeys(actor codec.Address, actionID ids.ID) state.Keys
StateKeysMaxChunks() []uint16
GetTypeID() uint8
ValidRange(Rules) (start int64, end int64)
Execute(
Expand All @@ -56,16 +55,12 @@ We break this interface down by organizing functions into different categories:

#### Fees and Pessimistic Concurrency Control

`StateKeys()` and `StateKeysMaxChunks()` provide a description of exactly how
key-value pairs in the state will be manipulated during the action’s execution.

`StateKeys()` returns the set of keys and the set of permissions that are
`StateKeys()` provides a description of exactly how
key-value pairs in the state will be manipulated during the action’s
execution. This method returns the set of keys and the set of permissions that are
required to operate on them (ie. Read/Write/Allocate).

`StateKeysMaxChunks()` returns the maximum size measured in 64 byte chunks of
the value that will be stored in the corresponding key index.

For example, `Transfer` implements these as:
For example, `Transfer` implements this method as:

```golang
func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
Expand All @@ -74,20 +69,12 @@ func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
string(storage.BalanceKey(t.To)): state.All,
}
}

func (*Transfer) StateKeysMaxChunks() []uint16 {
return []uint16{storage.BalanceChunks, storage.BalanceChunks}
}
```

This indicates that the actor’s balance key can be read or written, but will not
be allocated during execution. The `To` address may be read, written, or
allocated, since transferring may allocate a new account in storage.

Additionally, returning `[]uint16{storage.BalanceChunks, storage.BalanceChunks}`
or `[]uint16{1, 1}` means that both keys will store a value that takes up at
most 64 bytes.

`ComputeUnits()` returns a measure of the units of compute required to execute
the action.

Expand Down
21 changes: 0 additions & 21 deletions docs/tutorials/morpheusvm/morpheusvm.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,6 @@ func (t *Transfer) StateKeys(actor codec.Address, actionID ids.ID) state.Keys {
panic("unimplemented")
}

func (t *Transfer) StateKeysMaxChunks() []uint16 {
panic("unimplemented")
}

func (t *Transfer) Execute(ctx context.Context, r chain.Rules, mu state.Mutable, timestamp int64, actor codec.Address, actionID ids.ID) (output codec.Typed, err error) {
panic("unimplemented")
}
Expand Down Expand Up @@ -664,23 +660,6 @@ func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
}
```

### `StateKeysMaxChunks()`

Remember handling "chunk size" for the `BalanceKey` implementation?

We implement `StateKeysMaxChunks()` to provide the exact same idea to HyperSDK:

This function returns a slice of `uint16` values (one for each state key) and they
both update a single balance key-value pair, so we'll use `BalanceChunks` as before.

Let's update the function like so:

```golang
func (*Transfer) StateKeysMaxChunks() []uint16 {
return []uint16{storage.BalanceChunks, storage.BalanceChunks}
}
```

### `Execute()`

We start off by first defining the values necessary for this function at the
Expand Down
6 changes: 1 addition & 5 deletions examples/morpheusvm/actions/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,13 @@ func (*Transfer) GetTypeID() uint8 {
return mconsts.TransferID
}

func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
func (t *Transfer) StateKeys(actor codec.Address) state.Keys {
return state.Keys{
string(storage.BalanceKey(actor)): state.Read | state.Write,
string(storage.BalanceKey(t.To)): state.All,
}
}

func (*Transfer) StateKeysMaxChunks() []uint16 {
return []uint16{storage.BalanceChunks, storage.BalanceChunks}
}

func (t *Transfer) Execute(
ctx context.Context,
_ chain.Rules,
Expand Down
10 changes: 1 addition & 9 deletions examples/vmwithcontracts/actions/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,14 @@ func (*Call) GetTypeID() uint8 {
return mconsts.CallContractID
}

func (t *Call) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
func (t *Call) StateKeys(_ codec.Address) state.Keys {
result := state.Keys{}
for _, stateKeyPermission := range t.SpecifiedStateKeys {
result.Add(stateKeyPermission.Key, stateKeyPermission.Permission)
}
return result
}

func (t *Call) StateKeysMaxChunks() []uint16 {
result := make([]uint16, 0, len(t.SpecifiedStateKeys))
for range t.SpecifiedStateKeys {
result = append(result, 1)
}
return result
}

func (t *Call) Execute(
ctx context.Context,
_ chain.Rules,
Expand Down
6 changes: 1 addition & 5 deletions examples/vmwithcontracts/actions/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (*Deploy) GetTypeID() uint8 {
return mconsts.DeployID
}

func (d *Deploy) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
func (d *Deploy) StateKeys(_ codec.Address) state.Keys {
if d.address == codec.EmptyAddress {
d.address = storage.GetAddressForDeploy(0, d.CreationInfo)
}
Expand All @@ -43,10 +43,6 @@ func (d *Deploy) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
}
}

func (*Deploy) StateKeysMaxChunks() []uint16 {
return []uint16{storage.BalanceChunks}
}

func (d *Deploy) Execute(
ctx context.Context,
_ chain.Rules,
Expand Down
10 changes: 1 addition & 9 deletions examples/vmwithcontracts/actions/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/ava-labs/hypersdk/chain"
"github.com/ava-labs/hypersdk/codec"
"github.com/ava-labs/hypersdk/consts"
"github.com/ava-labs/hypersdk/examples/vmwithcontracts/storage"
"github.com/ava-labs/hypersdk/keys"
"github.com/ava-labs/hypersdk/state"
Expand All @@ -34,7 +33,7 @@ func (*Publish) GetTypeID() uint8 {
return mconsts.PublishID
}

func (t *Publish) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
func (t *Publish) StateKeys(_ codec.Address) state.Keys {
if t.id == nil {
hashedID := sha256.Sum256(t.ContractBytes)
t.id, _ = keys.Encode(storage.ContractsKey(hashedID[:]), len(t.ContractBytes))
Expand All @@ -44,13 +43,6 @@ func (t *Publish) StateKeys(_ codec.Address, _ ids.ID) state.Keys {
}
}

func (t *Publish) StateKeysMaxChunks() []uint16 {
if chunks, ok := keys.NumChunks(t.ContractBytes); ok {
return []uint16{chunks}
}
return []uint16{consts.MaxUint16}
}

func (t *Publish) Execute(
ctx context.Context,
_ chain.Rules,
Expand Down
6 changes: 1 addition & 5 deletions examples/vmwithcontracts/actions/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,13 @@ func (*Transfer) GetTypeID() uint8 {
return mconsts.TransferID
}

func (t *Transfer) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
func (t *Transfer) StateKeys(actor codec.Address) state.Keys {
return state.Keys{
string(storage.BalanceKey(actor)): state.Read | state.Write,
string(storage.BalanceKey(t.To)): state.All,
}
}

func (*Transfer) StateKeysMaxChunks() []uint16 {
return []uint16{storage.BalanceChunks, storage.BalanceChunks}
}

func (t *Transfer) Execute(
ctx context.Context,
_ chain.Rules,
Expand Down
7 changes: 7 additions & 0 deletions keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,10 @@ func Encode(key []byte, maxSize int) ([]byte, bool) {
func EncodeChunks(key []byte, maxChunks uint16) []byte {
return binary.BigEndian.AppendUint16(key, maxChunks)
}

func DecodeChunks(key []byte) (uint16, bool) {
if len(key) < 2 {
return 0, false
}
return binary.BigEndian.Uint16(key[len(key)-2:]), true
}
13 changes: 13 additions & 0 deletions state/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ func (k Keys) Add(key string, permission Permissions) bool {
return true
}

// Returns the chunk sizes of each key
func (k Keys) ChunkSizes() ([]uint16, bool) {
chunks := make([]uint16, 0, len(k))
for key := range k {
chunk, ok := keys.DecodeChunks([]byte(key))
if !ok {
return nil, false
}
chunks = append(chunks, chunk)
}
return chunks, true
}

// Has returns true if [p] has all the permissions that are contained in require
func (p Permissions) Has(require Permissions) bool {
return require&^p == 0
Expand Down

0 comments on commit 592cd7a

Please sign in to comment.