From 76bd405df2133edd177f026fc606ddf35b389ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Tue, 27 Feb 2024 16:59:46 -0800 Subject: [PATCH 01/39] test deployment migration, on filtered payloads --- .../migrations/deploy_migration_test.go | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 cmd/util/ledger/migrations/deploy_migration_test.go diff --git a/cmd/util/ledger/migrations/deploy_migration_test.go b/cmd/util/ledger/migrations/deploy_migration_test.go new file mode 100644 index 00000000000..506e7b1a685 --- /dev/null +++ b/cmd/util/ledger/migrations/deploy_migration_test.go @@ -0,0 +1,164 @@ +package migrations + +import ( + "fmt" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" +) + +func newBootstrapPayloads( + chainID flow.ChainID, + bootstrapProcedureOptions ...fvm.BootstrapProcedureOption, +) ([]*ledger.Payload, error) { + + ctx := fvm.NewContext( + fvm.WithChain(chainID.Chain()), + ) + + vm := fvm.NewVirtualMachine() + + storageSnapshot := snapshot.MapStorageSnapshot{} + + bootstrapProcedure := fvm.Bootstrap( + unittest.ServiceAccountPublicKey, + bootstrapProcedureOptions..., + ) + + executionSnapshot, _, err := vm.Run( + ctx, + bootstrapProcedure, + storageSnapshot, + ) + if err != nil { + return nil, err + } + + payloads := make([]*ledger.Payload, 0, len(executionSnapshot.WriteSet)) + + for registerID, registerValue := range executionSnapshot.WriteSet { + payloadKey := convert.RegisterIDToLedgerKey(registerID) + payload := ledger.NewPayload(payloadKey, registerValue) + payloads = append(payloads, payload) + } + + return payloads, nil +} + +func TestDeploy(t *testing.T) { + t.Parallel() + + const chainID = flow.Emulator + + chain := chainID.Chain() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + serviceAccountAddress := systemContracts.FlowServiceAccount.Address + fungibleTokenAddress := systemContracts.FungibleToken.Address + + targetAddress := serviceAccountAddress + + migration := NewDeploymentMigration( + chainID, + Contract{ + Name: "NewContract", + Code: []byte(fmt.Sprintf( + ` + import FungibleToken from %s + + access(all) + contract NewContract { + + access(all) + fun answer(): Int { + return 42 + } + } + `, + fungibleTokenAddress.HexWithPrefix(), + )), + }, + targetAddress, + zerolog.Nop(), + ) + + bootstrapPayloads, err := newBootstrapPayloads(chainID) + require.NoError(t, err) + + filteredPayloads := make([]*ledger.Payload, 0, len(bootstrapPayloads)) + + // TODO: move to NewTransactionBasedMigration + + // Filter the bootstrapped payloads to only include the target account (service account) + // and the account where the fungible token is deployed + + for _, payload := range bootstrapPayloads { + registerID, _, err := convert.PayloadToRegister(payload) + require.NoError(t, err) + + if len(registerID.Owner) > 0 { + registerAddress := flow.Address([]byte(registerID.Owner)) + switch registerAddress { + case targetAddress, fungibleTokenAddress: + filteredPayloads = append(filteredPayloads, payload) + } + } else { + filteredPayloads = append(filteredPayloads, payload) + } + } + + newPayloads, err := migration(filteredPayloads) + require.NoError(t, err) + + txBody := flow.NewTransactionBody(). + SetScript([]byte(fmt.Sprintf( + ` + import NewContract from %s + + transaction { + execute { + log(NewContract.answer()) + } + } + `, + targetAddress.HexWithPrefix(), + ))) + + vm := fvm.NewVirtualMachine() + + storageSnapshot := snapshot.MapStorageSnapshot{} + + for _, newPayload := range newPayloads { + registerID, registerValue, err := convert.PayloadToRegister(newPayload) + require.NoError(t, err) + + storageSnapshot[registerID] = registerValue + } + + ctx := fvm.NewContext( + fvm.WithChain(chain), + fvm.WithAuthorizationChecksEnabled(false), + fvm.WithSequenceNumberCheckAndIncrementEnabled(false), + fvm.WithCadenceLogging(true), + ) + + _, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, 0), + storageSnapshot, + ) + + require.NoError(t, err) + require.NoError(t, output.Err) + require.Len(t, output.Logs, 1) + require.Equal(t, "42", output.Logs[0]) +} From 8876cddb8d45555f26b0d07104c072905b6f79c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 15:08:19 -0800 Subject: [PATCH 02/39] pass logger --- cmd/util/ledger/migrations/cadence.go | 3 ++- .../ledger/migrations/change_contract_code_migration.go | 8 +++++--- cmd/util/ledger/migrations/staged_contracts_migration.go | 5 +++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 1b6418e41eb..ae8aa711d3d 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -209,7 +209,7 @@ func NewCadence1ContractsMigrations( stagedContracts []StagedContract, ) []ledger.Migration { - stagedContractsMigration := NewStagedContractsMigration(chainID). + stagedContractsMigration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() stagedContractsMigration.RegisterContractUpdates(stagedContracts) @@ -221,6 +221,7 @@ func NewCadence1ContractsMigrations( []AccountBasedMigration{ NewSystemContactsMigration( chainID, + log, SystemContractChangesOptions{ EVM: evmContractChange, }, diff --git a/cmd/util/ledger/migrations/change_contract_code_migration.go b/cmd/util/ledger/migrations/change_contract_code_migration.go index 3408fbc1248..28e01dfb225 100644 --- a/cmd/util/ledger/migrations/change_contract_code_migration.go +++ b/cmd/util/ledger/migrations/change_contract_code_migration.go @@ -5,6 +5,7 @@ import ( "github.com/onflow/cadence/runtime/common" coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" + "github.com/rs/zerolog" evm "github.com/onflow/flow-go/fvm/evm/stdlib" "github.com/onflow/flow-go/fvm/systemcontracts" @@ -17,9 +18,9 @@ type ChangeContractCodeMigration struct { var _ AccountBasedMigration = (*ChangeContractCodeMigration)(nil) -func NewChangeContractCodeMigration(chainID flow.ChainID) *ChangeContractCodeMigration { +func NewChangeContractCodeMigration(chainID flow.ChainID, log zerolog.Logger) *ChangeContractCodeMigration { return &ChangeContractCodeMigration{ - StagedContractsMigration: NewStagedContractsMigration(chainID). + StagedContractsMigration: NewStagedContractsMigration(chainID, log). // TODO: //WithContractUpdateValidation(). WithName("ChangeContractCodeMigration"), @@ -248,9 +249,10 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp func NewSystemContactsMigration( chainID flow.ChainID, + log zerolog.Logger, options SystemContractChangesOptions, ) *ChangeContractCodeMigration { - migration := NewChangeContractCodeMigration(chainID) + migration := NewChangeContractCodeMigration(chainID, log) for _, change := range SystemContractChanges(chainID, options) { migration.RegisterContractChange(change) } diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 3c3e1734546..1856bb63c5a 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -49,9 +49,10 @@ type Contract struct { var _ AccountBasedMigration = &StagedContractsMigration{} -func NewStagedContractsMigration(chainID flow.ChainID) *StagedContractsMigration { +func NewStagedContractsMigration(chainID flow.ChainID, log zerolog.Logger) *StagedContractsMigration { return &StagedContractsMigration{ name: "StagedContractsMigration", + log: log, chainID: chainID, stagedContracts: map[common.Address]map[flow.RegisterID]Contract{}, contractsByLocation: map[common.Location][]byte{}, @@ -77,7 +78,7 @@ func (m *StagedContractsMigration) Close() error { var sb strings.Builder sb.WriteString("failed to find all contract registers that need to be changed:\n") for address, contracts := range m.stagedContracts { - _, _ = fmt.Fprintf(&sb, "- address: %s\n", address) + _, _ = fmt.Fprintf(&sb, "- address: %s\n", address.HexWithPrefix()) for registerID := range contracts { _, _ = fmt.Fprintf(&sb, " - %s\n", flow.RegisterIDContractName(registerID)) } From 977d6d06b7cc716531c61a9875d7feda5cdfd035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 15:08:48 -0800 Subject: [PATCH 03/39] fail on contract update registrations with zero address --- cmd/util/ledger/migrations/staged_contracts_migration.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 1856bb63c5a..f91ad762f05 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -122,6 +122,15 @@ func (m *StagedContractsMigration) RegisterContractChange(change StagedContract) defer m.mutex.Unlock() address := change.Address + + if address == common.ZeroAddress { + m.log.Fatal().Msgf( + "invalid zero address for contract update: %s.%s", + address.HexWithPrefix(), + change.Name, + ) + } + if _, ok := m.stagedContracts[address]; !ok { m.stagedContracts[address] = map[flow.RegisterID]Contract{} } From 89075019bc973deb640fdf2762639fc111ae1f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 15:09:16 -0800 Subject: [PATCH 04/39] add missing address for FungibleTokenSwitchboard contract --- fvm/systemcontracts/system_contracts.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fvm/systemcontracts/system_contracts.go b/fvm/systemcontracts/system_contracts.go index 02985a77510..0d7fe21e594 100644 --- a/fvm/systemcontracts/system_contracts.go +++ b/fvm/systemcontracts/system_contracts.go @@ -169,9 +169,10 @@ func (c SystemContracts) AsTemplateEnv() templates.Environment { FungibleTokenAddress: c.FungibleToken.Address.Hex(), FungibleTokenMetadataViewsAddress: c.FungibleToken.Address.Hex(), - NonFungibleTokenAddress: c.NonFungibleToken.Address.Hex(), - MetadataViewsAddress: c.MetadataViews.Address.Hex(), - ViewResolverAddress: c.ViewResolver.Address.Hex(), + NonFungibleTokenAddress: c.NonFungibleToken.Address.Hex(), + MetadataViewsAddress: c.MetadataViews.Address.Hex(), + ViewResolverAddress: c.ViewResolver.Address.Hex(), + FungibleTokenSwitchboardAddress: c.FungibleToken.Address.Hex(), } } From aec38a338de46f544abfdaf442481014674fb604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 15:13:27 -0800 Subject: [PATCH 05/39] improve error: report any invalid address for given chain --- cmd/util/ledger/migrations/staged_contracts_migration.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index f91ad762f05..d09a17a01d9 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -123,9 +123,12 @@ func (m *StagedContractsMigration) RegisterContractChange(change StagedContract) address := change.Address - if address == common.ZeroAddress { + chain := m.chainID.Chain() + + if _, err := chain.IndexFromAddress(flow.Address(address)); err != nil { m.log.Fatal().Msgf( - "invalid zero address for contract update: %s.%s", + "invalid contract update: invalid address for chain %s: %s (%s)", + m.chainID, address.HexWithPrefix(), change.Name, ) From cdb58179be1856429a5992afb87b2ca5e91a35f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 16:20:32 -0800 Subject: [PATCH 06/39] refactor payload extraction into common function --- .../execution_state_extract.go | 85 +++++++++---------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 25bcadbf2ab..3a4a72e2a10 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -127,34 +127,17 @@ func extractExecutionState( log.Error().Err(err).Msgf("can not generate report for migrated state: %v", newMigratedState) } - exportPayloads := len(outputPayloadFile) > 0 - if exportPayloads { + if len(outputPayloadFile) > 0 { payloads := newTrie.AllPayloads() - log.Info().Msgf("sorting %d payloads", len(payloads)) - - // Sort payloads to produce deterministic payload file with - // same sequence of payloads inside. - payloads = util.SortPayloadsByAddress(payloads, nWorker) - - log.Info().Msgf("sorted %d payloads", len(payloads)) - - log.Info().Msgf("creating payloads file %s", outputPayloadFile) - - exportedPayloadCount, err := util.CreatePayloadFile( + return exportPayloads( log, - outputPayloadFile, payloads, + nWorker, + outputPayloadFile, exportPayloadsByAddresses, false, // payloads represents entire state. ) - if err != nil { - return fmt.Errorf("cannot generate payloads file: %w", err) - } - - log.Info().Msgf("Exported %d payloads out of %d payloads", exportedPayloadCount, len(payloads)) - - return nil } migratedState, err := createCheckpoint( @@ -251,33 +234,15 @@ func extractExecutionStateFromPayloads( return err } - exportPayloads := len(outputPayloadFile) > 0 - if exportPayloads { - - log.Info().Msgf("sorting %d payloads", len(payloads)) - - // Sort payloads to produce deterministic payload file with - // same sequence of payloads inside. - payloads = util.SortPayloadsByAddress(payloads, nWorker) - - log.Info().Msgf("sorted %d payloads", len(payloads)) - - log.Info().Msgf("creating payloads file %s", outputPayloadFile) - - exportedPayloadCount, err := util.CreatePayloadFile( + if len(outputPayloadFile) > 0 { + return exportPayloads( log, - outputPayloadFile, payloads, + nWorker, + outputPayloadFile, exportPayloadsByAddresses, inputPayloadsFromPartialState, ) - if err != nil { - return fmt.Errorf("cannot generate payloads file: %w", err) - } - - log.Info().Msgf("Exported %d payloads out of %d payloads", exportedPayloadCount, len(payloads)) - - return nil } newTrie, err := createTrieFromPayloads(log, payloads) @@ -304,6 +269,40 @@ func extractExecutionStateFromPayloads( return nil } +func exportPayloads( + log zerolog.Logger, + payloads []*ledger.Payload, + nWorker int, + outputPayloadFile string, + exportPayloadsByAddresses []common.Address, + inputPayloadsFromPartialState bool, +) error { + log.Info().Msgf("sorting %d payloads", len(payloads)) + + // Sort payloads to produce deterministic payload file with + // same sequence of payloads inside. + payloads = util.SortPayloadsByAddress(payloads, nWorker) + + log.Info().Msgf("sorted %d payloads", len(payloads)) + + log.Info().Msgf("creating payloads file %s", outputPayloadFile) + + exportedPayloadCount, err := util.CreatePayloadFile( + log, + outputPayloadFile, + payloads, + exportPayloadsByAddresses, + inputPayloadsFromPartialState, + ) + if err != nil { + return fmt.Errorf("cannot generate payloads file: %w", err) + } + + log.Info().Msgf("exported %d payloads out of %d payloads", exportedPayloadCount, len(payloads)) + + return nil +} + func migratePayloads(logger zerolog.Logger, payloads []*ledger.Payload, migrations []ledger.Migration) ([]*ledger.Payload, error) { if len(migrations) == 0 { From 9362929a4b171475f26cb692b48403956b036fad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 16:45:59 -0800 Subject: [PATCH 07/39] fix tests: pass logger --- .../change_contract_code_migration_test.go | 32 ++++++-- .../staged_contracts_migration_test.go | 80 +++++++++++-------- 2 files changed, 70 insertions(+), 42 deletions(-) diff --git a/cmd/util/ledger/migrations/change_contract_code_migration_test.go b/cmd/util/ledger/migrations/change_contract_code_migration_test.go index d23aefbc802..53147a7bb70 100644 --- a/cmd/util/ledger/migrations/change_contract_code_migration_test.go +++ b/cmd/util/ledger/migrations/change_contract_code_migration_test.go @@ -57,8 +57,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("no contracts", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -75,8 +77,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("1 contract - dont migrate", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -97,8 +101,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("1 contract - migrate", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -129,8 +135,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("2 contracts - migrate 1", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -163,8 +171,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("2 contracts - migrate 2", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -206,8 +216,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("2 contracts on different accounts - migrate 1", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -240,8 +252,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("not all contracts on one account migrated", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -276,8 +290,10 @@ func TestChangeContractCodeMigration(t *testing.T) { t.Run("not all accounts migrated", func(t *testing.T) { t.Parallel() - migration := migrations.NewChangeContractCodeMigration(flow.Emulator) log := zerolog.New(zerolog.NewTestWriter(t)) + + migration := migrations.NewChangeContractCodeMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration_test.go b/cmd/util/ledger/migrations/staged_contracts_migration_test.go index 8962a0b2790..53848308f8a 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration_test.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration_test.go @@ -66,11 +66,12 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator) - migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log) + migration.RegisterContractUpdates(stagedContracts) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -106,12 +107,13 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator). + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} - log := zerolog.New(logWriter) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -149,12 +151,13 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator). + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} - log := zerolog.New(logWriter) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -202,12 +205,13 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator). + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} - log := zerolog.New(logWriter) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -248,11 +252,12 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator) - migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log) + migration.RegisterContractUpdates(stagedContracts) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -310,10 +315,11 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator) - logWriter := &logWriter{} log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -355,10 +361,11 @@ func TestStagedContractsMigration(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator) - logWriter := &logWriter{} log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -425,11 +432,12 @@ func TestStagedContractsWithImports(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator) - migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log) + migration.RegisterContractUpdates(stagedContracts) + err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -485,12 +493,13 @@ func TestStagedContractsWithImports(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator). + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} - log := zerolog.New(logWriter) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -563,12 +572,13 @@ func TestStagedContractsWithImports(t *testing.T) { }, } - migration := NewStagedContractsMigration(flow.Emulator). + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(flow.Emulator, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) - logWriter := &logWriter{} - log := zerolog.New(logWriter) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -757,12 +767,13 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { }, } - migration := NewStagedContractsMigration(chainID) + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(chainID, log) migration.RegisterContractUpdates(stagedContracts) migration.WithContractUpdateValidation() - logWriter := &logWriter{} - log := zerolog.New(logWriter) err = migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -830,12 +841,13 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { }, } - migration := NewStagedContractsMigration(chainID) + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(chainID, log) migration.RegisterContractUpdates(stagedContracts) migration.WithContractUpdateValidation() - logWriter := &logWriter{} - log := zerolog.New(logWriter) err = migration.InitMigration(log, nil, 0) require.NoError(t, err) From 96a8ad14f96f6a9128ab52f6ed9c9d600dcf3e6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 28 Feb 2024 17:24:46 -0800 Subject: [PATCH 08/39] fix tests --- .../migrations/staged_contracts_migration.go | 2 +- .../staged_contracts_migration_test.go | 199 +++++++++++------- 2 files changed, 129 insertions(+), 72 deletions(-) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index d09a17a01d9..b70753884ce 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -126,7 +126,7 @@ func (m *StagedContractsMigration) RegisterContractChange(change StagedContract) chain := m.chainID.Chain() if _, err := chain.IndexFromAddress(flow.Address(address)); err != nil { - m.log.Fatal().Msgf( + m.log.Error().Msgf( "invalid contract update: invalid address for chain %s: %s (%s)", m.chainID, address.HexWithPrefix(), diff --git a/cmd/util/ledger/migrations/staged_contracts_migration_test.go b/cmd/util/ledger/migrations/staged_contracts_migration_test.go index 53848308f8a..c505c6432da 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration_test.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration_test.go @@ -42,13 +42,14 @@ func (l *logWriter) Write(bytes []byte) (int, error) { func TestStagedContractsMigration(t *testing.T) { t.Parallel() - address1, err := common.HexToAddress("0x1") - require.NoError(t, err) + chainID := flow.Emulator + addressGenerator := chainID.Chain().NewAddressGenerator() - address2, err := common.HexToAddress("0x2") + address1, err := addressGenerator.NextAddress() require.NoError(t, err) - ctx := context.Background() + address2, err := addressGenerator.NextAddress() + require.NoError(t, err) t.Run("one contract", func(t *testing.T) { t.Parallel() @@ -62,22 +63,24 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(newCode), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log) + migration := NewStagedContractsMigration(chainID, log) migration.RegisterContractUpdates(stagedContracts) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads, err := migration.MigrateAccount(ctx, address1, + payloads, err := migration.MigrateAccount( + context.Background(), + common.Address(address1), []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCode)), + newContractPayload(common.Address(address1), "A", []byte(oldCode)), }, ) require.NoError(t, err) @@ -103,23 +106,25 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(newCode), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log). + migration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads, err := migration.MigrateAccount(ctx, address1, + payloads, err := migration.MigrateAccount( + context.Background(), + common.Address(address1), []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCode)), + newContractPayload(common.Address(address1), "A", []byte(oldCode)), }, ) require.NoError(t, err) @@ -147,23 +152,25 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(newCode), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log). + migration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads, err := migration.MigrateAccount(ctx, address1, + payloads, err := migration.MigrateAccount( + context.Background(), + common.Address(address1), []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCode)), + newContractPayload(common.Address(address1), "A", []byte(oldCode)), }, ) require.NoError(t, err) @@ -194,31 +201,33 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(newCode1), }, - Address: address1, + Address: common.Address(address1), }, { Contract: Contract{ Name: "B", Code: []byte(newCode2), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log). + migration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads, err := migration.MigrateAccount(ctx, address1, + payloads, err := migration.MigrateAccount( + context.Background(), + common.Address(address1), []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCode1)), - newContractPayload(address1, "B", []byte(oldCode2)), + newContractPayload(common.Address(address1), "A", []byte(oldCode1)), + newContractPayload(common.Address(address1), "B", []byte(oldCode2)), }, ) require.NoError(t, err) @@ -248,28 +257,32 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(newCode), }, - Address: address2, + Address: common.Address(address2), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log) + migration := NewStagedContractsMigration(chainID, log) migration.RegisterContractUpdates(stagedContracts) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) payloads := []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCode)), - newContractPayload(address2, "A", []byte(oldCode)), + newContractPayload(common.Address(address1), "A", []byte(oldCode)), + newContractPayload(common.Address(address2), "A", []byte(oldCode)), } // Run migration for account 1, // There are no staged updates for contracts in account 1. // So codes should not have been updated. - payloads, err = migration.MigrateAccount(ctx, address1, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address1), + payloads, + ) require.NoError(t, err) require.Len(t, payloads, 2) require.Equal(t, oldCode, string(payloads[0].Value())) @@ -278,7 +291,11 @@ func TestStagedContractsMigration(t *testing.T) { // Run migration for account 2 // There is one staged update for contracts in account 2. // So one payload/contract-code should be updated, and the other should remain the same. - payloads, err = migration.MigrateAccount(ctx, address2, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address2), + payloads, + ) require.NoError(t, err) require.Len(t, payloads, 2) require.Equal(t, oldCode, string(payloads[0].Value())) @@ -304,30 +321,32 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(update1), }, - Address: address1, + Address: common.Address(address1), }, { Contract: Contract{ Name: "A", Code: []byte(update2), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log) + migration := NewStagedContractsMigration(chainID, log) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) migration.RegisterContractUpdates(stagedContracts) - payloads, err := migration.MigrateAccount(ctx, address1, + payloads, err := migration.MigrateAccount( + context.Background(), + common.Address(address1), []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCode)), + newContractPayload(common.Address(address1), "A", []byte(oldCode)), }, ) require.NoError(t, err) @@ -339,7 +358,7 @@ func TestStagedContractsMigration(t *testing.T) { require.Contains( t, logWriter.logs[0], - `existing staged update found for contract 0x0000000000000001.A. Previous update will be overwritten.`, + `existing staged update found`, ) require.Len(t, payloads, 1) @@ -357,14 +376,14 @@ func TestStagedContractsMigration(t *testing.T) { Name: "A", Code: []byte(newCode), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log) + migration := NewStagedContractsMigration(chainID, log) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) @@ -372,7 +391,11 @@ func TestStagedContractsMigration(t *testing.T) { migration.RegisterContractUpdates(stagedContracts) // NOTE: no payloads - _, err = migration.MigrateAccount(ctx, address1, nil) + _, err = migration.MigrateAccount( + context.Background(), + common.Address(address1), + nil, + ) require.ErrorContains(t, err, "failed to find all contract registers that need to be changed") }) } @@ -380,13 +403,15 @@ func TestStagedContractsMigration(t *testing.T) { func TestStagedContractsWithImports(t *testing.T) { t.Parallel() - address1, err := common.HexToAddress("0x1") - require.NoError(t, err) + chainID := flow.Emulator + + addressGenerator := chainID.Chain().NewAddressGenerator() - address2, err := common.HexToAddress("0x2") + address1, err := addressGenerator.NextAddress() require.NoError(t, err) - ctx := context.Background() + address2, err := addressGenerator.NextAddress() + require.NoError(t, err) t.Run("valid import", func(t *testing.T) { t.Parallel() @@ -421,35 +446,43 @@ func TestStagedContractsWithImports(t *testing.T) { Name: "A", Code: []byte(newCodeA), }, - Address: address1, + Address: common.Address(address1), }, { Contract: Contract{ Name: "B", Code: []byte(newCodeB), }, - Address: address2, + Address: common.Address(address2), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log) + migration := NewStagedContractsMigration(chainID, log) migration.RegisterContractUpdates(stagedContracts) err := migration.InitMigration(log, nil, 0) require.NoError(t, err) payloads := []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCodeA)), - newContractPayload(address2, "B", []byte(oldCodeB)), + newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), + newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), } - payloads, err = migration.MigrateAccount(ctx, address1, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address1), + payloads, + ) require.NoError(t, err) - payloads, err = migration.MigrateAccount(ctx, address2, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address2), + payloads, + ) require.NoError(t, err) err = migration.Close() @@ -489,14 +522,14 @@ func TestStagedContractsWithImports(t *testing.T) { Name: "A", Code: []byte(newCodeA), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log). + migration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) @@ -504,14 +537,22 @@ func TestStagedContractsWithImports(t *testing.T) { require.NoError(t, err) payloads := []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCodeA)), - newContractPayload(address2, "B", []byte(oldCodeB)), + newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), + newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), } - payloads, err = migration.MigrateAccount(ctx, address1, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address1), + payloads, + ) require.NoError(t, err) - payloads, err = migration.MigrateAccount(ctx, address2, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address2), + payloads, + ) require.NoError(t, err) err = migration.Close() @@ -561,21 +602,21 @@ func TestStagedContractsWithImports(t *testing.T) { Name: "A", Code: []byte(newCodeA), }, - Address: address1, + Address: common.Address(address1), }, { Contract: Contract{ Name: "C", Code: []byte(newCodeC), }, - Address: address1, + Address: common.Address(address1), }, } logWriter := &logWriter{} log := zerolog.New(logWriter) - migration := NewStagedContractsMigration(flow.Emulator, log). + migration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() migration.RegisterContractUpdates(stagedContracts) @@ -583,15 +624,23 @@ func TestStagedContractsWithImports(t *testing.T) { require.NoError(t, err) payloads := []*ledger.Payload{ - newContractPayload(address1, "A", []byte(oldCodeA)), - newContractPayload(address2, "B", []byte(oldCodeB)), - newContractPayload(address1, "C", []byte(oldCodeC)), + newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), + newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), + newContractPayload(common.Address(address1), "C", []byte(oldCodeC)), } - payloads, err = migration.MigrateAccount(ctx, address1, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address1), + payloads, + ) require.NoError(t, err) - payloads, err = migration.MigrateAccount(ctx, address2, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address2), + payloads, + ) require.NoError(t, err) err = migration.Close() @@ -716,10 +765,10 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { chainID := flow.Emulator systemContracts := systemcontracts.SystemContractsForChain(chainID) - address, err := common.HexToAddress("0x1") - require.NoError(t, err) + addressGenerator := chainID.Chain().NewAddressGenerator() - ctx := context.Background() + address, err := addressGenerator.NextAddress() + require.NoError(t, err) t.Run("FungibleToken.Vault", func(t *testing.T) { t.Parallel() @@ -763,7 +812,7 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { Name: "A", Code: []byte(newCodeA), }, - Address: address, + Address: common.Address(address), }, } @@ -778,11 +827,15 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { require.NoError(t, err) payloads := []*ledger.Payload{ - newContractPayload(address, "A", []byte(oldCodeA)), + newContractPayload(common.Address(address), "A", []byte(oldCodeA)), newContractPayload(ftAddress, "FungibleToken", []byte(ftContract)), } - payloads, err = migration.MigrateAccount(ctx, address, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address), + payloads, + ) require.NoError(t, err) err = migration.Close() @@ -837,7 +890,7 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { Name: "A", Code: []byte(newCodeA), }, - Address: address, + Address: common.Address(address), }, } @@ -852,11 +905,15 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { require.NoError(t, err) payloads := []*ledger.Payload{ - newContractPayload(address, "A", []byte(oldCodeA)), + newContractPayload(common.Address(address), "A", []byte(oldCodeA)), newContractPayload(otherAddress, "FungibleToken", []byte(ftContract)), } - payloads, err = migration.MigrateAccount(ctx, address, payloads) + payloads, err = migration.MigrateAccount( + context.Background(), + common.Address(address), + payloads, + ) require.NoError(t, err) err = migration.Close() From 1fc2d252b0cdf9e39e10a027a41574f05ba6111a Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Thu, 29 Feb 2024 19:55:43 +0100 Subject: [PATCH 09/39] Add logs to merge --- .../migrations/cadence_values_migration.go | 1 + .../ledger/migrations/deploy_migration.go | 2 ++ cmd/util/ledger/migrations/merge.go | 33 ++++++++++++++++--- 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index c03659c888f..c5c2a0b9ed0 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -163,6 +163,7 @@ func (m *CadenceBaseMigrator) MigrateAccount( return MergeRegisterChanges( migrationRuntime.Snapshot.Payloads, result.WriteSet, + flow.ConvertAddress(address), m.log, ) } diff --git a/cmd/util/ledger/migrations/deploy_migration.go b/cmd/util/ledger/migrations/deploy_migration.go index e68de9ce746..850898d6a47 100644 --- a/cmd/util/ledger/migrations/deploy_migration.go +++ b/cmd/util/ledger/migrations/deploy_migration.go @@ -53,6 +53,8 @@ func NewTransactionBasedMigration( return MergeRegisterChanges( snapshot.Payloads, executionSnapshot.WriteSet, + // we expect more than one address to change here + flow.EmptyAddress, logger, ) } diff --git a/cmd/util/ledger/migrations/merge.go b/cmd/util/ledger/migrations/merge.go index 458ba984ebe..1894022310e 100644 --- a/cmd/util/ledger/migrations/merge.go +++ b/cmd/util/ledger/migrations/merge.go @@ -11,6 +11,7 @@ import ( func MergeRegisterChanges( originalPayloads map[flow.RegisterID]*ledger.Payload, changes map[flow.RegisterID]flow.RegisterValue, + expectedAddress flow.Address, logger zerolog.Logger, ) ([]*ledger.Payload, error) { @@ -18,9 +19,25 @@ func MergeRegisterChanges( // Add all new payloads. for id, value := range changes { + delete(originalPayloads, id) if len(value) == 0 { continue } + + if expectedAddress != flow.EmptyAddress { + ownerAddress := flow.BytesToAddress([]byte(id.Owner)) + + if ownerAddress != expectedAddress { + // something was changed that does not belong to this account. Log it. + logger.Error(). + Str("key", id.String()). + Str("owner_address", ownerAddress.Hex()). + Str("account", expectedAddress.Hex()). + Hex("value", value). + Msg("key is part of the change set, but is for a different account") + } + } + key := convert.RegisterIDToLedgerKey(id) newPayloads = append(newPayloads, ledger.NewPayload(key, value)) } @@ -33,10 +50,18 @@ func MergeRegisterChanges( continue } - // If the payload had changed, then it has been added earlier. - // So skip old payload. - if _, contains := changes[id]; contains { - continue + if expectedAddress != flow.EmptyAddress { + ownerAddress := flow.BytesToAddress([]byte(id.Owner)) + + if ownerAddress != expectedAddress { + // something was changed that does not belong to this account. Log it. + logger.Error(). + Str("key", id.String()). + Str("owner_address", ownerAddress.Hex()). + Str("account", expectedAddress.Hex()). + Hex("value", value.Value()). + Msg("key is part of the original set, but is for a different account") + } } newPayloads = append(newPayloads, value) From 1a966ce8515ae5681a00e85f76e570ee10a1e738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 29 Feb 2024 11:50:55 -0800 Subject: [PATCH 10/39] check deployment only writes to authorizer account --- .../migrations/cadence_values_migration.go | 6 +++++- .../ledger/migrations/deploy_migration.go | 10 ++++++++-- cmd/util/ledger/migrations/merge.go | 19 ++++++++++--------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index da7c8d184e0..b781f061f92 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -160,10 +160,14 @@ func (m *CadenceBaseMigrator) MigrateAccount( } // Merge the changes to the original payloads. + expectedWriteAddress := flow.ConvertAddress(address) + expectedOriginalAddress := flow.ConvertAddress(address) + return MergeRegisterChanges( migrationRuntime.Snapshot.Payloads, result.WriteSet, - flow.ConvertAddress(address), + expectedWriteAddress, + expectedOriginalAddress, m.log, ) } diff --git a/cmd/util/ledger/migrations/deploy_migration.go b/cmd/util/ledger/migrations/deploy_migration.go index 850898d6a47..77289aa3a9e 100644 --- a/cmd/util/ledger/migrations/deploy_migration.go +++ b/cmd/util/ledger/migrations/deploy_migration.go @@ -17,6 +17,7 @@ func NewTransactionBasedMigration( tx *flow.TransactionBody, chainID flow.ChainID, logger zerolog.Logger, + expectedWriteAddress flow.Address, ) ledger.Migration { return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { @@ -53,7 +54,7 @@ func NewTransactionBasedMigration( return MergeRegisterChanges( snapshot.Payloads, executionSnapshot.WriteSet, - // we expect more than one address to change here + expectedWriteAddress, flow.EmptyAddress, logger, ) @@ -81,7 +82,12 @@ func NewDeploymentMigration( AddArgument(jsoncdc.MustEncode(cadence.String(contract.Code))). AddAuthorizer(authorizer) - return NewTransactionBasedMigration(tx, chainID, logger) + return NewTransactionBasedMigration( + tx, + chainID, + logger, + authorizer, + ) } func NewBurnerDeploymentMigration( diff --git a/cmd/util/ledger/migrations/merge.go b/cmd/util/ledger/migrations/merge.go index 1894022310e..52b3de5f478 100644 --- a/cmd/util/ledger/migrations/merge.go +++ b/cmd/util/ledger/migrations/merge.go @@ -11,7 +11,8 @@ import ( func MergeRegisterChanges( originalPayloads map[flow.RegisterID]*ledger.Payload, changes map[flow.RegisterID]flow.RegisterValue, - expectedAddress flow.Address, + expectedChangeAddress flow.Address, + expectedOriginalAddress flow.Address, logger zerolog.Logger, ) ([]*ledger.Payload, error) { @@ -24,15 +25,15 @@ func MergeRegisterChanges( continue } - if expectedAddress != flow.EmptyAddress { + if expectedChangeAddress != flow.EmptyAddress { ownerAddress := flow.BytesToAddress([]byte(id.Owner)) - if ownerAddress != expectedAddress { + if ownerAddress != expectedChangeAddress { // something was changed that does not belong to this account. Log it. logger.Error(). Str("key", id.String()). - Str("owner_address", ownerAddress.Hex()). - Str("account", expectedAddress.Hex()). + Str("actual_address", ownerAddress.Hex()). + Str("expected_address", expectedChangeAddress.Hex()). Hex("value", value). Msg("key is part of the change set, but is for a different account") } @@ -50,15 +51,15 @@ func MergeRegisterChanges( continue } - if expectedAddress != flow.EmptyAddress { + if expectedOriginalAddress != flow.EmptyAddress { ownerAddress := flow.BytesToAddress([]byte(id.Owner)) - if ownerAddress != expectedAddress { + if ownerAddress != expectedOriginalAddress { // something was changed that does not belong to this account. Log it. logger.Error(). Str("key", id.String()). - Str("owner_address", ownerAddress.Hex()). - Str("account", expectedAddress.Hex()). + Str("actual_address", ownerAddress.Hex()). + Str("expected_address", expectedOriginalAddress.Hex()). Hex("value", value.Value()). Msg("key is part of the original set, but is for a different account") } From 46f26d64392e4f832d90cc9f412feb78034b10ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 29 Feb 2024 12:31:22 -0800 Subject: [PATCH 11/39] check migrated payloads are only for migration account --- .../migrations/cadence_values_migration.go | 41 ++- .../migrations/staged_contracts_migration.go | 14 +- .../staged_contracts_migration_test.go | 267 ++++++++++++++---- 3 files changed, 252 insertions(+), 70 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index b781f061f92..81f29cc3e5f 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -8,16 +8,15 @@ import ( "errors" - "github.com/onflow/cadence/migrations/statictypes" - "github.com/onflow/cadence/runtime" - "github.com/rs/zerolog" - "github.com/onflow/cadence/migrations" "github.com/onflow/cadence/migrations/capcons" "github.com/onflow/cadence/migrations/entitlements" + "github.com/onflow/cadence/migrations/statictypes" "github.com/onflow/cadence/migrations/string_normalization" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" + "github.com/rs/zerolog" "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/cmd/util/ledger/util" @@ -118,6 +117,8 @@ func (m *CadenceBaseMigrator) MigrateAccount( oldPayloads []*ledger.Payload, ) ([]*ledger.Payload, error) { + checkPayloadsOwnership(oldPayloads, address, m.log) + // Create all the runtime components we need for the migration migrationRuntime, err := NewMigratorRuntime( @@ -172,6 +173,38 @@ func (m *CadenceBaseMigrator) MigrateAccount( ) } +func checkPayloadsOwnership(payloads []*ledger.Payload, address common.Address, log zerolog.Logger) { + for _, payload := range payloads { + checkPayloadOwnership(payload, address, log) + } +} + +func checkPayloadOwnership(payload *ledger.Payload, address common.Address, log zerolog.Logger) { + registerID, _, err := convert.PayloadToRegister(payload) + if err != nil { + log.Error().Err(err).Msg("failed to convert payload to register") + return + } + + owner := registerID.Owner + + if len(owner) > 0 { + payloadAddress, err := common.BytesToAddress([]byte(owner)) + if err != nil { + log.Error().Err(err).Msgf("failed to convert register owner to address: %x", owner) + return + } + + if payloadAddress != address { + log.Error().Msgf( + "payload address %s does not match expected address %s", + payloadAddress, + address, + ) + } + } +} + // NewCadence1ValueMigrator creates a new CadenceBaseMigrator // which runs some of the Cadence value migrations (static types, entitlements, strings) func NewCadence1ValueMigrator( diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index d4b26269662..8dcf2c573af 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -180,13 +180,15 @@ func (m *StagedContractsMigration) contractUpdatesForAccount( func (m *StagedContractsMigration) MigrateAccount( _ context.Context, address common.Address, - payloads []*ledger.Payload, + oldPayloads []*ledger.Payload, ) ([]*ledger.Payload, error) { + checkPayloadsOwnership(oldPayloads, address, m.log) + contractUpdates, ok := m.contractUpdatesForAccount(address) if !ok { // no contracts to change on this address - return payloads, nil + return oldPayloads, nil } elaborations := map[common.Location]*sema.Elaboration{} @@ -203,12 +205,12 @@ func (m *StagedContractsMigration) MigrateAccount( }, } - mr, err := NewMigratorRuntime(address, payloads, config) + mr, err := NewMigratorRuntime(address, oldPayloads, config) if err != nil { return nil, err } - for payloadIndex, payload := range payloads { + for payloadIndex, payload := range oldPayloads { key, err := payload.Key() if err != nil { return nil, err @@ -250,7 +252,7 @@ func (m *StagedContractsMigration) MigrateAccount( ) } else { // change contract code - payloads[payloadIndex] = ledger.NewPayload( + oldPayloads[payloadIndex] = ledger.NewPayload( key, newCode, ) @@ -270,7 +272,7 @@ func (m *StagedContractsMigration) MigrateAccount( return nil, fmt.Errorf(sb.String()) } - return payloads, nil + return oldPayloads, nil } func (m *StagedContractsMigration) checkContractUpdateValidity( diff --git a/cmd/util/ledger/migrations/staged_contracts_migration_test.go b/cmd/util/ledger/migrations/staged_contracts_migration_test.go index c505c6432da..b43cc23fe49 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration_test.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration_test.go @@ -270,36 +270,36 @@ func TestStagedContractsMigration(t *testing.T) { err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads := []*ledger.Payload{ + payloads1 := []*ledger.Payload{ newContractPayload(common.Address(address1), "A", []byte(oldCode)), + } + payloads2 := []*ledger.Payload{ newContractPayload(common.Address(address2), "A", []byte(oldCode)), } // Run migration for account 1, // There are no staged updates for contracts in account 1. // So codes should not have been updated. - payloads, err = migration.MigrateAccount( + payloads1, err = migration.MigrateAccount( context.Background(), common.Address(address1), - payloads, + payloads1, ) require.NoError(t, err) - require.Len(t, payloads, 2) - require.Equal(t, oldCode, string(payloads[0].Value())) - require.Equal(t, oldCode, string(payloads[1].Value())) + require.Len(t, payloads1, 1) + require.Equal(t, oldCode, string(payloads1[0].Value())) // Run migration for account 2 // There is one staged update for contracts in account 2. // So one payload/contract-code should be updated, and the other should remain the same. - payloads, err = migration.MigrateAccount( + payloads2, err = migration.MigrateAccount( context.Background(), common.Address(address2), - payloads, + payloads2, ) require.NoError(t, err) - require.Len(t, payloads, 2) - require.Equal(t, oldCode, string(payloads[0].Value())) - require.Equal(t, newCode, string(payloads[1].Value())) + require.Len(t, payloads2, 1) + require.Equal(t, newCode, string(payloads2[0].Value())) err = migration.Close() require.NoError(t, err) @@ -466,22 +466,24 @@ func TestStagedContractsWithImports(t *testing.T) { err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads := []*ledger.Payload{ + payloads1 := []*ledger.Payload{ newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), + } + payloads2 := []*ledger.Payload{ newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), } - payloads, err = migration.MigrateAccount( + payloads1, err = migration.MigrateAccount( context.Background(), common.Address(address1), - payloads, + payloads1, ) require.NoError(t, err) - payloads, err = migration.MigrateAccount( + payloads2, err = migration.MigrateAccount( context.Background(), common.Address(address2), - payloads, + payloads2, ) require.NoError(t, err) @@ -490,29 +492,33 @@ func TestStagedContractsWithImports(t *testing.T) { require.Empty(t, logWriter.logs) - require.Len(t, payloads, 2) - require.Equal(t, newCodeA, string(payloads[0].Value())) - require.Equal(t, newCodeB, string(payloads[1].Value())) + require.Len(t, payloads1, 1) + assert.Equal(t, newCodeA, string(payloads1[0].Value())) + + require.Len(t, payloads2, 1) + assert.Equal(t, newCodeB, string(payloads2[0].Value())) }) - t.Run("broken import", func(t *testing.T) { + t.Run("broken import, no update staged", func(t *testing.T) { t.Parallel() - oldCodeA := fmt.Sprintf(` - import B from %s - access(all) contract A {} - `, + oldCodeA := fmt.Sprintf( + ` + import B from %s + access(all) contract A {} + `, address2.HexWithPrefix(), ) oldCodeB := `pub contract B {} // not compatible` - newCodeA := fmt.Sprintf(` - import B from %s - access(all) contract A { - access(all) fun foo(a: B.C) {} - } - `, + newCodeA := fmt.Sprintf( + ` + import B from %s + access(all) contract A { + access(all) fun foo(a: B.C) {} + } + `, address2.HexWithPrefix(), ) @@ -536,22 +542,24 @@ func TestStagedContractsWithImports(t *testing.T) { err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads := []*ledger.Payload{ + payloads1 := []*ledger.Payload{ newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), + } + payloads2 := []*ledger.Payload{ newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), } - payloads, err = migration.MigrateAccount( + payloads1, err = migration.MigrateAccount( context.Background(), common.Address(address1), - payloads, + payloads1, ) require.NoError(t, err) - payloads, err = migration.MigrateAccount( + payloads2, err = migration.MigrateAccount( context.Background(), common.Address(address2), - payloads, + payloads2, ) require.NoError(t, err) @@ -562,13 +570,111 @@ func TestStagedContractsWithImports(t *testing.T) { require.Contains( t, logWriter.logs[0], + "cannot find declaration `B` in `ee82856bf20e2aa6.B`", + ) + + // Payloads should be the old ones + require.Len(t, payloads1, 1) + assert.Equal(t, oldCodeA, string(payloads1[0].Value())) + + require.Len(t, payloads2, 1) + assert.Equal(t, oldCodeB, string(payloads2[0].Value())) + }) + + t.Run("broken import ", func(t *testing.T) { + t.Parallel() + + oldCodeA := fmt.Sprintf( + ` + import B from %s + access(all) contract A {} + `, + address2.HexWithPrefix(), + ) + + oldCodeB := `pub contract B {} // not compatible` + + newCodeA := fmt.Sprintf( + ` + import B from %s + access(all) contract A { + access(all) fun foo(a: B.C) {} + } + `, + address2.HexWithPrefix(), + ) + + newCodeB := `pub contract B {} // not compatible` + + stagedContracts := []StagedContract{ + { + Contract: Contract{ + Name: "A", + Code: []byte(newCodeA), + }, + Address: common.Address(address1), + }, + { + Contract: Contract{ + Name: "B", + Code: []byte(newCodeB), + }, + Address: common.Address(address2), + }, + } + + logWriter := &logWriter{} + log := zerolog.New(logWriter) + + migration := NewStagedContractsMigration(chainID, log). + WithContractUpdateValidation() + migration.RegisterContractUpdates(stagedContracts) + + err := migration.InitMigration(log, nil, 0) + require.NoError(t, err) + + payloads1 := []*ledger.Payload{ + newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), + } + payloads2 := []*ledger.Payload{ + newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), + } + + payloads1, err = migration.MigrateAccount( + context.Background(), + common.Address(address1), + payloads1, + ) + require.NoError(t, err) + + payloads2, err = migration.MigrateAccount( + context.Background(), + common.Address(address2), + payloads2, + ) + require.NoError(t, err) + + err = migration.Close() + require.NoError(t, err) + + require.Len(t, logWriter.logs, 2) + assert.Contains( + t, + logWriter.logs[0], + "cannot find type in this scope: `B`", + ) + assert.Contains( + t, + logWriter.logs[1], "`pub` is no longer a valid access keyword", ) // Payloads should be the old ones - require.Len(t, payloads, 2) - require.Equal(t, oldCodeA, string(payloads[0].Value())) - require.Equal(t, oldCodeB, string(payloads[1].Value())) + require.Len(t, payloads1, 1) + assert.Equal(t, oldCodeA, string(payloads1[0].Value())) + + require.Len(t, payloads2, 1) + assert.Equal(t, oldCodeB, string(payloads2[0].Value())) }) t.Run("broken import in one, valid third contract", func(t *testing.T) { @@ -623,23 +729,26 @@ func TestStagedContractsWithImports(t *testing.T) { err := migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads := []*ledger.Payload{ + payloads1 := []*ledger.Payload{ newContractPayload(common.Address(address1), "A", []byte(oldCodeA)), - newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), newContractPayload(common.Address(address1), "C", []byte(oldCodeC)), } - payloads, err = migration.MigrateAccount( + payloads2 := []*ledger.Payload{ + newContractPayload(common.Address(address2), "B", []byte(oldCodeB)), + } + + payloads1, err = migration.MigrateAccount( context.Background(), common.Address(address1), - payloads, + payloads1, ) require.NoError(t, err) - payloads, err = migration.MigrateAccount( + payloads2, err = migration.MigrateAccount( context.Background(), common.Address(address2), - payloads, + payloads2, ) require.NoError(t, err) @@ -650,17 +759,19 @@ func TestStagedContractsWithImports(t *testing.T) { require.Contains( t, logWriter.logs[0], - "`pub` is no longer a valid access keyword", + "cannot find declaration `B` in `ee82856bf20e2aa6.B`", ) // A and B should be the old ones. // C should be updated. // Type checking failures in unrelated contracts should not // stop other contracts from being migrated. - require.Len(t, payloads, 3) - require.Equal(t, oldCodeA, string(payloads[0].Value())) - require.Equal(t, oldCodeB, string(payloads[1].Value())) - require.Equal(t, newCodeC, string(payloads[2].Value())) + require.Len(t, payloads1, 2) + require.Equal(t, oldCodeA, string(payloads1[0].Value())) + require.Equal(t, newCodeC, string(payloads1[1].Value())) + + require.Len(t, payloads2, 1) + require.Equal(t, oldCodeB, string(payloads2[0].Value())) }) } @@ -814,6 +925,13 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { }, Address: common.Address(address), }, + { + Contract: Contract{ + Name: "FungibleToken", + Code: []byte(ftContract), + }, + Address: ftAddress, + }, } logWriter := &logWriter{} @@ -826,15 +944,24 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { err = migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads := []*ledger.Payload{ + payloads1 := []*ledger.Payload{ newContractPayload(common.Address(address), "A", []byte(oldCodeA)), + } + payloads2 := []*ledger.Payload{ newContractPayload(ftAddress, "FungibleToken", []byte(ftContract)), } - payloads, err = migration.MigrateAccount( + payloads1, err = migration.MigrateAccount( context.Background(), common.Address(address), - payloads, + payloads1, + ) + require.NoError(t, err) + + payloads2, err = migration.MigrateAccount( + context.Background(), + ftAddress, + payloads2, ) require.NoError(t, err) @@ -843,8 +970,11 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { require.Empty(t, logWriter.logs) - require.Len(t, payloads, 2) - require.Equal(t, newCodeA, string(payloads[0].Value())) + require.Len(t, payloads1, 1) + assert.Equal(t, newCodeA, string(payloads1[0].Value())) + + require.Len(t, payloads2, 1) + assert.Equal(t, ftContract, string(payloads2[0].Value())) }) t.Run("other type", func(t *testing.T) { @@ -904,15 +1034,25 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { err = migration.InitMigration(log, nil, 0) require.NoError(t, err) - payloads := []*ledger.Payload{ + payloads1 := []*ledger.Payload{ newContractPayload(common.Address(address), "A", []byte(oldCodeA)), + } + + payloads2 := []*ledger.Payload{ newContractPayload(otherAddress, "FungibleToken", []byte(ftContract)), } - payloads, err = migration.MigrateAccount( + payloads1, err = migration.MigrateAccount( context.Background(), common.Address(address), - payloads, + payloads1, + ) + require.NoError(t, err) + + payloads2, err = migration.MigrateAccount( + context.Background(), + otherAddress, + payloads2, ) require.NoError(t, err) @@ -920,9 +1060,16 @@ func TestStagedContractsWithUpdateValidator(t *testing.T) { require.NoError(t, err) require.Len(t, logWriter.logs, 1) - assert.Contains(t, logWriter.logs[0], "cannot update contract `A`") + assert.Contains(t, + logWriter.logs[0], + "cannot find declaration `FungibleToken` in `0000000000000002.FungibleToken`", + ) + + require.Len(t, payloads1, 1) + assert.Equal(t, oldCodeA, string(payloads1[0].Value())) + + require.Len(t, payloads2, 1) + assert.Equal(t, ftContract, string(payloads2[0].Value())) - require.Len(t, payloads, 2) - require.Equal(t, oldCodeA, string(payloads[0].Value())) }) } From 5c6375d5190d1677718ffcd9c70a552a999ed2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 29 Feb 2024 12:59:38 -0800 Subject: [PATCH 12/39] name all migrations --- .../execution_state_extract.go | 8 +- cmd/util/ledger/migrations/cadence.go | 73 ++++++++++++------- .../migrations/cadence_values_migration.go | 4 +- .../cadence_values_migration_test.go | 10 ++- .../change_contract_code_migration.go | 2 +- 5 files changed, 64 insertions(+), 33 deletions(-) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 3a4a72e2a10..25a2e288bb1 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -382,7 +382,7 @@ func newMigrations( rwf := reporters.NewReportFileWriterFactory(dir, log) - return migrators.NewCadence1Migrations( + namedMigrations := migrators.NewCadence1Migrations( log, rwf, nWorker, @@ -390,4 +390,10 @@ func newMigrations( evmContractChange, stagedContracts, ) + + migrations := make([]ledger.Migration, len(namedMigrations)) + for _, migration := range namedMigrations { + migrations = append(migrations, migration.Migrate) + } + return migrations } diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index ae8aa711d3d..a3e3d228397 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -156,12 +156,17 @@ func fungibleTokenResolverRule( return oldType, newType } +type NamedMigration struct { + Name string + Migrate ledger.Migration +} + func NewCadence1ValueMigrations( log zerolog.Logger, rwf reporters.ReportWriterFactory, nWorker int, chainID flow.ChainID, -) (migrations []ledger.Migration) { +) (migrations []NamedMigration) { // Populated by CadenceLinkValueMigrator, // used by CadenceCapabilityValueMigrator @@ -169,7 +174,7 @@ func NewCadence1ValueMigrations( errorMessageHandler := &errorMessageHandler{} - for _, accountBasedMigration := range []AccountBasedMigration{ + for _, accountBasedMigration := range []*CadenceBaseMigrator{ NewCadence1ValueMigrator( rwf, errorMessageHandler, @@ -189,12 +194,15 @@ func NewCadence1ValueMigrations( } { migrations = append( migrations, - NewAccountBasedMigration( - log, - nWorker, []AccountBasedMigration{ - accountBasedMigration, - }, - ), + NamedMigration{ + Name: accountBasedMigration.name, + Migrate: NewAccountBasedMigration( + log, + nWorker, []AccountBasedMigration{ + accountBasedMigration, + }, + ), + }, ) } @@ -207,35 +215,44 @@ func NewCadence1ContractsMigrations( chainID flow.ChainID, evmContractChange EVMContractChange, stagedContracts []StagedContract, -) []ledger.Migration { +) []NamedMigration { + + systemContractsMigration := NewSystemContractsMigration( + chainID, + log, + SystemContractChangesOptions{ + EVM: evmContractChange, + }, + ) stagedContractsMigration := NewStagedContractsMigration(chainID, log). WithContractUpdateValidation() stagedContractsMigration.RegisterContractUpdates(stagedContracts) - return []ledger.Migration{ - NewAccountBasedMigration( - log, - nWorker, - []AccountBasedMigration{ - NewSystemContactsMigration( - chainID, - log, - SystemContractChangesOptions{ - EVM: evmContractChange, - }, - ), - }, - ), - NewBurnerDeploymentMigration(chainID, log), - NewAccountBasedMigration( + toAccountBasedMigration := func(migration AccountBasedMigration) ledger.Migration { + return NewAccountBasedMigration( log, nWorker, []AccountBasedMigration{ - stagedContractsMigration, + migration, }, - ), + ) + } + + return []NamedMigration{ + { + Name: "system-contracts-update-migration", + Migrate: toAccountBasedMigration(systemContractsMigration), + }, + { + Name: "burner-deployment-migration", + Migrate: NewBurnerDeploymentMigration(chainID, log), + }, + { + Name: "staged-contracts-update-migration", + Migrate: toAccountBasedMigration(stagedContractsMigration), + }, } } @@ -246,7 +263,7 @@ func NewCadence1Migrations( chainID flow.ChainID, evmContractChange EVMContractChange, stagedContracts []StagedContract, -) []ledger.Migration { +) []NamedMigration { return common.Concat( NewCadence1ContractsMigrations( log, diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 81f29cc3e5f..1aba2d79156 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -130,9 +130,11 @@ func (m *CadenceBaseMigrator) MigrateAccount( return nil, fmt.Errorf("failed to create migrator runtime: %w", err) } + storage := migrationRuntime.Storage + migration := migrations.NewStorageMigration( migrationRuntime.Interpreter, - migrationRuntime.Storage, + storage, ) reporter := newValueMigrationReporter(m.reporter, m.log, m.errorMessageHandler) diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 4196572010f..4889a43dbf3 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -87,8 +87,14 @@ func TestCadenceValuesMigration(t *testing.T) { ) for _, migration := range migrations { - payloads, err = migration(payloads) - require.NoError(t, err, "migration failed, logs: %v", logWriter.logs) + payloads, err = migration.Migrate(payloads) + require.NoError( + t, + err, + "migration `%s` failed, logs: %v", + migration.Name, + logWriter.logs, + ) } // Assert the migrated payloads diff --git a/cmd/util/ledger/migrations/change_contract_code_migration.go b/cmd/util/ledger/migrations/change_contract_code_migration.go index 28e01dfb225..de2192cedaa 100644 --- a/cmd/util/ledger/migrations/change_contract_code_migration.go +++ b/cmd/util/ledger/migrations/change_contract_code_migration.go @@ -247,7 +247,7 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp return contractChanges } -func NewSystemContactsMigration( +func NewSystemContractsMigration( chainID flow.ChainID, log zerolog.Logger, options SystemContractChangesOptions, From 050dd9f8f49d0115bdac75667daa8ab6133d376e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 29 Feb 2024 13:58:21 -0800 Subject: [PATCH 13/39] fix allocation --- cmd/util/cmd/execution-state-extract/execution_state_extract.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 25a2e288bb1..56b98e268d3 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -391,7 +391,7 @@ func newMigrations( stagedContracts, ) - migrations := make([]ledger.Migration, len(namedMigrations)) + migrations := make([]ledger.Migration, 0, len(namedMigrations)) for _, migration := range namedMigrations { migrations = append(migrations, migration.Migrate) } From c61927c5d681b1e51b9490bdf6c3a1f41eb54f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 29 Feb 2024 19:13:00 -0800 Subject: [PATCH 14/39] remove unnecessary coverage report --- cmd/util/ledger/migrations/migrator_runtime.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/util/ledger/migrations/migrator_runtime.go b/cmd/util/ledger/migrations/migrator_runtime.go index 8218ee52b8f..074d0428d77 100644 --- a/cmd/util/ledger/migrations/migrator_runtime.go +++ b/cmd/util/ledger/migrations/migrator_runtime.go @@ -80,7 +80,7 @@ func NewMigratorRuntime( ri, runtime.NewCodesAndPrograms(), runtimeStorage, - runtime.NewCoverageReport(), + nil, ) inter, err := interpreter.NewInterpreter( From 0e378d16517b26cb0ccc0f512cf43519df2ac7e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 1 Mar 2024 08:26:51 -0800 Subject: [PATCH 15/39] support multiple expected addresses, do not assume all deployments only write to target account --- .../migrations/cadence_values_migration.go | 9 +++++---- cmd/util/ledger/migrations/deploy_migration.go | 16 ++++++++++------ cmd/util/ledger/migrations/merge.go | 16 ++++++++-------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 1aba2d79156..0c4b3c633fb 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -163,14 +163,15 @@ func (m *CadenceBaseMigrator) MigrateAccount( } // Merge the changes to the original payloads. - expectedWriteAddress := flow.ConvertAddress(address) - expectedOriginalAddress := flow.ConvertAddress(address) + expectedAddresses := map[flow.Address]struct{}{ + flow.ConvertAddress(address): {}, + } return MergeRegisterChanges( migrationRuntime.Snapshot.Payloads, result.WriteSet, - expectedWriteAddress, - expectedOriginalAddress, + expectedAddresses, + expectedAddresses, m.log, ) } diff --git a/cmd/util/ledger/migrations/deploy_migration.go b/cmd/util/ledger/migrations/deploy_migration.go index 77289aa3a9e..a77ef67da6d 100644 --- a/cmd/util/ledger/migrations/deploy_migration.go +++ b/cmd/util/ledger/migrations/deploy_migration.go @@ -17,7 +17,7 @@ func NewTransactionBasedMigration( tx *flow.TransactionBody, chainID flow.ChainID, logger zerolog.Logger, - expectedWriteAddress flow.Address, + expectedWriteAddresses map[flow.Address]struct{}, ) ledger.Migration { return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { @@ -54,8 +54,8 @@ func NewTransactionBasedMigration( return MergeRegisterChanges( snapshot.Payloads, executionSnapshot.WriteSet, - expectedWriteAddress, - flow.EmptyAddress, + expectedWriteAddresses, + nil, logger, ) } @@ -65,6 +65,7 @@ func NewDeploymentMigration( chainID flow.ChainID, contract Contract, authorizer flow.Address, + expectedWriteAddresses map[flow.Address]struct{}, logger zerolog.Logger, ) ledger.Migration { @@ -86,7 +87,7 @@ func NewDeploymentMigration( tx, chainID, logger, - authorizer, + expectedWriteAddresses, ) } @@ -94,14 +95,17 @@ func NewBurnerDeploymentMigration( chainID flow.ChainID, logger zerolog.Logger, ) ledger.Migration { - + address := BurnerAddressForChain(chainID) return NewDeploymentMigration( chainID, Contract{ Name: "Burner", Code: coreContracts.Burner(), }, - BurnerAddressForChain(chainID), + address, + map[flow.Address]struct{}{ + address: {}, + }, logger, ) } diff --git a/cmd/util/ledger/migrations/merge.go b/cmd/util/ledger/migrations/merge.go index 52b3de5f478..81efe0a9fa5 100644 --- a/cmd/util/ledger/migrations/merge.go +++ b/cmd/util/ledger/migrations/merge.go @@ -11,8 +11,8 @@ import ( func MergeRegisterChanges( originalPayloads map[flow.RegisterID]*ledger.Payload, changes map[flow.RegisterID]flow.RegisterValue, - expectedChangeAddress flow.Address, - expectedOriginalAddress flow.Address, + expectedChangeAddresses map[flow.Address]struct{}, + expectedOriginalAddresses map[flow.Address]struct{}, logger zerolog.Logger, ) ([]*ledger.Payload, error) { @@ -25,15 +25,15 @@ func MergeRegisterChanges( continue } - if expectedChangeAddress != flow.EmptyAddress { + if expectedChangeAddresses != nil { ownerAddress := flow.BytesToAddress([]byte(id.Owner)) - if ownerAddress != expectedChangeAddress { + if _, ok := expectedChangeAddresses[ownerAddress]; !ok { // something was changed that does not belong to this account. Log it. logger.Error(). Str("key", id.String()). Str("actual_address", ownerAddress.Hex()). - Str("expected_address", expectedChangeAddress.Hex()). + Interface("expected_addresses", expectedChangeAddresses). Hex("value", value). Msg("key is part of the change set, but is for a different account") } @@ -51,15 +51,15 @@ func MergeRegisterChanges( continue } - if expectedOriginalAddress != flow.EmptyAddress { + if expectedOriginalAddresses != nil { ownerAddress := flow.BytesToAddress([]byte(id.Owner)) - if ownerAddress != expectedOriginalAddress { + if _, ok := expectedOriginalAddresses[ownerAddress]; !ok { // something was changed that does not belong to this account. Log it. logger.Error(). Str("key", id.String()). Str("actual_address", ownerAddress.Hex()). - Str("expected_address", expectedOriginalAddress.Hex()). + Interface("expected_addresses", expectedOriginalAddresses). Hex("value", value.Value()). Msg("key is part of the original set, but is for a different account") } From a50113aebea7188a1b5c42c3a47a5076201e2e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 1 Mar 2024 08:26:57 -0800 Subject: [PATCH 16/39] test deployment --- .../migrations/deploy_migration_test.go | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 cmd/util/ledger/migrations/deploy_migration_test.go diff --git a/cmd/util/ledger/migrations/deploy_migration_test.go b/cmd/util/ledger/migrations/deploy_migration_test.go new file mode 100644 index 00000000000..f013571ec97 --- /dev/null +++ b/cmd/util/ledger/migrations/deploy_migration_test.go @@ -0,0 +1,145 @@ +package migrations + +import ( + "fmt" + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/fvm/storage/snapshot" + "github.com/onflow/flow-go/fvm/systemcontracts" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/utils/unittest" +) + +func newBootstrapPayloads( + chainID flow.ChainID, + bootstrapProcedureOptions ...fvm.BootstrapProcedureOption, +) ([]*ledger.Payload, error) { + + ctx := fvm.NewContext( + fvm.WithChain(chainID.Chain()), + ) + + vm := fvm.NewVirtualMachine() + + storageSnapshot := snapshot.MapStorageSnapshot{} + + bootstrapProcedure := fvm.Bootstrap( + unittest.ServiceAccountPublicKey, + bootstrapProcedureOptions..., + ) + + executionSnapshot, _, err := vm.Run( + ctx, + bootstrapProcedure, + storageSnapshot, + ) + if err != nil { + return nil, err + } + + payloads := make([]*ledger.Payload, 0, len(executionSnapshot.WriteSet)) + + for registerID, registerValue := range executionSnapshot.WriteSet { + payloadKey := convert.RegisterIDToLedgerKey(registerID) + payload := ledger.NewPayload(payloadKey, registerValue) + payloads = append(payloads, payload) + } + + return payloads, nil +} + +func TestDeploy(t *testing.T) { + t.Parallel() + + const chainID = flow.Emulator + + chain := chainID.Chain() + + systemContracts := systemcontracts.SystemContractsForChain(chainID) + serviceAccountAddress := systemContracts.FlowServiceAccount.Address + fungibleTokenAddress := systemContracts.FungibleToken.Address + + targetAddress := serviceAccountAddress + + migration := NewDeploymentMigration( + chainID, + Contract{ + Name: "NewContract", + Code: []byte(fmt.Sprintf( + ` + import FungibleToken from %s + + access(all) + contract NewContract { + + access(all) + fun answer(): Int { + return 42 + } + } + `, + fungibleTokenAddress.HexWithPrefix(), + )), + }, + targetAddress, + map[flow.Address]struct{}{ + targetAddress: {}, + }, + zerolog.New(zerolog.NewTestWriter(t)), + ) + + payloads, err := newBootstrapPayloads(chainID) + require.NoError(t, err) + + newPayloads, err := migration(payloads) + require.NoError(t, err) + + txBody := flow.NewTransactionBody(). + SetScript([]byte(fmt.Sprintf( + ` + import NewContract from %s + + transaction { + execute { + log(NewContract.answer()) + } + } + `, + targetAddress.HexWithPrefix(), + ))) + + vm := fvm.NewVirtualMachine() + + storageSnapshot := snapshot.MapStorageSnapshot{} + + for _, newPayload := range newPayloads { + registerID, registerValue, err := convert.PayloadToRegister(newPayload) + require.NoError(t, err) + + storageSnapshot[registerID] = registerValue + } + + ctx := fvm.NewContext( + fvm.WithChain(chain), + fvm.WithAuthorizationChecksEnabled(false), + fvm.WithSequenceNumberCheckAndIncrementEnabled(false), + fvm.WithCadenceLogging(true), + ) + + _, output, err := vm.Run( + ctx, + fvm.Transaction(txBody, 0), + storageSnapshot, + ) + + require.NoError(t, err) + require.NoError(t, output.Err) + require.Len(t, output.Logs, 1) + require.Equal(t, "42", output.Logs[0]) +} From f19e509e9eb0df40cf83ce61d95b89fc68d2e2e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 1 Mar 2024 15:05:56 -0800 Subject: [PATCH 17/39] update API usage, new parameters location range and transfer elements need to be passed --- fvm/evm/stdlib/contract.go | 321 ++++++++++++++++++++----------------- 1 file changed, 177 insertions(+), 144 deletions(-) diff --git a/fvm/evm/stdlib/contract.go b/fvm/evm/stdlib/contract.go index 9400ab91a66..8df1f5feec7 100644 --- a/fvm/evm/stdlib/contract.go +++ b/fvm/evm/stdlib/contract.go @@ -112,73 +112,85 @@ func (e abiDecodingError) Error() string { func reportABIEncodingComputation( inter *interpreter.Interpreter, + locationRange interpreter.LocationRange, values *interpreter.ArrayValue, evmAddressTypeID common.TypeID, reportComputation func(intensity uint), ) { - values.Iterate(inter, func(element interpreter.Value) (resume bool) { - switch value := element.(type) { - case *interpreter.StringValue: - // Dynamic variables, such as strings, are encoded - // in 2+ chunks of 32 bytes. The first chunk contains - // the index where information for the string begin, - // the second chunk contains the number of bytes the - // string occupies, and the third chunk contains the - // value of the string itself. - computation := uint(2 * abiEncodingByteSize) - stringLength := len(value.Str) - chunks := math.Ceil(float64(stringLength) / float64(abiEncodingByteSize)) - computation += uint(chunks * abiEncodingByteSize) - reportComputation(computation) - - case interpreter.BoolValue, - interpreter.UInt8Value, - interpreter.UInt16Value, - interpreter.UInt32Value, - interpreter.UInt64Value, - interpreter.UInt128Value, - interpreter.UInt256Value, - interpreter.Int8Value, - interpreter.Int16Value, - interpreter.Int32Value, - interpreter.Int64Value, - interpreter.Int128Value, - interpreter.Int256Value: - - // Numeric and bool variables are also static variables - // with a fixed size of 32 bytes. - reportComputation(abiEncodingByteSize) - - case *interpreter.CompositeValue: - if value.TypeID() == evmAddressTypeID { - // EVM addresses are static variables with a fixed - // size of 32 bytes. + values.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + switch value := element.(type) { + case *interpreter.StringValue: + // Dynamic variables, such as strings, are encoded + // in 2+ chunks of 32 bytes. The first chunk contains + // the index where information for the string begin, + // the second chunk contains the number of bytes the + // string occupies, and the third chunk contains the + // value of the string itself. + computation := uint(2 * abiEncodingByteSize) + stringLength := len(value.Str) + chunks := math.Ceil(float64(stringLength) / float64(abiEncodingByteSize)) + computation += uint(chunks * abiEncodingByteSize) + reportComputation(computation) + + case interpreter.BoolValue, + interpreter.UInt8Value, + interpreter.UInt16Value, + interpreter.UInt32Value, + interpreter.UInt64Value, + interpreter.UInt128Value, + interpreter.UInt256Value, + interpreter.Int8Value, + interpreter.Int16Value, + interpreter.Int32Value, + interpreter.Int64Value, + interpreter.Int128Value, + interpreter.Int256Value: + + // Numeric and bool variables are also static variables + // with a fixed size of 32 bytes. reportComputation(abiEncodingByteSize) - } else { + + case *interpreter.CompositeValue: + if value.TypeID() == evmAddressTypeID { + // EVM addresses are static variables with a fixed + // size of 32 bytes. + reportComputation(abiEncodingByteSize) + } else { + panic(abiEncodingError{ + Type: value.StaticType(inter), + }) + } + case *interpreter.ArrayValue: + // Dynamic variables, such as arrays & slices, are encoded + // in 2+ chunks of 32 bytes. The first chunk contains + // the index where information for the array begin, + // the second chunk contains the number of bytes the + // array occupies, and the third chunk contains the + // values of the array itself. + computation := uint(2 * abiEncodingByteSize) + reportComputation(computation) + reportABIEncodingComputation( + inter, + locationRange, + value, + evmAddressTypeID, + reportComputation, + ) + + default: panic(abiEncodingError{ - Type: value.StaticType(inter), + Type: element.StaticType(inter), }) } - case *interpreter.ArrayValue: - // Dynamic variables, such as arrays & slices, are encoded - // in 2+ chunks of 32 bytes. The first chunk contains - // the index where information for the array begin, - // the second chunk contains the number of bytes the - // array occupies, and the third chunk contains the - // values of the array itself. - computation := uint(2 * abiEncodingByteSize) - reportComputation(computation) - reportABIEncodingComputation(inter, value, evmAddressTypeID, reportComputation) - - default: - panic(abiEncodingError{ - Type: element.StaticType(inter), - }) - } - // continue iteration - return true - }) + // continue iteration + return true + }, + false, + locationRange, + ) } // EVM.encodeABI @@ -221,6 +233,7 @@ func newInternalEVMTypeEncodeABIFunction( reportABIEncodingComputation( inter, + locationRange, valuesArray, evmAddressTypeID, func(intensity uint) { @@ -233,24 +246,29 @@ func newInternalEVMTypeEncodeABIFunction( values := make([]any, 0, size) arguments := make(gethABI.Arguments, 0, size) - valuesArray.Iterate(inter, func(element interpreter.Value) (resume bool) { - value, ty, err := encodeABI( - inter, - locationRange, - element, - element.StaticType(inter), - evmAddressTypeID, - ) - if err != nil { - panic(err) - } - - values = append(values, value) - arguments = append(arguments, gethABI.Argument{Type: ty}) - - // continue iteration - return true - }) + valuesArray.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + value, ty, err := encodeABI( + inter, + locationRange, + element, + element.StaticType(inter), + evmAddressTypeID, + ) + if err != nil { + panic(err) + } + + values = append(values, value) + arguments = append(arguments, gethABI.Argument{Type: ty}) + + // continue iteration + return true + }, + false, + locationRange, + ) encodedValues, err := arguments.Pack(values...) if err != nil { @@ -542,26 +560,31 @@ func encodeABI( } var index int - value.Iterate(inter, func(element interpreter.Value) (resume bool) { + value.Iterate( + inter, + func(element interpreter.Value) (resume bool) { - arrayElement, _, err := encodeABI( - inter, - locationRange, - element, - element.StaticType(inter), - evmAddressTypeID, - ) - if err != nil { - panic(err) - } + arrayElement, _, err := encodeABI( + inter, + locationRange, + element, + element.StaticType(inter), + evmAddressTypeID, + ) + if err != nil { + panic(err) + } - result.Index(index).Set(reflect.ValueOf(arrayElement)) + result.Index(index).Set(reflect.ValueOf(arrayElement)) - index++ + index++ - // continue iteration - return true - }) + // continue iteration + return true + }, + false, + locationRange, + ) return result.Interface(), arrayGethABIType, nil } @@ -631,31 +654,36 @@ func newInternalEVMTypeDecodeABIFunction( } var arguments gethABI.Arguments - typesArray.Iterate(inter, func(element interpreter.Value) (resume bool) { - typeValue, ok := element.(interpreter.TypeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - staticType := typeValue.Type - - gethABITy, ok := gethABIType(staticType, evmAddressTypeID) - if !ok { - panic(abiDecodingError{ - Type: staticType, - }) - } - - arguments = append( - arguments, - gethABI.Argument{ - Type: gethABITy, - }, - ) - - // continue iteration - return true - }) + typesArray.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + typeValue, ok := element.(interpreter.TypeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + staticType := typeValue.Type + + gethABITy, ok := gethABIType(staticType, evmAddressTypeID) + if !ok { + panic(abiDecodingError{ + Type: staticType, + }) + } + + arguments = append( + arguments, + gethABI.Argument{ + Type: gethABITy, + }, + ) + + // continue iteration + return true + }, + false, + locationRange, + ) decodedValues, err := arguments.Unpack(data) if err != nil { @@ -665,33 +693,38 @@ func newInternalEVMTypeDecodeABIFunction( var index int values := make([]interpreter.Value, 0, len(decodedValues)) - typesArray.Iterate(inter, func(element interpreter.Value) (resume bool) { - typeValue, ok := element.(interpreter.TypeValue) - if !ok { - panic(errors.NewUnreachableError()) - } - - staticType := typeValue.Type - - value, err := decodeABI( - inter, - locationRange, - decodedValues[index], - staticType, - location, - evmAddressTypeID, - ) - if err != nil { - panic(err) - } - - index++ - - values = append(values, value) - - // continue iteration - return true - }) + typesArray.Iterate( + inter, + func(element interpreter.Value) (resume bool) { + typeValue, ok := element.(interpreter.TypeValue) + if !ok { + panic(errors.NewUnreachableError()) + } + + staticType := typeValue.Type + + value, err := decodeABI( + inter, + locationRange, + decodedValues[index], + staticType, + location, + evmAddressTypeID, + ) + if err != nil { + panic(err) + } + + index++ + + values = append(values, value) + + // continue iteration + return true + }, + false, + locationRange, + ) arrayType := interpreter.NewVariableSizedStaticType( inter, From 947ebee9506e67de2d1b72acda531997e773921d Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:38:03 -0600 Subject: [PATCH 18/39] Add migration diff util to compare Cadence values This commit adds two flags to execution-state-extract cmd: * --diff to compare Cadence values and log diff results * --log-verbose-diff to log entire Cadence values Migration diff is output as JSON. The primary use case is for testing and debugging migrations. --- cmd/util/cmd/execution-state-extract/cmd.go | 28 +- .../execution_state_extract.go | 12 + .../execution_state_extract_test.go | 2 + cmd/util/ledger/migrations/cadence.go | 8 + .../ledger/migrations/cadence_value_diff.go | 745 ++++++++++++++++++ .../migrations/cadence_value_diff_test.go | 655 +++++++++++++++ .../migrations/cadence_values_migration.go | 39 +- .../cadence_values_migration_test.go | 2 + 8 files changed, 1486 insertions(+), 5 deletions(-) create mode 100644 cmd/util/ledger/migrations/cadence_value_diff.go create mode 100644 cmd/util/ledger/migrations/cadence_value_diff_test.go diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index 4e9987401af..ded0f26bd02 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -34,6 +34,8 @@ var ( flagValidateMigration bool flagAllowPartialStateFromPayloads bool flagLogVerboseValidationError bool + flagDiffMigration bool + flagLogVerboseDiff bool flagStagedContractsFile string flagInputPayloadFileName string flagOutputPayloadFileName string @@ -81,6 +83,12 @@ func init() { Cmd.Flags().BoolVar(&flagLogVerboseValidationError, "log-verbose-validation-error", false, "log entire Cadence values on validation error (atree migration)") + Cmd.Flags().BoolVar(&flagDiffMigration, "diff", false, + "compare Cadence values and log diff (migration)") + + Cmd.Flags().BoolVar(&flagLogVerboseDiff, "log-verbose-diff", false, + "log entire Cadence values on diff (requires --diff flag)") + Cmd.Flags().StringVar(&flagStagedContractsFile, "staged-contracts", "", "Staged contracts CSV file") @@ -139,6 +147,10 @@ func run(*cobra.Command, []string) { log.Fatal().Msg("--extract-payloads-by-address requires --output-payload-filename to be specified") } + if flagValidateMigration && flagDiffMigration { + log.Fatal().Msg("Both --validate and --diff are enabled, please specify only one (or none) of these") + } + if len(flagBlockHash) > 0 { blockID, err := flow.HexStringToIdentifier(flagBlockHash) if err != nil { @@ -241,11 +253,19 @@ func run(*cobra.Command, []string) { } if flagValidateMigration { - log.Warn().Msgf("atree migration validation flag is enabled and will increase duration of migration") + log.Warn().Msgf("--validate flag is enabled and will increase duration of migration") } if flagLogVerboseValidationError { - log.Warn().Msgf("atree migration has verbose validation error logging enabled which may increase size of log") + log.Warn().Msgf("--log-verbose-validation-error flag is enabled which may increase size of log") + } + + if flagDiffMigration { + log.Warn().Msgf("--diff flag is enabled and will increase duration of migration") + } + + if flagLogVerboseDiff { + log.Warn().Msgf("--log-verbose-diff flag is enabled which may increase size of log") } var inputMsg string @@ -299,6 +319,8 @@ func run(*cobra.Command, []string) { flagOutputDir, flagNWorker, !flagNoMigration, + flagDiffMigration, + flagLogVerboseDiff, chainID, evmContractChange, stagedContracts, @@ -314,6 +336,8 @@ func run(*cobra.Command, []string) { flagOutputDir, flagNWorker, !flagNoMigration, + flagDiffMigration, + flagLogVerboseDiff, chainID, evmContractChange, stagedContracts, diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 3a4a72e2a10..6ca825b08bd 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -37,6 +37,8 @@ func extractExecutionState( outputDir string, nWorker int, // number of concurrent worker to migration payloads runMigrations bool, + diffMigrations bool, + logVerboseDiff bool, chainID flow.ChainID, evmContractChange migrators.EVMContractChange, stagedContracts []migrators.StagedContract, @@ -97,6 +99,8 @@ func extractExecutionState( dir, nWorker, runMigrations, + diffMigrations, + logVerboseDiff, chainID, evmContractChange, stagedContracts, @@ -204,6 +208,8 @@ func extractExecutionStateFromPayloads( outputDir string, nWorker int, // number of concurrent worker to migation payloads runMigrations bool, + diffMigrations bool, + logVerboseDiff bool, chainID flow.ChainID, evmContractChange migrators.EVMContractChange, stagedContracts []migrators.StagedContract, @@ -224,6 +230,8 @@ func extractExecutionStateFromPayloads( dir, nWorker, runMigrations, + diffMigrations, + logVerboseDiff, chainID, evmContractChange, stagedContracts, @@ -372,6 +380,8 @@ func newMigrations( dir string, nWorker int, runMigrations bool, + diffMigrations bool, + logVerboseDiff bool, chainID flow.ChainID, evmContractChange migrators.EVMContractChange, stagedContracts []migrators.StagedContract, @@ -387,6 +397,8 @@ func newMigrations( rwf, nWorker, chainID, + diffMigrations, + logVerboseDiff, evmContractChange, stagedContracts, ) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go index 93958be5eef..5ffb56672d6 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go @@ -73,6 +73,8 @@ func TestExtractExecutionState(t *testing.T) { outdir, 10, false, + false, + false, flow.Emulator, // TODO: migrations.EVMContractChangeNone, diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index ae8aa711d3d..110143ed2ee 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -161,6 +161,8 @@ func NewCadence1ValueMigrations( rwf reporters.ReportWriterFactory, nWorker int, chainID flow.ChainID, + diffMigrations bool, + logVerboseDiff bool, ) (migrations []ledger.Migration) { // Populated by CadenceLinkValueMigrator, @@ -172,6 +174,8 @@ func NewCadence1ValueMigrations( for _, accountBasedMigration := range []AccountBasedMigration{ NewCadence1ValueMigrator( rwf, + diffMigrations, + logVerboseDiff, errorMessageHandler, NewCadence1CompositeStaticTypeConverter(chainID), NewCadence1InterfaceStaticTypeConverter(chainID), @@ -244,6 +248,8 @@ func NewCadence1Migrations( rwf reporters.ReportWriterFactory, nWorker int, chainID flow.ChainID, + diffMigrations bool, + logVerboseDiff bool, evmContractChange EVMContractChange, stagedContracts []StagedContract, ) []ledger.Migration { @@ -260,6 +266,8 @@ func NewCadence1Migrations( rwf, nWorker, chainID, + diffMigrations, + logVerboseDiff, ), ) } diff --git a/cmd/util/ledger/migrations/cadence_value_diff.go b/cmd/util/ledger/migrations/cadence_value_diff.go new file mode 100644 index 00000000000..39441adf205 --- /dev/null +++ b/cmd/util/ledger/migrations/cadence_value_diff.go @@ -0,0 +1,745 @@ +package migrations + +import ( + "fmt" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/ledger" +) + +type diffKind int + +const ( + storageMapExistDiffKind diffKind = iota // Storage map only exists in one state + storageMapKeyDiffKind // Storage map keys are different + storageMapValueDiffKind // Storage map values are different (only with verbose logging) + cadenceValueDiffKind // Cadence values are different + cadenceValueTypeDiffKind // Cadence value types are different + cadenceValueStaticTypeDiffKind // Cadence value static types are different +) + +var diffKindString = map[diffKind]string{ + storageMapExistDiffKind: "storage_map_exist_diff", + storageMapKeyDiffKind: "storage_map_key_diff", + storageMapValueDiffKind: "storage_map_value_diff", + cadenceValueDiffKind: "cadence_value_diff", + cadenceValueTypeDiffKind: "cadence_value_type_diff", + cadenceValueStaticTypeDiffKind: "cadence_value_static_type_diff", +} + +type diffErrorKind int + +const ( + storageMapKeyNotImplementingStorageMapKeyDiffErrorKind diffErrorKind = iota + cadenceValueNotImplementEquatableValueDiffErrorKind +) + +var diffErrorKindString = map[diffErrorKind]string{ + storageMapKeyNotImplementingStorageMapKeyDiffErrorKind: "error_storage_map_key_not_implemeting_StorageMapKey", + cadenceValueNotImplementEquatableValueDiffErrorKind: "error_cadence_value_not_implementing_EquatableValue", +} + +type diffProblem struct { + Address string + Domain string + Kind string + Msg string + Trace string `json:",omitempty"` +} + +type difference struct { + Address string + Domain string + Kind string + Msg string + Trace string `json:",omitempty"` + OldValue string `json:",omitempty"` + NewValue string `json:",omitempty"` +} + +type CadenceValueDiffReporter struct { + address common.Address + reportWriter reporters.ReportWriter + verboseLogging bool +} + +func NewCadenceValueDiffReporter( + address common.Address, + rw reporters.ReportWriter, + verboseLogging bool, +) *CadenceValueDiffReporter { + return &CadenceValueDiffReporter{ + address: address, + reportWriter: rw, + verboseLogging: verboseLogging, + } +} + +func (dr *CadenceValueDiffReporter) DiffStates(oldPayloads, newPayloads []*ledger.Payload, domains []string) error { + // Create all the runtime components we need for comparing Cadence values. + oldRuntime, err := newReadonlyStorageRuntime(oldPayloads) + if err != nil { + return fmt.Errorf("failed to create runtime with old state payloads: %w", err) + } + + newRuntime, err := newReadonlyStorageRuntime(newPayloads) + if err != nil { + return fmt.Errorf("failed to create runtime with new state payloads: %w", err) + } + + // Iterate through all domains and compare cadence values. + for _, domain := range domains { + err := dr.diffStorageDomain(oldRuntime, newRuntime, domain) + if err != nil { + return err + } + } + + return nil +} + +func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *readonlyStorageRuntime, domain string) error { + + oldStorageMap := oldRuntime.Storage.GetStorageMap(dr.address, domain, false) + + newStorageMap := newRuntime.Storage.GetStorageMap(dr.address, domain, false) + + if oldStorageMap == nil && newStorageMap == nil { + // No storage maps for this domain. + return nil + } + + if oldStorageMap == nil && newStorageMap != nil { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[storageMapExistDiffKind], + Msg: fmt.Sprintf( + "old storage map doesn't exist, new storage map has %d elements with keys %v", + newStorageMap.Count(), + getStorageMapKeys(newStorageMap), + ), + }) + + return nil + } + + if oldStorageMap != nil && newStorageMap == nil { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[storageMapExistDiffKind], + Msg: fmt.Sprintf( + "new storage map doesn't exist, old storage map has %d elements with keys %v", + oldStorageMap.Count(), + getStorageMapKeys(oldStorageMap), + ), + }) + + return nil + } + + oldKeys := getStorageMapKeys(oldStorageMap) + newKeys := getStorageMapKeys(newStorageMap) + + onlyOldKeys, onlyNewKeys, sharedKeys := diff(oldKeys, newKeys) + + // Log keys only present in old storage map + if len(onlyOldKeys) > 0 { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[storageMapKeyDiffKind], + Msg: fmt.Sprintf( + "old storage map has %d elements with keys %v, that are not present in new storge map", + len(onlyOldKeys), + onlyOldKeys, + ), + }) + } + + // Log keys only present in new storage map + if len(onlyNewKeys) > 0 { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[storageMapKeyDiffKind], + Msg: fmt.Sprintf( + "new storage map has %d elements with keys %v, that are not present in old storge map", + len(onlyNewKeys), + onlyNewKeys, + ), + }) + } + + // Compare elements present in both storage maps + for _, key := range sharedKeys { + + trace := fmt.Sprintf("%s[%v]", domain, key) + + var mapKey interpreter.StorageMapKey + + switch key := key.(type) { + case interpreter.StringAtreeValue: + mapKey = interpreter.StringStorageMapKey(key) + + case interpreter.StringStorageMapKey: + mapKey = key + + case interpreter.Uint64StorageMapKey: + mapKey = key + + default: + dr.reportWriter.Write( + diffProblem{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffErrorKindString[storageMapKeyNotImplementingStorageMapKeyDiffErrorKind], + Trace: trace, + Msg: fmt.Sprintf( + "invalid storage map key %v (%T), expected interpreter.StorageMapKey", + key, + key, + ), + }) + continue + } + + oldValue := oldStorageMap.ReadValue(nopMemoryGauge, mapKey) + + newValue := newStorageMap.ReadValue(nopMemoryGauge, mapKey) + + hasDifference := dr.diffValues( + oldRuntime.Interpreter, + oldValue, + newRuntime.Interpreter, + newValue, + domain, + trace, + ) + if hasDifference { + if dr.verboseLogging { + // Log potentially large values at top level only when verbose logging is enabled. + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[storageMapValueDiffKind], + Msg: "storage map elements are different", + Trace: trace, + OldValue: oldValue.String(), + NewValue: newValue.String(), + }) + } + } + + } + + return nil +} + +func (dr *CadenceValueDiffReporter) diffValues( + vInterpreter *interpreter.Interpreter, + v interpreter.Value, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, + domain string, + trace string, +) (hasDifference bool) { + switch v := v.(type) { + case *interpreter.ArrayValue: + return dr.diffCadenceArrayValue(vInterpreter, v, otherInterpreter, other, domain, trace) + + case *interpreter.CompositeValue: + return dr.diffCadenceCompositeValue(vInterpreter, v, otherInterpreter, other, domain, trace) + + case *interpreter.DictionaryValue: + return dr.diffCadenceDictionaryValue(vInterpreter, v, otherInterpreter, other, domain, trace) + + case *interpreter.SomeValue: + return dr.diffCadenceSomeValue(vInterpreter, v, otherInterpreter, other, domain, trace) + + default: + oldValue, ok := v.(interpreter.EquatableValue) + if !ok { + dr.reportWriter.Write( + diffProblem{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffErrorKindString[cadenceValueNotImplementEquatableValueDiffErrorKind], + Trace: trace, + Msg: fmt.Sprintf("old value doesn't implement interpreter.EquatableValue: %s (%T)", oldValue.String(), oldValue), + }) + return true + } + + if !oldValue.Equal(nil, interpreter.EmptyLocationRange, other) { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Msg: fmt.Sprintf("values differ: %T vs %T", oldValue, other), + Trace: trace, + OldValue: oldValue.String(), + NewValue: other.String(), + }) + return true + } + } + + return false +} + +func (dr *CadenceValueDiffReporter) diffCadenceSomeValue( + vInterpreter *interpreter.Interpreter, + v *interpreter.SomeValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, + domain string, + trace string, +) (hasDifference bool) { + otherSome, ok := other.(*interpreter.SomeValue) + if !ok { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("types differ: %T != %T", v, other), + }) + return true + } + + innerValue := v.InnerValue(vInterpreter, interpreter.EmptyLocationRange) + + otherInnerValue := otherSome.InnerValue(otherInterpreter, interpreter.EmptyLocationRange) + + return dr.diffValues(vInterpreter, innerValue, otherInterpreter, otherInnerValue, domain, trace) +} + +func (dr *CadenceValueDiffReporter) diffCadenceArrayValue( + vInterpreter *interpreter.Interpreter, + v *interpreter.ArrayValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, + domain string, + trace string, +) (hasDifference bool) { + otherArray, ok := other.(*interpreter.ArrayValue) + if !ok { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("types differ: %T != %T", v, other), + }) + return true + } + + if v.Type == nil && otherArray.Type != nil { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueStaticTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("array static types differ: nil != %s", otherArray.Type), + }) + } + + if v.Type != nil && otherArray.Type == nil { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueStaticTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("array static types differ: %s != nil", v.Type), + }) + } + + if v.Type != nil && otherArray.Type != nil && !v.Type.Equal(otherArray.Type) { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueStaticTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("array static types differ: %s != %s", v.Type, otherArray.Type), + }) + } + + count := v.Count() + if count != otherArray.Count() { + hasDifference = true + + d := difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Trace: trace, + Msg: fmt.Sprintf("array counts differ: %d != %d", count, otherArray.Count()), + } + + if dr.verboseLogging { + d.OldValue = v.String() + d.NewValue = other.String() + } + + dr.reportWriter.Write(d) + } + + // Compare array elements + for i := 0; i < min(count, otherArray.Count()); i++ { + element := v.Get(vInterpreter, interpreter.EmptyLocationRange, i) + otherElement := otherArray.Get(otherInterpreter, interpreter.EmptyLocationRange, i) + + elementTrace := fmt.Sprintf("%s[%d]", trace, i) + elementHasDifference := dr.diffValues(vInterpreter, element, otherInterpreter, otherElement, domain, elementTrace) + if elementHasDifference { + hasDifference = true + } + } + + return hasDifference +} + +func (dr *CadenceValueDiffReporter) diffCadenceCompositeValue( + vInterpreter *interpreter.Interpreter, + v *interpreter.CompositeValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, + domain string, + trace string, +) (hasDifference bool) { + otherComposite, ok := other.(*interpreter.CompositeValue) + if !ok { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("types differ: %T != %T", v, other), + }) + return true + } + + if !v.StaticType(vInterpreter).Equal(otherComposite.StaticType(otherInterpreter)) { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueStaticTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "composite static types differ: %s != %s", + v.StaticType(vInterpreter), + otherComposite.StaticType(otherInterpreter)), + }) + } + + if v.Kind != otherComposite.Kind { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueStaticTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "composite kinds differ: %d != %d", + v.Kind, + otherComposite.Kind, + ), + }) + } + + oldFieldNames := make([]string, 0, v.FieldCount()) + v.ForEachFieldName(func(fieldName string) bool { + oldFieldNames = append(oldFieldNames, fieldName) + return true + }) + + newFieldNames := make([]string, 0, otherComposite.FieldCount()) + otherComposite.ForEachFieldName(func(fieldName string) bool { + newFieldNames = append(newFieldNames, fieldName) + return true + }) + + onlyOldFieldNames, onlyNewFieldNames, sharedFieldNames := diff(oldFieldNames, newFieldNames) + + // Log field names only present in old composite value + if len(onlyOldFieldNames) > 0 { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "old composite value has %d fields with keys %v, that are not present in new composite value", + len(onlyOldFieldNames), + onlyOldFieldNames, + ), + }) + } + + // Log field names only present in new composite value + if len(onlyNewFieldNames) > 0 { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "new composite value has %d fields with keys %v, that are not present in old composite value", + len(onlyNewFieldNames), + onlyNewFieldNames, + ), + }) + } + + // Compare fields in both composite values + for _, fieldName := range sharedFieldNames { + fieldValue := v.GetField(vInterpreter, interpreter.EmptyLocationRange, fieldName) + otherFieldValue := otherComposite.GetField(otherInterpreter, interpreter.EmptyLocationRange, fieldName) + + fieldTrace := fmt.Sprintf("%s.%s", trace, fieldName) + fieldHasDifference := dr.diffValues(vInterpreter, fieldValue, otherInterpreter, otherFieldValue, domain, fieldTrace) + if fieldHasDifference { + hasDifference = true + } + } + + return hasDifference +} + +func (dr *CadenceValueDiffReporter) diffCadenceDictionaryValue( + vInterpreter *interpreter.Interpreter, + v *interpreter.DictionaryValue, + otherInterpreter *interpreter.Interpreter, + other interpreter.Value, + domain string, + trace string, +) (hasDifference bool) { + otherDictionary, ok := other.(*interpreter.DictionaryValue) + if !ok { + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf("types differ: %T != %T", v, other), + }) + return true + } + + if !v.Type.Equal(otherDictionary.Type) { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueStaticTypeDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "dict static types differ: %s != %s", + v.Type, + otherDictionary.Type), + }) + } + + oldKeys := make([]interpreter.Value, 0, v.Count()) + v.IterateKeys(vInterpreter, func(key interpreter.Value) (resume bool) { + oldKeys = append(oldKeys, key) + return true + }) + + newKeys := make([]interpreter.Value, 0, otherDictionary.Count()) + otherDictionary.IterateKeys(otherInterpreter, func(key interpreter.Value) (resume bool) { + newKeys = append(newKeys, key) + return true + }) + + onlyOldKeys, onlyNewKeys, sharedKeys := diffCadenceValues(oldKeys, newKeys) + + // Log keys only present in old dict value + if len(onlyOldKeys) > 0 { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "old dict value has %d elements with keys %v, that are not present in new dict value", + len(onlyOldKeys), + onlyOldKeys, + ), + }) + } + + // Log field names only present in new composite value + if len(onlyNewKeys) > 0 { + hasDifference = true + + dr.reportWriter.Write( + difference{ + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Trace: trace, + Msg: fmt.Sprintf( + "new dict value has %d elements with keys %v, that are not present in old dict value", + len(onlyNewKeys), + onlyNewKeys, + ), + }) + } + + // Compare elements in both dict values + for _, key := range sharedKeys { + valueTrace := fmt.Sprintf("%s[%v]", trace, key) + + oldValue, _ := v.Get(vInterpreter, interpreter.EmptyLocationRange, key) + + newValue, _ := otherDictionary.Get(otherInterpreter, interpreter.EmptyLocationRange, key) + + elementHasDifference := dr.diffValues(vInterpreter, oldValue, otherInterpreter, newValue, domain, valueTrace) + if elementHasDifference { + hasDifference = true + } + } + + return hasDifference +} + +func getStorageMapKeys(storageMap *interpreter.StorageMap) []any { + keys := make([]any, 0, storageMap.Count()) + + iter := storageMap.Iterator(nil) + for { + key := iter.NextKey() + if key == nil { + break + } + keys = append(keys, key) + } + + return keys +} + +func diff[T comparable](old, new []T) (onlyOld, onlyNew, shared []T) { + onlyOld = make([]T, 0, len(old)) + onlyNew = make([]T, 0, len(new)) + shared = make([]T, 0, min(len(old), len(new))) + + sharedNew := make([]bool, len(new)) + + for _, o := range old { + found := false + + for i, n := range new { + if o == n { + shared = append(shared, o) + found = true + sharedNew[i] = true + break + } + } + + if !found { + onlyOld = append(onlyOld, o) + } + } + + for i, shared := range sharedNew { + if !shared { + onlyNew = append(onlyNew, new[i]) + } + } + + return +} + +func diffCadenceValues(old, new []interpreter.Value) (onlyOld, onlyNew, shared []interpreter.Value) { + onlyOld = make([]interpreter.Value, 0, len(old)) + onlyNew = make([]interpreter.Value, 0, len(new)) + shared = make([]interpreter.Value, 0, min(len(old), len(new))) + + sharedNew := make([]bool, len(new)) + + for _, o := range old { + found := false + + for i, n := range new { + foundShared := false + + if ev, ok := o.(interpreter.EquatableValue); ok { + if ev.Equal(nil, interpreter.EmptyLocationRange, n) { + foundShared = true + } + } else { + if o == n { + foundShared = true + } + } + + if foundShared { + shared = append(shared, o) + found = true + sharedNew[i] = true + break + } + } + + if !found { + onlyOld = append(onlyOld, o) + } + } + + for i, shared := range sharedNew { + if !shared { + onlyNew = append(onlyNew, new[i]) + } + } + + return +} + +func min(a, b int) int { + if a <= b { + return a + } + return b +} diff --git a/cmd/util/ledger/migrations/cadence_value_diff_test.go b/cmd/util/ledger/migrations/cadence_value_diff_test.go new file mode 100644 index 00000000000..7e692dd1b04 --- /dev/null +++ b/cmd/util/ledger/migrations/cadence_value_diff_test.go @@ -0,0 +1,655 @@ +package migrations + +import ( + "fmt" + "testing" + + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/convert" + "github.com/onflow/flow-go/model/flow" + + "github.com/stretchr/testify/require" +) + +func TestDiffCadenceValues(t *testing.T) { + address, err := common.HexToAddress("0x1") + require.NoError(t, err) + + domain := common.PathDomainStorage.Identifier() + + t.Run("no diff", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, true) + + err := diffReporter.DiffStates( + createTestPayloads(t, address, domain), + createTestPayloads(t, address, domain), + []string{domain}, + ) + require.NoError(t, err) + require.Equal(t, 0, len(writer.entries)) + }) + + t.Run("one storage map doesn't exist", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, true) + + err := diffReporter.DiffStates( + createTestPayloads(t, address, domain), + nil, + []string{domain}, + ) + require.NoError(t, err) + require.Equal(t, 1, len(writer.entries)) + + diff := writer.entries[0].(difference) + require.Equal(t, diffKindString[storageMapExistDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + }) + + t.Run("storage maps have different sets of keys", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, true) + + err := diffReporter.DiffStates( + createTestPayloads(t, address, domain), + createStorageMapPayloads(t, address, domain, []string{"unique_key"}, []interpreter.Value{interpreter.UInt64Value(0)}), + []string{domain}, + ) + require.NoError(t, err) + + // 2 differences: + // - unique keys in old storage map + // - unique keys in new storage map + require.Equal(t, 2, len(writer.entries)) + + for _, entry := range writer.entries { + diff := entry.(difference) + require.Equal(t, diffKindString[storageMapKeyDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + } + }) + + t.Run("storage maps have overlapping keys", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, true) + + err := diffReporter.DiffStates( + createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(0), interpreter.UInt64Value(0)}), + createStorageMapPayloads(t, address, domain, []string{"2", "0"}, []interpreter.Value{interpreter.UInt64Value(0), interpreter.UInt64Value(0)}), + []string{domain}, + ) + require.NoError(t, err) + + // 2 entries: + // - unique keys in old storage map + // - unique keys in new storage map + require.Equal(t, 2, len(writer.entries)) + + for _, entry := range writer.entries { + diff := entry.(difference) + require.Equal(t, diffKindString[storageMapKeyDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + } + }) + + t.Run("storage maps have one different value", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, false) + + err := diffReporter.DiffStates( + createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(100), interpreter.UInt64Value(101)}), + createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(111), interpreter.UInt64Value(101)}), + []string{domain}, + ) + require.NoError(t, err) + + // 1 entries: + // - different value + require.Equal(t, 1, len(writer.entries)) + + diff := writer.entries[0].(difference) + require.Equal(t, diffKindString[cadenceValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.Equal(t, "storage[0]", diff.Trace) + require.Equal(t, "100", diff.OldValue) + require.Equal(t, "111", diff.NewValue) + }) + + t.Run("storage maps have multiple different values", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, false) + + err := diffReporter.DiffStates( + createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(100), interpreter.UInt64Value(101)}), + createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(111), interpreter.UInt64Value(102)}), + []string{domain}, + ) + require.NoError(t, err) + + // 2 entries with 2 different values: + require.Equal(t, 2, len(writer.entries)) + + for _, entry := range writer.entries { + diff := entry.(difference) + require.Equal(t, diffKindString[cadenceValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.True(t, diff.Trace == "storage[0]" || diff.Trace == "storage[1]") + } + }) + + t.Run("nested array value has different elements", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, false) + + createPayloads := func(arrayValues []interpreter.Value) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := NewMigratorRuntime( + address, + []*ledger.Payload{accountStatusPayload}, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + nestedArray := interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeUInt64, + }, + address, + arrayValues..., + ) + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(fmt.Sprintf("key_%d", storageMap.Count())), + interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + nestedArray, + ), + ) + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads + } + + err := diffReporter.DiffStates( + createPayloads([]interpreter.Value{interpreter.UInt64Value(0), interpreter.UInt64Value(2), interpreter.UInt64Value(4)}), + createPayloads([]interpreter.Value{interpreter.UInt64Value(1), interpreter.UInt64Value(3), interpreter.UInt64Value(5)}), + []string{domain}, + ) + require.NoError(t, err) + + // 3 entries: + // - different value + require.Equal(t, 3, len(writer.entries)) + + for _, entry := range writer.entries { + diff := entry.(difference) + require.Equal(t, diffKindString[cadenceValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.True(t, diff.Trace == "storage[key_0][0][0]" || diff.Trace == "storage[key_0][0][1]" || diff.Trace == "storage[key_0][0][2]") + + switch diff.Trace { + case "storage[key_0][0][0]": + require.Equal(t, "0", diff.OldValue) + require.Equal(t, "1", diff.NewValue) + + case "storage[key_0][0][1]": + require.Equal(t, "2", diff.OldValue) + require.Equal(t, "3", diff.NewValue) + + case "storage[key_0][0][2]": + require.Equal(t, "4", diff.OldValue) + require.Equal(t, "5", diff.NewValue) + } + } + }) + + t.Run("nested dict value has different elements", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, false) + + createPayloads := func(dictValues []interpreter.Value) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := NewMigratorRuntime( + address, + []*ledger.Payload{accountStatusPayload}, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + nestedDict := interpreter.NewDictionaryValueWithAddress( + mr.Interpreter, + interpreter.EmptyLocationRange, + &interpreter.DictionaryStaticType{ + KeyType: interpreter.PrimitiveStaticTypeAnyStruct, + ValueType: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + dictValues..., + ) + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(fmt.Sprintf("key_%d", storageMap.Count())), + interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + nestedDict, + ), + ) + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads + } + + err := diffReporter.DiffStates( + createPayloads( + []interpreter.Value{interpreter.NewUnmeteredStringValue("dict_key_0"), + interpreter.UInt64Value(0), + interpreter.NewUnmeteredStringValue("dict_key_1"), + interpreter.UInt64Value(2), + }), + createPayloads( + []interpreter.Value{interpreter.NewUnmeteredStringValue("dict_key_0"), + interpreter.UInt64Value(1), + interpreter.NewUnmeteredStringValue("dict_key_1"), + interpreter.UInt64Value(3), + }), + []string{domain}, + ) + require.NoError(t, err) + + // 2 entries: + // - different value + require.Equal(t, 2, len(writer.entries)) + + for _, entry := range writer.entries { + diff := entry.(difference) + require.Equal(t, diffKindString[cadenceValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.True(t, diff.Trace == "storage[key_0][0][\"dict_key_0\"]" || diff.Trace == "storage[key_0][0][\"dict_key_1\"]") + + switch diff.Trace { + case "storage[key_0][0][\"dict_key_0\"]": + require.Equal(t, "0", diff.OldValue) + require.Equal(t, "1", diff.NewValue) + + case "storage[key_0][0][\"dict_key_1\"]": + require.Equal(t, "2", diff.OldValue) + require.Equal(t, "3", diff.NewValue) + } + } + }) + + t.Run("nested composite value has different elements", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, false) + + createPayloads := func(compositeFields []string, compositeValues []interpreter.Value) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := NewMigratorRuntime( + address, + []*ledger.Payload{accountStatusPayload}, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + var fields []interpreter.CompositeField + + for i, fieldName := range compositeFields { + fields = append(fields, interpreter.CompositeField{Name: fieldName, Value: compositeValues[i]}) + } + + nestedComposite := interpreter.NewCompositeValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + common.StringLocation("test"), + "Test", + common.CompositeKindStructure, + fields, + address, + ) + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(fmt.Sprintf("key_%d", storageMap.Count())), + interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + nestedComposite, + ), + ) + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads + } + + err := diffReporter.DiffStates( + createPayloads( + []string{ + "Field_0", + "Field_1", + }, + []interpreter.Value{ + interpreter.UInt64Value(0), + interpreter.UInt64Value(2), + }), + createPayloads( + []string{ + "Field_0", + "Field_1", + }, + []interpreter.Value{ + interpreter.UInt64Value(1), + interpreter.UInt64Value(3), + }), + []string{domain}, + ) + require.NoError(t, err) + + // 2 entries: + // - different value + require.Equal(t, 2, len(writer.entries)) + + for _, entry := range writer.entries { + diff := entry.(difference) + require.Equal(t, diffKindString[cadenceValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.True(t, diff.Trace == "storage[key_0][0].Field_0" || diff.Trace == "storage[key_0][0].Field_1") + + switch diff.Trace { + case "storage[key_0][0].Field_0": + require.Equal(t, "0", diff.OldValue) + require.Equal(t, "1", diff.NewValue) + + case "storage[key_0][0].Field_1": + require.Equal(t, "2", diff.OldValue) + require.Equal(t, "3", diff.NewValue) + } + } + }) + + t.Run("nested composite value has different elements with verbose logging", func(t *testing.T) { + writer := &testReportWriter{} + + diffReporter := NewCadenceValueDiffReporter(address, writer, true) + + createPayloads := func(compositeFields []string, compositeValues []interpreter.Value) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := NewMigratorRuntime( + address, + []*ledger.Payload{accountStatusPayload}, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + var fields []interpreter.CompositeField + + for i, fieldName := range compositeFields { + fields = append(fields, interpreter.CompositeField{Name: fieldName, Value: compositeValues[i]}) + } + + nestedComposite := interpreter.NewCompositeValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + common.StringLocation("test"), + "Test", + common.CompositeKindStructure, + fields, + address, + ) + + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(fmt.Sprintf("key_%d", storageMap.Count())), + interpreter.NewArrayValue( + mr.Interpreter, + interpreter.EmptyLocationRange, + &interpreter.VariableSizedStaticType{ + Type: interpreter.PrimitiveStaticTypeAnyStruct, + }, + address, + nestedComposite, + ), + ) + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads + } + + err := diffReporter.DiffStates( + createPayloads( + []string{ + "Field_0", + "Field_1", + }, + []interpreter.Value{ + interpreter.UInt64Value(0), + interpreter.UInt64Value(2), + }), + createPayloads( + []string{ + "Field_0", + "Field_1", + }, + []interpreter.Value{ + interpreter.UInt64Value(1), + interpreter.UInt64Value(3), + }), + []string{domain}, + ) + require.NoError(t, err) + + // 3 entries: + // - 2 different values + // - verbose logging of storage map element + require.Equal(t, 3, len(writer.entries)) + + // Test 2 cadence value diff logs + for _, entry := range writer.entries[:2] { + diff := entry.(difference) + require.Equal(t, diffKindString[cadenceValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.True(t, diff.Trace == "storage[key_0][0].Field_0" || diff.Trace == "storage[key_0][0].Field_1") + + switch diff.Trace { + case "storage[key_0][0].Field_0": + require.Equal(t, "0", diff.OldValue) + require.Equal(t, "1", diff.NewValue) + + case "storage[key_0][0].Field_1": + require.Equal(t, "2", diff.OldValue) + require.Equal(t, "3", diff.NewValue) + } + } + + // Test storge map value diff log (only with verbose logging) + diff := writer.entries[2].(difference) + require.Equal(t, diffKindString[storageMapValueDiffKind], diff.Kind) + require.Equal(t, address.Hex(), diff.Address) + require.Equal(t, domain, diff.Domain) + require.Equal(t, "storage[key_0]", diff.Trace) + require.Equal(t, "[S.test.Test(Field_1: 2, Field_0: 0)]", diff.OldValue) + require.Equal(t, "[S.test.Test(Field_1: 3, Field_0: 1)]", diff.NewValue) + }) +} + +func createStorageMapPayloads(t *testing.T, address common.Address, domain string, keys []string, values []interpreter.Value) []*ledger.Payload { + + // Create account status payload + accountStatus := environment.NewAccountStatus() + accountStatusPayload := ledger.NewPayload( + convert.RegisterIDToLedgerKey( + flow.AccountStatusRegisterID(flow.ConvertAddress(address)), + ), + accountStatus.ToBytes(), + ) + + mr, err := NewMigratorRuntime( + address, + []*ledger.Payload{accountStatusPayload}, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + // Create new storage map + storageMap := mr.Storage.GetStorageMap(mr.Address, domain, true) + + for i, k := range keys { + storageMap.WriteValue( + mr.Interpreter, + interpreter.StringStorageMapKey(k), + values[i], + ) + } + + err = mr.Storage.Commit(mr.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := mr.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + payloads := make([]*ledger.Payload, 0, len(result.WriteSet)) + for id, value := range result.WriteSet { + key := convert.RegisterIDToLedgerKey(id) + payloads = append(payloads, ledger.NewPayload(key, value)) + } + + return payloads +} diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 81f29cc3e5f..10e8195e795 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -31,6 +31,8 @@ type CadenceBaseMigrator struct { name string log zerolog.Logger reporter reporters.ReportWriter + diffReporter reporters.ReportWriter + logVerboseDiff bool valueMigrations func( inter *interpreter.Interpreter, accounts environment.Accounts, @@ -46,6 +48,11 @@ var _ io.Closer = (*CadenceBaseMigrator)(nil) func (m *CadenceBaseMigrator) Close() error { // Close the report writer so it flushes to file. m.reporter.Close() + + if m.diffReporter != nil { + m.diffReporter.Close() + } + return nil } @@ -164,13 +171,29 @@ func (m *CadenceBaseMigrator) MigrateAccount( expectedWriteAddress := flow.ConvertAddress(address) expectedOriginalAddress := flow.ConvertAddress(address) - return MergeRegisterChanges( + newPayloads, err := MergeRegisterChanges( migrationRuntime.Snapshot.Payloads, result.WriteSet, expectedWriteAddress, expectedOriginalAddress, m.log, ) + if err != nil { + return nil, fmt.Errorf("failed to merge register changes: %w", err) + } + + if m.diffReporter != nil { + + accountDiffReporter := NewCadenceValueDiffReporter(address, m.diffReporter, m.logVerboseDiff) + + err = accountDiffReporter.DiffStates(oldPayloads, newPayloads, domains) + if err != nil { + // Log error returned from diff without returning error. + m.log.Err(err) + } + } + + return newPayloads, nil } func checkPayloadsOwnership(payloads []*ledger.Payload, address common.Address, log zerolog.Logger) { @@ -209,13 +232,23 @@ func checkPayloadOwnership(payload *ledger.Payload, address common.Address, log // which runs some of the Cadence value migrations (static types, entitlements, strings) func NewCadence1ValueMigrator( rwf reporters.ReportWriterFactory, + diffMigrations bool, + logVerboseDiff bool, errorMessageHandler *errorMessageHandler, compositeTypeConverter statictypes.CompositeTypeConverterFunc, interfaceTypeConverter statictypes.InterfaceTypeConverterFunc, ) *CadenceBaseMigrator { + + var diffReporter reporters.ReportWriter + if diffMigrations { + diffReporter = rwf.ReportWriter("cadence-value-migration-diff") + } + return &CadenceBaseMigrator{ - name: "cadence-value-migration", - reporter: rwf.ReportWriter("cadence-value-migrator"), + name: "cadence-value-migration", + reporter: rwf.ReportWriter("cadence-value-migrator"), + diffReporter: diffReporter, + logVerboseDiff: logVerboseDiff, valueMigrations: func( inter *interpreter.Interpreter, _ environment.Accounts, diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 4196572010f..13640992040 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -82,6 +82,8 @@ func TestCadenceValuesMigration(t *testing.T) { rwf, nWorker, chainID, + false, + false, evmContractChange, stagedContracts, ) From d8a5fa6255e33100466fa5577cf1e9a630f5fe31 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 4 Mar 2024 16:47:38 -0600 Subject: [PATCH 19/39] Enable migration diff for more Cadence migrations - Cadence1LinkValueMigrator - Cadence1CapabilityValueMigrator --- cmd/util/ledger/migrations/cadence.go | 4 +++ .../migrations/cadence_values_migration.go | 26 ++++++++++++++++--- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 463d1a91164..031efb91fe1 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -187,11 +187,15 @@ func NewCadence1ValueMigrations( ), NewCadence1LinkValueMigrator( rwf, + diffMigrations, + logVerboseDiff, errorMessageHandler, capabilityMapping, ), NewCadence1CapabilityValueMigrator( rwf, + diffMigrations, + logVerboseDiff, errorMessageHandler, capabilityMapping, ), diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 5b0bbb509f2..4ecbbd9e6da 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -274,12 +274,21 @@ func NewCadence1ValueMigrator( // It populates the given map with the IDs of the capability controller it issues. func NewCadence1LinkValueMigrator( rwf reporters.ReportWriterFactory, + diffMigrations bool, + logVerboseDiff bool, errorMessageHandler *errorMessageHandler, capabilityMapping *capcons.CapabilityMapping, ) *CadenceBaseMigrator { + var diffReporter reporters.ReportWriter + if diffMigrations { + diffReporter = rwf.ReportWriter("cadence-link-value-migration-diff") + } + return &CadenceBaseMigrator{ - name: "cadence-link-value-migration", - reporter: rwf.ReportWriter("cadence-link-value-migrator"), + name: "cadence-link-value-migration", + reporter: rwf.ReportWriter("cadence-link-value-migrator"), + diffReporter: diffReporter, + logVerboseDiff: logVerboseDiff, valueMigrations: func( _ *interpreter.Interpreter, accounts environment.Accounts, @@ -308,12 +317,21 @@ func NewCadence1LinkValueMigrator( // generated by the link value migration. func NewCadence1CapabilityValueMigrator( rwf reporters.ReportWriterFactory, + diffMigrations bool, + logVerboseDiff bool, errorMessageHandler *errorMessageHandler, capabilityMapping *capcons.CapabilityMapping, ) *CadenceBaseMigrator { + var diffReporter reporters.ReportWriter + if diffMigrations { + diffReporter = rwf.ReportWriter("cadence-capability-value-migration-diff") + } + return &CadenceBaseMigrator{ - name: "cadence-capability-value-migration", - reporter: rwf.ReportWriter("cadence-capability-value-migrator"), + name: "cadence-capability-value-migration", + reporter: rwf.ReportWriter("cadence-capability-value-migrator"), + diffReporter: diffReporter, + logVerboseDiff: logVerboseDiff, valueMigrations: func( _ *interpreter.Interpreter, _ environment.Accounts, From d977b352821a2180db8f1f9c0c41c484c9b56fbc Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 4 Mar 2024 17:08:16 -0600 Subject: [PATCH 20/39] Add Uint64AtreeValue to StorageMap key type assertion --- cmd/util/ledger/migrations/cadence_value_diff.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/util/ledger/migrations/cadence_value_diff.go b/cmd/util/ledger/migrations/cadence_value_diff.go index 39441adf205..574eeeee7a1 100644 --- a/cmd/util/ledger/migrations/cadence_value_diff.go +++ b/cmd/util/ledger/migrations/cadence_value_diff.go @@ -190,6 +190,9 @@ func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *re case interpreter.StringAtreeValue: mapKey = interpreter.StringStorageMapKey(key) + case interpreter.Uint64AtreeValue: + mapKey = interpreter.Uint64StorageMapKey(key) + case interpreter.StringStorageMapKey: mapKey = key From 4d03d8d2eb67f3571bf9199d5a7fbb0e2949b147 Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:04:44 -0600 Subject: [PATCH 21/39] Add old and new static types in migration diff --- .../ledger/migrations/cadence_value_diff.go | 48 +++++++++++-------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_value_diff.go b/cmd/util/ledger/migrations/cadence_value_diff.go index 574eeeee7a1..e8818b3c84d 100644 --- a/cmd/util/ledger/migrations/cadence_value_diff.go +++ b/cmd/util/ledger/migrations/cadence_value_diff.go @@ -51,13 +51,15 @@ type diffProblem struct { } type difference struct { - Address string - Domain string - Kind string - Msg string - Trace string `json:",omitempty"` - OldValue string `json:",omitempty"` - NewValue string `json:",omitempty"` + Address string + Domain string + Kind string + Msg string + Trace string `json:",omitempty"` + OldValue string `json:",omitempty"` + NewValue string `json:",omitempty"` + OldValueStaticType string `json:",omitempty"` + NewValueStaticType string `json:",omitempty"` } type CadenceValueDiffReporter struct { @@ -232,13 +234,15 @@ func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *re // Log potentially large values at top level only when verbose logging is enabled. dr.reportWriter.Write( difference{ - Address: dr.address.Hex(), - Domain: domain, - Kind: diffKindString[storageMapValueDiffKind], - Msg: "storage map elements are different", - Trace: trace, - OldValue: oldValue.String(), - NewValue: newValue.String(), + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[storageMapValueDiffKind], + Msg: "storage map elements are different", + Trace: trace, + OldValue: oldValue.String(), + NewValue: newValue.String(), + OldValueStaticType: oldValue.StaticType(oldRuntime.Interpreter).String(), + NewValueStaticType: newValue.StaticType(newRuntime.Interpreter).String(), }) } } @@ -286,13 +290,15 @@ func (dr *CadenceValueDiffReporter) diffValues( if !oldValue.Equal(nil, interpreter.EmptyLocationRange, other) { dr.reportWriter.Write( difference{ - Address: dr.address.Hex(), - Domain: domain, - Kind: diffKindString[cadenceValueDiffKind], - Msg: fmt.Sprintf("values differ: %T vs %T", oldValue, other), - Trace: trace, - OldValue: oldValue.String(), - NewValue: other.String(), + Address: dr.address.Hex(), + Domain: domain, + Kind: diffKindString[cadenceValueDiffKind], + Msg: fmt.Sprintf("values differ: %T vs %T", oldValue, other), + Trace: trace, + OldValue: v.String(), + NewValue: other.String(), + OldValueStaticType: v.StaticType(vInterpreter).String(), + NewValueStaticType: other.StaticType(otherInterpreter).String(), }) return true } From 80e915c60a18ee685b60b18a93f3c6b648abfebb Mon Sep 17 00:00:00 2001 From: Faye Amacker <33205765+fxamacker@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:48:08 -0600 Subject: [PATCH 22/39] Log error in DiffState instead of returning error --- .../ledger/migrations/cadence_value_diff.go | 45 ++++++++++++------- .../migrations/cadence_value_diff_test.go | 20 ++++----- .../migrations/cadence_values_migration.go | 6 +-- 3 files changed, 40 insertions(+), 31 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_value_diff.go b/cmd/util/ledger/migrations/cadence_value_diff.go index e8818b3c84d..94e148920e3 100644 --- a/cmd/util/ledger/migrations/cadence_value_diff.go +++ b/cmd/util/ledger/migrations/cadence_value_diff.go @@ -33,15 +33,23 @@ var diffKindString = map[diffKind]string{ type diffErrorKind int const ( - storageMapKeyNotImplementingStorageMapKeyDiffErrorKind diffErrorKind = iota + abortErrorKind diffErrorKind = iota + storageMapKeyNotImplementingStorageMapKeyDiffErrorKind cadenceValueNotImplementEquatableValueDiffErrorKind ) var diffErrorKindString = map[diffErrorKind]string{ + abortErrorKind: "error_diff_failed", storageMapKeyNotImplementingStorageMapKeyDiffErrorKind: "error_storage_map_key_not_implemeting_StorageMapKey", cadenceValueNotImplementEquatableValueDiffErrorKind: "error_cadence_value_not_implementing_EquatableValue", } +type diffError struct { + Address string + Kind string + Msg string +} + type diffProblem struct { Address string Domain string @@ -80,30 +88,37 @@ func NewCadenceValueDiffReporter( } } -func (dr *CadenceValueDiffReporter) DiffStates(oldPayloads, newPayloads []*ledger.Payload, domains []string) error { +func (dr *CadenceValueDiffReporter) DiffStates(oldPayloads, newPayloads []*ledger.Payload, domains []string) { // Create all the runtime components we need for comparing Cadence values. oldRuntime, err := newReadonlyStorageRuntime(oldPayloads) if err != nil { - return fmt.Errorf("failed to create runtime with old state payloads: %w", err) + dr.reportWriter.Write( + diffError{ + Address: dr.address.Hex(), + Kind: diffErrorKindString[abortErrorKind], + Msg: fmt.Sprintf("failed to create runtime with old state payloads: %s", err), + }) + return } newRuntime, err := newReadonlyStorageRuntime(newPayloads) if err != nil { - return fmt.Errorf("failed to create runtime with new state payloads: %w", err) + dr.reportWriter.Write( + diffError{ + Address: dr.address.Hex(), + Kind: diffErrorKindString[abortErrorKind], + Msg: fmt.Sprintf("failed to create runtime with new state payloads: %s", err), + }) + return } // Iterate through all domains and compare cadence values. for _, domain := range domains { - err := dr.diffStorageDomain(oldRuntime, newRuntime, domain) - if err != nil { - return err - } + dr.diffStorageDomain(oldRuntime, newRuntime, domain) } - - return nil } -func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *readonlyStorageRuntime, domain string) error { +func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *readonlyStorageRuntime, domain string) { oldStorageMap := oldRuntime.Storage.GetStorageMap(dr.address, domain, false) @@ -111,7 +126,7 @@ func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *re if oldStorageMap == nil && newStorageMap == nil { // No storage maps for this domain. - return nil + return } if oldStorageMap == nil && newStorageMap != nil { @@ -127,7 +142,7 @@ func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *re ), }) - return nil + return } if oldStorageMap != nil && newStorageMap == nil { @@ -143,7 +158,7 @@ func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *re ), }) - return nil + return } oldKeys := getStorageMapKeys(oldStorageMap) @@ -248,8 +263,6 @@ func (dr *CadenceValueDiffReporter) diffStorageDomain(oldRuntime, newRuntime *re } } - - return nil } func (dr *CadenceValueDiffReporter) diffValues( diff --git a/cmd/util/ledger/migrations/cadence_value_diff_test.go b/cmd/util/ledger/migrations/cadence_value_diff_test.go index 7e692dd1b04..20e5899810b 100644 --- a/cmd/util/ledger/migrations/cadence_value_diff_test.go +++ b/cmd/util/ledger/migrations/cadence_value_diff_test.go @@ -27,7 +27,7 @@ func TestDiffCadenceValues(t *testing.T) { diffReporter := NewCadenceValueDiffReporter(address, writer, true) - err := diffReporter.DiffStates( + diffReporter.DiffStates( createTestPayloads(t, address, domain), createTestPayloads(t, address, domain), []string{domain}, @@ -41,7 +41,7 @@ func TestDiffCadenceValues(t *testing.T) { diffReporter := NewCadenceValueDiffReporter(address, writer, true) - err := diffReporter.DiffStates( + diffReporter.DiffStates( createTestPayloads(t, address, domain), nil, []string{domain}, @@ -60,7 +60,7 @@ func TestDiffCadenceValues(t *testing.T) { diffReporter := NewCadenceValueDiffReporter(address, writer, true) - err := diffReporter.DiffStates( + diffReporter.DiffStates( createTestPayloads(t, address, domain), createStorageMapPayloads(t, address, domain, []string{"unique_key"}, []interpreter.Value{interpreter.UInt64Value(0)}), []string{domain}, @@ -85,7 +85,7 @@ func TestDiffCadenceValues(t *testing.T) { diffReporter := NewCadenceValueDiffReporter(address, writer, true) - err := diffReporter.DiffStates( + diffReporter.DiffStates( createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(0), interpreter.UInt64Value(0)}), createStorageMapPayloads(t, address, domain, []string{"2", "0"}, []interpreter.Value{interpreter.UInt64Value(0), interpreter.UInt64Value(0)}), []string{domain}, @@ -110,7 +110,7 @@ func TestDiffCadenceValues(t *testing.T) { diffReporter := NewCadenceValueDiffReporter(address, writer, false) - err := diffReporter.DiffStates( + diffReporter.DiffStates( createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(100), interpreter.UInt64Value(101)}), createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(111), interpreter.UInt64Value(101)}), []string{domain}, @@ -135,7 +135,7 @@ func TestDiffCadenceValues(t *testing.T) { diffReporter := NewCadenceValueDiffReporter(address, writer, false) - err := diffReporter.DiffStates( + diffReporter.DiffStates( createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(100), interpreter.UInt64Value(101)}), createStorageMapPayloads(t, address, domain, []string{"0", "1"}, []interpreter.Value{interpreter.UInt64Value(111), interpreter.UInt64Value(102)}), []string{domain}, @@ -220,7 +220,7 @@ func TestDiffCadenceValues(t *testing.T) { return payloads } - err := diffReporter.DiffStates( + diffReporter.DiffStates( createPayloads([]interpreter.Value{interpreter.UInt64Value(0), interpreter.UInt64Value(2), interpreter.UInt64Value(4)}), createPayloads([]interpreter.Value{interpreter.UInt64Value(1), interpreter.UInt64Value(3), interpreter.UInt64Value(5)}), []string{domain}, @@ -321,7 +321,7 @@ func TestDiffCadenceValues(t *testing.T) { return payloads } - err := diffReporter.DiffStates( + diffReporter.DiffStates( createPayloads( []interpreter.Value{interpreter.NewUnmeteredStringValue("dict_key_0"), interpreter.UInt64Value(0), @@ -433,7 +433,7 @@ func TestDiffCadenceValues(t *testing.T) { return payloads } - err := diffReporter.DiffStates( + diffReporter.DiffStates( createPayloads( []string{ "Field_0", @@ -551,7 +551,7 @@ func TestDiffCadenceValues(t *testing.T) { return payloads } - err := diffReporter.DiffStates( + diffReporter.DiffStates( createPayloads( []string{ "Field_0", diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 4ecbbd9e6da..ab99b8eccaf 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -189,11 +189,7 @@ func (m *CadenceBaseMigrator) MigrateAccount( accountDiffReporter := NewCadenceValueDiffReporter(address, m.diffReporter, m.logVerboseDiff) - err = accountDiffReporter.DiffStates(oldPayloads, newPayloads, domains) - if err != nil { - // Log error returned from diff without returning error. - m.log.Err(err) - } + accountDiffReporter.DiffStates(oldPayloads, newPayloads, domains) } return newPayloads, nil From a51ec2085ee741c99d1c4be24b6b5c63c222a959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 09:57:20 -0800 Subject: [PATCH 23/39] add flag to disable sorting of payloads --- cmd/util/cmd/execution-state-extract/cmd.go | 6 ++++++ .../execution_state_extract.go | 17 ++++++++++++----- .../execution_state_extract_test.go | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index ded0f26bd02..7d962bcf546 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -33,6 +33,7 @@ var ( flagNoReport bool flagValidateMigration bool flagAllowPartialStateFromPayloads bool + flagSortPayloads bool flagLogVerboseValidationError bool flagDiffMigration bool flagLogVerboseDiff bool @@ -95,6 +96,9 @@ func init() { Cmd.Flags().BoolVar(&flagAllowPartialStateFromPayloads, "allow-partial-state-from-payload-file", false, "allow input payload file containing partial state (e.g. not all accounts)") + Cmd.Flags().BoolVar(&flagSortPayloads, "sort-payloads", true, + "sort payloads (generate deterministic output)") + // If specified, the state will consist of payloads from the given input payload file. // If not specified, then the state will be extracted from the latest checkpoint file. // This flag can be used to reduce total duration of migrations when state extraction involves @@ -327,6 +331,7 @@ func run(*cobra.Command, []string) { flagInputPayloadFileName, flagOutputPayloadFileName, exportedAddresses, + flagSortPayloads, ) } else { err = extractExecutionState( @@ -343,6 +348,7 @@ func run(*cobra.Command, []string) { stagedContracts, flagOutputPayloadFileName, exportedAddresses, + flagSortPayloads, ) } diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index a2b5bf4619f..bebbed94e1a 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -44,6 +44,7 @@ func extractExecutionState( stagedContracts []migrators.StagedContract, outputPayloadFile string, exportPayloadsByAddresses []common.Address, + sortPayloads bool, ) error { log.Info().Msg("init WAL") @@ -141,6 +142,7 @@ func extractExecutionState( outputPayloadFile, exportPayloadsByAddresses, false, // payloads represents entire state. + sortPayloads, ) } @@ -216,6 +218,7 @@ func extractExecutionStateFromPayloads( inputPayloadFile string, outputPayloadFile string, exportPayloadsByAddresses []common.Address, + sortPayloads bool, ) error { inputPayloadsFromPartialState, payloads, err := util.ReadPayloadFile(log, inputPayloadFile) @@ -250,6 +253,7 @@ func extractExecutionStateFromPayloads( outputPayloadFile, exportPayloadsByAddresses, inputPayloadsFromPartialState, + sortPayloads, ) } @@ -284,14 +288,17 @@ func exportPayloads( outputPayloadFile string, exportPayloadsByAddresses []common.Address, inputPayloadsFromPartialState bool, + sortPayloads bool, ) error { - log.Info().Msgf("sorting %d payloads", len(payloads)) + if sortPayloads { + log.Info().Msgf("sorting %d payloads", len(payloads)) - // Sort payloads to produce deterministic payload file with - // same sequence of payloads inside. - payloads = util.SortPayloadsByAddress(payloads, nWorker) + // Sort payloads to produce deterministic payload file with + // same sequence of payloads inside. + payloads = util.SortPayloadsByAddress(payloads, nWorker) - log.Info().Msgf("sorted %d payloads", len(payloads)) + log.Info().Msgf("sorted %d payloads", len(payloads)) + } log.Info().Msgf("creating payloads file %s", outputPayloadFile) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go index 5ffb56672d6..10d9c76e4f1 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go @@ -81,6 +81,7 @@ func TestExtractExecutionState(t *testing.T) { nil, "", nil, + false, ) require.Error(t, err) }) From 3ec0ba1a8de52095b9c3a3c25608ff607ccf73a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 09:59:52 -0800 Subject: [PATCH 24/39] either deploy or update the burner contract, depending on network --- cmd/util/cmd/execution-state-extract/cmd.go | 10 ++++++ .../execution_state_extract.go | 6 ++++ .../execution_state_extract_test.go | 2 +- cmd/util/ledger/migrations/cadence.go | 33 ++++++++++++++----- .../cadence_values_migration_test.go | 3 ++ .../change_contract_code_migration.go | 25 +++++++++++++- 6 files changed, 68 insertions(+), 11 deletions(-) diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index 7d962bcf546..138a5c38b0c 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -311,6 +311,14 @@ func run(*cobra.Command, []string) { // TODO: evmContractChange := migrations.EVMContractChangeNone + var burnerContractChange migrations.BurnerContractChange + switch chainID { + case flow.Emulator: + burnerContractChange = migrations.BurnerContractChangeDeploy + case flow.Testnet, flow.Mainnet: + burnerContractChange = migrations.BurnerContractChangeUpdate + } + stagedContracts, err := migrations.StagedContractsFromCSV(flagStagedContractsFile) if err != nil { log.Fatal().Err(err).Msgf("error loading staged contracts: %s", err.Error()) @@ -327,6 +335,7 @@ func run(*cobra.Command, []string) { flagLogVerboseDiff, chainID, evmContractChange, + burnerContractChange, stagedContracts, flagInputPayloadFileName, flagOutputPayloadFileName, @@ -345,6 +354,7 @@ func run(*cobra.Command, []string) { flagLogVerboseDiff, chainID, evmContractChange, + burnerContractChange, stagedContracts, flagOutputPayloadFileName, exportedAddresses, diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index bebbed94e1a..8be35a9fb0d 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -41,6 +41,7 @@ func extractExecutionState( logVerboseDiff bool, chainID flow.ChainID, evmContractChange migrators.EVMContractChange, + burnerContractChange migrators.BurnerContractChange, stagedContracts []migrators.StagedContract, outputPayloadFile string, exportPayloadsByAddresses []common.Address, @@ -104,6 +105,7 @@ func extractExecutionState( logVerboseDiff, chainID, evmContractChange, + burnerContractChange, stagedContracts, ) @@ -214,6 +216,7 @@ func extractExecutionStateFromPayloads( logVerboseDiff bool, chainID flow.ChainID, evmContractChange migrators.EVMContractChange, + burnerContractChange migrators.BurnerContractChange, stagedContracts []migrators.StagedContract, inputPayloadFile string, outputPayloadFile string, @@ -237,6 +240,7 @@ func extractExecutionStateFromPayloads( logVerboseDiff, chainID, evmContractChange, + burnerContractChange, stagedContracts, ) @@ -391,6 +395,7 @@ func newMigrations( logVerboseDiff bool, chainID flow.ChainID, evmContractChange migrators.EVMContractChange, + burnerContractChange migrators.BurnerContractChange, stagedContracts []migrators.StagedContract, ) []ledger.Migration { if !runMigrations { @@ -407,6 +412,7 @@ func newMigrations( diffMigrations, logVerboseDiff, evmContractChange, + burnerContractChange, stagedContracts, ) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go index 10d9c76e4f1..bf0569f9f1f 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go @@ -76,8 +76,8 @@ func TestExtractExecutionState(t *testing.T) { false, false, flow.Emulator, - // TODO: migrations.EVMContractChangeNone, + migrations.BurnerContractChangeDeploy, nil, "", nil, diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 031efb91fe1..24bdb2db5c5 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -222,6 +222,7 @@ func NewCadence1ContractsMigrations( nWorker int, chainID flow.ChainID, evmContractChange EVMContractChange, + burnerContractChange BurnerContractChange, stagedContracts []StagedContract, ) []NamedMigration { @@ -229,7 +230,8 @@ func NewCadence1ContractsMigrations( chainID, log, SystemContractChangesOptions{ - EVM: evmContractChange, + EVM: evmContractChange, + Burner: burnerContractChange, }, ) @@ -248,20 +250,31 @@ func NewCadence1ContractsMigrations( ) } - return []NamedMigration{ - { + var migrations []NamedMigration + + if burnerContractChange == BurnerContractChangeDeploy { + migrations = append( + migrations, + NamedMigration{ + Name: "burner-deployment-migration", + Migrate: NewBurnerDeploymentMigration(chainID, log), + }, + ) + } + + migrations = append( + migrations, + NamedMigration{ Name: "system-contracts-update-migration", Migrate: toAccountBasedMigration(systemContractsMigration), }, - { - Name: "burner-deployment-migration", - Migrate: NewBurnerDeploymentMigration(chainID, log), - }, - { + NamedMigration{ Name: "staged-contracts-update-migration", Migrate: toAccountBasedMigration(stagedContractsMigration), }, - } + ) + + return migrations } func NewCadence1Migrations( @@ -272,6 +285,7 @@ func NewCadence1Migrations( diffMigrations bool, logVerboseDiff bool, evmContractChange EVMContractChange, + burnerContractChange BurnerContractChange, stagedContracts []StagedContract, ) []NamedMigration { return common.Concat( @@ -280,6 +294,7 @@ func NewCadence1Migrations( nWorker, chainID, evmContractChange, + burnerContractChange, stagedContracts, ), NewCadence1ValueMigrations( diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 12fd67cdad6..5a901124aed 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -67,6 +67,8 @@ func TestCadenceValuesMigration(t *testing.T) { // TODO: EVM contract is not deployed in snapshot yet, so can't update it const evmContractChange = EVMContractChangeNone + const burnerContractChange = BurnerContractChangeDeploy + stagedContracts := []StagedContract{ { Contract: Contract{ @@ -85,6 +87,7 @@ func TestCadenceValuesMigration(t *testing.T) { false, false, evmContractChange, + burnerContractChange, stagedContracts, ) diff --git a/cmd/util/ledger/migrations/change_contract_code_migration.go b/cmd/util/ledger/migrations/change_contract_code_migration.go index de2192cedaa..c9808f38542 100644 --- a/cmd/util/ledger/migrations/change_contract_code_migration.go +++ b/cmd/util/ledger/migrations/change_contract_code_migration.go @@ -48,8 +48,17 @@ const ( EVMContractChangeFull ) +type BurnerContractChange uint8 + +const ( + BurnerContractChangeNone BurnerContractChange = iota + BurnerContractChangeDeploy + BurnerContractChangeUpdate +) + type SystemContractChangesOptions struct { - EVM EVMContractChange + EVM EVMContractChange + Burner BurnerContractChange } func BurnerAddressForChain(chainID flow.ChainID) flow.Address { @@ -244,6 +253,20 @@ func SystemContractChanges(chainID flow.ChainID, options SystemContractChangesOp panic(fmt.Errorf("unsupported EVM contract change option: %d", options.EVM)) } + // Burner contract + if options.Burner == BurnerContractChangeUpdate { + contractChanges = append( + contractChanges, + StagedContract{ + Address: common.Address(flow.HexToAddress(env.BurnerAddress)), + Contract: Contract{ + Name: "Burner", + Code: coreContracts.Burner(), + }, + }, + ) + } + return contractChanges } From 19d2228a69e248348055afbaca907d55184e98e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 10:00:20 -0800 Subject: [PATCH 25/39] clean up --- .../execution_state_extract.go | 17 ++++++++++++++--- .../migrations/cadence_values_migration.go | 8 +++++++- .../migrations/staged_contracts_migration.go | 8 ++++++-- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 8be35a9fb0d..b8fcb4d2abb 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -82,7 +82,16 @@ func extractExecutionState( log.Info().Msg("init compactor") - compactor, err := complete.NewCompactor(led, diskWal, log, complete.DefaultCacheSize, checkpointDistance, checkpointsToKeep, atomic.NewBool(false), &metrics.NoopCollector{}) + compactor, err := complete.NewCompactor( + led, + diskWal, + log, + complete.DefaultCacheSize, + checkpointDistance, + checkpointsToKeep, + atomic.NewBool(false), + &metrics.NoopCollector{}, + ) if err != nil { return fmt.Errorf("cannot create compactor: %w", err) } @@ -125,13 +134,15 @@ func extractExecutionState( // create reporter reporter := reporters.NewExportReporter( log, - func() flow.StateCommitment { return targetHash }, + func() flow.StateCommitment { + return targetHash + }, ) newMigratedState := ledger.State(newTrie.RootHash()) err = reporter.Report(nil, newMigratedState) if err != nil { - log.Error().Err(err).Msgf("can not generate report for migrated state: %v", newMigratedState) + log.Err(err).Msgf("can not generate report for migrated state: %v", newMigratedState) } if len(outputPayloadFile) > 0 { diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index ab99b8eccaf..d1b55da02b3 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -146,6 +146,12 @@ func (m *CadenceBaseMigrator) MigrateAccount( reporter := newValueMigrationReporter(m.reporter, m.log, m.errorMessageHandler) + valueMigrations := m.valueMigrations( + migrationRuntime.Interpreter, + migrationRuntime.Accounts, + reporter, + ) + migration.Migrate( &migrations.AddressSliceIterator{ Addresses: []common.Address{ @@ -154,7 +160,7 @@ func (m *CadenceBaseMigrator) MigrateAccount( }, migration.NewValueMigrationsPathMigrator( reporter, - m.valueMigrations(migrationRuntime.Interpreter, migrationRuntime.Accounts, reporter)..., + valueMigrations..., ), ) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 8dcf2c573af..3f65df24ca6 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -246,7 +246,7 @@ func (m *StagedContractsMigration) MigrateAccount( if err != nil { m.log.Error().Err(err). Msgf( - "fail to update contract %s in account %s", + "failed to update contract %s in account %s", name, address.HexWithPrefix(), ) @@ -265,7 +265,11 @@ func (m *StagedContractsMigration) MigrateAccount( if len(contractUpdates) > 0 { var sb strings.Builder - _, _ = fmt.Fprintf(&sb, "failed to find all contract registers that need to be changed for address %s:\n", address) + _, _ = fmt.Fprintf( + &sb, + "failed to find all contract registers that need to be changed for address %s:\n", + address.HexWithPrefix(), + ) for registerID := range contractUpdates { _, _ = fmt.Fprintf(&sb, " - %s\n", flow.RegisterIDContractName(registerID)) } From 6c0259cc50a4855950f70d2bd80110ce36d288d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 10:03:00 -0800 Subject: [PATCH 26/39] log addresses for which contract updates are staged can be used for filtering the input state --- .../ledger/migrations/staged_contracts_migration.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 3f65df24ca6..a1047a660e1 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -107,6 +107,18 @@ func (m *StagedContractsMigration) InitMigration( } m.contractsByLocation[burnerLocation] = coreContracts.Burner() + addresses := make([]string, 0, len(m.stagedContracts)) + + for address := range m.stagedContracts { + addresses = append(addresses, address.String()) + } + + m.log.Debug().Msgf( + "initialized staged contracts migration for chain %s for addresses %s", + m.chainID, + strings.Join(addresses, ","), + ) + return nil } From f3d56ff382cbcaa46e4b1ed9f938fe04f271880a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 10:05:17 -0800 Subject: [PATCH 27/39] debug log account statistics: payload counts and byte size --- .../execution_state_extract.go | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index b8fcb4d2abb..375acf43020 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -15,6 +15,7 @@ import ( "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/ledger/common/hash" "github.com/onflow/flow-go/ledger/common/pathfinder" "github.com/onflow/flow-go/ledger/complete" @@ -217,6 +218,20 @@ func writeStatusFile(fileName string, e error) error { return err } +func ByteCountIEC(b int64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +} + func extractExecutionStateFromPayloads( log zerolog.Logger, dir string, @@ -242,6 +257,33 @@ func extractExecutionStateFromPayloads( log.Info().Msgf("read %d payloads", len(payloads)) + type accountInfo struct { + count int + size uint64 + } + payloadCountByAddress := make(map[string]accountInfo) + + for _, payload := range payloads { + registerID, payloadValue, err := convert.PayloadToRegister(payload) + if err != nil { + return fmt.Errorf("cannot convert payload to register: %w", err) + } + owner := registerID.Owner + accountInfo := payloadCountByAddress[owner] + accountInfo.count++ + accountInfo.size += uint64(len(payloadValue)) + payloadCountByAddress[owner] = accountInfo + } + + for address, info := range payloadCountByAddress { + log.Debug().Msgf( + "address %x has %d payloads and a total size of %s", + address, + info.count, + ByteCountIEC(int64(info.size)), + ) + } + migrations := newMigrations( log, dir, From 8be637d4005b436b4f8ec2cae1dd3a1cda6e554a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 10:05:38 -0800 Subject: [PATCH 28/39] fix address of burner contract --- cmd/util/ledger/migrations/staged_contracts_migration.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index a1047a660e1..9ad62d046e4 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -103,7 +103,7 @@ func (m *StagedContractsMigration) InitMigration( // Manually register burner contract burnerLocation := common.AddressLocation{ Name: "Burner", - Address: common.Address(m.chainID.Chain().ServiceAddress()), + Address: common.Address(BurnerAddressForChain(m.chainID)), } m.contractsByLocation[burnerLocation] = coreContracts.Burner() From 0cd52f21f300c34e0bd0cf8beac282c30c4d899a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 15:31:07 -0800 Subject: [PATCH 29/39] add option to prune random beacon history (for development purposes) --- cmd/util/cmd/execution-state-extract/cmd.go | 8 +- .../execution_state_extract.go | 6 ++ .../migrations/account_storage_migration.go | 74 +++++++++++++++ cmd/util/ledger/migrations/cadence.go | 30 +++++- .../migrations/cadence_values_migration.go | 40 +++++--- .../cadence_values_migration_test.go | 1 + .../ledger/migrations/deploy_migration.go | 51 ---------- cmd/util/ledger/migrations/prune_migration.go | 95 ++++++++++++++++++- .../migrations/transaction_migration.go | 59 ++++++++++++ 9 files changed, 292 insertions(+), 72 deletions(-) create mode 100644 cmd/util/ledger/migrations/account_storage_migration.go create mode 100644 cmd/util/ledger/migrations/transaction_migration.go diff --git a/cmd/util/cmd/execution-state-extract/cmd.go b/cmd/util/cmd/execution-state-extract/cmd.go index 138a5c38b0c..c526dc3664a 100644 --- a/cmd/util/cmd/execution-state-extract/cmd.go +++ b/cmd/util/cmd/execution-state-extract/cmd.go @@ -34,6 +34,7 @@ var ( flagValidateMigration bool flagAllowPartialStateFromPayloads bool flagSortPayloads bool + flagPrune bool flagLogVerboseValidationError bool flagDiffMigration bool flagLogVerboseDiff bool @@ -97,7 +98,10 @@ func init() { "allow input payload file containing partial state (e.g. not all accounts)") Cmd.Flags().BoolVar(&flagSortPayloads, "sort-payloads", true, - "sort payloads (generate deterministic output)") + "sort payloads (generate deterministic output; disable only for development purposes)") + + Cmd.Flags().BoolVar(&flagPrune, "prune", false, + "prune the state (for development purposes)") // If specified, the state will consist of payloads from the given input payload file. // If not specified, then the state will be extracted from the latest checkpoint file. @@ -341,6 +345,7 @@ func run(*cobra.Command, []string) { flagOutputPayloadFileName, exportedAddresses, flagSortPayloads, + flagPrune, ) } else { err = extractExecutionState( @@ -359,6 +364,7 @@ func run(*cobra.Command, []string) { flagOutputPayloadFileName, exportedAddresses, flagSortPayloads, + flagPrune, ) } diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 375acf43020..0868311eaf8 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -47,6 +47,7 @@ func extractExecutionState( outputPayloadFile string, exportPayloadsByAddresses []common.Address, sortPayloads bool, + prune bool, ) error { log.Info().Msg("init WAL") @@ -117,6 +118,7 @@ func extractExecutionState( evmContractChange, burnerContractChange, stagedContracts, + prune, ) newState := ledger.State(targetHash) @@ -248,6 +250,7 @@ func extractExecutionStateFromPayloads( outputPayloadFile string, exportPayloadsByAddresses []common.Address, sortPayloads bool, + prune bool, ) error { inputPayloadsFromPartialState, payloads, err := util.ReadPayloadFile(log, inputPayloadFile) @@ -295,6 +298,7 @@ func extractExecutionStateFromPayloads( evmContractChange, burnerContractChange, stagedContracts, + prune, ) payloads, err = migratePayloads(log, payloads, migrations) @@ -450,6 +454,7 @@ func newMigrations( evmContractChange migrators.EVMContractChange, burnerContractChange migrators.BurnerContractChange, stagedContracts []migrators.StagedContract, + prune bool, ) []ledger.Migration { if !runMigrations { return nil @@ -467,6 +472,7 @@ func newMigrations( evmContractChange, burnerContractChange, stagedContracts, + prune, ) migrations := make([]ledger.Migration, 0, len(namedMigrations)) diff --git a/cmd/util/ledger/migrations/account_storage_migration.go b/cmd/util/ledger/migrations/account_storage_migration.go new file mode 100644 index 00000000000..060d41cf168 --- /dev/null +++ b/cmd/util/ledger/migrations/account_storage_migration.go @@ -0,0 +1,74 @@ +package migrations + +import ( + "fmt" + + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/model/flow" +) + +func NewAccountStorageMigration( + address common.Address, + log zerolog.Logger, + migrate func(*runtime.Storage, *interpreter.Interpreter) error, +) ledger.Migration { + return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + + migrationRuntime, err := NewMigratorRuntime( + address, + payloads, + util.RuntimeInterfaceConfig{}, + ) + if err != nil { + return nil, fmt.Errorf("failed to create migrator runtime: %w", err) + } + + storage := migrationRuntime.Storage + inter := migrationRuntime.Interpreter + + err = migrate(storage, inter) + if err != nil { + return nil, fmt.Errorf("failed to migrate storage: %w", err) + } + + err = storage.Commit(inter, false) + if err != nil { + return nil, fmt.Errorf("failed to commit changes: %w", err) + } + + err = storage.CheckHealth() + if err != nil { + log.Err(err).Msg("storage health check failed") + } + + // finalize the transaction + result, err := migrationRuntime.TransactionState.FinalizeMainTransaction() + if err != nil { + return nil, fmt.Errorf("failed to finalize main transaction: %w", err) + } + + // Merge the changes to the original payloads. + expectedAddresses := map[flow.Address]struct{}{ + flow.Address(address): {}, + } + + newPayloads, err := MergeRegisterChanges( + migrationRuntime.Snapshot.Payloads, + result.WriteSet, + expectedAddresses, + nil, + log, + ) + if err != nil { + return nil, fmt.Errorf("failed to merge register changes: %w", err) + } + + return newPayloads, nil + } +} diff --git a/cmd/util/ledger/migrations/cadence.go b/cmd/util/ledger/migrations/cadence.go index 24bdb2db5c5..b0874c6a71b 100644 --- a/cmd/util/ledger/migrations/cadence.go +++ b/cmd/util/ledger/migrations/cadence.go @@ -287,8 +287,26 @@ func NewCadence1Migrations( evmContractChange EVMContractChange, burnerContractChange BurnerContractChange, stagedContracts []StagedContract, + prune bool, ) []NamedMigration { - return common.Concat( + + var migrations []NamedMigration + + if prune { + migration := NewCadence1PruneMigration(chainID, log) + if migration != nil { + migrations = append( + migrations, + NamedMigration{ + Name: "prune-migration", + Migrate: migration, + }, + ) + } + } + + migrations = append( + migrations, NewCadence1ContractsMigrations( log, nWorker, @@ -296,7 +314,11 @@ func NewCadence1Migrations( evmContractChange, burnerContractChange, stagedContracts, - ), + )..., + ) + + migrations = append( + migrations, NewCadence1ValueMigrations( log, rwf, @@ -304,6 +326,8 @@ func NewCadence1Migrations( chainID, diffMigrations, logVerboseDiff, - ), + )..., ) + + return migrations } diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index d1b55da02b3..88476186217 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -169,6 +169,11 @@ func (m *CadenceBaseMigrator) MigrateAccount( return nil, fmt.Errorf("failed to commit changes: %w", err) } + err = storage.CheckHealth() + if err != nil { + m.log.Err(err).Msg("storage health check failed") + } + // finalize the transaction result, err := migrationRuntime.TransactionState.FinalizeMainTransaction() if err != nil { @@ -177,7 +182,7 @@ func (m *CadenceBaseMigrator) MigrateAccount( // Merge the changes to the original payloads. expectedAddresses := map[flow.Address]struct{}{ - flow.ConvertAddress(address): {}, + flow.Address(address): {}, } newPayloads, err := MergeRegisterChanges( @@ -409,22 +414,27 @@ func (t *cadenceValueMigrationReporter) Migrated( }) } -func (t *cadenceValueMigrationReporter) Error( - storageKey interpreter.StorageKey, - storageMapKey interpreter.StorageMapKey, - migration string, - err error, -) { +func (t *cadenceValueMigrationReporter) Error(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, - message, - ) + var migrationErr migrations.StorageMigrationError + + if errors.As(err, &migrationErr) { + storageKey := migrationErr.StorageKey + storageMapKey := migrationErr.StorageMapKey + migration := migrationErr.Migration + + t.log.Error().Msgf( + "failed to run %s in account %s, domain %s, key %s: %s", + migration, + storageKey.Address, + storageKey.Key, + storageMapKey, + message, + ) + } else { + t.log.Error().Msgf("failed to run migration: %s", message) + } } func (t *cadenceValueMigrationReporter) MigratedPathCapability( diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 5a901124aed..b197a4cd75e 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -89,6 +89,7 @@ func TestCadenceValuesMigration(t *testing.T) { evmContractChange, burnerContractChange, stagedContracts, + false, ) for _, migration := range migrations { diff --git a/cmd/util/ledger/migrations/deploy_migration.go b/cmd/util/ledger/migrations/deploy_migration.go index a77ef67da6d..0bc82e6f08a 100644 --- a/cmd/util/ledger/migrations/deploy_migration.go +++ b/cmd/util/ledger/migrations/deploy_migration.go @@ -6,61 +6,10 @@ import ( coreContracts "github.com/onflow/flow-core-contracts/lib/go/contracts" "github.com/rs/zerolog" - "github.com/onflow/flow-go/cmd/util/ledger/util" - "github.com/onflow/flow-go/engine/execution/computation" - "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/model/flow" ) -func NewTransactionBasedMigration( - tx *flow.TransactionBody, - chainID flow.ChainID, - logger zerolog.Logger, - expectedWriteAddresses map[flow.Address]struct{}, -) ledger.Migration { - return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { - - options := computation.DefaultFVMOptions(chainID, false, false) - options = append(options, - fvm.WithContractDeploymentRestricted(false), - fvm.WithContractRemovalRestricted(false), - fvm.WithAuthorizationChecksEnabled(false), - fvm.WithSequenceNumberCheckAndIncrementEnabled(false), - fvm.WithTransactionFeesEnabled(false)) - ctx := fvm.NewContext(options...) - - snapshot, err := util.NewPayloadSnapshot(payloads) - if err != nil { - return nil, err - } - - vm := fvm.NewVirtualMachine() - - executionSnapshot, res, err := vm.Run( - ctx, - fvm.Transaction(tx, 0), - snapshot, - ) - - if err != nil { - return nil, err - } - - if res.Err != nil { - return nil, res.Err - } - - return MergeRegisterChanges( - snapshot.Payloads, - executionSnapshot.WriteSet, - expectedWriteAddresses, - nil, - logger, - ) - } -} - func NewDeploymentMigration( chainID flow.ChainID, contract Contract, diff --git a/cmd/util/ledger/migrations/prune_migration.go b/cmd/util/ledger/migrations/prune_migration.go index 3b694965568..4ed6467bea6 100644 --- a/cmd/util/ledger/migrations/prune_migration.go +++ b/cmd/util/ledger/migrations/prune_migration.go @@ -1,12 +1,20 @@ package migrations import ( + "fmt" + + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/rs/zerolog" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/model/flow" ) -// PruneMigration removes all the payloads with empty value +// PruneEmptyMigration removes all the payloads with empty value // this prunes the trie for values that has been deleted -func PruneMigration(payload []ledger.Payload) ([]ledger.Payload, error) { +func PruneEmptyMigration(payload []ledger.Payload) ([]ledger.Payload, error) { newPayload := make([]ledger.Payload, 0, len(payload)) for _, p := range payload { if len(p.Value()) > 0 { @@ -15,3 +23,86 @@ func PruneMigration(payload []ledger.Payload) ([]ledger.Payload, error) { } return newPayload, nil } + +// NewCadence1PruneMigration prunes some values from the service account in the Testnet state +func NewCadence1PruneMigration(chainID flow.ChainID, log zerolog.Logger) ledger.Migration { + if chainID != flow.Testnet { + return nil + } + + serviceAccountAddress := common.Address(chainID.Chain().ServiceAddress()) + + migrate := func(storage *runtime.Storage, inter *interpreter.Interpreter) error { + + err := pruneRandomBeaconHistory(storage, inter, log, serviceAccountAddress) + if err != nil { + return err + } + + return nil + } + + return NewAccountStorageMigration( + serviceAccountAddress, + log, + migrate, + ) +} + +func pruneRandomBeaconHistory( + storage *runtime.Storage, + inter *interpreter.Interpreter, + log zerolog.Logger, + serviceAccountAddress common.Address, +) error { + + log.Info().Msgf("pruning RandomBeaconHistory from service account %s", serviceAccountAddress) + + contracts := storage.GetStorageMap(serviceAccountAddress, runtime.StorageDomainContract, false) + if contracts == nil { + return fmt.Errorf("failed to get contracts storage map") + } + + randomBeaconHistory, ok := contracts.ReadValue( + nil, + interpreter.StringStorageMapKey("RandomBeaconHistory"), + ).(*interpreter.CompositeValue) + if !ok { + return fmt.Errorf("failed to read RandomBeaconHistory contract") + } + + randomSourceHistory, ok := randomBeaconHistory.GetField( + inter, + interpreter.EmptyLocationRange, + "randomSourceHistory", + ).(*interpreter.ArrayValue) + if !ok { + return fmt.Errorf("failed to read randomSourceHistory field") + } + + // Remove all but the last value from the randomSourceHistory + oldCount := randomSourceHistory.Count() + removalCount := oldCount - 1 + + for i := 0; i < removalCount; i++ { + randomSourceHistory.RemoveWithoutTransfer( + inter, + interpreter.EmptyLocationRange, + // NOTE: always remove the first element + 0, + ) + } + + // Check + if randomSourceHistory.Count() != 1 { + return fmt.Errorf("failed to prune randomSourceHistory") + } + + log.Info().Msgf( + "pruned %d entried from RandomBeaconHistory in service account %s", + removalCount, + serviceAccountAddress, + ) + + return nil +} diff --git a/cmd/util/ledger/migrations/transaction_migration.go b/cmd/util/ledger/migrations/transaction_migration.go new file mode 100644 index 00000000000..16ad691c4ed --- /dev/null +++ b/cmd/util/ledger/migrations/transaction_migration.go @@ -0,0 +1,59 @@ +package migrations + +import ( + "github.com/rs/zerolog" + + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/engine/execution/computation" + "github.com/onflow/flow-go/fvm" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/model/flow" +) + +func NewTransactionBasedMigration( + tx *flow.TransactionBody, + chainID flow.ChainID, + logger zerolog.Logger, + expectedWriteAddresses map[flow.Address]struct{}, +) ledger.Migration { + return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) { + + options := computation.DefaultFVMOptions(chainID, false, false) + options = append(options, + fvm.WithContractDeploymentRestricted(false), + fvm.WithContractRemovalRestricted(false), + fvm.WithAuthorizationChecksEnabled(false), + fvm.WithSequenceNumberCheckAndIncrementEnabled(false), + fvm.WithTransactionFeesEnabled(false)) + ctx := fvm.NewContext(options...) + + snapshot, err := util.NewPayloadSnapshot(payloads) + if err != nil { + return nil, err + } + + vm := fvm.NewVirtualMachine() + + executionSnapshot, res, err := vm.Run( + ctx, + fvm.Transaction(tx, 0), + snapshot, + ) + + if err != nil { + return nil, err + } + + if res.Err != nil { + return nil, res.Err + } + + return MergeRegisterChanges( + snapshot.Payloads, + executionSnapshot.WriteSet, + expectedWriteAddresses, + nil, + logger, + ) + } +} From 1b2fddd9d8e75885045d13ac31cdc13fa8f1431b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Thu, 7 Mar 2024 17:12:37 -0800 Subject: [PATCH 30/39] improve messages --- cmd/util/ledger/migrations/prune_migration.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/util/ledger/migrations/prune_migration.go b/cmd/util/ledger/migrations/prune_migration.go index 4ed6467bea6..780f526ef07 100644 --- a/cmd/util/ledger/migrations/prune_migration.go +++ b/cmd/util/ledger/migrations/prune_migration.go @@ -56,7 +56,7 @@ func pruneRandomBeaconHistory( serviceAccountAddress common.Address, ) error { - log.Info().Msgf("pruning RandomBeaconHistory from service account %s", serviceAccountAddress) + log.Info().Msgf("pruning RandomBeaconHistory in service account %s", serviceAccountAddress) contracts := storage.GetStorageMap(serviceAccountAddress, runtime.StorageDomainContract, false) if contracts == nil { @@ -99,7 +99,7 @@ func pruneRandomBeaconHistory( } log.Info().Msgf( - "pruned %d entried from RandomBeaconHistory in service account %s", + "pruned %d entries in RandomBeaconHistory in service account %s", removalCount, serviceAccountAddress, ) From 0b4d0cf6bb8a6625c4c51ab3e7ef12a030150cf8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 09:05:47 -0800 Subject: [PATCH 31/39] enable zero copy for payload decoding --- cmd/util/ledger/util/payload_file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/util/ledger/util/payload_file.go b/cmd/util/ledger/util/payload_file.go index 76d80a79cf5..1aad4a1bc10 100644 --- a/cmd/util/ledger/util/payload_file.go +++ b/cmd/util/ledger/util/payload_file.go @@ -314,7 +314,7 @@ func ReadPayloadFile(logger zerolog.Logger, payloadFile string) (bool, []*ledger return false, nil, fmt.Errorf("can't decode payload in CBOR: %w", err) } - payload, err := ledger.DecodePayloadWithoutPrefix(rawPayload, false, payloadEncodingVersion) + payload, err := ledger.DecodePayloadWithoutPrefix(rawPayload, true, payloadEncodingVersion) if err != nil { return false, nil, fmt.Errorf("can't decode payload 0x%x: %w", rawPayload, err) } From bd4636df18fea3ebbbdb7ec48ce5d7963144fc12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 12:35:43 -0800 Subject: [PATCH 32/39] add missing argument --- .../cmd/execution-state-extract/execution_state_extract_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go index bf0569f9f1f..82ba3bac242 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract_test.go @@ -82,6 +82,7 @@ func TestExtractExecutionState(t *testing.T) { "", nil, false, + false, ) require.Error(t, err) }) From aff00e93ad6bd4d231a77ed269dc23ac3e97877b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 13:14:19 -0800 Subject: [PATCH 33/39] remove debug log --- cmd/util/ledger/migrations/staged_contracts_migration.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 9ad62d046e4..3943ec1920e 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -113,12 +113,6 @@ func (m *StagedContractsMigration) InitMigration( addresses = append(addresses, address.String()) } - m.log.Debug().Msgf( - "initialized staged contracts migration for chain %s for addresses %s", - m.chainID, - strings.Join(addresses, ","), - ) - return nil } From ddfa6d501aece82d166be82a387d9f1794aa8804 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 13:58:47 -0800 Subject: [PATCH 34/39] remove TODO --- cmd/util/ledger/migrations/cadence_values_migration_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index b197a4cd75e..d7d4be78d11 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -59,9 +59,7 @@ func TestCadenceValuesMigration(t *testing.T) { logWriter := &writer{} logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) - // TODO: >1 breaks atree storage map iteration - // and requires LinkValueMigration.LinkValueMigration to be thread-safe - const nWorker = 1 + const nWorker = 2 const chainID = flow.Emulator // TODO: EVM contract is not deployed in snapshot yet, so can't update it From b12ca8c9e093e00cb76d9297b98db109c6a0b051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 13:58:57 -0800 Subject: [PATCH 35/39] lint --- cmd/util/ledger/migrations/staged_contracts_migration.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cmd/util/ledger/migrations/staged_contracts_migration.go b/cmd/util/ledger/migrations/staged_contracts_migration.go index 3943ec1920e..fe3e5f9ad98 100644 --- a/cmd/util/ledger/migrations/staged_contracts_migration.go +++ b/cmd/util/ledger/migrations/staged_contracts_migration.go @@ -107,12 +107,6 @@ func (m *StagedContractsMigration) InitMigration( } m.contractsByLocation[burnerLocation] = coreContracts.Burner() - addresses := make([]string, 0, len(m.stagedContracts)) - - for address := range m.stagedContracts { - addresses = append(addresses, address.String()) - } - return nil } From 0c17a58d1b2a010062a2376c2b929095194556da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 14:15:28 -0800 Subject: [PATCH 36/39] test that migration of bootstrapped TN state succeeds --- .../cadence_values_migration_test.go | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index d7d4be78d11..44b4d0f41f3 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -697,3 +697,50 @@ func (r *testReportWriter) Write(entry any) { } func (r *testReportWriter) Close() {} + +func TestBootstrappedStateMigration(t *testing.T) { + t.Parallel() + + rwf := &testReportWriterFactory{} + + logWriter := &writer{} + logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) + + const nWorker = 2 + + const chainID = flow.Emulator + // TODO: EVM contract is not deployed in snapshot yet, so can't update it + const evmContractChange = EVMContractChangeNone + + const burnerContractChange = BurnerContractChangeUpdate + + payloads, err := newBootstrapPayloads(chainID) + require.NoError(t, err) + + migrations := NewCadence1Migrations( + logger, + rwf, + nWorker, + chainID, + false, + false, + evmContractChange, + burnerContractChange, + nil, + false, + ) + + for _, migration := range migrations { + payloads, err = migration.Migrate(payloads) + require.NoError( + t, + err, + "migration `%s` failed, logs: %v", + migration.Name, + logWriter.logs, + ) + } + + // Check error logs. + require.Empty(t, logWriter.logs) +} From 3f109068fae79b065353c84d4ae096835f1bfd88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 14:47:03 -0800 Subject: [PATCH 37/39] add test case for program loading error --- .../cadence_values_migration_test.go | 123 +++++++++++++++++- 1 file changed, 120 insertions(+), 3 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index 44b4d0f41f3..cec2fdc3aac 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -7,10 +7,8 @@ import ( "sync" "testing" - "github.com/rs/zerolog" - _ "github.com/glebarez/go-sqlite" - + "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -744,3 +742,122 @@ func TestBootstrappedStateMigration(t *testing.T) { // Check error logs. require.Empty(t, logWriter.logs) } + +func TestProgramLoadingError(t *testing.T) { + t.Parallel() + + rwf := &testReportWriterFactory{} + + logWriter := &writer{} + logger := zerolog.New(logWriter).Level(zerolog.ErrorLevel) + + const nWorker = 2 + + const chainID = flow.Emulator + chain := chainID.Chain() + + testAddress := common.Address(chain.ServiceAddress()) + + // TODO: EVM contract is not deployed in snapshot yet, so can't update it + const evmContractChange = EVMContractChangeNone + + const burnerContractChange = BurnerContractChangeUpdate + + payloads, err := newBootstrapPayloads(chainID) + require.NoError(t, err) + + runtime, err := NewMigratorRuntime( + testAddress, + payloads, + util.RuntimeInterfaceConfig{}, + ) + require.NoError(t, err) + + storage := runtime.Storage + + storageMap := storage.GetStorageMap( + testAddress, + common.PathDomainStorage.Identifier(), + true, + ) + + const nonExistingContractName = "NonExistingContract" + nonExistingContractLocation := common.NewAddressLocation(nil, testAddress, nonExistingContractName) + + const nonExistingStructQualifiedIdentifier = nonExistingContractName + ".NonExistingStruct" + + capabilityValue := interpreter.NewUnmeteredCapabilityValue( + 0, + interpreter.AddressValue(testAddress), + interpreter.NewReferenceStaticType( + nil, + interpreter.UnauthorizedAccess, + interpreter.NewCompositeStaticType( + nil, + nonExistingContractLocation, + nonExistingStructQualifiedIdentifier, + nonExistingContractLocation.TypeID(nil, nonExistingStructQualifiedIdentifier), + ), + ), + ) + + storageMap.WriteValue( + runtime.Interpreter, + interpreter.StringStorageMapKey("test"), + capabilityValue, + ) + + err = storage.Commit(runtime.Interpreter, false) + require.NoError(t, err) + + // finalize the transaction + result, err := runtime.TransactionState.FinalizeMainTransaction() + require.NoError(t, err) + + // Merge the changes to the original payloads. + + expectedAddresses := map[flow.Address]struct{}{ + flow.Address(testAddress): {}, + } + + payloads, err = MergeRegisterChanges( + runtime.Snapshot.Payloads, + result.WriteSet, + expectedAddresses, + nil, + logger, + ) + require.NoError(t, err) + + // Migrate + + migrations := NewCadence1Migrations( + logger, + rwf, + nWorker, + chainID, + false, + false, + evmContractChange, + burnerContractChange, + nil, + false, + ) + + for _, migration := range migrations { + payloads, err = migration.Migrate(payloads) + require.NoError( + t, + err, + "migration `%s` failed, logs: %v", + migration.Name, + logWriter.logs, + ) + } + + // Check error logs + require.Len(t, logWriter.logs, 1) + + log := logWriter.logs[0] + require.Contains(t, log, "error getting program") +} From c2d79ebc6257fd82c79dbbc793cc4036a8ee4a38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 15:12:17 -0800 Subject: [PATCH 38/39] program loading errors should include the parsing/checking error, but no stack trace --- .../migrations/cadence_values_migration.go | 46 +++++++++++-------- .../cadence_values_migration_test.go | 35 +++++++++++--- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/cmd/util/ledger/migrations/cadence_values_migration.go b/cmd/util/ledger/migrations/cadence_values_migration.go index 88476186217..e5c9a76f212 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration.go +++ b/cmd/util/ledger/migrations/cadence_values_migration.go @@ -15,6 +15,7 @@ import ( "github.com/onflow/cadence/migrations/string_normalization" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" + cadenceErrors "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/rs/zerolog" @@ -362,7 +363,7 @@ type errorMessageHandler struct { reportedProgramLoadingErrors sync.Map } -func (t *errorMessageHandler) FormatError(err error) string { +func (t *errorMessageHandler) FormatError(err error) (message string, showStack bool) { // Only report program loading errors once, // omit full error message for subsequent occurrences @@ -372,11 +373,13 @@ func (t *errorMessageHandler) FormatError(err error) string { location := programLoadingError.Location _, ok := t.reportedProgramLoadingErrors.LoadOrStore(location, struct{}{}) if ok { - return "error getting program" + return "error getting program", false } + + return err.Error(), false } - return err.Error() + return err.Error(), true } // cadenceValueMigrationReporter is the reporter for cadence value migrations @@ -415,26 +418,31 @@ func (t *cadenceValueMigrationReporter) Migrated( } func (t *cadenceValueMigrationReporter) Error(err error) { - message := t.errorMessageHandler.FormatError(err) var migrationErr migrations.StorageMigrationError - if errors.As(err, &migrationErr) { - storageKey := migrationErr.StorageKey - storageMapKey := migrationErr.StorageMapKey - migration := migrationErr.Migration - - t.log.Error().Msgf( - "failed to run %s in account %s, domain %s, key %s: %s", - migration, - storageKey.Address, - storageKey.Key, - storageMapKey, - message, - ) - } else { - t.log.Error().Msgf("failed to run migration: %s", message) + if !errors.As(err, &migrationErr) { + panic(cadenceErrors.NewUnreachableError()) } + + message, showStack := t.errorMessageHandler.FormatError(migrationErr.Err) + + storageKey := migrationErr.StorageKey + storageMapKey := migrationErr.StorageMapKey + migration := migrationErr.Migration + + if showStack && len(migrationErr.Stack) > 0 { + message = fmt.Sprintf("%s\n%s", message, migrationErr.Stack) + } + + t.log.Error().Msgf( + "failed to run %s in account %s, domain %s, key %s: %s", + migration, + storageKey.Address, + storageKey.Key, + storageMapKey, + message, + ) } func (t *cadenceValueMigrationReporter) MigratedPathCapability( diff --git a/cmd/util/ledger/migrations/cadence_values_migration_test.go b/cmd/util/ledger/migrations/cadence_values_migration_test.go index cec2fdc3aac..832efb65577 100644 --- a/cmd/util/ledger/migrations/cadence_values_migration_test.go +++ b/cmd/util/ledger/migrations/cadence_values_migration_test.go @@ -2,6 +2,7 @@ package migrations import ( _ "embed" + "encoding/json" "fmt" "io" "sync" @@ -743,7 +744,7 @@ func TestBootstrappedStateMigration(t *testing.T) { require.Empty(t, logWriter.logs) } -func TestProgramLoadingError(t *testing.T) { +func TestProgramParsingError(t *testing.T) { t.Parallel() rwf := &testReportWriterFactory{} @@ -781,10 +782,10 @@ func TestProgramLoadingError(t *testing.T) { true, ) - const nonExistingContractName = "NonExistingContract" - nonExistingContractLocation := common.NewAddressLocation(nil, testAddress, nonExistingContractName) + const contractName = "C" + contractLocation := common.NewAddressLocation(nil, testAddress, contractName) - const nonExistingStructQualifiedIdentifier = nonExistingContractName + ".NonExistingStruct" + const nonExistingStructQualifiedIdentifier = contractName + ".NonExistingStruct" capabilityValue := interpreter.NewUnmeteredCapabilityValue( 0, @@ -794,9 +795,9 @@ func TestProgramLoadingError(t *testing.T) { interpreter.UnauthorizedAccess, interpreter.NewCompositeStaticType( nil, - nonExistingContractLocation, + contractLocation, nonExistingStructQualifiedIdentifier, - nonExistingContractLocation.TypeID(nil, nonExistingStructQualifiedIdentifier), + contractLocation.TypeID(nil, nonExistingStructQualifiedIdentifier), ), ), ) @@ -829,6 +830,17 @@ func TestProgramLoadingError(t *testing.T) { ) require.NoError(t, err) + // Set the code for the old program + + payloads = append( + payloads, + newContractPayload( + testAddress, + contractName, + []byte(`pub contract C {}`), + ), + ) + // Migrate migrations := NewCadence1Migrations( @@ -859,5 +871,14 @@ func TestProgramLoadingError(t *testing.T) { require.Len(t, logWriter.logs, 1) log := logWriter.logs[0] - require.Contains(t, log, "error getting program") + + var entry struct { + Message string `json:"message"` + } + + err = json.Unmarshal([]byte(log), &entry) + require.NoError(t, err) + + assert.Contains(t, entry.Message, "`pub` is no longer a valid access keyword") + assert.NotContains(t, entry.Message, "runtime/debug.Stack()") } From afeb4364fa58f40be32bec75b41b49e5b1ff6d2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 8 Mar 2024 15:51:17 -0800 Subject: [PATCH 39/39] avoid account info computation if it is not logged --- .../execution_state_extract.go | 52 +++++++++++-------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/cmd/util/cmd/execution-state-extract/execution_state_extract.go b/cmd/util/cmd/execution-state-extract/execution_state_extract.go index 0868311eaf8..f5c14ec7eaa 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -260,31 +260,34 @@ func extractExecutionStateFromPayloads( log.Info().Msgf("read %d payloads", len(payloads)) - type accountInfo struct { - count int - size uint64 - } - payloadCountByAddress := make(map[string]accountInfo) + if log.Debug().Enabled() { - for _, payload := range payloads { - registerID, payloadValue, err := convert.PayloadToRegister(payload) - if err != nil { - return fmt.Errorf("cannot convert payload to register: %w", err) + type accountInfo struct { + count int + size uint64 + } + payloadCountByAddress := make(map[string]accountInfo) + + for _, payload := range payloads { + registerID, payloadValue, err := convert.PayloadToRegister(payload) + if err != nil { + return fmt.Errorf("cannot convert payload to register: %w", err) + } + owner := registerID.Owner + accountInfo := payloadCountByAddress[owner] + accountInfo.count++ + accountInfo.size += uint64(len(payloadValue)) + payloadCountByAddress[owner] = accountInfo } - owner := registerID.Owner - accountInfo := payloadCountByAddress[owner] - accountInfo.count++ - accountInfo.size += uint64(len(payloadValue)) - payloadCountByAddress[owner] = accountInfo - } - for address, info := range payloadCountByAddress { - log.Debug().Msgf( - "address %x has %d payloads and a total size of %s", - address, - info.count, - ByteCountIEC(int64(info.size)), - ) + for address, info := range payloadCountByAddress { + log.Debug().Msgf( + "address %x has %d payloads and a total size of %s", + address, + info.count, + ByteCountIEC(int64(info.size)), + ) + } } migrations := newMigrations( @@ -460,6 +463,8 @@ func newMigrations( return nil } + log.Info().Msgf("initializing migrations") + rwf := reporters.NewReportFileWriterFactory(dir, log) namedMigrations := migrators.NewCadence1Migrations( @@ -479,5 +484,8 @@ func newMigrations( for _, migration := range namedMigrations { migrations = append(migrations, migration.Migrate) } + + log.Info().Msgf("initialized migrations") + return migrations }