Skip to content

Commit

Permalink
[chain] allow StateKeys to be specified with a Permission (Read/Alloc…
Browse files Browse the repository at this point in the history
…ate/Write) (ava-labs#693)

* [chain] statekeys with mode

* [chain] update mock

* [chain] fix lint

* [chain] update docs

* [chain] pass Mode into executor and tstate

* [chain] fix lint

* [chain] remove types

* [chain] impl bit flag for Mode

* [chain] fix test lint

* [chain] inline state.NewKey and remove intermediate k.Name var

* [chain] move consts

* [chain] rename Mode to Permission

* [chain] rename NewKey and HasPermission to use same param var name

* [chain] use single scope map, honour permissions, add unit tests to verify perms

* [chain] remove misc

* [chain] address comments

* [chain] update builder and block tsv keys to write

* [chain] table driven tests

* [chain] use map and prevent duplicate keys and updating key perms

* [chain] support updating of key perms

* [chain] update warp key perms

* [chain] change AddKeyPerm to Add

* [chain] add TODOs and comments

* [chain] create PermissionBit type

* [chain] rename NewPermission to Permission

* [chain] change PermissionBit to int and use switch statement

* [chain] add Allocate permission

* [chain] fix simulator tests

* [chain] address feedback

* [state] add keys test

* [chain] use Permission struct on SDK side and bitset for VM implementations

* Standardize permissions checks

* nit

* [chain] remove ToBytes and FromBytes, fix tests

---------

Co-authored-by: Stephen Buttolph <[email protected]>
  • Loading branch information
wlawt and StephenButtolph authored Feb 2, 2024
1 parent 4bd6d72 commit 0eaf9be
Show file tree
Hide file tree
Showing 28 changed files with 493 additions and 138 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -685,7 +685,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, txID ids.ID) []string
StateKeys(actor codec.Address, txID ids.ID) state.Keys

// Execute actually runs the [Action]. Any state changes that the [Action] performs should
// be done here.
Expand Down
7 changes: 6 additions & 1 deletion chain/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,12 @@ func (b *StatelessBlock) innerVerify(ctx context.Context, vctx VerifyContext) er
heightKeyStr := string(heightKey)
timestampKeyStr := string(timestampKey)
feeKeyStr := string(feeKey)
tsv := ts.NewView(set.Of(heightKeyStr, timestampKeyStr, feeKeyStr), map[string][]byte{

keys := make(state.Keys)
keys.Add(heightKeyStr, state.Write)
keys.Add(timestampKeyStr, state.Write)
keys.Add(feeKeyStr, state.Write)
tsv := ts.NewView(keys, map[string][]byte{
heightKeyStr: parentHeightRaw,
timestampKeyStr: parentTimestampRaw,
feeKeyStr: parentFeeManager.Bytes(),
Expand Down
8 changes: 7 additions & 1 deletion chain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/ava-labs/hypersdk/executor"
"github.com/ava-labs/hypersdk/keys"
"github.com/ava-labs/hypersdk/state"
"github.com/ava-labs/hypersdk/tstate"
)

Expand Down Expand Up @@ -444,7 +445,12 @@ func BuildBlock(
timestampKey := TimestampKey(b.vm.StateManager().TimestampKey())
timestampKeyStr := string(timestampKey)
feeKeyStr := string(feeKey)
tsv := ts.NewView(set.Of(heightKeyStr, timestampKeyStr, feeKeyStr), map[string][]byte{

keys := make(state.Keys)
keys.Add(heightKeyStr, state.Write)
keys.Add(timestampKeyStr, state.Write)
keys.Add(feeKeyStr, state.Write)
tsv := ts.NewView(keys, map[string][]byte{
heightKeyStr: binary.BigEndian.AppendUint64(nil, parent.Hght),
timestampKeyStr: binary.BigEndian.AppendUint64(nil, uint64(parent.Tmstmp)),
feeKeyStr: parentFeeManager.Bytes(),
Expand Down
4 changes: 2 additions & 2 deletions chain/dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ type FeeHandler interface {
//
// All keys specified must be suffixed with the number of chunks that could ever be read from that
// key (formatted as a big-endian uint16). This is used to automatically calculate storage usage.
SponsorStateKeys(addr codec.Address) []string
SponsorStateKeys(addr codec.Address) state.Keys

// CanDeduct returns an error if [amount] cannot be paid by [addr].
CanDeduct(ctx context.Context, addr codec.Address, im state.Immutable, amount uint64) error
Expand Down Expand Up @@ -248,7 +248,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, txID ids.ID) []string
StateKeys(actor codec.Address, txID ids.ID) state.Keys

// Execute actually runs the [Action]. Any state changes that the [Action] performs should
// be done here.
Expand Down
4 changes: 2 additions & 2 deletions chain/mock_action.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 9 additions & 10 deletions chain/transaction.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (

"github.com/ava-labs/avalanchego/database"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/platformvm/warp"

"github.com/ava-labs/hypersdk/codec"
Expand Down Expand Up @@ -47,7 +46,7 @@ type Transaction struct {
// prevents duplicates (like txID). We will not allow 2 instances of the same
// warpID from the same sourceChainID to be accepted.
warpID ids.ID
stateKeys set.Set[string]
stateKeys state.Keys
}

type WarpResult struct {
Expand Down Expand Up @@ -122,34 +121,34 @@ func (t *Transaction) Expiry() int64 { return t.Base.Timestamp }

func (t *Transaction) MaxFee() uint64 { return t.Base.MaxFee }

func (t *Transaction) StateKeys(sm StateManager) (set.Set[string], error) {
func (t *Transaction) StateKeys(sm StateManager) (state.Keys, error) {
if t.stateKeys != nil {
return t.stateKeys, nil
}

// Verify the formatting of state keys passed by the controller
actionKeys := t.Action.StateKeys(t.Auth.Actor(), t.ID())
sponsorKeys := sm.SponsorStateKeys(t.Auth.Sponsor())
stateKeys := set.NewSet[string](len(actionKeys) + len(sponsorKeys))
for _, arr := range [][]string{actionKeys, sponsorKeys} {
for _, k := range arr {
stateKeys := make(state.Keys)
for _, m := range []state.Keys{actionKeys, sponsorKeys} {
for k, v := range m {
if !keys.Valid(k) {
return nil, ErrInvalidKeyValue
}
stateKeys.Add(k)
stateKeys.Add(k, v)
}
}

// Add keys used to manage warp operations
if t.WarpMessage != nil {
p := sm.IncomingWarpKeyPrefix(t.WarpMessage.SourceChainID, t.warpID)
k := keys.EncodeChunks(p, MaxIncomingWarpChunks)
stateKeys.Add(string(k))
stateKeys.Add(string(k), state.Read|state.Write)
}
if t.Action.OutputsWarpMessage() {
p := sm.OutgoingWarpKeyPrefix(t.id)
k := keys.EncodeChunks(p, MaxOutgoingWarpChunks)
stateKeys.Add(string(k))
stateKeys.Add(string(k), state.Write)
}

// Cache keys if called again
Expand Down Expand Up @@ -454,7 +453,7 @@ func (t *Transaction) Execute(

// Because we compute the fee before [Auth.Refund] is called, we need
// to pessimistically precompute the storage it will change.
for _, key := range s.SponsorStateKeys(t.Auth.Sponsor()) {
for key := range s.SponsorStateKeys(t.Auth.Sponsor()) {
// maxChunks will be greater than the chunks read in any of these keys,
// so we don't need to check for pre-existing values.
maxChunks, ok := keys.MaxChunks([]byte(key))
Expand Down
8 changes: 4 additions & 4 deletions examples/morpheusvm/actions/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ func (*Transfer) GetTypeID() uint8 {
return mconsts.TransferID
}

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

Expand Down
6 changes: 3 additions & 3 deletions examples/morpheusvm/storage/state_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ func (*StateManager) OutgoingWarpKeyPrefix(txID ids.ID) []byte {
return OutgoingWarpKeyPrefix(txID)
}

func (*StateManager) SponsorStateKeys(addr codec.Address) []string {
return []string{
string(BalanceKey(addr)),
func (*StateManager) SponsorStateKeys(addr codec.Address) state.Keys {
return state.Keys{
string(BalanceKey(addr)): state.Read | state.Write,
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/tokenvm/actions/burn_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ func (*BurnAsset) GetTypeID() uint8 {
return burnAssetID
}

func (b *BurnAsset) StateKeys(actor codec.Address, _ ids.ID) []string {
return []string{
string(storage.AssetKey(b.Asset)),
string(storage.BalanceKey(actor, b.Asset)),
func (b *BurnAsset) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.AssetKey(b.Asset)): state.Read | state.Write,
string(storage.BalanceKey(actor, b.Asset)): state.Read | state.Write,
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/tokenvm/actions/close_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ func (*CloseOrder) GetTypeID() uint8 {
return closeOrderID
}

func (c *CloseOrder) StateKeys(actor codec.Address, _ ids.ID) []string {
return []string{
string(storage.OrderKey(c.Order)),
string(storage.BalanceKey(actor, c.Out)),
func (c *CloseOrder) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.OrderKey(c.Order)): state.Read | state.Write,
string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write,
}
}

Expand Down
6 changes: 3 additions & 3 deletions examples/tokenvm/actions/create_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ func (*CreateAsset) GetTypeID() uint8 {
return createAssetID
}

func (*CreateAsset) StateKeys(_ codec.Address, txID ids.ID) []string {
return []string{
string(storage.AssetKey(txID)),
func (*CreateAsset) StateKeys(_ codec.Address, txID ids.ID) state.Keys {
return state.Keys{
string(storage.AssetKey(txID)): state.Write,
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/tokenvm/actions/create_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@ func (*CreateOrder) GetTypeID() uint8 {
return createOrderID
}

func (c *CreateOrder) StateKeys(actor codec.Address, txID ids.ID) []string {
return []string{
string(storage.BalanceKey(actor, c.Out)),
string(storage.OrderKey(txID)),
func (c *CreateOrder) StateKeys(actor codec.Address, txID ids.ID) state.Keys {
return state.Keys{
string(storage.BalanceKey(actor, c.Out)): state.Read | state.Write,
string(storage.OrderKey(txID)): state.Write,
}
}

Expand Down
16 changes: 8 additions & 8 deletions examples/tokenvm/actions/export_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ func (*ExportAsset) GetTypeID() uint8 {
return exportAssetID
}

func (e *ExportAsset) StateKeys(actor codec.Address, _ ids.ID) []string {
func (e *ExportAsset) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
if e.Return {
return []string{
string(storage.AssetKey(e.Asset)),
string(storage.BalanceKey(actor, e.Asset)),
return state.Keys{
string(storage.AssetKey(e.Asset)): state.Read | state.Write,
string(storage.BalanceKey(actor, e.Asset)): state.Read | state.Write,
}
}
return []string{
string(storage.AssetKey(e.Asset)),
string(storage.LoanKey(e.Asset, e.Destination)),
string(storage.BalanceKey(actor, e.Asset)),
return state.Keys{
string(storage.AssetKey(e.Asset)): state.Read | state.Write,
string(storage.LoanKey(e.Asset, e.Destination)): state.Read | state.Write,
string(storage.BalanceKey(actor, e.Asset)): state.Read | state.Write,
}
}

Expand Down
12 changes: 6 additions & 6 deletions examples/tokenvm/actions/fill_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ func (*FillOrder) GetTypeID() uint8 {
return fillOrderID
}

func (f *FillOrder) StateKeys(actor codec.Address, _ ids.ID) []string {
return []string{
string(storage.OrderKey(f.Order)),
string(storage.BalanceKey(f.Owner, f.In)),
string(storage.BalanceKey(actor, f.In)),
string(storage.BalanceKey(actor, f.Out)),
func (f *FillOrder) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
return state.Keys{
string(storage.OrderKey(f.Order)): state.Read | state.Write,
string(storage.BalanceKey(f.Owner, f.In)): state.Read | state.Write,
string(storage.BalanceKey(actor, f.In)): state.Read | state.Write,
string(storage.BalanceKey(actor, f.Out)): state.Read | state.Write,
}
}

Expand Down
26 changes: 13 additions & 13 deletions examples/tokenvm/actions/import_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,38 +38,38 @@ func (*ImportAsset) GetTypeID() uint8 {
return importAssetID
}

func (i *ImportAsset) StateKeys(actor codec.Address, _ ids.ID) []string {
func (i *ImportAsset) StateKeys(actor codec.Address, _ ids.ID) state.Keys {
var (
keys []string
keys state.Keys
assetID ids.ID
)
if i.warpTransfer.Return {
assetID = i.warpTransfer.Asset
keys = []string{
string(storage.AssetKey(i.warpTransfer.Asset)),
string(storage.LoanKey(i.warpTransfer.Asset, i.warpMessage.SourceChainID)),
string(storage.BalanceKey(i.warpTransfer.To, i.warpTransfer.Asset)),
keys = state.Keys{
string(storage.AssetKey(i.warpTransfer.Asset)): state.Read | state.Write,
string(storage.LoanKey(i.warpTransfer.Asset, i.warpMessage.SourceChainID)): state.Read | state.Write,
string(storage.BalanceKey(i.warpTransfer.To, i.warpTransfer.Asset)): state.Read | state.Write,
}
} else {
assetID = ImportedAssetID(i.warpTransfer.Asset, i.warpMessage.SourceChainID)
keys = []string{
string(storage.AssetKey(assetID)),
string(storage.BalanceKey(i.warpTransfer.To, assetID)),
keys = state.Keys{
string(storage.AssetKey(assetID)): state.Read | state.Write,
string(storage.BalanceKey(i.warpTransfer.To, assetID)): state.Read | state.Write,
}
}

// If the [warpTransfer] specified a reward, we add the state key to make
// sure it is paid.
if i.warpTransfer.Reward > 0 {
keys = append(keys, string(storage.BalanceKey(actor, assetID)))
keys.Add(string(storage.BalanceKey(actor, assetID)), state.Read|state.Write)
}

// If the [warpTransfer] requests a swap, we add the state keys to transfer
// the required balances.
if i.Fill && i.warpTransfer.SwapIn > 0 {
keys = append(keys, string(storage.BalanceKey(actor, i.warpTransfer.AssetOut)))
keys = append(keys, string(storage.BalanceKey(actor, assetID)))
keys = append(keys, string(storage.BalanceKey(i.warpTransfer.To, i.warpTransfer.AssetOut)))
keys.Add(string(storage.BalanceKey(actor, i.warpTransfer.AssetOut)), state.Read|state.Write)
keys.Add(string(storage.BalanceKey(actor, assetID)), state.Read|state.Write)
keys.Add(string(storage.BalanceKey(i.warpTransfer.To, i.warpTransfer.AssetOut)), state.Read|state.Write)
}
return keys
}
Expand Down
8 changes: 4 additions & 4 deletions examples/tokenvm/actions/mint_asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ func (*MintAsset) GetTypeID() uint8 {
return mintAssetID
}

func (m *MintAsset) StateKeys(codec.Address, ids.ID) []string {
return []string{
string(storage.AssetKey(m.Asset)),
string(storage.BalanceKey(m.To, m.Asset)),
func (m *MintAsset) StateKeys(codec.Address, ids.ID) state.Keys {
return state.Keys{
string(storage.AssetKey(m.Asset)): state.Read | state.Write,
string(storage.BalanceKey(m.To, m.Asset)): state.Read | state.Write,
}
}

Expand Down
8 changes: 4 additions & 4 deletions examples/tokenvm/actions/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@ func (*Transfer) GetTypeID() uint8 {
return transferID
}

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

Expand Down
6 changes: 3 additions & 3 deletions examples/tokenvm/controller/state_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ func (*StateManager) OutgoingWarpKeyPrefix(txID ids.ID) []byte {
return storage.OutgoingWarpKeyPrefix(txID)
}

func (*StateManager) SponsorStateKeys(addr codec.Address) []string {
return []string{
string(storage.BalanceKey(addr, ids.Empty)),
func (*StateManager) SponsorStateKeys(addr codec.Address) state.Keys {
return state.Keys{
string(storage.BalanceKey(addr, ids.Empty)): state.Read | state.Write,
}
}

Expand Down
6 changes: 5 additions & 1 deletion executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"sync"

"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/hypersdk/state"
)

const defaultSetSize = 8
Expand Down Expand Up @@ -105,7 +106,10 @@ func (e *Executor) createWorker() {

// Run executes [f] after all previously enqueued [f] with
// overlapping [conflicts] are executed.
func (e *Executor) Run(conflicts set.Set[string], f func() error) {
// TODO: Handle read-only/write-only keys (currently the executor
// treats everything still as ReadWrite, see issue below)
// https://github.com/ava-labs/hypersdk/issues/709
func (e *Executor) Run(conflicts state.Keys, f func() error) {
e.l.Lock()
defer e.l.Unlock()

Expand Down
Loading

0 comments on commit 0eaf9be

Please sign in to comment.