diff --git a/go.mod b/go.mod index c4e6b2a6..d917e845 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/onflow/cadence v1.0.0-preview.2.0.20240201184233-a301a4152f6b github.com/onflow/crypto v0.25.0 github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20240125214229-b7a95136dd0d - github.com/onflow/flow-go v0.33.2-0.20240126211806-97279f96695f + github.com/onflow/flow-go v0.33.2-0.20240202003043-efb29d915946 github.com/onflow/flow-go-sdk v1.0.0-M1 github.com/onflow/flow-nft/lib/go/contracts v1.1.1-0.20240125205553-d2b571fb3fad github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20231213135419-ae911cc351a2 diff --git a/go.sum b/go.sum index 11e0f82f..48aca8fb 100644 --- a/go.sum +++ b/go.sum @@ -1987,8 +1987,8 @@ github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20240125214229- github.com/onflow/flow-core-contracts/lib/go/templates v0.15.1-0.20240125214229-b7a95136dd0d/go.mod h1:MZ2j5YVTQiSE0B99zuaYhxvGG5GcvimWpQK1Fw/1QBg= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240125205519-2e80d9b4bd01 h1:8iKk5RuFvhe7NQyAO3c+xiVvv38RB/yopHdWxp4AbL8= github.com/onflow/flow-ft/lib/go/contracts v0.7.1-0.20240125205519-2e80d9b4bd01/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= -github.com/onflow/flow-go v0.33.2-0.20240126211806-97279f96695f h1:F1y95CpteZn0i4v0FDGjKiqI13Xlir3hX4x0C1xMRoc= -github.com/onflow/flow-go v0.33.2-0.20240126211806-97279f96695f/go.mod h1:9q+c+fuTpc/emueM/2bI/Ih2jw3V+9WS3Eu+pWBuLW0= +github.com/onflow/flow-go v0.33.2-0.20240202003043-efb29d915946 h1:YhEktpKe9wHOZDyzIMqVTuROWEGU9QFVmJ+s5u3M8Os= +github.com/onflow/flow-go v0.33.2-0.20240202003043-efb29d915946/go.mod h1:9q+c+fuTpc/emueM/2bI/Ih2jw3V+9WS3Eu+pWBuLW0= github.com/onflow/flow-go-sdk v1.0.0-M1 h1:mke/ebYwNRRWPZqcwCV56Alx0A8psew43ZbSEUQ4TL8= github.com/onflow/flow-go-sdk v1.0.0-M1/go.mod h1:TDW0MNuCs4SvqYRUzkbRnRmHQL1h4X8wURsCw9P9beo= github.com/onflow/flow-nft/lib/go/contracts v1.1.1-0.20240125205553-d2b571fb3fad h1:I6LD9BOsilGbiqhGjP86FIIXJe0YdUz75d/oWdHFzDI= diff --git a/storage/migration/cadence_values_migration.go b/storage/migration/cadence_values_migration.go index e04e198e..b9a4a77a 100644 --- a/storage/migration/cadence_values_migration.go +++ b/storage/migration/cadence_values_migration.go @@ -20,37 +20,27 @@ package migration import ( "context" - "database/sql" - "encoding/binary" - "encoding/hex" - "os" - "strconv" - "strings" - "github.com/rs/zerolog" - "github.com/onflow/flow-emulator/storage" "github.com/onflow/flow-emulator/storage/sqlite" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/flow-go/cmd/util/ledger/migrations" - "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/ledger" - "github.com/onflow/flow-go/ledger/common/convert" - "github.com/onflow/flow-go/model/flow" ) func MigrateCadenceValues(store *sqlite.Store) error { - payloads, payloadInfo, accounts, err := payloadsAndAccountsFromSnapshot(store.DB()) + payloads, payloadInfo, accounts, err := util.PayloadsAndAccountsFromEmulatorSnapshot(store.DB()) if err != nil { return err } rwf := &ReportWriterFactory{} capabilityIDs := map[interpreter.AddressPath]interpreter.UInt64Value{} - logger := newConsoleLogger() + logger := NewConsoleLogger() payloads, err = migrateLinkValues(rwf, logger, capabilityIDs, accounts, payloads) if err != nil { @@ -62,7 +52,7 @@ func MigrateCadenceValues(store *sqlite.Store) error { return err } - return writePayloadsToSnapshot(store, payloads, payloadInfo) + return WritePayloadsToSnapshot(store, payloads, payloadInfo) } func migrateLinkValues( @@ -124,191 +114,3 @@ func migrateCadenceValues( return payloads, nil } - -func newConsoleLogger() zerolog.Logger { - writer := zerolog.ConsoleWriter{ - Out: os.Stdout, - } - - return zerolog.New(writer). - With(). - Timestamp(). - Logger(). - Level(zerolog.InfoLevel) -} - -func writePayloadsToSnapshot( - store *sqlite.Store, - payloads []*ledger.Payload, - payloadInfoSet map[flow.RegisterID]payloadMetaInfo, -) error { - - const storeName = storage.LedgerStoreName - - for _, payload := range payloads { - key, err := payload.Key() - if err != nil { - return err - } - - registerId, err := convert.LedgerKeyToRegisterID(key) - if err != nil { - return err - } - - registerIdBytes := []byte(registerId.String()) - - value := payload.Value() - - payloadInfo, ok := payloadInfoSet[registerId] - if ok { - // Insert the values back with the existing height and version. - err = store.SetBytesWithVersionAndHeight( - nil, - storeName, - registerIdBytes, - value, - payloadInfo.version, - payloadInfo.height, - ) - } else { - // If this is a new payload, use the current block height, and default version. - err = store.SetBytes( - nil, - storeName, - registerIdBytes, - value, - ) - } - - if err != nil { - return err - } - } - - return nil -} - -func payloadsAndAccountsFromSnapshot(db *sql.DB) ( - []*ledger.Payload, - map[flow.RegisterID]payloadMetaInfo, - []common.Address, - error, -) { - rows, err := db.Query("SELECT key, value, version, height FROM ledger") - if err != nil { - return nil, nil, nil, err - } - - var payloads []*ledger.Payload - var accounts []common.Address - accountsSet := make(map[common.Address]struct{}) - - payloadSet := make(map[flow.RegisterID]payloadMetaInfo) - - for rows.Next() { - var hexKey, hexValue string - var height, version uint64 - - err := rows.Scan(&hexKey, &hexValue, &height, &version) - if err != nil { - return nil, nil, nil, err - } - - key, err := hex.DecodeString(hexKey) - if err != nil { - return nil, nil, nil, err - } - - value, err := hex.DecodeString(hexValue) - if err != nil { - return nil, nil, nil, err - } - - registerId, address := registerIDKeyFromString(string(key)) - - if _, contains := accountsSet[address]; !contains { - accountsSet[address] = struct{}{} - accounts = append(accounts, address) - } - - ledgerKey := convert.RegisterIDToLedgerKey(registerId) - - payload := ledger.NewPayload( - ledgerKey, - value, - ) - - payloads = append(payloads, payload) - payloadSet[registerId] = payloadMetaInfo{ - height: height, - version: version, - } - } - - return payloads, payloadSet, accounts, nil -} - -// registerIDKeyFromString is the inverse of `flow.RegisterID.String()` method. -func registerIDKeyFromString(s string) (flow.RegisterID, common.Address) { - parts := strings.SplitN(s, "/", 2) - - owner := parts[0] - key := parts[1] - - address, err := common.HexToAddress(owner) - if err != nil { - panic(err) - } - - var decodedKey string - - switch key[0] { - case '$': - b := make([]byte, 9) - b[0] = '$' - - int64Value, err := strconv.ParseInt(key[1:], 10, 64) - if err != nil { - panic(err) - } - - binary.BigEndian.PutUint64(b[1:], uint64(int64Value)) - - decodedKey = string(b) - case '#': - decoded, err := hex.DecodeString(key[1:]) - if err != nil { - panic(err) - } - decodedKey = string(decoded) - default: - panic("Invalid register key") - } - - return flow.RegisterID{ - Owner: string(address.Bytes()), - Key: decodedKey, - }, - address -} - -type payloadMetaInfo struct { - height, version uint64 -} - -type ReportWriterFactory struct{} - -func (_m *ReportWriterFactory) ReportWriter(_ string) reporters.ReportWriter { - return &NOOPWriter{} -} - -type NOOPWriter struct{} - -var _ reporters.ReportWriter = &NOOPWriter{} - -func (r *NOOPWriter) Write(_ any) { - // NO-OP -} - -func (r *NOOPWriter) Close() {} diff --git a/storage/migration/cadence_values_migration_test.go b/storage/migration/migration_test.go similarity index 87% rename from storage/migration/cadence_values_migration_test.go rename to storage/migration/migration_test.go index 0ac093d3..c70a5235 100644 --- a/storage/migration/cadence_values_migration_test.go +++ b/storage/migration/migration_test.go @@ -37,15 +37,8 @@ func TestStateMigration(t *testing.T) { tempEmulatorStatePath := tempEmulatorState.Name() - defer func() { - err := tempEmulatorState.Close() - require.NoError(t, err) - }() - - defer func() { - err := os.Remove(tempEmulatorStatePath) - require.NoError(t, err) - }() + defer tempEmulatorState.Close() + defer os.Remove(tempEmulatorStatePath) content, err := os.ReadFile(emulatorStateFile) require.NoError(t, err) @@ -58,6 +51,11 @@ func TestStateMigration(t *testing.T) { store, err := sqlite.New(tempEmulatorStatePath) require.NoError(t, err) + // First migrate the system contracts + err = MigrateSystemContracts(store) + require.NoError(t, err) + + // Then migrate the values. err = MigrateCadenceValues(store) require.NoError(t, err) } diff --git a/storage/migration/system_contracts_migration.go b/storage/migration/system_contracts_migration.go new file mode 100644 index 00000000..b9a07630 --- /dev/null +++ b/storage/migration/system_contracts_migration.go @@ -0,0 +1,82 @@ +/* + * Flow Emulator + * + * Copyright 2024 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package migration + +import ( + "context" + "github.com/rs/zerolog" + + "github.com/onflow/flow-emulator/storage/sqlite" + + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/model/flow" +) + +func MigrateSystemContracts(store *sqlite.Store) error { + payloads, payloadInfo, accounts, err := util.PayloadsAndAccountsFromEmulatorSnapshot(store.DB()) + if err != nil { + return err + } + + logger := NewConsoleLogger() + + payloads, err = migrateSystemContracts(logger, accounts, payloads) + if err != nil { + return err + } + + return WritePayloadsToSnapshot(store, payloads, payloadInfo) +} + +func migrateSystemContracts( + logger zerolog.Logger, + accounts []common.Address, + payloads []*ledger.Payload, +) ([]*ledger.Payload, error) { + + migration := migrations.ChangeContractCodeMigration{} + + systemContractChanges := migrations.SystemContractChanges(flow.Emulator) + + for _, contractChange := range systemContractChanges { + migration.RegisterContractChange( + contractChange.Address, + contractChange.ContractName, + contractChange.NewContractCode, + ) + } + + err := migration.InitMigration(logger, nil, 0) + if err != nil { + return nil, err + } + + for _, account := range accounts { + ctx := context.Background() + payloads, err = migration.MigrateAccount(ctx, account, payloads) + if err != nil { + return nil, err + } + } + return payloads, nil +} diff --git a/storage/migration/utils.go b/storage/migration/utils.go new file mode 100644 index 00000000..1f9f4de3 --- /dev/null +++ b/storage/migration/utils.go @@ -0,0 +1,114 @@ +/* + * Flow Emulator + * + * Copyright 2024 Dapper Labs, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package migration + +import ( + "context" + "github.com/onflow/flow-emulator/storage" + "github.com/onflow/flow-emulator/storage/sqlite" + "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog" + "os" +) + +func WritePayloadsToSnapshot( + store *sqlite.Store, + payloads []*ledger.Payload, + payloadInfoSet map[flow.RegisterID]util.PayloadMetaInfo, +) error { + + const storeName = storage.LedgerStoreName + + ctx := context.TODO() + + for _, payload := range payloads { + key, err := payload.Key() + if err != nil { + return err + } + + registerId, err := convert.LedgerKeyToRegisterID(key) + if err != nil { + return err + } + + registerIdBytes := []byte(registerId.String()) + + value := payload.Value() + + payloadInfo, ok := payloadInfoSet[registerId] + if ok { + // Insert the values back with the existing height and version. + err = store.SetBytesWithVersionAndHeight( + ctx, + storeName, + registerIdBytes, + value, + payloadInfo.Version, + payloadInfo.Height, + ) + } else { + // If this is a new payload, use the current block height, and default version. + err = store.SetBytes( + ctx, + storeName, + registerIdBytes, + value, + ) + } + + if err != nil { + return err + } + } + + return nil +} + +func NewConsoleLogger() zerolog.Logger { + writer := zerolog.ConsoleWriter{ + Out: os.Stdout, + } + + return zerolog.New(writer). + With(). + Timestamp(). + Logger(). + Level(zerolog.InfoLevel) +} + +type ReportWriterFactory struct{} + +func (_m *ReportWriterFactory) ReportWriter(_ string) reporters.ReportWriter { + return &NOOPWriter{} +} + +type NOOPWriter struct{} + +var _ reporters.ReportWriter = &NOOPWriter{} + +func (r *NOOPWriter) Write(_ any) { + // NO-OP +} + +func (r *NOOPWriter) Close() {}