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

Cadence Account Storage Map Migration #6761

Open
wants to merge 22 commits into
base: auto-update-onflow-cadence-v1.3.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
5c65c32
update to Cadence with account storage map support: feature/combine-d…
turbolent Nov 26, 2024
e7d04a4
inject scheduleAccountV2Migration function into service account Accou…
turbolent Nov 26, 2024
95ca4de
update dependencies in other modules
turbolent Nov 26, 2024
8e6fbbf
export address generator constructor
turbolent Nov 27, 2024
de4b564
add AccountV2Migration contract
turbolent Nov 27, 2024
f611094
temporarily unrestrict availability scheduleAccountV2Migration function
turbolent Nov 27, 2024
c75d4fc
call account migration function from system chunk transaction
turbolent Nov 27, 2024
b31696c
add missing argument for new chain parameter
turbolent Nov 27, 2024
0906f96
update to latest commit of feature branch
turbolent Nov 27, 2024
2a04742
add function to return storage format of an account
turbolent Nov 27, 2024
5794140
fix setup for AccountV2Migration contract
turbolent Dec 3, 2024
b1c86c5
adjust expected state commitments and system chunk transaction hashes
turbolent Dec 3, 2024
06420d1
remove chain ID arguments
turbolent Dec 3, 2024
0a393ef
add new weight for string template expressions
turbolent Dec 3, 2024
2b1ecac
Merge branch 'auto-update-onflow-cadence-v1.3.0' into bastian/account…
turbolent Dec 4, 2024
a1ca332
fix address generation
turbolent Dec 5, 2024
8b597cb
test account v2 format migration of service account
turbolent Dec 5, 2024
f86dd29
remove enabled flag, use batch size
turbolent Dec 6, 2024
814c47a
verify migration event
turbolent Dec 6, 2024
e910cd0
add integration test for account storage format v2 migration using in…
turbolent Dec 6, 2024
a57912f
add a new Migration system account contract, call it in system chunk tx
turbolent Dec 12, 2024
d24f07e
adjust expected hashes
turbolent Dec 12, 2024
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
5 changes: 3 additions & 2 deletions engine/execution/computation/computer/computer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -598,7 +598,8 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
// create a block with 2 collections with 2 transactions each
block := generateBlock(collectionCount, transactionsPerCollection, rag)

serviceEvents := systemcontracts.ServiceEventsForChain(execCtx.Chain.ChainID())
chainID := execCtx.Chain.ChainID()
serviceEvents := systemcontracts.ServiceEventsForChain(chainID)

randomSource := unittest.EpochSetupRandomSourceFixture()
payload, err := ccf.Decode(nil, unittest.EpochSetupFixtureCCF(randomSource))
Expand Down Expand Up @@ -747,7 +748,7 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) {
// make sure event index sequence are valid
for i := 0; i < result.BlockExecutionResult.Size(); i++ {
collectionResult := result.CollectionExecutionResultAt(i)
unittest.EnsureEventsIndexSeq(t, collectionResult.Events(), execCtx.Chain.ChainID())
unittest.EnsureEventsIndexSeq(t, collectionResult.Events(), chainID)
}

sEvents := result.AllServiceEvents() // all events should have been collected
Expand Down
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 @@ -53,7 +53,7 @@ func TestBootstrapLedger(t *testing.T) {
}

func TestBootstrapLedger_ZeroTokenSupply(t *testing.T) {
expectedStateCommitmentBytes, _ := hex.DecodeString("6e70a1ff40e4312a547d588a4355a538610bc22844a1faa907b4ec333ff1eca9")
expectedStateCommitmentBytes, _ := hex.DecodeString("0696f3e4336f3e4dbc1d4599e48b13ec27643111ef971a1ca630d89a0f051593")
expectedStateCommitment, err := flow.ToStateCommitment(expectedStateCommitmentBytes)
require.NoError(t, err)

Expand Down
89 changes: 89 additions & 0 deletions fvm/accountV2Migration/AccountV2Migration.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
access(all)
contract AccountV2Migration {

access(all)
enum StorageFormat: UInt8 {
access(all)
case Unknown

access(all)
case V1

access(all)
case V2
}

access(all)
event Migrated(
addressStartIndex: UInt64,
count: UInt64
)

access(all)
resource Admin {
access(all)
fun setNextAddressStartIndex(_ nextAddressStartIndex: UInt64) {
AccountV2Migration.nextAddressStartIndex = nextAddressStartIndex
}

access(all)
fun setBatchSize(_ batchSize: UInt64) {
AccountV2Migration.batchSize = batchSize
}
turbolent marked this conversation as resolved.
Show resolved Hide resolved

access(all)
fun migrateNextBatch() {
AccountV2Migration.migrateNextBatch()
}
}

access(all)
let adminStoragePath: StoragePath

access(all)
var nextAddressStartIndex: UInt64

access(all)
var batchSize: UInt64

init() {
self.adminStoragePath = /storage/accountV2MigrationAdmin
self.nextAddressStartIndex = 1
self.batchSize = 0

self.account.storage.save(
<-create Admin(),
to: self.adminStoragePath
)
Comment on lines +50 to +57
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really small, but the batching logic could probably live in the Migration contract instead of in the V2 one

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #6761 (comment): The idea is that all logic related to the particular migration, the "account v2 migration", lives in AccountV2Migration, and Migration only delegates.

If we add the logic of the "account v2 migration" to the Migration contract, we won't be able to reuse it for other purposes, as the contract update rules e.g. don't allow removing enums, etc.

}

access(account)
fun migrateNextBatch() {
let batchSize = self.batchSize
if batchSize <= 0 {
return
}

let startIndex = self.nextAddressStartIndex

if !scheduleAccountV2Migration(
addressStartIndex: startIndex,
count: batchSize
) {
return
}

self.nextAddressStartIndex = startIndex + batchSize

emit Migrated(
addressStartIndex: startIndex,
count: batchSize
)
}

access(all)
fun getAccountStorageFormat(address: Address): StorageFormat? {
let rawStorageFormat = getAccountStorageFormat(address: address)
return StorageFormat(rawValue: rawStorageFormat)
}
}
224 changes: 224 additions & 0 deletions fvm/accountV2Migration/contract.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package accountV2Migration

import (
_ "embed"

"github.com/onflow/cadence/common"
cadenceErrors "github.com/onflow/cadence/errors"
"github.com/onflow/cadence/interpreter"
"github.com/onflow/cadence/runtime"
"github.com/onflow/cadence/sema"
"github.com/onflow/cadence/stdlib"

"github.com/onflow/flow-go/fvm/errors"
"github.com/onflow/flow-go/fvm/systemcontracts"
"github.com/onflow/flow-go/model/flow"
)

//go:embed AccountV2Migration.cdc
var ContractCode []byte

const ContractName = "AccountV2Migration"

const scheduleAccountV2MigrationFunctionName = "scheduleAccountV2Migration"

// scheduleAccountV2MigrationType is the type of the `scheduleAccountV2Migration` function.
// This defines the signature as `func(addressStartIndex: UInt64, count: UInt64): Bool`
var scheduleAccountV2MigrationType = &sema.FunctionType{
Parameters: []sema.Parameter{
{
Identifier: "addressStartIndex",
TypeAnnotation: sema.UInt64TypeAnnotation,
},
{
Identifier: "count",
TypeAnnotation: sema.UInt64TypeAnnotation,
},
},
ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.BoolType),
}

func DeclareFunctions(environment runtime.Environment, chainID flow.ChainID) {
declareScheduleAccountV2MigrationFunction(environment, chainID)
declareGetAccountStorageFormatFunction(environment, chainID)
}

func declareScheduleAccountV2MigrationFunction(environment runtime.Environment, chainID flow.ChainID) {

functionType := scheduleAccountV2MigrationType

functionValue := stdlib.StandardLibraryValue{
Name: scheduleAccountV2MigrationFunctionName,
Type: functionType,
Kind: common.DeclarationKindFunction,
Value: interpreter.NewUnmeteredStaticHostFunctionValue(
functionType,
func(invocation interpreter.Invocation) interpreter.Value {
inter := invocation.Interpreter

// Get interpreter storage

storage := inter.Storage()

runtimeStorage, ok := storage.(*runtime.Storage)
if !ok {
panic(cadenceErrors.NewUnexpectedError("interpreter storage is not a runtime.Storage"))
}

// Check the number of arguments

actualArgumentCount := len(invocation.Arguments)
expectedArgumentCount := len(functionType.Parameters)

if actualArgumentCount != expectedArgumentCount {
panic(errors.NewInvalidArgumentErrorf(
"incorrect number of arguments: got %d, expected %d",
actualArgumentCount,
expectedArgumentCount,
))
}

// Get addressStartIndex argument

firstArgument := invocation.Arguments[0]
addressStartIndexValue, ok := firstArgument.(interpreter.UInt64Value)
if !ok {
panic(errors.NewInvalidArgumentErrorf(
"incorrect type for argument 0: got `%s`, expected `%s`",
firstArgument.StaticType(inter),
sema.UInt64Type,
))
}
addressStartIndex := uint64(addressStartIndexValue)

// Get count argument

secondArgument := invocation.Arguments[1]
countValue, ok := secondArgument.(interpreter.UInt64Value)
if !ok {
panic(errors.NewInvalidArgumentErrorf(
"incorrect type for argument 1: got `%s`, expected `%s`",
secondArgument.StaticType(inter),
sema.UInt64Type,
))
}
count := uint64(countValue)

// Schedule the account V2 migration for addresses

addressGenerator := chainID.Chain().NewAddressGeneratorAtIndex(addressStartIndex)
for i := uint64(0); i < count; i++ {
address := addressGenerator.CurrentAddress()
if !runtimeStorage.ScheduleV2Migration(common.Address(address)) {
return interpreter.FalseValue
}

_, err := addressGenerator.NextAddress()
if err != nil {
panic(err)
}
}

return interpreter.TrueValue
},
),
}

sc := systemcontracts.SystemContractsForChain(chainID)

accountV2MigrationLocation := common.NewAddressLocation(
nil,
common.Address(sc.AccountV2Migration.Address),
ContractName,
)

environment.DeclareValue(
functionValue,
accountV2MigrationLocation,
)
}

const getAccountStorageFormatFunctionName = "getAccountStorageFormat"

// getAccountStorageFormatType is the type of the `getAccountStorageFormat` function.
// This defines the signature as `func(address: Address): UInt8`
var getAccountStorageFormatType = &sema.FunctionType{
Parameters: []sema.Parameter{
{
Identifier: "address",
TypeAnnotation: sema.AddressTypeAnnotation,
},
},
ReturnTypeAnnotation: sema.NewTypeAnnotation(sema.UInt8Type),
}

func declareGetAccountStorageFormatFunction(environment runtime.Environment, chainID flow.ChainID) {

functionType := getAccountStorageFormatType

functionValue := stdlib.StandardLibraryValue{
Name: getAccountStorageFormatFunctionName,
Type: functionType,
Kind: common.DeclarationKindFunction,
Value: interpreter.NewUnmeteredStaticHostFunctionValue(
functionType,
func(invocation interpreter.Invocation) interpreter.Value {
inter := invocation.Interpreter

// Get interpreter storage

storage := inter.Storage()

runtimeStorage, ok := storage.(*runtime.Storage)
if !ok {
panic(cadenceErrors.NewUnexpectedError("interpreter storage is not a runtime.Storage"))
}

// Check the number of arguments

actualArgumentCount := len(invocation.Arguments)
expectedArgumentCount := len(functionType.Parameters)

if actualArgumentCount != expectedArgumentCount {
panic(errors.NewInvalidArgumentErrorf(
"incorrect number of arguments: got %d, expected %d",
actualArgumentCount,
expectedArgumentCount,
))
}

// Get addressStartIndex argument

firstArgument := invocation.Arguments[0]
addressValue, ok := firstArgument.(interpreter.AddressValue)
if !ok {
panic(errors.NewInvalidArgumentErrorf(
"incorrect type for argument 0: got `%s`, expected `%s`",
firstArgument.StaticType(inter),
sema.TheAddressType,
))
}
address := common.Address(addressValue)

// Get and return the storage format for the account

return interpreter.UInt8Value(runtimeStorage.AccountStorageFormat(address))
},
),
}

sc := systemcontracts.SystemContractsForChain(chainID)

accountV2MigrationLocation := common.NewAddressLocation(
nil,
common.Address(sc.AccountV2Migration.Address),
ContractName,
)

environment.DeclareValue(
functionValue,
accountV2MigrationLocation,
)
}

const MigratedEventTypeQualifiedIdentifier = ContractName + ".Migrated"
Loading
Loading