From 349f70eae41fc1168872634a49685c5145481f86 Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Wed, 16 Aug 2023 18:48:00 +0200 Subject: [PATCH] Atree migration initial commit atree register migration interpreter fix atree migration interpreter pool atree migration create snapshot once Atree migrator graceful shutdown Atree migration GetOrLoadProgram fix atree migration add extra logs atree migration timer for long migrations Atree migration overhaul logging atree migration change interpreter pool design atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree migration trying to get program cache to actually work atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix atree register migration fix --- cmd/util/cmd/checkpoint-list-tries/cmd.go | 2 +- .../execution_state_extract.go | 13 +- .../migrations/account_based_migration.go | 351 +++++++++----- .../ledger/migrations/account_migration.go | 21 +- .../migrations/atree_register_migration.go | 442 ++++++++++++++++++ .../atree_register_migration_test.go | 132 ++++++ .../migrations/storage_fees_migration.go | 5 +- .../test-data/bootstrapped_v0.31/.gitignore | 4 + .../test-data/bootstrapped_v0.31/00000000 | Bin 0 -> 425984 bytes .../reporters/fungible_token_tracker.go | 4 +- .../util/migration_runtime_interface.go | 307 ++++++++++++ cmd/util/ledger/util/nop_meter.go | 44 ++ .../{migrations/utils.go => util/util.go} | 44 +- go.mod | 2 +- go.sum | 4 +- insecure/go.mod | 2 +- insecure/go.sum | 8 +- integration/go.mod | 2 +- integration/go.sum | 4 +- ledger/complete/checkpoint_benchmark_test.go | 6 +- ledger/complete/wal/checkpoint_v5_test.go | 4 +- .../complete/wal/checkpoint_v6_leaf_reader.go | 4 +- ledger/complete/wal/checkpoint_v6_reader.go | 18 +- ledger/complete/wal/checkpoint_v6_test.go | 34 +- ledger/complete/wal/checkpoint_v6_writer.go | 20 +- ledger/complete/wal/checkpointer.go | 22 +- ledger/complete/wal/checkpointer_test.go | 6 +- .../wal/checkpointer_versioning_test.go | 6 +- ledger/complete/wal/syncrename.go | 2 +- ledger/complete/wal/syncrename_test.go | 2 +- module/util/log.go | 88 +++- module/util/log_test.go | 57 ++- 32 files changed, 1425 insertions(+), 235 deletions(-) create mode 100644 cmd/util/ledger/migrations/atree_register_migration.go create mode 100644 cmd/util/ledger/migrations/atree_register_migration_test.go create mode 100644 cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore create mode 100644 cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/00000000 create mode 100644 cmd/util/ledger/util/migration_runtime_interface.go create mode 100644 cmd/util/ledger/util/nop_meter.go rename cmd/util/ledger/{migrations/utils.go => util/util.go} (68%) diff --git a/cmd/util/cmd/checkpoint-list-tries/cmd.go b/cmd/util/cmd/checkpoint-list-tries/cmd.go index 26e5ca01c8b..830075bc5c8 100644 --- a/cmd/util/cmd/checkpoint-list-tries/cmd.go +++ b/cmd/util/cmd/checkpoint-list-tries/cmd.go @@ -29,7 +29,7 @@ func init() { func run(*cobra.Command, []string) { log.Info().Msgf("loading checkpoint %v", flagCheckpoint) - tries, err := wal.LoadCheckpoint(flagCheckpoint, &log.Logger) + tries, err := wal.LoadCheckpoint(flagCheckpoint, log.Logger) if err != nil { log.Fatal().Err(err).Msg("error while loading checkpoint") } 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 dbcb82d53e5..37f0fee3073 100644 --- a/cmd/util/cmd/execution-state-extract/execution_state_extract.go +++ b/cmd/util/cmd/execution-state-extract/execution_state_extract.go @@ -9,6 +9,7 @@ import ( "github.com/rs/zerolog" "go.uber.org/atomic" + migrators "github.com/onflow/flow-go/cmd/util/ledger/migrations" "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/hash" @@ -82,10 +83,15 @@ func extractExecutionState( <-compactor.Done() }() - var migrations []ledger.Migration + var migrations = []ledger.Migration{ + migrators.MigrateAtreeRegisters( + log, + reporters.NewReportFileWriterFactory(dir, log), + nWorker), + } newState := ledger.State(targetHash) - // migrate the trie if there migrations + // migrate the trie if there are migrations newTrie, err := led.MigrateAt( newState, migrations, @@ -97,7 +103,8 @@ func extractExecutionState( } // create reporter - reporter := reporters.NewExportReporter(log, + reporter := reporters.NewExportReporter( + log, func() flow.StateCommitment { return targetHash }, ) diff --git a/cmd/util/ledger/migrations/account_based_migration.go b/cmd/util/ledger/migrations/account_based_migration.go index 0172e04737f..cb71887e934 100644 --- a/cmd/util/ledger/migrations/account_based_migration.go +++ b/cmd/util/ledger/migrations/account_based_migration.go @@ -1,189 +1,316 @@ package migrations import ( + "container/heap" + "context" "fmt" + "io" + "sync" + "time" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/module/util" + moduleUtil "github.com/onflow/flow-go/module/util" ) -// PayloadToAccount takes a payload and return: -// - (address, true, nil) if the payload is for an account, the account address is returned -// - ("", false, nil) if the payload is not for an account -// - ("", false, err) if running into any exception -func PayloadToAccount(p ledger.Payload) (string, bool, error) { - k, err := p.Key() - if err != nil { - return "", false, fmt.Errorf("could not find key for payload: %w", err) - } - id, err := KeyToRegisterID(k) - if err != nil { - return "", false, fmt.Errorf("error converting key to register ID") - } - if len([]byte(id.Owner)) != flow.AddressLength { - return "", false, nil - } - return id.Owner, true, nil +// AccountMigrator takes all the Payloads that belong to the given account +// and return the migrated Payloads +type AccountMigrator interface { + MigratePayloads(ctx context.Context, address common.Address, payloads []ledger.Payload) ([]ledger.Payload, error) } -// PayloadGroup groups payloads by account. -// For global payloads, it's stored under NonAccountPayloads field -type PayloadGroup struct { - NonAccountPayloads []ledger.Payload - Accounts map[string][]ledger.Payload -} +// AccountMigratorFactory creates an AccountMigrator +type AccountMigratorFactory func(allPayloads []ledger.Payload, nWorker int) (AccountMigrator, error) -// PayloadGrouping is a reducer function that adds the given payload to the corresponding -// group under its account -func PayloadGrouping(groups *PayloadGroup, payload ledger.Payload) (*PayloadGroup, error) { - address, isAccount, err := PayloadToAccount(payload) - if err != nil { - return nil, err - } - - if isAccount { - groups.Accounts[address] = append(groups.Accounts[address], payload) - } else { - groups.NonAccountPayloads = append(groups.NonAccountPayloads, payload) +func CreateAccountBasedMigration( + log zerolog.Logger, + migratorFactory AccountMigratorFactory, + nWorker int, +) func(payloads []ledger.Payload) ([]ledger.Payload, error) { + return func(payloads []ledger.Payload) ([]ledger.Payload, error) { + return MigrateByAccount( + log, + migratorFactory, + payloads, + nWorker, + ) } - - return groups, nil } -// AccountMigrator takes all the payloads that belong to the given account -// and return the migrated payloads -type AccountMigrator interface { - MigratePayloads(account string, payloads []ledger.Payload) ([]ledger.Payload, error) -} - -// MigrateByAccount teaks a migrator function and all the payloads, and return the migrated payloads -func MigrateByAccount(migrator AccountMigrator, allPayloads []ledger.Payload, nWorker int) ( - []ledger.Payload, error) { - groups := &PayloadGroup{ +// MigrateByAccount teaks a migrator function and all the Payloads, and return the migrated Payloads +func MigrateByAccount( + log zerolog.Logger, + migratorFactory AccountMigratorFactory, + allPayloads []ledger.Payload, + nWorker int, +) ( + []ledger.Payload, + error, +) { + groups := &payloadGroup{ NonAccountPayloads: make([]ledger.Payload, 0), - Accounts: make(map[string][]ledger.Payload), + Accounts: make(map[common.Address][]ledger.Payload), } - log.Info().Msgf("start grouping for a total of %v payloads", len(allPayloads)) + log.Info().Msgf("start grouping for a total of %v Payloads", len(allPayloads)) var err error - logGrouping := util.LogProgress("grouping payload", len(allPayloads), &log.Logger) + logGrouping := moduleUtil.LogProgress("grouping payload", len(allPayloads), log) for i, payload := range allPayloads { - groups, err = PayloadGrouping(groups, payload) + groups, err = payloadGrouping(groups, payload) if err != nil { return nil, err } logGrouping(i) } - log.Info().Msgf("finish grouping for payloads by account: %v groups in total, %v NonAccountPayloads", + log.Info().Msgf("finish grouping for Payloads by account: %v groups in total, %v NonAccountPayloads", len(groups.Accounts), len(groups.NonAccountPayloads)) - // migrate the payloads under accounts - migrated, err := MigrateGroupConcurrently(migrator, groups.Accounts, nWorker) + migrator, err := migratorFactory(allPayloads, nWorker) + if err != nil { + return nil, fmt.Errorf("could not create account migrator: %w", err) + } + + log.Info(). + Str("migrator", fmt.Sprintf("%T", migrator)). + Int("nWorker", nWorker). + Msgf("created migrator") + + defer func() { + // close the migrator if it's a Closer + if migrator, ok := migrator.(io.Closer); ok { + if err := migrator.Close(); err != nil { + log.Error().Err(err).Msg("error closing migrator") + } + } + }() + + // migrate the Payloads under accounts + migrated, err := MigrateGroupConcurrently(log, migrator, groups.Accounts, nWorker) if err != nil { return nil, fmt.Errorf("could not migrate group: %w", err) } - log.Info().Msgf("finished migrating payloads for %v account", len(groups.Accounts)) + log.Info().Msgf("finished migrating Payloads for %v account", len(groups.Accounts)) // add the non accounts which don't need to be migrated migrated = append(migrated, groups.NonAccountPayloads...) - log.Info().Msgf("finished migrating all account based payloads, total migrated payloads: %v", len(migrated)) + log.Info().Msgf("finished migrating all account based Payloads, total migrated Payloads: %v", len(migrated)) return migrated, nil } -// MigrateGroupSequentially migrate the payloads in the given payloadsByAccount map which +// MigrateGroupConcurrently migrate the Payloads in the given payloadsByAccount map which // using the migrator -func MigrateGroupSequentially( +// It's similar to MigrateGroupSequentially, except it will migrate different groups concurrently +func MigrateGroupConcurrently( + log zerolog.Logger, migrator AccountMigrator, - payloadsByAccount map[string][]ledger.Payload, -) ( - []ledger.Payload, error) { + payloadsByAccount map[common.Address][]ledger.Payload, + nWorker int, +) ([]ledger.Payload, error) { + + const logTopNDurations = 20 + + ctx := context.Background() + ctx, cancel := context.WithCancel(ctx) + defer cancel() + + jobs := make(chan jobMigrateAccountGroup, len(payloadsByAccount)) + + wg := sync.WaitGroup{} + wg.Add(nWorker) + resultCh := make(chan *migrationResult, len(payloadsByAccount)) + for i := 0; i < int(nWorker); i++ { + go func() { + + defer wg.Done() + + for { + select { + case <-ctx.Done(): + return + case job, ok := <-jobs: + if !ok { + return + } + start := time.Now() + + accountMigrated, err := migrator.MigratePayloads(ctx, job.Address, job.Payloads) + + resultCh <- &migrationResult{ + migrationDuration: migrationDuration{ + Address: job.Address, + Duration: time.Since(start), + }, + Migrated: accountMigrated, + Err: err, + } + } + } + }() + } - logAccount := util.LogProgress("processing account group", len(payloadsByAccount), &log.Logger) + go func() { + for address, payloads := range payloadsByAccount { + select { + case <-ctx.Done(): + return + case jobs <- jobMigrateAccountGroup{ + Address: address, + Payloads: payloads, + }: + } + } + }() + + // read job results + logAccount := moduleUtil.LogProgress("processing account group", len(payloadsByAccount), log) - i := 0 migrated := make([]ledger.Payload, 0) - for address, payloads := range payloadsByAccount { - accountMigrated, err := migrator.MigratePayloads(address, payloads) + + durations := &migrationDurations{} + var err error + for i := 0; i < len(payloadsByAccount); i++ { + result := <-resultCh + err = result.Err if err != nil { - return nil, fmt.Errorf("could not migrate for account address %v: %w", address, err) + cancel() + log.Error(). + Err(err). + Msg("error migrating account") + break + } + + if durations.Len() < logTopNDurations || result.Duration > (*durations)[0].Duration { + if durations.Len() == logTopNDurations { + heap.Pop(durations) // remove the element with the smallest duration + } + heap.Push(durations, result.migrationDuration) } + accountMigrated := result.Migrated migrated = append(migrated, accountMigrated...) logAccount(i) - i++ + } + close(jobs) + + // make sure to exit all workers before returning from this function + // so that the migrator can be closed properly + wg.Wait() + + log.Info(). + Array("top_longest_migrations", durations.Array()). + Msgf("Top longest migrations") + + if err != nil { + return nil, fmt.Errorf("fail to migrate payload: %w", err) } return migrated, nil } type jobMigrateAccountGroup struct { - Account string + Address common.Address Payloads []ledger.Payload } type migrationResult struct { + migrationDuration + Migrated []ledger.Payload Err error } -// MigrateGroupConcurrently migrate the payloads in the given payloadsByAccount map which -// using the migrator -// It's similar to MigrateGroupSequentially, except it will migrate different groups concurrently -func MigrateGroupConcurrently( - migrator AccountMigrator, - payloadsByAccount map[string][]ledger.Payload, - nWorker int, -) ( - []ledger.Payload, error) { +// payloadToAccount takes a payload and return: +// - (address, true, nil) if the payload is for an account, the account address is returned +// - ("", false, nil) if the payload is not for an account +// - ("", false, err) if running into any exception +func payloadToAccount(p ledger.Payload) (common.Address, bool, error) { + k, err := p.Key() + if err != nil { + return common.Address{}, false, fmt.Errorf("could not find key for payload: %w", err) + } - jobs := make(chan jobMigrateAccountGroup, len(payloadsByAccount)) - go func() { - for account, payloads := range payloadsByAccount { - jobs <- jobMigrateAccountGroup{ - Account: account, - Payloads: payloads, - } - } - close(jobs) - }() + id, err := util.KeyToRegisterID(k) + if err != nil { + return common.Address{}, false, fmt.Errorf("error converting key to register ID") + } - resultCh := make(chan *migrationResult) - for i := 0; i < int(nWorker); i++ { - go func() { - for job := range jobs { - accountMigrated, err := migrator.MigratePayloads(job.Account, job.Payloads) - resultCh <- &migrationResult{ - Migrated: accountMigrated, - Err: err, - } - } - }() + if len([]byte(id.Owner)) != flow.AddressLength { + return common.Address{}, false, nil } - // read job results - logAccount := util.LogProgress("processing account group", len(payloadsByAccount), &log.Logger) + address, err := common.BytesToAddress([]byte(id.Owner)) + if err != nil { + return common.Address{}, false, fmt.Errorf("invalid account address: %w", err) + } - migrated := make([]ledger.Payload, 0) + return address, true, nil +} - for i := 0; i < len(payloadsByAccount); i++ { - result := <-resultCh - if result.Err != nil { - return nil, fmt.Errorf("fail to migrate payload: %w", result.Err) - } +// payloadGroup groups Payloads by account. +// For global Payloads, it's stored under NonAccountPayloads field +type payloadGroup struct { + NonAccountPayloads []ledger.Payload + Accounts map[common.Address][]ledger.Payload +} - accountMigrated := result.Migrated - migrated = append(migrated, accountMigrated...) - logAccount(i) +// payloadGrouping is a reducer function that adds the given payload to the corresponding +// group under its account +func payloadGrouping(groups *payloadGroup, payload ledger.Payload) (*payloadGroup, error) { + address, isAccount, err := payloadToAccount(payload) + if err != nil { + return nil, err } - return migrated, nil + if isAccount { + groups.Accounts[address] = append(groups.Accounts[address], payload) + } else { + groups.NonAccountPayloads = append(groups.NonAccountPayloads, payload) + } + + return groups, nil +} + +type migrationDuration struct { + Address common.Address + Duration time.Duration +} + +// implement heap methods for the timer results +type migrationDurations []migrationDuration + +func (h *migrationDurations) Len() int { return len(*h) } +func (h *migrationDurations) Less(i, j int) bool { + return (*h)[i].Duration < (*h)[j].Duration +} +func (h *migrationDurations) Swap(i, j int) { + (*h)[i], (*h)[j] = (*h)[j], (*h)[i] +} +func (h *migrationDurations) Push(x interface{}) { + *h = append(*h, x.(migrationDuration)) +} +func (h *migrationDurations) Pop() interface{} { + old := *h + n := len(old) + x := old[n-1] + *h = old[0 : n-1] + return x +} + +func (h *migrationDurations) Array() zerolog.LogArrayMarshaler { + array := zerolog.Arr() + for _, result := range *h { + array = array.Str(fmt.Sprintf("%s: %s", result.Address.Hex(), result.Duration.String())) + } + return array } diff --git a/cmd/util/ledger/migrations/account_migration.go b/cmd/util/ledger/migrations/account_migration.go index 51c123712f1..bba8dddc1dd 100644 --- a/cmd/util/ledger/migrations/account_migration.go +++ b/cmd/util/ledger/migrations/account_migration.go @@ -1,21 +1,32 @@ package migrations import ( + "context" "fmt" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" + "github.com/onflow/cadence/runtime/common" + + "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/model/flow" ) -func MigrateAccountUsage(payloads []ledger.Payload, nWorker int) ([]ledger.Payload, error) { - return MigrateByAccount(AccountUsageMigrator{}, payloads, nWorker) +func MigrateAccountUsage(log zerolog.Logger, nWorker int) func([]ledger.Payload) ([]ledger.Payload, error) { + return CreateAccountBasedMigration( + log, + func(allPayloads []ledger.Payload, nWorker int) (AccountMigrator, error) { + return AccountUsageMigrator{}, nil + }, + nWorker, + ) } func payloadSize(key ledger.Key, payload ledger.Payload) (uint64, error) { - id, err := KeyToRegisterID(key) + id, err := util.KeyToRegisterID(key) if err != nil { return 0, err } @@ -31,7 +42,7 @@ type AccountUsageMigrator struct{} // AccountUsageMigrator iterate through each payload, and calculate the storage usage // and update the accoutns status with the updated storage usage -func (m AccountUsageMigrator) MigratePayloads(account string, payloads []ledger.Payload) ([]ledger.Payload, error) { +func (m AccountUsageMigrator) MigratePayloads(ctx context.Context, address common.Address, payloads []ledger.Payload) ([]ledger.Payload, error) { var status *environment.AccountStatus var statusIndex int totalSize := uint64(0) @@ -62,7 +73,7 @@ func (m AccountUsageMigrator) MigratePayloads(account string, payloads []ledger. } if status == nil { - return nil, fmt.Errorf("could not find account status for account %v", account) + return nil, fmt.Errorf("could not find account status for account %v", address.Hex()) } // update storage used diff --git a/cmd/util/ledger/migrations/atree_register_migration.go b/cmd/util/ledger/migrations/atree_register_migration.go new file mode 100644 index 00000000000..6ed72dfa0ca --- /dev/null +++ b/cmd/util/ledger/migrations/atree_register_migration.go @@ -0,0 +1,442 @@ +package migrations + +import ( + "context" + "fmt" + "github.com/onflow/atree" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + "github.com/rs/zerolog" + "io" + + "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/storage/derived" + "github.com/onflow/flow-go/fvm/storage/logical" + "github.com/onflow/flow-go/fvm/storage/state" + "github.com/onflow/flow-go/fvm/tracing" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/model/flow" +) + +func MigrateAtreeRegisters( + log zerolog.Logger, + rwf reporters.ReportWriterFactory, + nWorker int, +) func([]ledger.Payload) ([]ledger.Payload, error) { + return CreateAccountBasedMigration( + log, + func(allRegisters []ledger.Payload, nWorker int) (AccountMigrator, error) { + return NewMigrator( + log.With().Str("component", "atree-register-migrator").Logger(), + rwf, + allRegisters, + ) + }, + nWorker, + ) +} + +// AtreeRegisterMigrator is a migrator that converts the storage of an account from the +// old atree format to the new atree format. +// Account "storage used" should be correctly updated after the migration. +type AtreeRegisterMigrator struct { + log zerolog.Logger + + snapshot *util.PayloadSnapshot + + rw reporters.ReportWriter + sampler zerolog.Logger +} + +var _ AccountMigrator = (*AtreeRegisterMigrator)(nil) +var _ io.Closer = (*AtreeRegisterMigrator)(nil) + +func NewMigrator( + log zerolog.Logger, + rwf reporters.ReportWriterFactory, + allRegisters []ledger.Payload, +) (*AtreeRegisterMigrator, error) { + // creating a snapshot of all the registers will take a while + snapshot, err := util.NewPayloadSnapshot(allRegisters) + if err != nil { + return nil, fmt.Errorf("failed to create payload snapshot: %w", err) + } + log.Info().Msgf("created snapshot") + + migrator := &AtreeRegisterMigrator{ + snapshot: snapshot, + + log: log, + + rw: rwf.ReportWriter("atree-register-migrator"), + } + + return migrator, nil +} + +func (m *AtreeRegisterMigrator) Close() error { + m.rw.Close() + return nil +} + +func (m *AtreeRegisterMigrator) MigratePayloads( + _ context.Context, + address common.Address, + payloads []ledger.Payload, +) ([]ledger.Payload, error) { + // don't migrate the zero address + // these are the non-account registers and are not atrees + if address == common.ZeroAddress { + return payloads, nil + } + + err := m.checkStorageHealth(address, payloads) + if err != nil { + return nil, fmt.Errorf("storage health issues for address %s: %w", address.Hex(), err) + } + + // Do the storage conversion + changes, err := m.migrateAccountStorage(address) + if err != nil { + return nil, fmt.Errorf("failed to convert storage for address %s: %w", address.Hex(), err) + } + + return m.validateChangesAndCreateNewRegisters(payloads, address, changes) +} + +func (m *AtreeRegisterMigrator) migrateAccountStorage( + address common.Address, +) (map[flow.RegisterID]flow.RegisterValue, error) { + + // create all the runtime components we need for the migration + r, err := m.newMigratorRuntime() + if err != nil { + return nil, fmt.Errorf("failed to create migrator runtime: %w", err) + } + + // iterate through all domains and migrate them + for _, domain := range domains { + err := m.convertStorageDomain(address, r.Storage, r.Interpreter, domain) + if err != nil { + return nil, fmt.Errorf("failed to convert storage domain %s : %w", domain, err) + } + } + + // commit the storage changes + err = r.Storage.Commit(r.Interpreter, true) + if err != nil { + return nil, fmt.Errorf("failed to commit storage: %w", err) + } + + // finalize the transaction + result, err := r.TransactionState.FinalizeMainTransaction() + if err != nil { + return nil, fmt.Errorf("failed to finalize main transaction: %w", err) + } + + return result.WriteSet, nil +} + +func (m *AtreeRegisterMigrator) convertStorageDomain( + address common.Address, + storage *runtime.Storage, + inter *interpreter.Interpreter, + domain string, +) error { + storageMap := storage.GetStorageMap(address, domain, false) + if storageMap == nil { + // no storage for this domain + return nil + } + + iterator := storageMap.Iterator(util.NopMemoryGauge{}) + keys := make([]interpreter.StringStorageMapKey, 0) + // to be safe avoid modifying the map while iterating + for { + key := iterator.NextKey() + if key == nil { + break + } + + stringKey, ok := key.(interpreter.StringAtreeValue) + if !ok { + return fmt.Errorf("invalid key type %T, expected interpreter.StringAtreeValue", key) + } + + keys = append(keys, interpreter.StringStorageMapKey(stringKey)) + } + + for _, key := range keys { + err := func() error { + var value interpreter.Value + + err := capturePanic(func() { + value = storageMap.ReadValue(util.NopMemoryGauge{}, key) + }) + if err != nil { + return fmt.Errorf("failed to read value for key %s: %w", key, err) + } + + err = capturePanic(func() { + // force the value to be read entirely + value = value.Clone(inter) + }) + if err != nil { + return fmt.Errorf("failed to clone value for key %s: %w", key, err) + } + + err = capturePanic(func() { + // set value will first purge the old value + storageMap.SetValue(inter, key, value) + }) + if err != nil { + return fmt.Errorf("failed to set value for key %s: %w", key, err) + } + + return nil + }() + if err != nil { + m.log.Debug().Err(err).Msgf("failed to migrate key %s", key) + + m.rw.Write(migrationError{ + Address: address.Hex(), + Key: string(key), + Kind: "migration_failure", + Msg: err.Error(), + }) + } + } + + return nil +} + +func (m *AtreeRegisterMigrator) newMigratorRuntime() ( + *migratorRuntime, + error, +) { + // the registers will be read concurrently by multiple workers, but won't be modified + transactionState := state.NewTransactionState(m.snapshot, state.DefaultParameters()) + accounts := environment.NewAccounts(transactionState) + + derivedBlockData := derived.NewEmptyDerivedBlockData(logical.EndOfBlockExecutionTime) + + programs := + environment.NewPrograms( + tracing.NewMockTracerSpan(), + util.NopMeter{}, + environment.NoopMetricsReporter{}, + struct { + state.NestedTransactionPreparer + derived.DerivedTransactionPreparer + }{ + NestedTransactionPreparer: transactionState, + // TODO: reuse this + DerivedTransactionPreparer: derivedBlockData.NewSnapshotReadDerivedTransactionData(), + }, + accounts, + ) + + accountsAtreeLedger := util.NewAccountsAtreeLedger(accounts) + storage := runtime.NewStorage(accountsAtreeLedger, util.NopMemoryGauge{}) + + env := runtime.NewBaseInterpreterEnvironment(runtime.Config{ + AccountLinkingEnabled: true, + // Attachments are enabled everywhere except for Mainnet + AttachmentsEnabled: true, + // Capability Controllers are enabled everywhere except for Mainnet + CapabilityControllersEnabled: true, + }) + + ri := util.MigrationRuntimeInterface{ + Accounts: accounts, + Programs: programs, + } + + env.Configure( + ri, + runtime.NewCodesAndPrograms(), + storage, + runtime.NewCoverageReport(), + ) + + inter, err := interpreter.NewInterpreter( + nil, + nil, + env.InterpreterConfig) + if err != nil { + return nil, err + } + + return &migratorRuntime{ + TransactionState: transactionState, + Interpreter: inter, + Storage: storage, + }, nil +} + +type migratorRuntime struct { + TransactionState state.NestedTransactionPreparer + Interpreter *interpreter.Interpreter + Storage *runtime.Storage +} + +func (m *AtreeRegisterMigrator) validateChangesAndCreateNewRegisters( + payloads []ledger.Payload, + address common.Address, + changes map[flow.RegisterID]flow.RegisterValue, +) ([]ledger.Payload, error) { + originalPayloadsSnapshot, err := util.NewPayloadSnapshot(payloads) + if err != nil { + return nil, fmt.Errorf("failed to create payload snapshot: %w", err) + } + originalPayloads := originalPayloadsSnapshot.Payloads + newPayloads := make([]ledger.Payload, 0, len(originalPayloads)) + + for id, value := range changes { + // delete all values that were changed from the original payloads + delete(originalPayloads, id) + + if len(value) == 0 { + // value was deleted + continue + } + ownerAddress, err := common.BytesToAddress([]byte(id.Owner)) + if err != nil { + return nil, fmt.Errorf("failed to convert owner address: %w", err) + } + + if ownerAddress.Hex() != address.Hex() { + // something was changed that does not belong to this account. Log it. + m.log.Error(). + Str("key", id.String()). + Str("owner_address", ownerAddress.Hex()). + Str("account", address.Hex()). + Msg("key is part of the change set, but is for a different account") + + return nil, fmt.Errorf("register for a different account was produced during migration") + } + + newPayloads = append(newPayloads, *ledger.NewPayload(util.RegisterIDToKey(id), value)) + } + + // add all values that were not changed + for id, value := range originalPayloads { + if len(value) == 0 { + // this is strange, but we don't want to add empty values. Log it. + m.log.Warn().Msgf("empty value for key %s", id) + continue + } + if id.IsInternalState() { + // this is expected. Move it to the new payload + newPayloads = append(newPayloads, *ledger.NewPayload(util.RegisterIDToKey(id), value)) + continue + } + + isADomainKey := false + for _, domain := range domains { + if id.Key == domain { + isADomainKey = true + break + } + } + if isADomainKey { + // TODO: check if this is really expected + // this is expected. Move it to the new payload + newPayloads = append(newPayloads, *ledger.NewPayload(util.RegisterIDToKey(id), value)) + continue + } + + // something was not moved. Log it. + m.log.Debug(). + Str("key", id.String()). + Str("account", address.Hex()). + Str("value", fmt.Sprintf("%x", value)). + Msg("Key was not migrated") + m.rw.Write(migrationError{ + Address: address.Hex(), + Key: id.String(), + Kind: "not_migrated", + Msg: fmt.Sprintf("%x", value), + }) + } + return newPayloads, nil +} + +func (m *AtreeRegisterMigrator) checkStorageHealth( + address common.Address, + payloads []ledger.Payload, +) error { + snapshot, err := util.NewPayloadSnapshot(payloads) + if err != nil { + return fmt.Errorf("failed to create payload snapshot: %w", err) + } + + transactionState := state.NewTransactionState(snapshot, state.DefaultParameters()) + accounts := environment.NewAccounts(transactionState) + + accountsAtreeLedger := util.NewAccountsAtreeLedger(accounts) + storage := runtime.NewStorage(accountsAtreeLedger, util.NopMemoryGauge{}) + + _, err = atree.CheckStorageHealth(storage, 4) + if err != nil { + m.log.Info(). + Err(err). + Str("account", address.Hex()). + Msg("Account storage health issue") + m.rw.Write(migrationError{ + Address: address.Hex(), + Key: "", + Kind: "storage_health_problem_1", + Msg: err.Error(), + }) + } + + err = storage.CheckHealth() + if err != nil { + m.log.Info(). + Err(err). + Str("account", address.Hex()). + Msg("Account storage health issue") + m.rw.Write(migrationError{ + Address: address.Hex(), + Key: "", + Kind: "storage_health_problem_2", + Msg: err.Error(), + }) + } + return nil + +} + +// capturePanic captures panics and converts them to errors +// this is needed for some cadence functions that panic on error +func capturePanic(f func()) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("panic: %v", r) + } + }() + + f() + + return +} + +// convert all domains +var domains = []string{ + common.PathDomainStorage.Identifier(), + common.PathDomainPrivate.Identifier(), + common.PathDomainPublic.Identifier(), + runtime.StorageDomainContract, +} + +// migrationError is a struct for reporting errors +type migrationError struct { + Address string + Key string + Kind string + Msg string +} diff --git a/cmd/util/ledger/migrations/atree_register_migration_test.go b/cmd/util/ledger/migrations/atree_register_migration_test.go new file mode 100644 index 00000000000..b5a020f12ca --- /dev/null +++ b/cmd/util/ledger/migrations/atree_register_migration_test.go @@ -0,0 +1,132 @@ +package migrations_test + +import ( + "testing" + + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/cmd/util/ledger/reporters" + "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/ledger" + "github.com/onflow/flow-go/ledger/common/pathfinder" + "github.com/onflow/flow-go/ledger/complete" + "github.com/onflow/flow-go/ledger/complete/wal" + "github.com/onflow/flow-go/module/metrics" +) + +func TestAtreeRegisterMigration(t *testing.T) { + log := zerolog.New(zerolog.NewTestWriter(t)) + dir := t.TempDir() + // Localnet v0.31 was used to produce an execution state that can be used for the tests. + t.Run( + "test v0.31 state", + testWithExistingState( + + log, + "test-data/bootstrapped_v0.31", + migrations.MigrateAtreeRegisters(log, reporters.NewReportFileWriterFactory(dir, log), 2), + func(t *testing.T, oldPayloads []ledger.Payload, newPayloads []ledger.Payload) { + + newSnapshot, err := util.NewPayloadSnapshot(oldPayloads) + require.NoError(t, err) + + for _, payload := range oldPayloads { + key, err := payload.Key() + require.NoError(t, err) + regId, err := util.KeyToRegisterID(key) + require.NoError(t, err) + value, err := newSnapshot.Get(regId) + require.NoError(t, err) + + // TODO: currently the migration does not change the payload values because + // the atree version is not changed. This should be changed in the future. + require.Equal(t, []byte(payload.Value()), value) + } + + // commented out helper code to dump the payloads to csv files for manual inspection + //dumpPayloads := func(n string, payloads []ledger.Payload) { + // f, err := os.Create(n) + // require.NoError(t, err) + // + // defer f.Close() + // + // for _, payload := range payloads { + // key, err := payload.Key() + // require.NoError(t, err) + // _, err = f.WriteString(fmt.Sprintf("%x,%s\n", key.String(), payload.Value())) + // require.NoError(t, err) + // } + //} + //dumpPayloads("old.csv", oldPayloads) + //dumpPayloads("new.csv", newPayloads) + + }, + ), + ) + +} + +func testWithExistingState( + log zerolog.Logger, + inputDir string, + migration ledger.Migration, + f func( + t *testing.T, + oldPayloads []ledger.Payload, + newPayloads []ledger.Payload, + ), +) func(t *testing.T) { + return func(t *testing.T) { + diskWal, err := wal.NewDiskWAL( + log, + nil, + metrics.NewNoopCollector(), + inputDir, + complete.DefaultCacheSize, + pathfinder.PathByteSize, + wal.SegmentSize, + ) + require.NoError(t, err) + + led, err := complete.NewLedger( + diskWal, + complete.DefaultCacheSize, + &metrics.NoopCollector{}, + log, + complete.DefaultPathFinderVersion) + require.NoError(t, err) + + var oldPayloads []ledger.Payload + + // we sandwitch the migration between two identity migrations + // so we can capture the Payloads before and after the migration + var mig = []ledger.Migration{ + func(payloads []ledger.Payload) ([]ledger.Payload, error) { + oldPayloads = make([]ledger.Payload, len(payloads)) + copy(oldPayloads, payloads) + return payloads, nil + }, + + migration, + + func(newPayloads []ledger.Payload) ([]ledger.Payload, error) { + + f(t, oldPayloads, newPayloads) + + return newPayloads, nil + }, + } + + newState, err := led.MostRecentTouchedState() + require.NoError(t, err) + + _, err = led.MigrateAt( + newState, + mig, + complete.DefaultPathFinderVersion, + ) + require.NoError(t, err) + } +} diff --git a/cmd/util/ledger/migrations/storage_fees_migration.go b/cmd/util/ledger/migrations/storage_fees_migration.go index d55a725d90b..5e12ef95182 100644 --- a/cmd/util/ledger/migrations/storage_fees_migration.go +++ b/cmd/util/ledger/migrations/storage_fees_migration.go @@ -1,6 +1,7 @@ package migrations import ( + "github.com/onflow/flow-go/cmd/util/ledger/util" fvm "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/utils" @@ -30,7 +31,7 @@ func StorageFeesMigration(payload []ledger.Payload) ([]ledger.Payload, error) { u = u + uint64(storageUsedByStorageUsed) newPayload = append(newPayload, *ledger.NewPayload( - registerIDToKey(id), + util.RegisterIDToKey(id), utils.Uint64ToBinary(u), )) } @@ -42,7 +43,7 @@ func incrementStorageUsed(p ledger.Payload, used map[string]uint64) error { if err != nil { return err } - id, err := KeyToRegisterID(k) + id, err := util.KeyToRegisterID(k) if err != nil { return err } diff --git a/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore new file mode 100644 index 00000000000..0342671c3bc --- /dev/null +++ b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/.gitignore @@ -0,0 +1,4 @@ +* + +!.gitignore +!00000000 diff --git a/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/00000000 b/cmd/util/ledger/migrations/test-data/bootstrapped_v0.31/00000000 new file mode 100644 index 0000000000000000000000000000000000000000..48a6440b89f9d445a6a7d62daf80dffe4d10e612 GIT binary patch literal 425984 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 literal 0 HcmV?d00001 diff --git a/cmd/util/ledger/reporters/fungible_token_tracker.go b/cmd/util/ledger/reporters/fungible_token_tracker.go index 0bb1db764bd..bb438921847 100644 --- a/cmd/util/ledger/reporters/fungible_token_tracker.go +++ b/cmd/util/ledger/reporters/fungible_token_tracker.go @@ -13,7 +13,7 @@ import ( "github.com/onflow/cadence/runtime/common" "github.com/onflow/cadence/runtime/interpreter" - "github.com/onflow/flow-go/cmd/util/ledger/migrations" + "github.com/onflow/flow-go/cmd/util/ledger/util" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/storage/state" @@ -146,7 +146,7 @@ func (r *FungibleTokenTracker) worker( state.DefaultParameters()) accounts := environment.NewAccounts(txnState) storage := cadenceRuntime.NewStorage( - &migrations.AccountsAtreeLedger{Accounts: accounts}, + &util.AccountsAtreeLedger{Accounts: accounts}, nil, ) diff --git a/cmd/util/ledger/util/migration_runtime_interface.go b/cmd/util/ledger/util/migration_runtime_interface.go new file mode 100644 index 00000000000..ed3561bcbcf --- /dev/null +++ b/cmd/util/ledger/util/migration_runtime_interface.go @@ -0,0 +1,307 @@ +package util + +import ( + "fmt" + "time" + + "go.opentelemetry.io/otel/attribute" + + "github.com/onflow/atree" + "github.com/onflow/cadence" + "github.com/onflow/cadence/runtime" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/interpreter" + + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/model/flow" +) + +type MigrationRuntimeInterface struct { + Accounts environment.Accounts + Programs *environment.Programs + + // GetOrLoadProgramFunc allows for injecting extra logic + GetOrLoadProgramFunc func(location runtime.Location, load func() (*interpreter.Program, error)) (*interpreter.Program, error) +} + +func newMigrationRuntimeInterface( + Accounts environment.Accounts, + Programs *environment.Programs, +) MigrationRuntimeInterface { + + mri := MigrationRuntimeInterface{ + Accounts: Accounts, + Programs: Programs, + } + + return mri +} + +func (m MigrationRuntimeInterface) ResolveLocation( + identifiers []runtime.Identifier, + location runtime.Location, +) ([]runtime.ResolvedLocation, error) { + + addressLocation, isAddress := location.(common.AddressLocation) + + // if the location is not an address location, e.g. an identifier location (`import Crypto`), + // then return a single resolved location which declares all identifiers. + if !isAddress { + return []runtime.ResolvedLocation{ + { + Location: location, + Identifiers: identifiers, + }, + }, nil + } + + // if the location is an address, + // and no specific identifiers where requested in the import statement, + // then fetch all identifiers at this address + if len(identifiers) == 0 { + address := flow.Address(addressLocation.Address) + + contractNames, err := m.Accounts.GetContractNames(address) + if err != nil { + return nil, fmt.Errorf("ResolveLocation failed: %w", err) + } + + // if there are no contractNames deployed, + // then return no resolved locations + if len(contractNames) == 0 { + return nil, nil + } + + identifiers = make([]runtime.Identifier, len(contractNames)) + + for i := range identifiers { + identifiers[i] = runtime.Identifier{ + Identifier: contractNames[i], + } + } + } + + // return one resolved location per identifier. + // each resolved location is an address contract location + resolvedLocations := make([]runtime.ResolvedLocation, len(identifiers)) + for i := range resolvedLocations { + identifier := identifiers[i] + resolvedLocations[i] = runtime.ResolvedLocation{ + Location: common.AddressLocation{ + Address: addressLocation.Address, + Name: identifier.Identifier, + }, + Identifiers: []runtime.Identifier{identifier}, + } + } + + return resolvedLocations, nil +} + +func (m MigrationRuntimeInterface) GetCode(location runtime.Location) ([]byte, error) { + contractLocation, ok := location.(common.AddressLocation) + if !ok { + return nil, fmt.Errorf("GetCode failed: expected AddressLocation") + } + + add, err := m.Accounts.GetContract(contractLocation.Name, flow.Address(contractLocation.Address)) + if err != nil { + return nil, fmt.Errorf("GetCode failed: %w", err) + } + + return add, nil +} + +func (m MigrationRuntimeInterface) GetAccountContractCode( + l common.AddressLocation, +) (code []byte, err error) { + return m.Accounts.GetContract(l.Name, flow.Address(l.Address)) +} + +func (m MigrationRuntimeInterface) GetOrLoadProgram(location runtime.Location, load func() (*interpreter.Program, error)) (*interpreter.Program, error) { + if m.GetOrLoadProgramFunc != nil { + return m.GetOrLoadProgramFunc(location, load) + } + + return m.Programs.GetOrLoadProgram(location, load) +} + +func (m MigrationRuntimeInterface) MeterMemory(_ common.MemoryUsage) error { + return nil +} + +func (m MigrationRuntimeInterface) MeterComputation(_ common.ComputationKind, _ uint) error { + return nil +} + +func (m MigrationRuntimeInterface) GetValue(_, _ []byte) (value []byte, err error) { + panic("unexpected GetValue call") +} + +func (m MigrationRuntimeInterface) SetValue(_, _, _ []byte) (err error) { + panic("unexpected SetValue call") +} + +func (m MigrationRuntimeInterface) CreateAccount(_ runtime.Address) (address runtime.Address, err error) { + panic("unexpected CreateAccount call") +} + +func (m MigrationRuntimeInterface) AddEncodedAccountKey(_ runtime.Address, _ []byte) error { + panic("unexpected AddEncodedAccountKey call") +} + +func (m MigrationRuntimeInterface) RevokeEncodedAccountKey(_ runtime.Address, _ int) (publicKey []byte, err error) { + panic("unexpected RevokeEncodedAccountKey call") +} + +func (m MigrationRuntimeInterface) AddAccountKey(_ runtime.Address, _ *runtime.PublicKey, _ runtime.HashAlgorithm, _ int) (*runtime.AccountKey, error) { + panic("unexpected AddAccountKey call") +} + +func (m MigrationRuntimeInterface) GetAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { + panic("unexpected GetAccountKey call") +} + +func (m MigrationRuntimeInterface) RevokeAccountKey(_ runtime.Address, _ int) (*runtime.AccountKey, error) { + panic("unexpected RevokeAccountKey call") +} + +func (m MigrationRuntimeInterface) UpdateAccountContractCode(_ common.AddressLocation, _ []byte) (err error) { + panic("unexpected UpdateAccountContractCode call") +} + +func (m MigrationRuntimeInterface) RemoveAccountContractCode(common.AddressLocation) (err error) { + panic("unexpected RemoveAccountContractCode call") +} + +func (m MigrationRuntimeInterface) GetSigningAccounts() ([]runtime.Address, error) { + panic("unexpected GetSigningAccounts call") +} + +func (m MigrationRuntimeInterface) ProgramLog(_ string) error { + panic("unexpected ProgramLog call") +} + +func (m MigrationRuntimeInterface) EmitEvent(_ cadence.Event) error { + panic("unexpected EmitEvent call") +} + +func (m MigrationRuntimeInterface) ValueExists(_, _ []byte) (exists bool, err error) { + panic("unexpected ValueExists call") +} + +func (m MigrationRuntimeInterface) GenerateUUID() (uint64, error) { + panic("unexpected GenerateUUID call") +} + +func (m MigrationRuntimeInterface) GetComputationLimit() uint64 { + panic("unexpected GetComputationLimit call") +} + +func (m MigrationRuntimeInterface) SetComputationUsed(_ uint64) error { + panic("unexpected SetComputationUsed call") +} + +func (m MigrationRuntimeInterface) DecodeArgument(_ []byte, _ cadence.Type) (cadence.Value, error) { + panic("unexpected DecodeArgument call") +} + +func (m MigrationRuntimeInterface) GetCurrentBlockHeight() (uint64, error) { + panic("unexpected GetCurrentBlockHeight call") +} + +func (m MigrationRuntimeInterface) GetBlockAtHeight(_ uint64) (block runtime.Block, exists bool, err error) { + panic("unexpected GetBlockAtHeight call") +} + +func (m MigrationRuntimeInterface) UnsafeRandom() (uint64, error) { + panic("unexpected UnsafeRandom call") +} + +func (m MigrationRuntimeInterface) VerifySignature(_ []byte, _ string, _ []byte, _ []byte, _ runtime.SignatureAlgorithm, _ runtime.HashAlgorithm) (bool, error) { + panic("unexpected VerifySignature call") +} + +func (m MigrationRuntimeInterface) Hash(_ []byte, _ string, _ runtime.HashAlgorithm) ([]byte, error) { + panic("unexpected Hash call") +} + +func (m MigrationRuntimeInterface) GetAccountBalance(_ common.Address) (value uint64, err error) { + panic("unexpected GetAccountBalance call") +} + +func (m MigrationRuntimeInterface) GetAccountAvailableBalance(_ common.Address) (value uint64, err error) { + panic("unexpected GetAccountAvailableBalance call") +} + +func (m MigrationRuntimeInterface) GetStorageUsed(_ runtime.Address) (value uint64, err error) { + panic("unexpected GetStorageUsed call") +} + +func (m MigrationRuntimeInterface) GetStorageCapacity(_ runtime.Address) (value uint64, err error) { + panic("unexpected GetStorageCapacity call") +} + +func (m MigrationRuntimeInterface) ImplementationDebugLog(_ string) error { + panic("unexpected ImplementationDebugLog call") +} + +func (m MigrationRuntimeInterface) ValidatePublicKey(_ *runtime.PublicKey) error { + panic("unexpected ValidatePublicKey call") +} + +func (m MigrationRuntimeInterface) GetAccountContractNames(_ runtime.Address) ([]string, error) { + panic("unexpected GetAccountContractNames call") +} + +func (m MigrationRuntimeInterface) AllocateStorageIndex(_ []byte) (atree.StorageIndex, error) { + panic("unexpected AllocateStorageIndex call") +} + +func (m MigrationRuntimeInterface) ComputationUsed() (uint64, error) { + panic("unexpected ComputationUsed call") +} + +func (m MigrationRuntimeInterface) MemoryUsed() (uint64, error) { + panic("unexpected MemoryUsed call") +} + +func (m MigrationRuntimeInterface) InteractionUsed() (uint64, error) { + panic("unexpected InteractionUsed call") +} + +func (m MigrationRuntimeInterface) SetInterpreterSharedState(_ *interpreter.SharedState) { + panic("unexpected SetInterpreterSharedState call") +} + +func (m MigrationRuntimeInterface) GetInterpreterSharedState() *interpreter.SharedState { + panic("unexpected GetInterpreterSharedState call") +} + +func (m MigrationRuntimeInterface) AccountKeysCount(_ runtime.Address) (uint64, error) { + panic("unexpected AccountKeysCount call") +} + +func (m MigrationRuntimeInterface) BLSVerifyPOP(_ *runtime.PublicKey, _ []byte) (bool, error) { + panic("unexpected BLSVerifyPOP call") +} + +func (m MigrationRuntimeInterface) BLSAggregateSignatures(_ [][]byte) ([]byte, error) { + panic("unexpected BLSAggregateSignatures call") +} + +func (m MigrationRuntimeInterface) BLSAggregatePublicKeys(_ []*runtime.PublicKey) (*runtime.PublicKey, error) { + panic("unexpected BLSAggregatePublicKeys call") +} + +func (m MigrationRuntimeInterface) ResourceOwnerChanged(_ *interpreter.Interpreter, _ *interpreter.CompositeValue, _ common.Address, _ common.Address) { + panic("unexpected ResourceOwnerChanged call") +} + +func (m MigrationRuntimeInterface) GenerateAccountID(_ common.Address) (uint64, error) { + panic("unexpected GenerateAccountID call") +} + +func (m MigrationRuntimeInterface) RecordTrace(_ string, _ runtime.Location, _ time.Duration, _ []attribute.KeyValue) { + panic("unexpected RecordTrace call") +} diff --git a/cmd/util/ledger/util/nop_meter.go b/cmd/util/ledger/util/nop_meter.go new file mode 100644 index 00000000000..4304b1ec9cb --- /dev/null +++ b/cmd/util/ledger/util/nop_meter.go @@ -0,0 +1,44 @@ +package util + +import ( + "github.com/onflow/cadence/runtime/common" + + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/meter" +) + +type NopMeter struct{} + +func (n NopMeter) MeterComputation(_ common.ComputationKind, _ uint) error { + return nil +} + +func (n NopMeter) ComputationUsed() (uint64, error) { + return 0, nil +} + +func (n NopMeter) ComputationIntensities() meter.MeteredComputationIntensities { + return meter.MeteredComputationIntensities{} +} + +func (n NopMeter) MeterMemory(_ common.MemoryUsage) error { + return nil +} + +func (n NopMeter) MemoryUsed() (uint64, error) { + return 0, nil +} + +func (n NopMeter) MeterEmittedEvent(_ uint64) error { + return nil +} + +func (n NopMeter) TotalEmittedEventBytes() uint64 { + return 0 +} + +func (n NopMeter) InteractionUsed() (uint64, error) { + return 0, nil +} + +var _ environment.Meter = NopMeter{} diff --git a/cmd/util/ledger/migrations/utils.go b/cmd/util/ledger/util/util.go similarity index 68% rename from cmd/util/ledger/migrations/utils.go rename to cmd/util/ledger/util/util.go index 506efe61db0..a55513769eb 100644 --- a/cmd/util/ledger/migrations/utils.go +++ b/cmd/util/ledger/util/util.go @@ -1,12 +1,14 @@ -package migrations +package util import ( "fmt" "github.com/onflow/atree" + "github.com/onflow/cadence/runtime/common" "github.com/onflow/flow-go/engine/execution/state" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/model/flow" ) @@ -24,7 +26,7 @@ func KeyToRegisterID(key ledger.Key) (flow.RegisterID, error) { ), nil } -func registerIDToKey(registerID flow.RegisterID) ledger.Key { +func RegisterIDToKey(registerID flow.RegisterID) ledger.Key { newKey := ledger.Key{} newKey.KeyParts = []ledger.KeyPart{ { @@ -89,3 +91,41 @@ func (a *AccountsAtreeLedger) AllocateStorageIndex(owner []byte) (atree.StorageI } return v, nil } + +type PayloadSnapshot struct { + Payloads map[flow.RegisterID]flow.RegisterValue +} + +var _ snapshot.StorageSnapshot = (*PayloadSnapshot)(nil) + +func NewPayloadSnapshot(payloads []ledger.Payload) (*PayloadSnapshot, error) { + l := &PayloadSnapshot{ + Payloads: make(map[flow.RegisterID][]byte, len(payloads)), + } + for _, payload := range payloads { + key, err := payload.Key() + if err != nil { + return nil, err + } + id, err := KeyToRegisterID(key) + if err != nil { + return nil, err + } + l.Payloads[id] = payload.Value() + } + return l, nil +} + +func (p PayloadSnapshot) Get(id flow.RegisterID) (flow.RegisterValue, error) { + value := p.Payloads[id] + return value, nil +} + +// NopMemoryGauge is a no-op implementation of the MemoryGauge interface +type NopMemoryGauge struct{} + +func (n NopMemoryGauge) MeterMemory(common.MemoryUsage) error { + return nil +} + +var _ common.MemoryGauge = (*NopMemoryGauge)(nil) diff --git a/go.mod b/go.mod index d5890cfa995..6d6db1b8375 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,7 @@ require ( github.com/multiformats/go-multiaddr-dns v0.3.1 github.com/multiformats/go-multihash v0.2.3 github.com/onflow/atree v0.6.0 - github.com/onflow/cadence v0.39.14 + github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2 github.com/onflow/flow v0.3.4 github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 diff --git a/go.sum b/go.sum index 73eadfddbf8..9e11a49fe44 100644 --- a/go.sum +++ b/go.sum @@ -1240,8 +1240,8 @@ github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA= -github.com/onflow/cadence v0.39.14 h1:YoR3YFUga49rqzVY1xwI6I2ZDBmvwGh13jENncsleC8= -github.com/onflow/cadence v0.39.14/go.mod h1:OIJLyVBPa339DCBQXBfGaorT4tBjQh9gSKe+ZAIyyh0= +github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2 h1:p2f+A94QRsuEXeQRX9cwdEywNneJ6Rlw6LYq0ao5PyA= +github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2/go.mod h1:OIJLyVBPa339DCBQXBfGaorT4tBjQh9gSKe+ZAIyyh0= github.com/onflow/flow v0.3.4 h1:FXUWVdYB90f/rjNcY0Owo30gL790tiYff9Pb/sycXYE= github.com/onflow/flow v0.3.4/go.mod h1:lzyAYmbu1HfkZ9cfnL5/sjrrsnJiUU8fRL26CqLP7+c= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d h1:B7PdhdUNkve5MVrekWDuQf84XsGBxNZ/D3x+QQ8XeVs= diff --git a/insecure/go.mod b/insecure/go.mod index b727c8ecf6c..0d52624926a 100644 --- a/insecure/go.mod +++ b/insecure/go.mod @@ -180,7 +180,7 @@ require ( github.com/multiformats/go-multistream v0.4.1 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/onflow/atree v0.6.0 // indirect - github.com/onflow/cadence v0.39.14 // indirect + github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2 // indirect github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d // indirect github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 // indirect github.com/onflow/flow-ft/lib/go/contracts v0.7.0 // indirect diff --git a/insecure/go.sum b/insecure/go.sum index 308ac3ab9f3..8d0660ffb87 100644 --- a/insecure/go.sum +++ b/insecure/go.sum @@ -1216,8 +1216,8 @@ github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA= -github.com/onflow/cadence v0.39.14 h1:YoR3YFUga49rqzVY1xwI6I2ZDBmvwGh13jENncsleC8= -github.com/onflow/cadence v0.39.14/go.mod h1:OIJLyVBPa339DCBQXBfGaorT4tBjQh9gSKe+ZAIyyh0= +github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2 h1:p2f+A94QRsuEXeQRX9cwdEywNneJ6Rlw6LYq0ao5PyA= +github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2/go.mod h1:OIJLyVBPa339DCBQXBfGaorT4tBjQh9gSKe+ZAIyyh0= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d h1:B7PdhdUNkve5MVrekWDuQf84XsGBxNZ/D3x+QQ8XeVs= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d/go.mod h1:xAiV/7TKhw863r6iO3CS5RnQ4F+pBY1TxD272BsILlo= github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 h1:X25A1dNajNUtE+KoV76wQ6BR6qI7G65vuuRXxDDqX7E= @@ -1228,13 +1228,13 @@ github.com/onflow/flow-go-sdk v0.24.0/go.mod h1:IoptMLPyFXWvyd9yYA6/4EmSeeozl6nJ github.com/onflow/flow-go-sdk v0.41.9 h1:cyplhhhc0RnfOAan2t7I/7C9g1hVGDDLUhWj6ZHAkk4= github.com/onflow/flow-go-sdk v0.41.9/go.mod h1:e9Q5TITCy7g08lkdQJxP8fAKBnBoC5FjALvUKr36j4I= github.com/onflow/flow-go/crypto v0.21.3/go.mod h1:vI6V4CY3R6c4JKBxdcRiR/AnjBfL8OSD97bJc60cLuQ= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= -github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/flow-go/crypto v0.24.9 h1:0EQp+kSZYJepMIiSypfJVe7tzsPcb6UXOdOtsTCDhBs= github.com/onflow/flow-go/crypto v0.24.9/go.mod h1:fqCzkIBBMRRkciVrvW21rECKq1oD7Q6u+bCI78lfNX0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0 h1:rhUDeD27jhLwOqQKI/23008CYfnqXErrJvc4EFRP2a0= github.com/onflow/flow-nft/lib/go/contracts v1.1.0/go.mod h1:YsvzYng4htDgRB9sa9jxdwoTuuhjK8WYWXTyLkIigZY= github.com/onflow/flow/protobuf/go/flow v0.2.2/go.mod h1:gQxYqCfkI8lpnKsmIjwtN2mV/N2PIwc1I+RUK4HPIc8= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce h1:YQKijiQaq8SF1ayNqp3VVcwbBGXSnuHNHq4GQmVGybE= +github.com/onflow/flow/protobuf/go/flow v0.3.2-0.20230628215638-83439d22e0ce/go.mod h1:NA2pX2nw8zuaxfKphhKsk00kWLwfd+tv8mS23YXO4Sk= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d h1:QcOAeEyF3iAUHv21LQ12sdcsr0yFrJGoGLyCAzYYtvI= github.com/onflow/go-bitswap v0.0.0-20230703214630-6d3db958c73d/go.mod h1:GCPpiyRoHncdqPj++zPr9ZOYBX4hpJ0pYZRYqSE8VKk= github.com/onflow/sdks v0.5.0 h1:2HCRibwqDaQ1c9oUApnkZtEAhWiNY2GTpRD5+ftdkN8= diff --git a/integration/go.mod b/integration/go.mod index e47e8bac3c3..3daf2018d37 100644 --- a/integration/go.mod +++ b/integration/go.mod @@ -17,7 +17,7 @@ require ( github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-badger2 v0.1.3 github.com/ipfs/go-ipfs-blockstore v1.3.0 - github.com/onflow/cadence v0.39.14 + github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2 github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 github.com/onflow/flow-emulator v0.53.0 diff --git a/integration/go.sum b/integration/go.sum index 4aac8d7305d..e3542bd0b34 100644 --- a/integration/go.sum +++ b/integration/go.sum @@ -1354,8 +1354,8 @@ github.com/onflow/atree v0.1.0-beta1.0.20211027184039-559ee654ece9/go.mod h1:+6x github.com/onflow/atree v0.6.0 h1:j7nQ2r8npznx4NX39zPpBYHmdy45f4xwoi+dm37Jk7c= github.com/onflow/atree v0.6.0/go.mod h1:gBHU0M05qCbv9NN0kijLWMgC47gHVNBIp4KmsVFi0tc= github.com/onflow/cadence v0.20.1/go.mod h1:7mzUvPZUIJztIbr9eTvs+fQjWWHTF8veC+yk4ihcNIA= -github.com/onflow/cadence v0.39.14 h1:YoR3YFUga49rqzVY1xwI6I2ZDBmvwGh13jENncsleC8= -github.com/onflow/cadence v0.39.14/go.mod h1:OIJLyVBPa339DCBQXBfGaorT4tBjQh9gSKe+ZAIyyh0= +github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2 h1:p2f+A94QRsuEXeQRX9cwdEywNneJ6Rlw6LYq0ao5PyA= +github.com/onflow/cadence v0.39.15-0.20230823153602-c1edd8a467e2/go.mod h1:OIJLyVBPa339DCBQXBfGaorT4tBjQh9gSKe+ZAIyyh0= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d h1:B7PdhdUNkve5MVrekWDuQf84XsGBxNZ/D3x+QQ8XeVs= github.com/onflow/flow-core-contracts/lib/go/contracts v1.2.4-0.20230703193002-53362441b57d/go.mod h1:xAiV/7TKhw863r6iO3CS5RnQ4F+pBY1TxD272BsILlo= github.com/onflow/flow-core-contracts/lib/go/templates v1.2.3 h1:X25A1dNajNUtE+KoV76wQ6BR6qI7G65vuuRXxDDqX7E= diff --git a/ledger/complete/checkpoint_benchmark_test.go b/ledger/complete/checkpoint_benchmark_test.go index 177804be5a7..5d5ae9f726c 100644 --- a/ledger/complete/checkpoint_benchmark_test.go +++ b/ledger/complete/checkpoint_benchmark_test.go @@ -58,7 +58,7 @@ func benchmarkStoreCheckpoint(b *testing.B, version int, concurrent bool) { }() // Load checkpoint - tries, err := wal.LoadCheckpoint(*checkpointFile, &log) + tries, err := wal.LoadCheckpoint(*checkpointFile, log) if err != nil { b.Fatalf("cannot load checkpoint: %s", err) } @@ -69,7 +69,7 @@ func benchmarkStoreCheckpoint(b *testing.B, version int, concurrent bool) { // Serialize checkpoint V5. switch version { case 5: - err = wal.StoreCheckpointV5(outputDir, fileName, &log, tries...) + err = wal.StoreCheckpointV5(outputDir, fileName, log, tries...) case 6: if concurrent { err = wal.StoreCheckpointV6Concurrently(tries, outputDir, fileName, &log) @@ -102,7 +102,7 @@ func BenchmarkLoadCheckpoint(b *testing.B) { b.ResetTimer() // Load checkpoint - _, err = wal.LoadCheckpoint(*checkpointFile, &log) + _, err = wal.LoadCheckpoint(*checkpointFile, log) b.StopTimer() elapsed := time.Since(start) diff --git a/ledger/complete/wal/checkpoint_v5_test.go b/ledger/complete/wal/checkpoint_v5_test.go index 9721a50d04e..4422d3376c0 100644 --- a/ledger/complete/wal/checkpoint_v5_test.go +++ b/ledger/complete/wal/checkpoint_v5_test.go @@ -15,12 +15,12 @@ func TestCopyCheckpointFileV5(t *testing.T) { tries := createSimpleTrie(t) fileName := "checkpoint" logger := unittest.Logger() - require.NoErrorf(t, StoreCheckpointV5(dir, fileName, &logger, tries...), "fail to store checkpoint") + require.NoErrorf(t, StoreCheckpointV5(dir, fileName, logger, tries...), "fail to store checkpoint") to := filepath.Join(dir, "newfolder") newPaths, err := CopyCheckpointFile(fileName, dir, to) require.NoError(t, err) log.Info().Msgf("copied to :%v", newPaths) - decoded, err := LoadCheckpoint(filepath.Join(to, fileName), &logger) + decoded, err := LoadCheckpoint(filepath.Join(to, fileName), logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) requireTriesEqual(t, tries, decoded) }) diff --git a/ledger/complete/wal/checkpoint_v6_leaf_reader.go b/ledger/complete/wal/checkpoint_v6_leaf_reader.go index 77dbc0716b5..169bf895cbd 100644 --- a/ledger/complete/wal/checkpoint_v6_leaf_reader.go +++ b/ledger/complete/wal/checkpoint_v6_leaf_reader.go @@ -29,7 +29,7 @@ func nodeToLeaf(leaf *node.Node) *LeafNode { // OpenAndReadLeafNodesFromCheckpointV6 takes a channel for pushing the leaf nodes that are read from // the given checkpoint file specified by dir and fileName. // It returns when finish reading the checkpoint file and the input channel can be closed. -func OpenAndReadLeafNodesFromCheckpointV6(allLeafNodesCh chan<- *LeafNode, dir string, fileName string, logger *zerolog.Logger) (errToReturn error) { +func OpenAndReadLeafNodesFromCheckpointV6(allLeafNodesCh chan<- *LeafNode, dir string, fileName string, logger zerolog.Logger) (errToReturn error) { // we are the only sender of the channel, closing it after done defer func() { close(allLeafNodesCh) @@ -68,7 +68,7 @@ func OpenAndReadLeafNodesFromCheckpointV6(allLeafNodesCh chan<- *LeafNode, dir s return nil } -func readCheckpointSubTrieLeafNodes(leafNodesCh chan<- *LeafNode, dir string, fileName string, index int, checksum uint32, logger *zerolog.Logger) error { +func readCheckpointSubTrieLeafNodes(leafNodesCh chan<- *LeafNode, dir string, fileName string, index int, checksum uint32, logger zerolog.Logger) error { return processCheckpointSubTrie(dir, fileName, index, checksum, logger, func(reader *Crc32Reader, nodesCount uint64) error { scratch := make([]byte, 1024*4) // must not be less than 1024 diff --git a/ledger/complete/wal/checkpoint_v6_reader.go b/ledger/complete/wal/checkpoint_v6_reader.go index 98a9b2f4b77..63791e0f46c 100644 --- a/ledger/complete/wal/checkpoint_v6_reader.go +++ b/ledger/complete/wal/checkpoint_v6_reader.go @@ -31,7 +31,7 @@ var ErrEOFNotReached = errors.New("expect to reach EOF, but actually didn't") // it returns (nil, os.ErrNotExist) if a certain file is missing, use (os.IsNotExist to check) // it returns (nil, ErrEOFNotReached) if a certain part file is malformed // it returns (nil, err) if running into any exception -func readCheckpointV6(headerFile *os.File, logger *zerolog.Logger) ([]*trie.MTrie, error) { +func readCheckpointV6(headerFile *os.File, logger zerolog.Logger) ([]*trie.MTrie, error) { // the full path of header file headerPath := headerFile.Name() dir, fileName := filepath.Split(headerPath) @@ -53,7 +53,7 @@ func readCheckpointV6(headerFile *os.File, logger *zerolog.Logger) ([]*trie.MTri // TODO making number of goroutine configable for reading subtries, which can help us // test the code on machines that don't have as much RAM as EN by using fewer goroutines. - subtrieNodes, err := readSubTriesConcurrently(dir, fileName, subtrieChecksums, &lg) + subtrieNodes, err := readSubTriesConcurrently(dir, fileName, subtrieChecksums, lg) if err != nil { return nil, fmt.Errorf("could not read subtrie from dir: %w", err) } @@ -61,7 +61,7 @@ func readCheckpointV6(headerFile *os.File, logger *zerolog.Logger) ([]*trie.MTri lg.Info().Uint32("topsum", topTrieChecksum). Msg("finish reading all v6 subtrie files, start reading top level tries") - tries, err := readTopLevelTries(dir, fileName, subtrieNodes, topTrieChecksum, &lg) + tries, err := readTopLevelTries(dir, fileName, subtrieNodes, topTrieChecksum, lg) if err != nil { return nil, fmt.Errorf("could not read top level nodes or tries: %w", err) } @@ -83,7 +83,7 @@ func readCheckpointV6(headerFile *os.File, logger *zerolog.Logger) ([]*trie.MTri } // OpenAndReadCheckpointV6 open the checkpoint file and read it with readCheckpointV6 -func OpenAndReadCheckpointV6(dir string, fileName string, logger *zerolog.Logger) ( +func OpenAndReadCheckpointV6(dir string, fileName string, logger zerolog.Logger) ( tries []*trie.MTrie, errToReturn error, ) { @@ -127,7 +127,7 @@ func filePathPattern(dir string, fileName string) string { // readCheckpointHeader takes a file path and returns subtrieChecksums and topTrieChecksum // any error returned are exceptions -func readCheckpointHeader(filepath string, logger *zerolog.Logger) ( +func readCheckpointHeader(filepath string, logger zerolog.Logger) ( checksumsOfSubtries []uint32, checksumOfTopTrie uint32, errToReturn error, @@ -278,7 +278,7 @@ type resultReadSubTrie struct { Err error } -func readSubTriesConcurrently(dir string, fileName string, subtrieChecksums []uint32, logger *zerolog.Logger) ([][]*node.Node, error) { +func readSubTriesConcurrently(dir string, fileName string, subtrieChecksums []uint32, logger zerolog.Logger) ([][]*node.Node, error) { numOfSubTries := len(subtrieChecksums) jobs := make(chan jobReadSubtrie, numOfSubTries) @@ -325,7 +325,7 @@ func readSubTriesConcurrently(dir string, fileName string, subtrieChecksums []ui return nodesGroups, nil } -func readCheckpointSubTrie(dir string, fileName string, index int, checksum uint32, logger *zerolog.Logger) ( +func readCheckpointSubTrie(dir string, fileName string, index int, checksum uint32, logger zerolog.Logger) ( []*node.Node, error, ) { @@ -372,7 +372,7 @@ func processCheckpointSubTrie( fileName string, index int, checksum uint32, - logger *zerolog.Logger, + logger zerolog.Logger, processNode func(*Crc32Reader, uint64) error, ) ( errToReturn error, @@ -498,7 +498,7 @@ func readSubTriesFooter(f *os.File) (uint64, uint32, error) { // 5. node count // 6. trie count // 7. checksum -func readTopLevelTries(dir string, fileName string, subtrieNodes [][]*node.Node, topTrieChecksum uint32, logger *zerolog.Logger) ( +func readTopLevelTries(dir string, fileName string, subtrieNodes [][]*node.Node, topTrieChecksum uint32, logger zerolog.Logger) ( rootTries []*trie.MTrie, errToReturn error, ) { diff --git a/ledger/complete/wal/checkpoint_v6_test.go b/ledger/complete/wal/checkpoint_v6_test.go index fb98777e0ec..78a52624bbc 100644 --- a/ledger/complete/wal/checkpoint_v6_test.go +++ b/ledger/complete/wal/checkpoint_v6_test.go @@ -170,7 +170,7 @@ func TestEncodeSubTrie(t *testing.T) { for index, roots := range subtrieRoots { unittest.RunWithTempDir(t, func(dir string) { uniqueIndices, nodeCount, checksum, err := storeCheckpointSubTrie( - index, roots, estimatedSubtrieNodeCount, dir, file, &logger) + index, roots, estimatedSubtrieNodeCount, dir, file, logger) require.NoError(t, err) // subtrie roots might have duplciates, that why we group the them, @@ -205,7 +205,7 @@ func TestEncodeSubTrie(t *testing.T) { uniqueIndices, nodeCount, checksum) // all the nodes - nodes, err := readCheckpointSubTrie(dir, file, index, checksum, &logger) + nodes, err := readCheckpointSubTrie(dir, file, index, checksum, logger) require.NoError(t, err) for _, root := range roots { @@ -263,7 +263,7 @@ func TestWriteAndReadCheckpointV6EmptyTrie(t *testing.T) { fileName := "checkpoint-empty-trie" logger := unittest.Logger() require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") - decoded, err := OpenAndReadCheckpointV6(dir, fileName, &logger) + decoded, err := OpenAndReadCheckpointV6(dir, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) requireTriesEqual(t, tries, decoded) }) @@ -275,7 +275,7 @@ func TestWriteAndReadCheckpointV6SimpleTrie(t *testing.T) { fileName := "checkpoint" logger := unittest.Logger() require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") - decoded, err := OpenAndReadCheckpointV6(dir, fileName, &logger) + decoded, err := OpenAndReadCheckpointV6(dir, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) requireTriesEqual(t, tries, decoded) }) @@ -287,7 +287,7 @@ func TestWriteAndReadCheckpointV6MultipleTries(t *testing.T) { fileName := "checkpoint-multi-file" logger := unittest.Logger() require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, fileName, &logger), "fail to store checkpoint") - decoded, err := OpenAndReadCheckpointV6(dir, fileName, &logger) + decoded, err := OpenAndReadCheckpointV6(dir, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) requireTriesEqual(t, tries, decoded) }) @@ -322,7 +322,7 @@ func TestWriteAndReadCheckpointV6LeafEmptyTrie(t *testing.T) { bufSize := 10 leafNodesCh := make(chan *LeafNode, bufSize) go func() { - err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, &logger) + err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) }() for range leafNodesCh { @@ -340,7 +340,7 @@ func TestWriteAndReadCheckpointV6LeafSimpleTrie(t *testing.T) { bufSize := 1 leafNodesCh := make(chan *LeafNode, bufSize) go func() { - err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, &logger) + err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) }() resultPayloads := make([]ledger.Payload, 0) @@ -363,7 +363,7 @@ func TestWriteAndReadCheckpointV6LeafMultipleTries(t *testing.T) { bufSize := 5 leafNodesCh := make(chan *LeafNode, bufSize) go func() { - err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, &logger) + err := OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) }() resultPayloads := make([]ledger.Payload, 0) @@ -422,7 +422,7 @@ func compareFiles(file1, file2 string) error { return nil } -func storeCheckpointV5(tries []*trie.MTrie, dir string, fileName string, logger *zerolog.Logger) error { +func storeCheckpointV5(tries []*trie.MTrie, dir string, fileName string, logger zerolog.Logger) error { return StoreCheckpointV5(dir, fileName, logger, tries...) } @@ -432,8 +432,8 @@ func TestWriteAndReadCheckpointV5(t *testing.T) { fileName := "checkpoint1" logger := unittest.Logger() - require.NoErrorf(t, storeCheckpointV5(tries, dir, fileName, &logger), "fail to store checkpoint") - decoded, err := LoadCheckpoint(filepath.Join(dir, fileName), &logger) + require.NoErrorf(t, storeCheckpointV5(tries, dir, fileName, logger), "fail to store checkpoint") + decoded, err := LoadCheckpoint(filepath.Join(dir, fileName), logger) require.NoErrorf(t, err, "fail to load checkpoint") requireTriesEqual(t, tries, decoded) }) @@ -448,12 +448,12 @@ func TestWriteAndReadCheckpointV6ThenBackToV5(t *testing.T) { // store tries into v6 then read back, then store into v5 require.NoErrorf(t, StoreCheckpointV6Concurrently(tries, dir, "checkpoint-v6", &logger), "fail to store checkpoint") - decoded, err := OpenAndReadCheckpointV6(dir, "checkpoint-v6", &logger) + decoded, err := OpenAndReadCheckpointV6(dir, "checkpoint-v6", logger) require.NoErrorf(t, err, "fail to read checkpoint %v/checkpoint-v6", dir) - require.NoErrorf(t, storeCheckpointV5(decoded, dir, "checkpoint-v6-v5", &logger), "fail to store checkpoint") + require.NoErrorf(t, storeCheckpointV5(decoded, dir, "checkpoint-v6-v5", logger), "fail to store checkpoint") // store tries directly into v5 checkpoint - require.NoErrorf(t, storeCheckpointV5(tries, dir, "checkpoint-v5", &logger), "fail to store checkpoint") + require.NoErrorf(t, storeCheckpointV5(tries, dir, "checkpoint-v5", logger), "fail to store checkpoint") // compare the two v5 checkpoint files should be identical require.NoError(t, compareFiles( @@ -511,7 +511,7 @@ func TestAllPartFileExist(t *testing.T) { err = os.Remove(fileToDelete) require.NoError(t, err, "fail to remove part file") - _, err = OpenAndReadCheckpointV6(dir, fileName, &logger) + _, err = OpenAndReadCheckpointV6(dir, fileName, logger) require.ErrorIs(t, err, os.ErrNotExist, "wrong error type returned") } }) @@ -541,7 +541,7 @@ func TestAllPartFileExistLeafReader(t *testing.T) { bufSize := 10 leafNodesCh := make(chan *LeafNode, bufSize) - err = OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, &logger) + err = OpenAndReadLeafNodesFromCheckpointV6(leafNodesCh, dir, fileName, logger) require.ErrorIs(t, err, os.ErrNotExist, "wrong error type returned") } }) @@ -585,7 +585,7 @@ func TestCopyCheckpointFileV6(t *testing.T) { newPaths, err := CopyCheckpointFile(fileName, dir, to) require.NoError(t, err) log.Info().Msgf("copied to :%v", newPaths) - decoded, err := OpenAndReadCheckpointV6(to, fileName, &logger) + decoded, err := OpenAndReadCheckpointV6(to, fileName, logger) require.NoErrorf(t, err, "fail to read checkpoint %v/%v", dir, fileName) requireTriesEqual(t, tries, decoded) }) diff --git a/ledger/complete/wal/checkpoint_v6_writer.go b/ledger/complete/wal/checkpoint_v6_writer.go index 7b138a61085..ea2021f2156 100644 --- a/ledger/complete/wal/checkpoint_v6_writer.go +++ b/ledger/complete/wal/checkpoint_v6_writer.go @@ -103,7 +103,7 @@ func storeCheckpointV6( subTrieRootAndTopLevelTrieCount(tries), outputDir, outputFile, - &lg, + lg, nWorker, ) if err != nil { @@ -113,12 +113,12 @@ func storeCheckpointV6( lg.Info().Msgf("subtrie have been stored. sub trie node count: %v", subTriesNodeCount) topTrieChecksum, err := storeTopLevelNodesAndTrieRoots( - tries, subTrieRootIndices, subTriesNodeCount, outputDir, outputFile, &lg) + tries, subTrieRootIndices, subTriesNodeCount, outputDir, outputFile, lg) if err != nil { return fmt.Errorf("could not store top level tries: %w", err) } - err = storeCheckpointHeader(subTrieChecksums, topTrieChecksum, outputDir, outputFile, &lg) + err = storeCheckpointHeader(subTrieChecksums, topTrieChecksum, outputDir, outputFile, lg) if err != nil { return fmt.Errorf("could not store checkpoint header: %w", err) } @@ -136,7 +136,7 @@ func storeCheckpointHeader( topTrieChecksum uint32, outputDir string, outputFile string, - logger *zerolog.Logger, + logger zerolog.Logger, ) ( errToReturn error, ) { @@ -207,7 +207,7 @@ func storeTopLevelNodesAndTrieRoots( subTriesNodeCount uint64, outputDir string, outputFile string, - logger *zerolog.Logger, + logger zerolog.Logger, ) ( checksumOfTopTriePartFile uint32, errToReturn error, @@ -319,7 +319,7 @@ func storeSubTrieConcurrently( subAndTopNodeCount int, // useful for preallocating memory for the node indices map to be returned outputDir string, outputFile string, - logger *zerolog.Logger, + logger zerolog.Logger, nWorker uint, ) ( map[*node.Node]uint64, // node indices @@ -399,13 +399,13 @@ func storeSubTrieConcurrently( return results, nodeCounter, checksums, nil } -func createWriterForTopTries(dir string, file string, logger *zerolog.Logger) (io.WriteCloser, error) { +func createWriterForTopTries(dir string, file string, logger zerolog.Logger) (io.WriteCloser, error) { _, topTriesFileName := filePathTopTries(dir, file) return createClosableWriter(dir, topTriesFileName, logger) } -func createWriterForSubtrie(dir string, file string, logger *zerolog.Logger, index int) (io.WriteCloser, error) { +func createWriterForSubtrie(dir string, file string, logger zerolog.Logger, index int) (io.WriteCloser, error) { _, subTriesFileName, err := filePathSubTries(dir, file, index) if err != nil { return nil, err @@ -414,7 +414,7 @@ func createWriterForSubtrie(dir string, file string, logger *zerolog.Logger, ind return createClosableWriter(dir, subTriesFileName, logger) } -func createClosableWriter(dir string, fileName string, logger *zerolog.Logger) (io.WriteCloser, error) { +func createClosableWriter(dir string, fileName string, logger zerolog.Logger) (io.WriteCloser, error) { fullPath := path.Join(dir, fileName) if utilsio.FileExists(fullPath) { return nil, fmt.Errorf("checkpoint part file %v already exists", fullPath) @@ -447,7 +447,7 @@ func storeCheckpointSubTrie( estimatedSubtrieNodeCount int, // for estimate the amount of memory to be preallocated outputDir string, outputFile string, - logger *zerolog.Logger, + logger zerolog.Logger, ) ( rootNodesOfAllSubtries map[*node.Node]uint64, // the stored position of each unique root node totalSubtrieNodeCount uint64, diff --git a/ledger/complete/wal/checkpointer.go b/ledger/complete/wal/checkpointer.go index 6b9239f1c22..5e58ff95235 100644 --- a/ledger/complete/wal/checkpointer.go +++ b/ledger/complete/wal/checkpointer.go @@ -267,7 +267,7 @@ func NumberToFilename(n int) string { } func (c *Checkpointer) CheckpointWriter(to int) (io.WriteCloser, error) { - return CreateCheckpointWriterForFile(c.dir, NumberToFilename(to), &c.wal.log) + return CreateCheckpointWriterForFile(c.dir, NumberToFilename(to), c.wal.log) } func (c *Checkpointer) Dir() string { @@ -275,7 +275,7 @@ func (c *Checkpointer) Dir() string { } // CreateCheckpointWriterForFile returns a file writer that will write to a temporary file and then move it to the checkpoint folder by renaming it. -func CreateCheckpointWriterForFile(dir, filename string, logger *zerolog.Logger) (io.WriteCloser, error) { +func CreateCheckpointWriterForFile(dir, filename string, logger zerolog.Logger) (io.WriteCloser, error) { fullname := path.Join(dir, filename) @@ -312,7 +312,7 @@ func CreateCheckpointWriterForFile(dir, filename string, logger *zerolog.Logger) // as for each node, the children have been previously encountered. // TODO: evaluate alternatives to CRC32 since checkpoint file is many GB in size. // TODO: add concurrency if the performance gains are enough to offset complexity. -func StoreCheckpointV5(dir string, fileName string, logger *zerolog.Logger, tries ...*trie.MTrie) ( +func StoreCheckpointV5(dir string, fileName string, logger zerolog.Logger, tries ...*trie.MTrie) ( // error // Note, the above code, which didn't define the name "err" for the returned error, would be wrong, // beause err needs to be defined in order to be updated by the defer function @@ -428,7 +428,7 @@ func StoreCheckpointV5(dir string, fileName string, logger *zerolog.Logger, trie // Index 0 is a special case with nil node. traversedSubtrieNodes[nil] = 0 - logging := logProgress(fmt.Sprintf("storing %v-th sub trie roots", i), estimatedSubtrieNodeCount, &log.Logger) + logging := logProgress(fmt.Sprintf("storing %v-th sub trie roots", i), estimatedSubtrieNodeCount, log.Logger) for _, root := range subTrieRoot { // Empty trie is always added to forest as starting point and // empty trie's root is nil. It remains in the forest until evicted @@ -516,7 +516,7 @@ func StoreCheckpointV5(dir string, fileName string, logger *zerolog.Logger, trie return nil } -func logProgress(msg string, estimatedSubtrieNodeCount int, logger *zerolog.Logger) func(nodeCounter uint64) { +func logProgress(msg string, estimatedSubtrieNodeCount int, logger zerolog.Logger) func(nodeCounter uint64) { lg := util.LogProgress(msg, estimatedSubtrieNodeCount, logger) return func(index uint64) { lg(int(index)) @@ -601,12 +601,12 @@ func getNodesAtLevel(root *node.Node, level uint) []*node.Node { func (c *Checkpointer) LoadCheckpoint(checkpoint int) ([]*trie.MTrie, error) { filepath := path.Join(c.dir, NumberToFilename(checkpoint)) - return LoadCheckpoint(filepath, &c.wal.log) + return LoadCheckpoint(filepath, c.wal.log) } func (c *Checkpointer) LoadRootCheckpoint() ([]*trie.MTrie, error) { filepath := path.Join(c.dir, bootstrap.FilenameWALRootCheckpoint) - return LoadCheckpoint(filepath, &c.wal.log) + return LoadCheckpoint(filepath, c.wal.log) } func (c *Checkpointer) HasRootCheckpoint() (bool, error) { @@ -628,7 +628,7 @@ func (c *Checkpointer) RemoveCheckpoint(checkpoint int) error { return deleteCheckpointFiles(c.dir, name) } -func LoadCheckpoint(filepath string, logger *zerolog.Logger) ( +func LoadCheckpoint(filepath string, logger zerolog.Logger) ( tries []*trie.MTrie, errToReturn error) { file, err := os.Open(filepath) @@ -648,7 +648,7 @@ func LoadCheckpoint(filepath string, logger *zerolog.Logger) ( return readCheckpoint(file, logger) } -func readCheckpoint(f *os.File, logger *zerolog.Logger) ([]*trie.MTrie, error) { +func readCheckpoint(f *os.File, logger zerolog.Logger) ([]*trie.MTrie, error) { // Read header: magic (2 bytes) + version (2 bytes) header := make([]byte, headerSize) @@ -888,7 +888,7 @@ func readCheckpointV4(f *os.File) ([]*trie.MTrie, error) { // readCheckpointV5 decodes checkpoint file (version 5) and returns a list of tries. // Checkpoint file header (magic and version) are verified by the caller. -func readCheckpointV5(f *os.File, logger *zerolog.Logger) ([]*trie.MTrie, error) { +func readCheckpointV5(f *os.File, logger zerolog.Logger) ([]*trie.MTrie, error) { logger.Info().Msgf("reading v5 checkpoint file") // Scratch buffer is used as temporary buffer that reader can read into. @@ -1006,7 +1006,7 @@ func readCheckpointV5(f *os.File, logger *zerolog.Logger) ([]*trie.MTrie, error) // causes two checkpoint files to be cached for each checkpointing, eventually // caching hundreds of GB. // CAUTION: no-op when GOOS != linux. -func evictFileFromLinuxPageCache(f *os.File, fsync bool, logger *zerolog.Logger) error { +func evictFileFromLinuxPageCache(f *os.File, fsync bool, logger zerolog.Logger) error { err := fadviseNoLinuxPageCache(f.Fd(), fsync) if err != nil { return err diff --git a/ledger/complete/wal/checkpointer_test.go b/ledger/complete/wal/checkpointer_test.go index 40ec3ff5925..a0a828748d3 100644 --- a/ledger/complete/wal/checkpointer_test.go +++ b/ledger/complete/wal/checkpointer_test.go @@ -531,12 +531,12 @@ func Test_StoringLoadingCheckpoints(t *testing.T) { fullpath := path.Join(dir, "temp-checkpoint") - err = realWAL.StoreCheckpointV5(dir, "temp-checkpoint", &logger, updatedTrie) + err = realWAL.StoreCheckpointV5(dir, "temp-checkpoint", logger, updatedTrie) require.NoError(t, err) t.Run("works without data modification", func(t *testing.T) { logger := unittest.Logger() - tries, err := realWAL.LoadCheckpoint(fullpath, &logger) + tries, err := realWAL.LoadCheckpoint(fullpath, logger) require.NoError(t, err) require.Equal(t, 1, len(tries)) require.Equal(t, updatedTrie, tries[0]) @@ -554,7 +554,7 @@ func Test_StoringLoadingCheckpoints(t *testing.T) { require.NoError(t, err) logger := unittest.Logger() - tries, err := realWAL.LoadCheckpoint(fullpath, &logger) + tries, err := realWAL.LoadCheckpoint(fullpath, logger) require.Error(t, err) require.Nil(t, tries) require.Contains(t, err.Error(), "checksum") diff --git a/ledger/complete/wal/checkpointer_versioning_test.go b/ledger/complete/wal/checkpointer_versioning_test.go index 58c85a3d2dc..af2d6ab4acd 100644 --- a/ledger/complete/wal/checkpointer_versioning_test.go +++ b/ledger/complete/wal/checkpointer_versioning_test.go @@ -20,7 +20,7 @@ func TestLoadCheckpointV1(t *testing.T) { } logger := zerolog.Nop() - tries, err := LoadCheckpoint("test_data/checkpoint.v1", &logger) + tries, err := LoadCheckpoint("test_data/checkpoint.v1", logger) require.NoError(t, err) require.Equal(t, len(expectedRootHash), len(tries)) @@ -40,7 +40,7 @@ func TestLoadCheckpointV3(t *testing.T) { } logger := zerolog.Nop() - tries, err := LoadCheckpoint("test_data/checkpoint.v3", &logger) + tries, err := LoadCheckpoint("test_data/checkpoint.v3", logger) require.NoError(t, err) require.Equal(t, len(expectedRootHash), len(tries)) @@ -60,7 +60,7 @@ func TestLoadCheckpointV4(t *testing.T) { } logger := zerolog.Nop() - tries, err := LoadCheckpoint("test_data/checkpoint.v4", &logger) + tries, err := LoadCheckpoint("test_data/checkpoint.v4", logger) require.NoError(t, err) require.Equal(t, len(expectedRootHash), len(tries)) diff --git a/ledger/complete/wal/syncrename.go b/ledger/complete/wal/syncrename.go index 140d4534006..28a0e47cfea 100644 --- a/ledger/complete/wal/syncrename.go +++ b/ledger/complete/wal/syncrename.go @@ -21,7 +21,7 @@ type WriterSeekerCloser interface { // to target one as the last step. This help avoid situation when writing is // interrupted and unusable file but with target name exists. type SyncOnCloseRenameFile struct { - logger *zerolog.Logger + logger zerolog.Logger file *os.File targetName string savedError error // savedError is the first error returned from Write. Close() renames temp file to target file only if savedError is nil. diff --git a/ledger/complete/wal/syncrename_test.go b/ledger/complete/wal/syncrename_test.go index 406905a631b..c8ee860f487 100644 --- a/ledger/complete/wal/syncrename_test.go +++ b/ledger/complete/wal/syncrename_test.go @@ -34,7 +34,7 @@ func Test_RenameHappensAfterClosing(t *testing.T) { file: file, targetName: fullFileName, Writer: writer, - logger: &logger, + logger: logger, } sampleBytes := []byte{2, 1, 3, 7} diff --git a/module/util/log.go b/module/util/log.go index 45807b9757d..d949e042168 100644 --- a/module/util/log.go +++ b/module/util/log.go @@ -1,6 +1,9 @@ package util import ( + "sync" + "time" + "github.com/rs/zerolog" ) @@ -8,18 +11,91 @@ import ( // it prints the progress from 0% to 100% to indicate the index from 0 to (total - 1) has been // processed. // useful to report the progress of processing the index from 0 to (total - 1) -func LogProgress(msg string, total int, logger *zerolog.Logger) func(currentIndex int) { - logThreshold := float64(0) +func LogProgress(msg string, total int, log zerolog.Logger) func(currentIndex int) { + nth := uint32(total / 10) // sample every 10% by default + if nth == 0 { + nth = 1 + } + + sampler := log.Sample(newProgressLogsSampler(nth, 60*time.Second)) + + start := time.Now() return func(currentIndex int) { percentage := float64(100) if total > 0 { percentage = (float64(currentIndex+1) / float64(total)) * 100. // currentIndex+1 assuming zero based indexing } - // report every 10 percent - if percentage >= logThreshold { - logger.Info().Msgf("%s progress: %v percent", msg, logThreshold) - logThreshold += 10 + etaString := "unknown" + if percentage > 0 { + eta := time.Duration(float64(time.Since(start)) / percentage * (100 - percentage)) + etaString = eta.String() + + } + + if currentIndex+1 != total { + sampler.Info().Msgf("%s progress %d/%d (%.1f%%) eta %s", msg, currentIndex+1, total, percentage, etaString) + } else { + log.Info().Msgf("%s progress %d/%d (%.1f%%) total time %s", msg, currentIndex+1, total, percentage, time.Since(start)) } } } + +type TimedSampler struct { + start time.Time + Duration time.Duration + mu sync.Mutex +} + +var _ zerolog.Sampler = (*TimedSampler)(nil) + +func NewTimedSampler(duration time.Duration) *TimedSampler { + return &TimedSampler{ + start: time.Now(), + Duration: duration, + mu: sync.Mutex{}, + } +} + +func (s *TimedSampler) Sample(_ zerolog.Level) bool { + s.mu.Lock() + defer s.mu.Unlock() + + if time.Since(s.start) > s.Duration { + s.start = time.Now() + return true + } + return false +} + +func (s *TimedSampler) Reset() { + s.mu.Lock() + defer s.mu.Unlock() + + s.start = time.Now() +} + +type progressLogsSampler struct { + basicSampler *zerolog.BasicSampler + timedSampler *TimedSampler +} + +var _ zerolog.Sampler = (*progressLogsSampler)(nil) + +// newProgressLogsSampler returns a sampler that samples every nth log +// and also samples a log if the last log was more than duration ago +func newProgressLogsSampler(nth uint32, duration time.Duration) zerolog.Sampler { + return &progressLogsSampler{ + basicSampler: &zerolog.BasicSampler{N: nth}, + timedSampler: NewTimedSampler(duration), + } +} + +func (s *progressLogsSampler) Sample(lvl zerolog.Level) bool { + sample := s.basicSampler.Sample(lvl) + if sample { + s.timedSampler.Reset() + return true + } + return s.timedSampler.Sample(lvl) +} diff --git a/module/util/log_test.go b/module/util/log_test.go index 9d1d4851dcd..4593f575343 100644 --- a/module/util/log_test.go +++ b/module/util/log_test.go @@ -2,6 +2,7 @@ package util import ( "bytes" + "fmt" "testing" "github.com/rs/zerolog" @@ -12,48 +13,46 @@ func TestLogProgress40(t *testing.T) { buf := bytes.NewBufferString("") lg := zerolog.New(buf) total := 40 - logger := LogProgress("test", total, &lg) + logger := LogProgress("test", total, lg) for i := 0; i < total; i++ { logger(i) } - expectedLogs := - `{"level":"info","message":"test progress: 0 percent"} -{"level":"info","message":"test progress: 10 percent"} -{"level":"info","message":"test progress: 20 percent"} -{"level":"info","message":"test progress: 30 percent"} -{"level":"info","message":"test progress: 40 percent"} -{"level":"info","message":"test progress: 50 percent"} -{"level":"info","message":"test progress: 60 percent"} -{"level":"info","message":"test progress: 70 percent"} -{"level":"info","message":"test progress: 80 percent"} -{"level":"info","message":"test progress: 90 percent"} -{"level":"info","message":"test progress: 100 percent"} -` - require.Equal(t, expectedLogs, buf.String()) + expectedLogs := []string{ + `test progress 1/40 (2.5%)`, + `test progress 5/40 (12.5%)`, + `test progress 9/40 (22.5%)`, + `test progress 13/40 (32.5%)`, + `test progress 17/40 (42.5%)`, + `test progress 21/40 (52.5%)`, + `test progress 25/40 (62.5%)`, + `test progress 29/40 (72.5%)`, + `test progress 33/40 (82.5%)`, + `test progress 37/40 (92.5%)`, + `test progress 40/40 (100.0%)`, + } + + for _, log := range expectedLogs { + require.Contains(t, buf.String(), log, total) + } } func TestLogProgress1000(t *testing.T) { for total := 11; total < 1000; total++ { buf := bytes.NewBufferString("") lg := zerolog.New(buf) - logger := LogProgress("test", total, &lg) + logger := LogProgress("test", total, lg) for i := 0; i < total; i++ { logger(i) } - expectedLogs := `{"level":"info","message":"test progress: 0 percent"} -{"level":"info","message":"test progress: 10 percent"} -{"level":"info","message":"test progress: 20 percent"} -{"level":"info","message":"test progress: 30 percent"} -{"level":"info","message":"test progress: 40 percent"} -{"level":"info","message":"test progress: 50 percent"} -{"level":"info","message":"test progress: 60 percent"} -{"level":"info","message":"test progress: 70 percent"} -{"level":"info","message":"test progress: 80 percent"} -{"level":"info","message":"test progress: 90 percent"} -{"level":"info","message":"test progress: 100 percent"} -` - require.Equal(t, expectedLogs, buf.String(), total) + expectedLogs := []string{ + fmt.Sprintf(`test progress 1/%d`, total), + fmt.Sprintf(`test progress %d/%d (100.0%%)`, total, total), + } + + for _, log := range expectedLogs { + require.Contains(t, buf.String(), log, total) + } } }