Skip to content

Commit

Permalink
Merge pull request #80 from onflow/janez/storage-fees-storage-limiter
Browse files Browse the repository at this point in the history
Storage fees: storage limiter
  • Loading branch information
janezpodhostnik committed Nov 17, 2020
2 parents ba6c009 + 4c540ff commit 25135b2
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 22 deletions.
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("d1ac84222a9e6312d288f918b16f1c887128ee1a1fb7506ad4c3e55a0b491f7b")
var expectedStateCommitment, _ = hex.DecodeString("f3b24f02064671fd2384c4283a60afbfecf94306796b4881cd915524886f19a5")

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

errCodeStorageCapacityExceeded = 11

errCodeExecution = 100
)

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

// An StorageCapacityExceededError indicates that a given key has an invalid hash algorithm.
type StorageCapacityExceededError struct {
Address flow.Address
StorageUsed uint64
StorageCapacity uint64
}

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

func (e *StorageCapacityExceededError) Code() uint32 {
return errCodeStorageCapacityExceeded
}

type ExecutionError struct {
Err runtime.Error
}
Expand Down
53 changes: 52 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 @@ -455,6 +458,54 @@ func TestBlockContext_ExecuteTransaction_GasLimit(t *testing.T) {
}
}

func TestBlockContext_ExecuteTransaction_StorageLimit(t *testing.T) {
t.Run("Storing too much data fails", vmTest(
func(t *testing.T, vm *fvm.VirtualMachine, chain flow.Chain, ctx fvm.Context, ledger state.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(
"Container",
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.StorageCapacityExceededError{}).Code(), tx.Err.Code())
}))
}

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

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

var (
Expand Down Expand Up @@ -109,6 +110,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 @@ -348,7 +357,23 @@ func (a *Accounts) setStorageUsed(address flow.Address, used uint64) error {
return a.setValue(address, false, keyStorageUsed, usedBinary)
}

// GetValue returns a value stored in address' storage
func (a *Accounts) GetStorageCapacity(account flow.Address) (uint64, error) {
storageCapacityRegister, err := a.getValue(account, false, keyStorageCapacity)
if err != nil {
return 0, err
}
storageCapacity, _, err := utils.ReadUint64(storageCapacityRegister)
if err != nil {
return 0, err
}
return storageCapacity, nil
}

func (a *Accounts) SetStorageCapacity(account flow.Address, capacity uint64) error {
capacityBinary := utils.Uint64ToBinary(capacity)
return a.setValue(account, false, keyStorageCapacity, capacityBinary)
}

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), 3) // storage_used + exists + key count
// storage_used + storage_capacity + exists + code + key count
require.Equal(t, len(ledger.RegisterTouches), 4)
})

t.Run("Fails if account exists", func(t *testing.T) {
Expand Down Expand Up @@ -173,7 +174,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 @@ -190,7 +191,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 @@ -209,7 +210,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 @@ -228,7 +229,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 @@ -247,7 +248,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 @@ -266,7 +267,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 @@ -290,7 +291,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
34 changes: 30 additions & 4 deletions fvm/state/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,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 +57,24 @@ 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)
}

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

0 comments on commit 25135b2

Please sign in to comment.