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

Storage fees: storage limiter #80

Merged
Merged
Show file tree
Hide file tree
Changes from 9 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
2 changes: 1 addition & 1 deletion engine/execution/state/bootstrap/bootstrap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestBootstrapLedger(t *testing.T) {
}

func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) {
var expectedStateCommitment, _ = hex.DecodeString("489e6a75628cee6ae24662e86d642178c6ab3210d7a213d81de45664bac8cf4f")
var expectedStateCommitment, _ = hex.DecodeString("4989ce4db0a3a46f9db3423735f28124d09c5396acd86b80b4ca9bd0fb075ee0")

unittest.RunWithTempDir(t, func(dbDir string) {

Expand Down
4 changes: 4 additions & 0 deletions engine/execution/state/delta/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ func (v *View) Set(owner, controller, key string, value flow.RegisterValue) {
v.delta.Set(owner, controller, key, value)
}

func (v *View) RegisterUpdates() ([]flow.RegisterID, []flow.RegisterValue) {
return v.delta.RegisterUpdates()
}

func (v *View) updateSpock(value []byte) error {
_, err := v.spockSecretHasher.Write(value)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions fvm/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func defaultContext() Context {
NewTransactionSequenceNumberChecker(),
NewTransactionFeeDeductor(),
NewTransactionInvocator(),
NewTransactionStorageLimiter(),
},
ScriptProcessors: []ScriptProcessor{
NewScriptInvocator(),
Expand Down
17 changes: 17 additions & 0 deletions fvm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ const (

errCodeInvalidHashAlgorithm = 10

errCodeOverStorageCapacity = 11
janezpodhostnik marked this conversation as resolved.
Show resolved Hide resolved

errCodeExecution = 100
)

Expand Down Expand Up @@ -213,6 +215,21 @@ func (e *InvalidHashAlgorithmError) Code() uint32 {
return errCodeInvalidHashAlgorithm
}

// An OverStorageCapacityError indicates that a given key has an invalid hash algorithm.
janezpodhostnik marked this conversation as resolved.
Show resolved Hide resolved
type OverStorageCapacityError struct {
janezpodhostnik marked this conversation as resolved.
Show resolved Hide resolved
Address flow.Address
StorageUsed uint64
StorageCapacity uint64
}

func (e *OverStorageCapacityError) Error() string {
return fmt.Sprintf("address %s storage %d is over capacity %d", e.Address, e.StorageUsed, e.StorageCapacity)
}

func (e *OverStorageCapacityError) Code() uint32 {
return errCodeOverStorageCapacity
}

type ExecutionError struct {
Err runtime.Error
}
Expand Down
71 changes: 70 additions & 1 deletion fvm/fvm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package fvm_test

import (
"crypto/rand"
"encoding/base64"
"fmt"
"strconv"
"testing"
Expand All @@ -16,6 +17,7 @@ import (

"github.com/onflow/flow-go/crypto"
"github.com/onflow/flow-go/crypto/hash"
"github.com/onflow/flow-go/engine/execution/state/delta"
"github.com/onflow/flow-go/engine/execution/testutil"
"github.com/onflow/flow-go/fvm"
fvmmock "github.com/onflow/flow-go/fvm/mock"
Expand Down Expand Up @@ -47,7 +49,8 @@ func vmTest(

ctx := fvm.NewContext(opts...)

ledger := state.NewMapLedger()
mapLedger := state.NewMapLedger()
ledger := delta.NewView(mapLedger.Get)

err = vm.Run(
ctx,
Expand Down Expand Up @@ -426,6 +429,72 @@ func TestBlockContext_ExecuteTransaction_GasLimit(t *testing.T) {
}
}

func TestBlockContext_ExecuteTransaction_StorageLimit(t *testing.T) {
rt := runtime.NewInterpreterRuntime()

chain := flow.Mainnet.Chain()

vm := fvm.New(rt)

cache, err := fvm.NewLRUASTCache(CacheSize)
require.NoError(t, err)

ctx := fvm.NewContext(fvm.WithChain(chain), fvm.WithASTCache(cache))

t.Run("Storing too much data fails", func(t *testing.T) {
mapLedger := state.NewMapLedger()
ledger := delta.NewView(mapLedger.Get)

_ = vm.Run(
ctx,
fvm.Bootstrap(unittest.ServiceAccountPublicKey, unittest.GenesisTokenSupply),
ledger,
)

// Create an account private key.
privateKeys, err := testutil.GenerateAccountPrivateKeys(1)
require.NoError(t, err)

// Bootstrap a ledger, creating accounts with the provided private keys and the root account.
accounts, err := testutil.CreateAccounts(vm, ledger, privateKeys, chain)
require.NoError(t, err)

b := make([]byte, 100000) // 100k bytes
_, err = rand.Read(b)
require.NoError(t, err)
longString := base64.StdEncoding.EncodeToString(b)
txBody := testutil.CreateContractDeploymentTransaction(
fmt.Sprintf(`
access(all) contract Container {
access(all) resource Counter {
pub var longString: String
init() {
self.longString = "%s"
}
}
}
`, longString),
accounts[0],
chain)

txBody.SetProposalKey(chain.ServiceAddress(), 0, 0)
txBody.SetPayer(chain.ServiceAddress())

err = testutil.SignPayload(txBody, accounts[0], privateKeys[0])
require.NoError(t, err)

err = testutil.SignEnvelope(txBody, chain.ServiceAddress(), unittest.ServiceAccountPrivateKey)
require.NoError(t, err)

tx := fvm.Transaction(txBody)

err = vm.Run(ctx, tx, ledger)
require.NoError(t, err)

assert.Equal(t, (&fvm.OverStorageCapacityError{}).Code(), tx.Err.Code())
})
janezpodhostnik marked this conversation as resolved.
Show resolved Hide resolved
}

var createAccountScript = []byte(`
transaction {
prepare(signer: AuthAccount) {
Expand Down
35 changes: 29 additions & 6 deletions fvm/state/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ import (
)

const (
keyExists = "exists"
keyCode = "code"
keyPublicKeyCount = "public_key_count"
keyStorageUsed = "storage_used"
uint64StorageSize = 8
keyExists = "exists"
keyCode = "code"
keyPublicKeyCount = "public_key_count"
keyStorageUsed = "storage_used"
keyStorageCapacity = "storage_capacity"
uint64StorageSize = 8
)

var (
Expand Down Expand Up @@ -97,6 +98,14 @@ func (a *Accounts) Create(publicKeys []flow.AccountPublicKey, newAddress flow.Ad
return err
}

// set storage capacity to 0.
// It must be set with the storage contract before the end of this transaction.
// TODO: for this PR set storage capacity to 100kB, remove this in the next PR to this feature branch
err = a.SetStorageCapacity(newAddress, 100000)
if err != nil {
return err
}

// mark that this account exists
err = a.setValue(newAddress, false, keyExists, []byte{1})
if err != nil {
Expand Down Expand Up @@ -298,7 +307,21 @@ func (a *Accounts) setStorageUsed(address flow.Address, used uint64) error {
return a.setValue(address, false, keyStorageUsed, buffer)
}

// GetValue returns a value stored in address' storage
func (a *Accounts) GetStorageCapacity(owner flow.Address) (uint64, error) {
storageCapacityRegister, err := a.getValue(owner, false, keyStorageCapacity)
if err != nil {
return 0, err
}
storageUsed := binary.LittleEndian.Uint64(storageCapacityRegister)
return storageUsed, nil
}

func (a *Accounts) SetStorageCapacity(owner flow.Address, capacity uint64) error {
buffer := make([]byte, uint64StorageSize)
binary.LittleEndian.PutUint64(buffer, capacity)
return a.setValue(owner, false, keyStorageCapacity, buffer)
}

func (a *Accounts) GetValue(address flow.Address, key string) (flow.RegisterValue, error) {
return a.getValue(address, false, key)
}
Expand Down
17 changes: 9 additions & 8 deletions fvm/state/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ func TestAccounts_Create(t *testing.T) {
err := accounts.Create(nil, address)
require.NoError(t, err)

require.Equal(t, len(ledger.RegisterTouches), 4) // storage_used + exists + code + key count
// storage_used + storage_capacity + exists + code + key count
require.Equal(t, len(ledger.RegisterTouches), 5)
})

t.Run("Fails if account exists", func(t *testing.T) {
Expand Down Expand Up @@ -85,7 +86,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9)) // exists: 1 byte, storage_used 8 bytes
require.Equal(t, storageUsed, uint64(17)) // exists: 1 byte, storage_ used & capacity 16 bytes
})

t.Run("Storage used on register set increases", func(t *testing.T) {
Expand All @@ -102,7 +103,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9+12)) // exists: 1 byte, storage_used 8 bytes, some_key 12
require.Equal(t, storageUsed, uint64(17+12)) // exists: 1 byte, storage_ used & capacity 16 bytes, some_key 12
})

t.Run("Storage used, set twice on same register to same value, stays the same", func(t *testing.T) {
Expand All @@ -121,7 +122,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9+12)) // exists: 1 byte, storage_used 8 bytes, some_key 12
require.Equal(t, storageUsed, uint64(17+12)) // exists: 1 byte, storage_ used & capacity 16 bytes, some_key 12
})

t.Run("Storage used, set twice on same register to larger value, increases", func(t *testing.T) {
Expand All @@ -140,7 +141,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9+13)) // exists: 1 byte, storage_used 8 bytes, some_key 13
require.Equal(t, storageUsed, uint64(17+13)) // exists: 1 byte, storage_ used & capacity 16 bytes, some_key 13
})

t.Run("Storage used, set twice on same register to smaller value, decreases", func(t *testing.T) {
Expand All @@ -159,7 +160,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9+11)) // exists: 1 byte, storage_used 8 bytes, some_key 11
require.Equal(t, storageUsed, uint64(17+11)) // exists: 1 byte, storage_ used & capacity 16 bytes, some_key 11
})

t.Run("Storage used, after register deleted, decreases", func(t *testing.T) {
Expand All @@ -178,7 +179,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9+0)) // exists: 1 byte, storage_used 8 bytes, some_key 0
require.Equal(t, storageUsed, uint64(17+0)) // exists: 1 byte, storage_ used & capacity 16 bytes, some_key 0
})

t.Run("Storage used on a complex scenario has correct value", func(t *testing.T) {
Expand All @@ -202,7 +203,7 @@ func TestAccount_StorageUsed(t *testing.T) {

storageUsed, err := accounts.GetStorageUsed(address)
require.NoError(t, err)
require.Equal(t, storageUsed, uint64(9+34)) // exists: 1 byte, storage_used 8 bytes, other 34
require.Equal(t, storageUsed, uint64(17+34)) // exists: 1 byte, storage_ used & capacity 16 bytes, other 34
})
}

Expand Down
37 changes: 33 additions & 4 deletions fvm/state/ledger.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package state

import (
"sort"
"strings"

"github.com/onflow/flow-go/model/flow"
Expand All @@ -12,33 +13,41 @@ type Ledger interface {
Get(owner, controller, key string) (flow.RegisterValue, error)
Touch(owner, controller, key string)
Delete(owner, controller, key string)
RegisterUpdates() ([]flow.RegisterID, []flow.RegisterValue)
}

// A MapLedger is a naive ledger storage implementation backed by a simple map.
//
// This implementation is designed for testing purposes.
type MapLedger struct {
Registers map[string]flow.RegisterValue
Registers map[string]flow.RegisterEntry
RegisterTouches map[string]bool
}

func NewMapLedger() *MapLedger {
return &MapLedger{
Registers: make(map[string]flow.RegisterValue),
Registers: make(map[string]flow.RegisterEntry),
RegisterTouches: make(map[string]bool),
}
}

func (m MapLedger) Set(owner, controller, key string, value flow.RegisterValue) {
k := fullKey(owner, controller, key)
m.RegisterTouches[k] = true
m.Registers[k] = value
m.Registers[k] = flow.RegisterEntry{
Key: flow.RegisterID{
Owner: owner,
Controller: controller,
Key: key,
},
Value: value,
}
}

func (m MapLedger) Get(owner, controller, key string) (flow.RegisterValue, error) {
k := fullKey(owner, controller, key)
m.RegisterTouches[k] = true
return m.Registers[k], nil
return m.Registers[k].Value, nil
}

func (m MapLedger) Touch(owner, controller, key string) {
Expand All @@ -49,6 +58,26 @@ func (m MapLedger) Delete(owner, controller, key string) {
delete(m.Registers, fullKey(owner, controller, key))
}

func (m MapLedger) RegisterUpdates() ([]flow.RegisterID, []flow.RegisterValue) {
data := make(flow.RegisterEntries, 0, len(m.Registers))

for _, v := range m.Registers {
data = append(data, v)
}

sort.Sort(&data)
janezpodhostnik marked this conversation as resolved.
Show resolved Hide resolved

ids := make([]flow.RegisterID, 0, len(m.Registers))
values := make([]flow.RegisterValue, 0, len(m.Registers))

for _, v := range data {
ids = append(ids, v.Key)
values = append(values, v.Value)
}

return ids, values
}

func fullKey(owner, controller, key string) string {
// https://en.wikipedia.org/wiki/C0_and_C1_control_codes#Field_separators
return strings.Join([]string{owner, controller, key}, "\x1F")
Expand Down
Loading