diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 009bd002d13..1b6418e41eb 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -167,14 +167,25 @@ func NewCadence1ValueMigrations( // used by CadenceCapabilityValueMigrator capabilityMapping := &capcons.CapabilityMapping{} + errorMessageHandler := &errorMessageHandler{} + for _, accountBasedMigration := range []AccountBasedMigration{ NewCadence1ValueMigrator( rwf, + errorMessageHandler, NewCadence1CompositeStaticTypeConverter(chainID), NewCadence1InterfaceStaticTypeConverter(chainID), ), - NewCadence1LinkValueMigrator(rwf, capabilityMapping), - NewCadence1CapabilityValueMigrator(rwf, capabilityMapping), + NewCadence1LinkValueMigrator( + rwf, + errorMessageHandler, + capabilityMapping, + ), + NewCadence1CapabilityValueMigrator( + rwf, + errorMessageHandler, + capabilityMapping, + ), } { migrations = append( migrations, @@ -236,7 +247,18 @@ func NewCadence1Migrations( stagedContracts []StagedContract, ) []ledger.Migration { return common.Concat( - NewCadence1ContractsMigrations(log, nWorker, chainID, evmContractChange, stagedContracts), - NewCadence1ValueMigrations(log, rwf, nWorker, chainID), + NewCadence1ContractsMigrations( + log, + nWorker, + chainID, + evmContractChange, + stagedContracts, + ), + NewCadence1ValueMigrations( + log, + rwf, + nWorker, + chainID, + ), ) } diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index b3b345a2a43..c03659c888f 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -4,6 +4,9 @@ import ( "context" "fmt" "io" + "sync" + + "errors" "github.com/onflow/cadence/migrations/statictypes" "github.com/onflow/cadence/runtime" @@ -21,6 +24,7 @@ import ( "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" "github.com/onflow/flow-go/model/flow" ) @@ -34,6 +38,7 @@ type CadenceBaseMigrator struct { reporter *cadenceValueMigrationReporter, ) []migrations.ValueMigration runtimeInterfaceConfig util.RuntimeInterfaceConfig + errorMessageHandler *errorMessageHandler } var _ AccountBasedMigration = (*CadenceBaseMigrator)(nil) @@ -55,33 +60,58 @@ func (m *CadenceBaseMigrator) InitMigration( // The MigrateAccount function is only given the payloads for the account to be migrated. // However, the migration needs to be able to get the code for contracts of any account. - fullPayloadSnapshot, err := util.NewPayloadSnapshot(allPayloads) + contracts, err := getContractMap(allPayloads) if err != nil { return err } - m.runtimeInterfaceConfig = util.RuntimeInterfaceConfig{ + m.runtimeInterfaceConfig.GetContractCodeFunc = func(location runtime.Location) ([]byte, error) { + addressLocation, ok := location.(common.AddressLocation) + if !ok { + return nil, nil + } - GetContractCodeFunc: func(location runtime.Location) ([]byte, error) { - addressLocation, ok := location.(common.AddressLocation) - if !ok { - return nil, nil - } - contractRegisterID := flow.ContractRegisterID( - flow.Address(addressLocation.Address), - addressLocation.Name, - ) - contract, err := fullPayloadSnapshot.Get(contractRegisterID) - if err != nil { - return nil, fmt.Errorf("failed to get contract code: %w", err) - } - return contract, nil - }, + contract, ok := contracts[addressLocation] + if !ok { + return nil, fmt.Errorf("failed to get contract code for location %s", location) + } + + return contract, nil } return nil } +func getContractMap(allPayloads []*ledger.Payload) (map[common.AddressLocation][]byte, error) { + contracts := make(map[common.AddressLocation][]byte) + + for _, payload := range allPayloads { + registerID, registerValue, err := convert.PayloadToRegister(payload) + if err != nil { + return nil, fmt.Errorf("failed to convert payload to register: %w", err) + } + + contractName := flow.RegisterIDContractName(registerID) + if contractName == "" { + continue + } + + address, err := common.BytesToAddress([]byte(registerID.Owner)) + if err != nil { + return nil, fmt.Errorf("failed to convert register owner to address: %w", err) + } + + addressLocation := common.AddressLocation{ + Address: address, + Name: contractName, + } + + contracts[addressLocation] = registerValue + } + + return contracts, nil +} + func (m *CadenceBaseMigrator) MigrateAccount( _ context.Context, address common.Address, @@ -104,9 +134,7 @@ func (m *CadenceBaseMigrator) MigrateAccount( migrationRuntime.Storage, ) - reporter := newValueMigrationReporter(m.reporter, m.log) - - m.log.Info().Msg("Migrating cadence values") + reporter := newValueMigrationReporter(m.reporter, m.log, m.errorMessageHandler) migration.Migrate( &migrations.AddressSliceIterator{ @@ -120,7 +148,6 @@ func (m *CadenceBaseMigrator) MigrateAccount( ), ) - m.log.Info().Msg("Committing changes") err = migration.Commit() if err != nil { return nil, fmt.Errorf("failed to commit changes: %w", err) @@ -144,6 +171,7 @@ func (m *CadenceBaseMigrator) MigrateAccount( // which runs some of the Cadence value migrations (static types, entitlements, strings) func NewCadence1ValueMigrator( rwf reporters.ReportWriterFactory, + errorMessageHandler *errorMessageHandler, compositeTypeConverter statictypes.CompositeTypeConverterFunc, interfaceTypeConverter statictypes.InterfaceTypeConverterFunc, ) *CadenceBaseMigrator { @@ -163,6 +191,7 @@ func NewCadence1ValueMigrator( string_normalization.NewStringNormalizingMigration(), } }, + errorMessageHandler: errorMessageHandler, } } @@ -171,6 +200,7 @@ func NewCadence1ValueMigrator( // It populates the given map with the IDs of the capability controller it issues. func NewCadence1LinkValueMigrator( rwf reporters.ReportWriterFactory, + errorMessageHandler *errorMessageHandler, capabilityMapping *capcons.CapabilityMapping, ) *CadenceBaseMigrator { return &CadenceBaseMigrator{ @@ -194,6 +224,7 @@ func NewCadence1LinkValueMigrator( }, } }, + errorMessageHandler: errorMessageHandler, } } @@ -203,6 +234,7 @@ func NewCadence1LinkValueMigrator( // generated by the link value migration. func NewCadence1CapabilityValueMigrator( rwf reporters.ReportWriterFactory, + errorMessageHandler *errorMessageHandler, capabilityMapping *capcons.CapabilityMapping, ) *CadenceBaseMigrator { return &CadenceBaseMigrator{ @@ -220,23 +252,54 @@ func NewCadence1CapabilityValueMigrator( }, } }, + errorMessageHandler: errorMessageHandler, } } +// errorMessageHandler formats error messages from errors. +// It only reports program loading errors once. +type errorMessageHandler struct { + // common.Location -> struct{} + reportedProgramLoadingErrors sync.Map +} + +func (t *errorMessageHandler) FormatError(err error) string { + + // Only report program loading errors once, + // omit full error message for subsequent occurrences + + var programLoadingError environment.ProgramLoadingError + if errors.As(err, &programLoadingError) { + location := programLoadingError.Location + _, ok := t.reportedProgramLoadingErrors.LoadOrStore(location, struct{}{}) + if ok { + return "error getting program" + } + } + + return err.Error() +} + // cadenceValueMigrationReporter is the reporter for cadence value migrations type cadenceValueMigrationReporter struct { - rw reporters.ReportWriter - log zerolog.Logger + reportWriter reporters.ReportWriter + log zerolog.Logger + errorMessageHandler *errorMessageHandler } var _ capcons.LinkMigrationReporter = &cadenceValueMigrationReporter{} var _ capcons.CapabilityMigrationReporter = &cadenceValueMigrationReporter{} var _ migrations.Reporter = &cadenceValueMigrationReporter{} -func newValueMigrationReporter(rw reporters.ReportWriter, log zerolog.Logger) *cadenceValueMigrationReporter { +func newValueMigrationReporter( + reportWriter reporters.ReportWriter, + log zerolog.Logger, + errorMessageHandler *errorMessageHandler, +) *cadenceValueMigrationReporter { return &cadenceValueMigrationReporter{ - rw: rw, - log: log, + reportWriter: reportWriter, + log: log, + errorMessageHandler: errorMessageHandler, } } @@ -245,7 +308,7 @@ func (t *cadenceValueMigrationReporter) Migrated( storageMapKey interpreter.StorageMapKey, migration string, ) { - t.rw.Write(cadenceValueMigrationReportEntry{ + t.reportWriter.Write(cadenceValueMigrationReportEntry{ StorageKey: storageKey, StorageMapKey: storageMapKey, Migration: migration, @@ -258,13 +321,15 @@ func (t *cadenceValueMigrationReporter) Error( migration string, err error, ) { + message := t.errorMessageHandler.FormatError(err) + t.log.Error().Msgf( "failed to run %s in account %s, domain %s, key %s: %s", migration, storageKey.Address, storageKey.Key, storageMapKey, - err, + message, ) } @@ -273,7 +338,7 @@ func (t *cadenceValueMigrationReporter) MigratedPathCapability( addressPath interpreter.AddressPath, borrowType *interpreter.ReferenceStaticType, ) { - t.rw.Write(capConsPathCapabilityMigrationEntry{ + t.reportWriter.Write(capConsPathCapabilityMigrationEntry{ AccountAddress: accountAddress, AddressPath: addressPath, BorrowType: borrowType, @@ -284,7 +349,7 @@ func (t *cadenceValueMigrationReporter) MissingCapabilityID( accountAddress common.Address, addressPath interpreter.AddressPath, ) { - t.rw.Write(capConsMissingCapabilityIDEntry{ + t.reportWriter.Write(capConsMissingCapabilityIDEntry{ AccountAddress: accountAddress, AddressPath: addressPath, }) @@ -294,18 +359,18 @@ func (t *cadenceValueMigrationReporter) MigratedLink( accountAddressPath interpreter.AddressPath, capabilityID interpreter.UInt64Value, ) { - t.rw.Write(capConsLinkMigrationEntry{ + t.reportWriter.Write(capConsLinkMigrationEntry{ AccountAddressPath: accountAddressPath, CapabilityID: capabilityID, }) } func (t *cadenceValueMigrationReporter) CyclicLink(err capcons.CyclicLinkError) { - t.rw.Write(err) + t.reportWriter.Write(err) } func (t *cadenceValueMigrationReporter) MissingTarget(accountAddressPath interpreter.AddressPath) { - t.rw.Write(capConsMissingTargetEntry{ + t.reportWriter.Write(capConsMissingTargetEntry{ AddressPath: accountAddressPath, }) } diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 0c139d7dc2c..5e377526f00 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -88,7 +88,7 @@ func TestCadenceValuesMigration(t *testing.T) { for _, migration := range migrations { payloads, err = migration(payloads) - require.NoError(t, err) + require.NoError(t, err, "migration failed, logs: %v", logWriter.logs) } // Assert the migrated payloads @@ -104,6 +104,11 @@ func TestCadenceValuesMigration(t *testing.T) { // TODO: //func TestCadenceValuesMigrationWithSwappedOrder(t *testing.T) { +var flowTokenAddress = func() common.Address { + address, _ := common.HexToAddress("0ae53cb6e3f42a79") + return address +}() + func checkMigratedPayloads( t *testing.T, address common.Address, @@ -130,10 +135,6 @@ func checkMigratedPayloads( var values []interpreter.Value for key, value := iterator.Next(); key != nil; key, value = iterator.Next() { - identifier := string(key.(interpreter.StringAtreeValue)) - if identifier == "flowTokenVault" || identifier == "flowTokenReceiver" { - continue - } values = append(values, value) } @@ -143,6 +144,12 @@ func checkMigratedPayloads( "Test", ) + flowTokenLocation := common.NewAddressLocation( + nil, + flowTokenAddress, + "FlowToken", + ) + fooInterfaceType := interpreter.NewInterfaceStaticTypeComputeTypeID( nil, testContractLocation, @@ -323,6 +330,25 @@ func checkMigratedPayloads( ), interpreter.NewUnmeteredStringValue("auth_ref"), ), + + interpreter.NewCompositeValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + flowTokenLocation, + "FlowToken.Vault", + common.CompositeKindResource, + []interpreter.CompositeField{ + { + Value: interpreter.NewUnmeteredUFix64Value(0.001 * sema.Fix64Factor), + Name: "balance", + }, + { + Value: interpreter.NewUnmeteredUInt64Value(11240984669916758018), + Name: "uuid", + }, + }, + address, + ), } require.Equal(t, len(expectedValues), len(values)) diff --git a/cmd/util/ledger/migrations/change_contract_code_migration.go b/cmd/util/ledger/migrations/change_contract_code_migration.go index 863113a29d5..eadfa65113d 100644 --- a/cmd/util/ledger/migrations/change_contract_code_migration.go +++ b/cmd/util/ledger/migrations/change_contract_code_migration.go @@ -56,10 +56,44 @@ type SystemContractChangesOptions struct { EVM EVMContractChange } +func BurnerAddressForChain(chainID flow.ChainID) flow.Address { + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + serviceAccountAddress := systemContracts.FlowServiceAccount.Address + fungibleTokenAddress := systemContracts.FungibleToken.Address + + switch chainID { + case flow.Mainnet, flow.Testnet: + return fungibleTokenAddress + + case flow.Emulator, flow.Localnet: + return serviceAccountAddress + + default: + panic(fmt.Errorf("unsupported chain ID: %s", chainID)) + } +} + func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOptions) []StagedContract { systemContracts := systemcontracts.SystemContractsForChain(chainID) - var stakingCollectionAddress, stakingProxyAddress common.Address + serviceAccountAddress := systemContracts.FlowServiceAccount.Address + idTableStakingAddress := systemContracts.IDTableStaking.Address + clusterQCAddress := systemContracts.ClusterQC.Address + dkgAddress := systemContracts.DKG.Address + fungibleTokenAddress := systemContracts.FungibleToken.Address + flowTokenAddress := systemContracts.FlowToken.Address + flowFeesAddress := systemContracts.FlowFees.Address + flowStorageFeesAddress := systemContracts.FlowStorageFees.Address + viewResolverAddress := systemContracts.ViewResolver.Address + metadataViewsAddress := systemContracts.MetadataViews.Address + fungibleTokenMetadataViewsAddress := common.Address(fungibleTokenAddress) + fungibleTokenSwitchboardAddress := common.Address(fungibleTokenAddress) + + burnerAddress := BurnerAddressForChain(chainID) + + var stakingCollectionAddress common.Address + var stakingProxyAddress common.Address switch chainID { case flow.Mainnet: @@ -70,38 +104,36 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp stakingCollectionAddress = mustHexAddress("0x95e019a17d0e23d7") stakingProxyAddress = mustHexAddress("0x7aad92e5a0715d21") - case flow.Emulator: - stakingCollectionAddress = common.Address(systemContracts.FlowServiceAccount.Address) - stakingProxyAddress = common.Address(systemContracts.FlowServiceAccount.Address) + case flow.Emulator, flow.Localnet: + stakingCollectionAddress = common.Address(serviceAccountAddress) + stakingProxyAddress = common.Address(serviceAccountAddress) default: panic(fmt.Errorf("unsupported chain ID: %s", chainID)) } lockedTokensAddress := stakingCollectionAddress - fungibleTokenMetadataViewsAddress := common.Address(systemContracts.FungibleToken.Address) - fungibleTokenSwitchboardAddress := common.Address(systemContracts.FungibleToken.Address) contractChanges := []StagedContract{ // epoch related contracts NewSystemContractChange( systemContracts.Epoch, coreContracts.FlowEpoch( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), - systemContracts.IDTableStaking.Address.HexWithPrefix(), - systemContracts.ClusterQC.Address.HexWithPrefix(), - systemContracts.DKG.Address.HexWithPrefix(), - systemContracts.FlowFees.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), + idTableStakingAddress.HexWithPrefix(), + clusterQCAddress.HexWithPrefix(), + dkgAddress.HexWithPrefix(), + flowFeesAddress.HexWithPrefix(), ), ), NewSystemContractChange( systemContracts.IDTableStaking, coreContracts.FlowIDTableStaking( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), - systemContracts.FlowFees.Address.HexWithPrefix(), - systemContracts.FlowServiceAccount.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), + flowFeesAddress.HexWithPrefix(), + burnerAddress.HexWithPrefix(), true, ), ), @@ -118,10 +150,10 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp NewSystemContractChange( systemContracts.FlowServiceAccount, coreContracts.FlowServiceAccount( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), - systemContracts.FlowFees.Address.HexWithPrefix(), - systemContracts.FlowStorageFees.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), + flowFeesAddress.HexWithPrefix(), + flowStorageFeesAddress.HexWithPrefix(), ), ), NewSystemContractChange( @@ -135,8 +167,8 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp NewSystemContractChange( systemContracts.FlowStorageFees, coreContracts.FlowStorageFees( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), ), ), { @@ -144,14 +176,14 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp Contract: Contract{ Name: "FlowStakingCollection", Code: coreContracts.FlowStakingCollection( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), - systemContracts.IDTableStaking.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), + idTableStakingAddress.HexWithPrefix(), stakingProxyAddress.HexWithPrefix(), lockedTokensAddress.HexWithPrefix(), - systemContracts.FlowStorageFees.Address.HexWithPrefix(), - systemContracts.ClusterQC.Address.HexWithPrefix(), - systemContracts.DKG.Address.HexWithPrefix(), + flowStorageFeesAddress.HexWithPrefix(), + clusterQCAddress.HexWithPrefix(), + dkgAddress.HexWithPrefix(), systemContracts.Epoch.Address.HexWithPrefix(), ), }, @@ -168,11 +200,11 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp Contract: Contract{ Name: "LockedTokens", Code: coreContracts.FlowLockedTokens( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), - systemContracts.IDTableStaking.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), + idTableStakingAddress.HexWithPrefix(), stakingProxyAddress.HexWithPrefix(), - systemContracts.FlowStorageFees.Address.HexWithPrefix(), + flowStorageFeesAddress.HexWithPrefix(), ), }, }, @@ -181,26 +213,26 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp NewSystemContractChange( systemContracts.FlowFees, coreContracts.FlowFees( - systemContracts.FungibleToken.Address.HexWithPrefix(), - systemContracts.FlowToken.Address.HexWithPrefix(), - systemContracts.FlowStorageFees.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), + flowTokenAddress.HexWithPrefix(), + flowStorageFeesAddress.HexWithPrefix(), ), ), NewSystemContractChange( systemContracts.FlowToken, coreContracts.FlowToken( - systemContracts.FungibleToken.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), fungibleTokenMetadataViewsAddress.HexWithPrefix(), - systemContracts.MetadataViews.Address.HexWithPrefix(), - systemContracts.ViewResolver.Address.HexWithPrefix(), + metadataViewsAddress.HexWithPrefix(), + burnerAddress.HexWithPrefix(), ), ), NewSystemContractChange( systemContracts.FungibleToken, ftContracts.FungibleToken( // Use `Hex()`, since this method adds the prefix. - systemContracts.ViewResolver.Address.Hex(), - systemContracts.FlowServiceAccount.Address.Hex(), + viewResolverAddress.Hex(), + burnerAddress.Hex(), ), ), { @@ -209,9 +241,9 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp Name: "FungibleTokenMetadataViews", Code: ftContracts.FungibleTokenMetadataViews( // Use `Hex()`, since this method adds the prefix. - systemContracts.FungibleToken.Address.Hex(), - systemContracts.MetadataViews.Address.Hex(), - systemContracts.ViewResolver.Address.Hex(), + fungibleTokenAddress.Hex(), + metadataViewsAddress.Hex(), + viewResolverAddress.Hex(), ), }, }, @@ -220,15 +252,15 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp NewSystemContractChange( systemContracts.NonFungibleToken, nftContracts.NonFungibleToken( - sdk.Address(systemContracts.ViewResolver.Address), + sdk.Address(viewResolverAddress), ), ), NewSystemContractChange( systemContracts.MetadataViews, nftContracts.MetadataViews( - sdk.Address(systemContracts.FungibleToken.Address), + sdk.Address(fungibleTokenAddress), sdk.Address(systemContracts.NonFungibleToken.Address), - sdk.Address(systemContracts.ViewResolver.Address), + sdk.Address(viewResolverAddress), ), ), NewSystemContractChange( @@ -237,7 +269,11 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp ), } - if chainID != flow.Emulator { + switch chainID { + case flow.Emulator, flow.Localnet: + // skip + + default: contractChanges = append( contractChanges, StagedContract{ @@ -245,7 +281,7 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp Contract: Contract{ Name: "FungibleTokenSwitchboard", Code: ftContracts.FungibleTokenSwitchboard( - systemContracts.FungibleToken.Address.HexWithPrefix(), + fungibleTokenAddress.HexWithPrefix(), ), }, }, @@ -263,7 +299,7 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp NewSystemContractChange( systemContracts.EVMContract, evm.ContractCode( - systemContracts.FlowToken.Address, + flowTokenAddress, abiOnly, ), ), diff --git a/cmd/util/ledger/migrations/deploy_migration.go b/cmd/util/ledger/migrations/deploy_migration.go index f0c5e3dab74..e68de9ce746 100644 --- a/cmd/util/ledger/migrations/deploy_migration.go +++ b/cmd/util/ledger/migrations/deploy_migration.go @@ -93,7 +93,7 @@ func NewBurnerDeploymentMigration( Name: "Burner", Code: coreContracts.Burner(), }, - chainID.Chain().ServiceAddress(), + BurnerAddressForChain(chainID), logger, ) } diff --git a/cmd/util/ledger/migrations/migrator_runtime.go b/cmd/util/ledger/migrations/migrator_runtime.go index 4e747694b63..d55af03e17c 100644 --- a/cmd/util/ledger/migrations/migrator_runtime.go +++ b/cmd/util/ledger/migrations/migrator_runtime.go @@ -68,6 +68,7 @@ func newMigratorRuntime( accounts, ), RuntimeInterfaceConfig: config, + ProgramErrors: map[common.Location]error{}, } env := runtime.NewBaseInterpreterEnvironment(runtime.Config{ diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 18a8f02fd06..3c3e1734546 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -180,6 +180,7 @@ func (m *StagedContractsMigration) MigrateAccount( config := util.RuntimeInterfaceConfig{ GetContractCodeFunc: func(location runtime.Location) ([]byte, error) { + // TODO: also consider updated system contracts return m.contractsByLocation[location], nil }, GetOrLoadProgramListener: func(location runtime.Location, program *interpreter.Program, err error) { diff --git a/cmd/util/ledger/migrations/staged_contracts_migration_test.go b/cmd/util/ledger/migrations/staged_contracts_migration_test.go index 216959e7c83..8962a0b2790 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration_test.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration_test.go @@ -339,6 +339,35 @@ func TestStagedContractsMigration(t *testing.T) { require.Len(t, payloads, 1) require.Equal(t, update2, string(payloads[0].Value())) }) + + t.Run("missing old contract", func(t *testing.T) { + t.Parallel() + + newCode := "access(all) contract A { access(all) struct B {} }" + + stagedContracts := []StagedContract{ + { + Contract: Contract{ + Name: "A", + Code: []byte(newCode), + }, + Address: address1, + }, + } + + migration := NewStagedContractsMigration(flow.Emulator) + + logWriter := &logWriter{} + log := zerolog.New(logWriter) + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + migration.RegisterContractUpdates(stagedContracts) + + // NOTE: no payloads + _, err = migration.MigrateAccount(ctx, address1, nil) + require.ErrorContains(t, err, "failed to find all contract registers that need to be changed") + }) } func TestStagedContractsWithImports(t *testing.T) { diff --git a/cmd/util/ledger/util/migration_runtime_interface.go b/cmd/util/ledger/util/migration_runtime_interface.go index 529b1a351d8..e7850494378 100644 --- a/cmd/util/ledger/util/migration_runtime_interface.go +++ b/cmd/util/ledger/util/migration_runtime_interface.go @@ -19,11 +19,12 @@ import ( // MigrationRuntimeInterface is a runtime interface that can be used in migrations. type MigrationRuntimeInterface struct { RuntimeInterfaceConfig - Accounts environment.Accounts - Programs *environment.Programs + Accounts environment.Accounts + Programs *environment.Programs + ProgramErrors map[common.Location]error } -func (m MigrationRuntimeInterface) ResolveLocation( +func (m *MigrationRuntimeInterface) ResolveLocation( identifiers []runtime.Identifier, location runtime.Location, ) ([]runtime.ResolvedLocation, error) { @@ -84,7 +85,7 @@ func (m MigrationRuntimeInterface) ResolveLocation( return resolvedLocations, nil } -func (m MigrationRuntimeInterface) GetCode(location runtime.Location) ([]byte, error) { +func (m *MigrationRuntimeInterface) GetCode(location runtime.Location) ([]byte, error) { contractLocation, ok := location.(common.AddressLocation) if !ok { return nil, fmt.Errorf("GetCode failed: expected AddressLocation") @@ -98,7 +99,7 @@ func (m MigrationRuntimeInterface) GetCode(location runtime.Location) ([]byte, e return add, nil } -func (m MigrationRuntimeInterface) GetAccountContractCode( +func (m *MigrationRuntimeInterface) GetAccountContractCode( location common.AddressLocation, ) (code []byte, err error) { // First look for staged contracts. @@ -114,10 +115,13 @@ func (m MigrationRuntimeInterface) GetAccountContractCode( return m.Accounts.GetContract(location.Name, flow.Address(location.Address)) } -func (m MigrationRuntimeInterface) GetOrLoadProgram( +func (m *MigrationRuntimeInterface) GetOrLoadProgram( location runtime.Location, load func() (*interpreter.Program, error), -) (program *interpreter.Program, err error) { +) ( + program *interpreter.Program, + err error, +) { defer func() { if m.GetOrLoadProgramListener != nil { @@ -125,197 +129,226 @@ func (m MigrationRuntimeInterface) GetOrLoadProgram( } }() - if m.GetOrLoadProgramFunc != nil { - return m.GetOrLoadProgramFunc(location, load) - } + return m.Programs.GetOrLoadProgram( + location, + func() (*interpreter.Program, error) { + // If the program is already known to be invalid, + // then return the error immediately, + // without attempting to load the program again + if err, ok := m.ProgramErrors[location]; ok { + return nil, err + } - return m.Programs.GetOrLoadProgram(location, load) + // Otherwise, load the program. + // If an error occurs, then record it for subsequent calls + program, err := load() + if err != nil { + m.ProgramErrors[location] = err + } + + return program, err + }, + ) } -func (m MigrationRuntimeInterface) MeterMemory(_ common.MemoryUsage) error { +func (m *MigrationRuntimeInterface) MeterMemory(_ common.MemoryUsage) error { return nil } -func (m MigrationRuntimeInterface) MeterComputation(_ common.ComputationKind, _ uint) error { +func (m *MigrationRuntimeInterface) MeterComputation(_ common.ComputationKind, _ uint) error { return nil } -func (m MigrationRuntimeInterface) GetValue(_, _ []byte) (value []byte, err error) { +func (m *MigrationRuntimeInterface) GetValue(_, _ []byte) (value []byte, err error) { panic("unexpected GetValue call") } -func (m MigrationRuntimeInterface) SetValue(_, _, _ []byte) (err error) { +func (m *MigrationRuntimeInterface) SetValue(_, _, _ []byte) (err error) { panic("unexpected SetValue call") } -func (m MigrationRuntimeInterface) CreateAccount(_ runtime.Address) (address runtime.Address, err error) { +func (m *MigrationRuntimeInterface) CreateAccount(_ runtime.Address) (address runtime.Address, err error) { panic("unexpected CreateAccount call") } -func (m MigrationRuntimeInterface) AddEncodedAccountKey(_ runtime.Address, _ []byte) error { +func (m *MigrationRuntimeInterface) AddEncodedAccountKey(_ runtime.Address, _ []byte) error { panic("unexpected AddEncodedAccountKey call") } -func (m MigrationRuntimeInterface) RevokeEncodedAccountKey(_ runtime.Address, _ int) (publicKey []byte, err error) { +func (m *MigrationRuntimeInterface) RevokeEncodedAccountKey(_ runtime.Address, _ int) (publicKey []byte, err error) { panic("unexpected RevokeEncodedAccountKey call") } -func (m MigrationRuntimeInterface) AddAccountKey(_ runtime.Address, _ *runtime.PublicKey, _ runtime.HashAlgorithm, _ int) (*runtime.AccountKey, error) { +func (m *MigrationRuntimeInterface) AddAccountKey( + _ runtime.Address, + _ *runtime.PublicKey, + _ runtime.HashAlgorithm, + _ int, +) (*runtime.AccountKey, error) { panic("unexpected AddAccountKey call") } -func (m MigrationRuntimeInterface) GetAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { +func (m *MigrationRuntimeInterface) GetAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { panic("unexpected GetAccountKey call") } -func (m MigrationRuntimeInterface) RevokeAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { +func (m *MigrationRuntimeInterface) RevokeAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { panic("unexpected RevokeAccountKey call") } -func (m MigrationRuntimeInterface) UpdateAccountContractCode(_ common.AddressLocation, _ []byte) (err error) { +func (m *MigrationRuntimeInterface) UpdateAccountContractCode(_ common.AddressLocation, _ []byte) (err error) { panic("unexpected UpdateAccountContractCode call") } -func (m MigrationRuntimeInterface) RemoveAccountContractCode(common.AddressLocation) (err error) { +func (m *MigrationRuntimeInterface) RemoveAccountContractCode(common.AddressLocation) (err error) { panic("unexpected RemoveAccountContractCode call") } -func (m MigrationRuntimeInterface) GetSigningAccounts() ([]runtime.Address, error) { +func (m *MigrationRuntimeInterface) GetSigningAccounts() ([]runtime.Address, error) { panic("unexpected GetSigningAccounts call") } -func (m MigrationRuntimeInterface) ProgramLog(_ string) error { +func (m *MigrationRuntimeInterface) ProgramLog(_ string) error { panic("unexpected ProgramLog call") } -func (m MigrationRuntimeInterface) EmitEvent(_ cadence.Event) error { +func (m *MigrationRuntimeInterface) EmitEvent(_ cadence.Event) error { panic("unexpected EmitEvent call") } -func (m MigrationRuntimeInterface) ValueExists(_, _ []byte) (exists bool, err error) { +func (m *MigrationRuntimeInterface) ValueExists(_, _ []byte) (exists bool, err error) { panic("unexpected ValueExists call") } -func (m MigrationRuntimeInterface) GenerateUUID() (uint64, error) { +func (m *MigrationRuntimeInterface) GenerateUUID() (uint64, error) { panic("unexpected GenerateUUID call") } -func (m MigrationRuntimeInterface) GetComputationLimit() uint64 { +func (m *MigrationRuntimeInterface) GetComputationLimit() uint64 { panic("unexpected GetComputationLimit call") } -func (m MigrationRuntimeInterface) SetComputationUsed(_ uint64) error { +func (m *MigrationRuntimeInterface) SetComputationUsed(_ uint64) error { panic("unexpected SetComputationUsed call") } -func (m MigrationRuntimeInterface) DecodeArgument(_ []byte, _ cadence.Type) (cadence.Value, error) { +func (m *MigrationRuntimeInterface) DecodeArgument(_ []byte, _ cadence.Type) (cadence.Value, error) { panic("unexpected DecodeArgument call") } -func (m MigrationRuntimeInterface) GetCurrentBlockHeight() (uint64, error) { +func (m *MigrationRuntimeInterface) GetCurrentBlockHeight() (uint64, error) { panic("unexpected GetCurrentBlockHeight call") } -func (m MigrationRuntimeInterface) GetBlockAtHeight(_ uint64) (block runtime.Block, exists bool, err error) { +func (m *MigrationRuntimeInterface) GetBlockAtHeight(_ uint64) (block runtime.Block, exists bool, err error) { panic("unexpected GetBlockAtHeight call") } -func (m MigrationRuntimeInterface) ReadRandom([]byte) error { +func (m *MigrationRuntimeInterface) ReadRandom([]byte) error { panic("unexpected ReadRandom call") } -func (m MigrationRuntimeInterface) VerifySignature(_ []byte, _ string, _ []byte, _ []byte, _ runtime.SignatureAlgorithm, _ runtime.HashAlgorithm) (bool, error) { +func (m *MigrationRuntimeInterface) VerifySignature( + _ []byte, + _ string, + _ []byte, + _ []byte, + _ runtime.SignatureAlgorithm, + _ runtime.HashAlgorithm, +) (bool, error) { panic("unexpected VerifySignature call") } -func (m MigrationRuntimeInterface) Hash(_ []byte, _ string, _ runtime.HashAlgorithm) ([]byte, error) { +func (m *MigrationRuntimeInterface) Hash(_ []byte, _ string, _ runtime.HashAlgorithm) ([]byte, error) { panic("unexpected Hash call") } -func (m MigrationRuntimeInterface) GetAccountBalance(_ common.Address) (value uint64, err error) { +func (m *MigrationRuntimeInterface) GetAccountBalance(_ common.Address) (value uint64, err error) { panic("unexpected GetAccountBalance call") } -func (m MigrationRuntimeInterface) GetAccountAvailableBalance(_ common.Address) (value uint64, err error) { +func (m *MigrationRuntimeInterface) GetAccountAvailableBalance(_ common.Address) (value uint64, err error) { panic("unexpected GetAccountAvailableBalance call") } -func (m MigrationRuntimeInterface) GetStorageUsed(_ runtime.Address) (value uint64, err error) { +func (m *MigrationRuntimeInterface) GetStorageUsed(_ runtime.Address) (value uint64, err error) { panic("unexpected GetStorageUsed call") } -func (m MigrationRuntimeInterface) GetStorageCapacity(_ runtime.Address) (value uint64, err error) { +func (m *MigrationRuntimeInterface) GetStorageCapacity(_ runtime.Address) (value uint64, err error) { panic("unexpected GetStorageCapacity call") } -func (m MigrationRuntimeInterface) ImplementationDebugLog(_ string) error { +func (m *MigrationRuntimeInterface) ImplementationDebugLog(_ string) error { panic("unexpected ImplementationDebugLog call") } -func (m MigrationRuntimeInterface) ValidatePublicKey(_ *runtime.PublicKey) error { +func (m *MigrationRuntimeInterface) ValidatePublicKey(_ *runtime.PublicKey) error { panic("unexpected ValidatePublicKey call") } -func (m MigrationRuntimeInterface) GetAccountContractNames(_ runtime.Address) ([]string, error) { +func (m *MigrationRuntimeInterface) GetAccountContractNames(_ runtime.Address) ([]string, error) { panic("unexpected GetAccountContractNames call") } -func (m MigrationRuntimeInterface) AllocateStorageIndex(_ []byte) (atree.StorageIndex, error) { +func (m *MigrationRuntimeInterface) AllocateStorageIndex(_ []byte) (atree.StorageIndex, error) { panic("unexpected AllocateStorageIndex call") } -func (m MigrationRuntimeInterface) ComputationUsed() (uint64, error) { +func (m *MigrationRuntimeInterface) ComputationUsed() (uint64, error) { panic("unexpected ComputationUsed call") } -func (m MigrationRuntimeInterface) MemoryUsed() (uint64, error) { +func (m *MigrationRuntimeInterface) MemoryUsed() (uint64, error) { panic("unexpected MemoryUsed call") } -func (m MigrationRuntimeInterface) InteractionUsed() (uint64, error) { +func (m *MigrationRuntimeInterface) InteractionUsed() (uint64, error) { panic("unexpected InteractionUsed call") } -func (m MigrationRuntimeInterface) SetInterpreterSharedState(_ *interpreter.SharedState) { +func (m *MigrationRuntimeInterface) SetInterpreterSharedState(_ *interpreter.SharedState) { panic("unexpected SetInterpreterSharedState call") } -func (m MigrationRuntimeInterface) GetInterpreterSharedState() *interpreter.SharedState { +func (m *MigrationRuntimeInterface) GetInterpreterSharedState() *interpreter.SharedState { panic("unexpected GetInterpreterSharedState call") } -func (m MigrationRuntimeInterface) AccountKeysCount(_ runtime.Address) (uint64, error) { +func (m *MigrationRuntimeInterface) AccountKeysCount(_ runtime.Address) (uint64, error) { panic("unexpected AccountKeysCount call") } -func (m MigrationRuntimeInterface) BLSVerifyPOP(_ *runtime.PublicKey, _ []byte) (bool, error) { +func (m *MigrationRuntimeInterface) BLSVerifyPOP(_ *runtime.PublicKey, _ []byte) (bool, error) { panic("unexpected BLSVerifyPOP call") } -func (m MigrationRuntimeInterface) BLSAggregateSignatures(_ [][]byte) ([]byte, error) { +func (m *MigrationRuntimeInterface) BLSAggregateSignatures(_ [][]byte) ([]byte, error) { panic("unexpected BLSAggregateSignatures call") } -func (m MigrationRuntimeInterface) BLSAggregatePublicKeys(_ []*runtime.PublicKey) (*runtime.PublicKey, error) { +func (m *MigrationRuntimeInterface) BLSAggregatePublicKeys(_ []*runtime.PublicKey) (*runtime.PublicKey, error) { panic("unexpected BLSAggregatePublicKeys call") } -func (m MigrationRuntimeInterface) ResourceOwnerChanged(_ *interpreter.Interpreter, _ *interpreter.CompositeValue, _ common.Address, _ common.Address) { +func (m *MigrationRuntimeInterface) ResourceOwnerChanged( + _ *interpreter.Interpreter, + _ *interpreter.CompositeValue, + _ common.Address, + _ common.Address, +) { panic("unexpected ResourceOwnerChanged call") } -func (m MigrationRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) { +func (m *MigrationRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) { panic("unexpected GenerateAccountID call") } -func (m MigrationRuntimeInterface) RecordTrace(_ string, _ runtime.Location, _ time.Duration, _ []attribute.KeyValue) { +func (m *MigrationRuntimeInterface) RecordTrace(_ string, _ runtime.Location, _ time.Duration, _ []attribute.KeyValue) { panic("unexpected RecordTrace call") } type RuntimeInterfaceConfig struct { - // GetOrLoadProgramFunc allows for injecting extra logic - GetOrLoadProgramFunc func(location runtime.Location, load func() (*interpreter.Program, error)) (*interpreter.Program, error) - GetOrLoadProgramListener func(runtime.Location, *interpreter.Program, error) // GetContractCodeFunc allows for injecting extra logic for code lookup diff --git a/fvm/environment/programs.go b/fvm/environment/programs.go index 85c52a843d9..8ca57cdbd10 100644 --- a/fvm/environment/programs.go +++ b/fvm/environment/programs.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/hashicorp/go-multierror" + "golang.org/x/xerrors" "github.com/onflow/cadence" jsoncdc "github.com/onflow/cadence/encoding/json" @@ -18,6 +19,22 @@ import ( "github.com/onflow/flow-go/module/trace" ) +type ProgramLoadingError struct { + Err error + Location common.Location +} + +func (p ProgramLoadingError) Unwrap() error { + return p.Err +} + +var _ error = ProgramLoadingError{} +var _ xerrors.Wrapper = ProgramLoadingError{} + +func (p ProgramLoadingError) Error() string { + return fmt.Sprintf("error getting program %v: %s", p.Location, p.Err) +} + // Programs manages operations around cadence program parsing. // // Note that cadence guarantees that Get/Set methods are called in a LIFO @@ -126,7 +143,10 @@ func (programs *Programs) getOrLoadAddressProgram( loader, ) if err != nil { - return nil, fmt.Errorf("error getting program %v: %w", location, err) + return nil, ProgramLoadingError{ + Err: err, + Location: location, + } } // Add dependencies to the stack. diff --git a/go.mod b/go.mod index dd3bf2c364c..d4df1c6c6b2 100644 --- a/go.mod +++ b/go.mod @@ -112,6 +112,7 @@ require ( github.com/onflow/wal v0.0.0-20240208022732-d756cd497d3b github.com/slok/go-http-metrics v0.10.0 github.com/sony/gobreaker v0.5.0 + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 google.golang.org/genproto/googleapis/api v0.0.0-20240108191215-35c7eff3a6b1 google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405 gopkg.in/yaml.v2 v2.4.0 @@ -310,7 +311,6 @@ require ( golang.org/x/net v0.20.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/term v0.16.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect gonum.org/v1/gonum v0.14.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240108191215-35c7eff3a6b1 // indirect