github.com/onflow/flow-go@v0.33.17/cmd/util/ledger/migrations/account_based_migration.go (about)

     1  package migrations
     2  
     3  import (
     4  	"container/heap"
     5  	"context"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/rs/zerolog"
    12  
    13  	"github.com/onflow/cadence/runtime/common"
    14  
    15  	"github.com/onflow/flow-go/cmd/util/ledger/util"
    16  	"github.com/onflow/flow-go/ledger"
    17  	moduleUtil "github.com/onflow/flow-go/module/util"
    18  )
    19  
    20  // logTopNDurations is the number of longest migrations to log at the end of the migration
    21  const logTopNDurations = 20
    22  
    23  // AccountBasedMigration is an interface for migrations that migrate account by account
    24  // concurrently getting all the payloads for each account at a time.
    25  type AccountBasedMigration interface {
    26  	InitMigration(
    27  		log zerolog.Logger,
    28  		allPayloads []*ledger.Payload,
    29  		nWorkers int,
    30  	) error
    31  	MigrateAccount(
    32  		ctx context.Context,
    33  		address common.Address,
    34  		payloads []*ledger.Payload,
    35  	) ([]*ledger.Payload, error)
    36  	io.Closer
    37  }
    38  
    39  // CreateAccountBasedMigration creates a migration function that migrates the payloads
    40  // account by account using the given migrations
    41  // accounts are processed concurrently using the given number of workers
    42  // but each account is processed sequentially by the given migrations in order.
    43  // The migrations InitMigration function is called once before the migration starts
    44  // And the Close function is called once after the migration finishes if the migration
    45  // is a finisher.
    46  func CreateAccountBasedMigration(
    47  	log zerolog.Logger,
    48  	nWorker int,
    49  	migrations []AccountBasedMigration,
    50  ) func(payloads []*ledger.Payload) ([]*ledger.Payload, error) {
    51  	return func(payloads []*ledger.Payload) ([]*ledger.Payload, error) {
    52  		return MigrateByAccount(
    53  			log,
    54  			nWorker,
    55  			payloads,
    56  			migrations,
    57  		)
    58  	}
    59  }
    60  
    61  // MigrateByAccount takes migrations and all the Payloads,
    62  // and returns the migrated Payloads.
    63  func MigrateByAccount(
    64  	log zerolog.Logger,
    65  	nWorker int,
    66  	allPayloads []*ledger.Payload,
    67  	migrations []AccountBasedMigration,
    68  ) (
    69  	[]*ledger.Payload,
    70  	error,
    71  ) {
    72  	if len(allPayloads) == 0 {
    73  		return allPayloads, nil
    74  	}
    75  
    76  	for i, migrator := range migrations {
    77  		if err := migrator.InitMigration(
    78  			log.With().
    79  				Int("migration_index", i).
    80  				Logger(),
    81  			allPayloads,
    82  			nWorker,
    83  		); err != nil {
    84  			return nil, fmt.Errorf("could not init migration: %w", err)
    85  		}
    86  	}
    87  
    88  	log.Info().
    89  		Int("inner_migrations", len(migrations)).
    90  		Int("nWorker", nWorker).
    91  		Msgf("created account migrations")
    92  
    93  	defer func() {
    94  		for i, migrator := range migrations {
    95  			log.Info().
    96  				Int("migration_index", i).
    97  				Type("migration", migrator).
    98  				Msg("closing migration")
    99  			if err := migrator.Close(); err != nil {
   100  				log.Error().Err(err).Msg("error closing migration")
   101  			}
   102  		}
   103  	}()
   104  
   105  	// group the Payloads by account
   106  	accountGroups := util.GroupPayloadsByAccount(log, allPayloads, nWorker)
   107  
   108  	// migrate the Payloads under accounts
   109  	migrated, err := MigrateGroupConcurrently(log, migrations, accountGroups, nWorker)
   110  
   111  	if err != nil {
   112  		return nil, fmt.Errorf("could not migrate accounts: %w", err)
   113  	}
   114  
   115  	log.Info().
   116  		Int("account_count", accountGroups.Len()).
   117  		Int("payload_count", len(allPayloads)).
   118  		Msgf("finished migrating Payloads")
   119  
   120  	return migrated, nil
   121  }
   122  
   123  // MigrateGroupConcurrently migrate the Payloads in the given account groups.
   124  // It uses nWorker to process the Payloads concurrently. The Payloads in each account
   125  // are processed sequentially by the given migrations in order.
   126  func MigrateGroupConcurrently(
   127  	log zerolog.Logger,
   128  	migrations []AccountBasedMigration,
   129  	accountGroups *util.PayloadAccountGrouping,
   130  	nWorker int,
   131  ) ([]*ledger.Payload, error) {
   132  
   133  	ctx := context.Background()
   134  	ctx, cancel := context.WithCancelCause(ctx)
   135  	defer cancel(nil)
   136  
   137  	jobs := make(chan jobMigrateAccountGroup, accountGroups.Len())
   138  
   139  	wg := sync.WaitGroup{}
   140  	wg.Add(nWorker)
   141  	resultCh := make(chan *migrationResult, accountGroups.Len())
   142  	for i := 0; i < nWorker; i++ {
   143  		go func() {
   144  			defer wg.Done()
   145  
   146  			for {
   147  				select {
   148  				case <-ctx.Done():
   149  					return
   150  				case job, ok := <-jobs:
   151  					if !ok {
   152  						return
   153  					}
   154  					start := time.Now()
   155  
   156  					// This is not an account, but service level keys.
   157  					if util.IsServiceLevelAddress(job.Address) {
   158  						resultCh <- &migrationResult{
   159  							migrationDuration: migrationDuration{
   160  								Address:      job.Address,
   161  								Duration:     time.Since(start),
   162  								PayloadCount: len(job.Payloads),
   163  							},
   164  							Migrated: job.Payloads,
   165  						}
   166  						continue
   167  					}
   168  
   169  					if _, ok := knownProblematicAccounts[job.Address]; ok {
   170  						log.Info().
   171  							Hex("address", job.Address[:]).
   172  							Int("payload_count", len(job.Payloads)).
   173  							Msg("skipping problematic account")
   174  						resultCh <- &migrationResult{
   175  							migrationDuration: migrationDuration{
   176  								Address:      job.Address,
   177  								Duration:     time.Since(start),
   178  								PayloadCount: len(job.Payloads),
   179  							},
   180  							Migrated: job.Payloads,
   181  						}
   182  						continue
   183  					}
   184  
   185  					var err error
   186  					accountMigrated := job.Payloads
   187  					for m, migrator := range migrations {
   188  
   189  						select {
   190  						case <-ctx.Done():
   191  							return
   192  						default:
   193  						}
   194  
   195  						accountMigrated, err = migrator.MigrateAccount(ctx, job.Address, accountMigrated)
   196  						if err != nil {
   197  							log.Error().
   198  								Err(err).
   199  								Int("migration_index", m).
   200  								Type("migration", migrator).
   201  								Hex("address", job.Address[:]).
   202  								Msg("could not migrate account")
   203  							cancel(fmt.Errorf("could not migrate account: %w", err))
   204  							return
   205  						}
   206  					}
   207  
   208  					resultCh <- &migrationResult{
   209  						migrationDuration: migrationDuration{
   210  							Address:      job.Address,
   211  							Duration:     time.Since(start),
   212  							PayloadCount: len(job.Payloads),
   213  						},
   214  						Migrated: accountMigrated,
   215  					}
   216  				}
   217  			}
   218  		}()
   219  	}
   220  
   221  	go func() {
   222  		defer close(jobs)
   223  		for {
   224  			g, err := accountGroups.Next()
   225  			if err != nil {
   226  				cancel(fmt.Errorf("could not get next account group: %w", err))
   227  				return
   228  			}
   229  
   230  			if g == nil {
   231  				break
   232  			}
   233  
   234  			job := jobMigrateAccountGroup{
   235  				Address:  g.Address,
   236  				Payloads: g.Payloads,
   237  			}
   238  
   239  			select {
   240  			case <-ctx.Done():
   241  				return
   242  			case jobs <- job:
   243  			}
   244  		}
   245  	}()
   246  
   247  	// read job results
   248  	logAccount := moduleUtil.LogProgress(
   249  		log,
   250  		moduleUtil.DefaultLogProgressConfig(
   251  			"processing account group",
   252  			accountGroups.Len(),
   253  		),
   254  	)
   255  
   256  	migrated := make([]*ledger.Payload, 0, accountGroups.AllPayloadsCount())
   257  	durations := newMigrationDurations(logTopNDurations)
   258  	contextDone := false
   259  	for i := 0; i < accountGroups.Len(); i++ {
   260  		select {
   261  		case <-ctx.Done():
   262  			contextDone = true
   263  			break
   264  		case result := <-resultCh:
   265  			durations.Add(result)
   266  
   267  			accountMigrated := result.Migrated
   268  			migrated = append(migrated, accountMigrated...)
   269  			logAccount(1)
   270  		}
   271  		if contextDone {
   272  			break
   273  		}
   274  	}
   275  
   276  	// make sure to exit all workers before returning from this function
   277  	// so that the migrator can be closed properly
   278  	log.Info().Msg("waiting for migration workers to finish")
   279  	wg.Wait()
   280  
   281  	log.Info().
   282  		Array("top_longest_migrations", durations.Array()).
   283  		Msgf("Top longest migrations")
   284  
   285  	if ctx.Err() != nil {
   286  		return nil, fmt.Errorf("fail to migrate payload: %w", ctx.Err())
   287  	}
   288  
   289  	return migrated, nil
   290  }
   291  
   292  var knownProblematicAccounts = map[common.Address]string{
   293  	// Testnet accounts with broken contracts
   294  	mustHexToAddress("434a1f199a7ae3ba"): "Broken contract FanTopPermission",
   295  	mustHexToAddress("454c9991c2b8d947"): "Broken contract Test",
   296  	mustHexToAddress("48602d8056ff9d93"): "Broken contract FanTopPermission",
   297  	mustHexToAddress("5d63c34d7f05e5a4"): "Broken contract FanTopPermission",
   298  	mustHexToAddress("5e3448b3cffb97f2"): "Broken contract FanTopPermission",
   299  	mustHexToAddress("7d8c7e050c694eaa"): "Broken contract Test",
   300  	mustHexToAddress("ba53f16ede01972d"): "Broken contract FanTopPermission",
   301  	mustHexToAddress("c843c1f5a4805c3a"): "Broken contract FanTopPermission",
   302  	mustHexToAddress("48d3be92e6e4a973"): "Broken contract FanTopPermission",
   303  	// Mainnet account
   304  }
   305  
   306  func mustHexToAddress(hex string) common.Address {
   307  	address, err := common.HexToAddress(hex)
   308  	if err != nil {
   309  		panic(err)
   310  	}
   311  	return address
   312  }
   313  
   314  type jobMigrateAccountGroup struct {
   315  	Address  common.Address
   316  	Payloads []*ledger.Payload
   317  }
   318  
   319  type migrationResult struct {
   320  	migrationDuration
   321  
   322  	Migrated []*ledger.Payload
   323  }
   324  
   325  type migrationDuration struct {
   326  	Address      common.Address
   327  	Duration     time.Duration
   328  	PayloadCount int
   329  }
   330  
   331  // migrationDurations implements heap methods for the timer results
   332  type migrationDurations struct {
   333  	v []migrationDuration
   334  
   335  	KeepTopN int
   336  }
   337  
   338  // newMigrationDurations creates a new migrationDurations which are used to track the
   339  // accounts that took the longest time to migrate.
   340  func newMigrationDurations(keepTopN int) *migrationDurations {
   341  	return &migrationDurations{
   342  		v:        make([]migrationDuration, 0, keepTopN),
   343  		KeepTopN: keepTopN,
   344  	}
   345  }
   346  
   347  func (h *migrationDurations) Len() int { return len(h.v) }
   348  func (h *migrationDurations) Less(i, j int) bool {
   349  	return h.v[i].Duration < h.v[j].Duration
   350  }
   351  func (h *migrationDurations) Swap(i, j int) {
   352  	h.v[i], h.v[j] = h.v[j], h.v[i]
   353  }
   354  func (h *migrationDurations) Push(x interface{}) {
   355  	h.v = append(h.v, x.(migrationDuration))
   356  }
   357  func (h *migrationDurations) Pop() interface{} {
   358  	old := h.v
   359  	n := len(old)
   360  	x := old[n-1]
   361  	h.v = old[0 : n-1]
   362  	return x
   363  }
   364  
   365  func (h *migrationDurations) Array() zerolog.LogArrayMarshaler {
   366  	array := zerolog.Arr()
   367  	for _, result := range h.v {
   368  		array = array.Str(fmt.Sprintf("%s [payloads: %d]: %s",
   369  			result.Address.Hex(),
   370  			result.PayloadCount,
   371  			result.Duration.String(),
   372  		))
   373  	}
   374  	return array
   375  }
   376  
   377  func (h *migrationDurations) Add(result *migrationResult) {
   378  	if h.Len() < h.KeepTopN || result.Duration > h.v[0].Duration {
   379  		if h.Len() == h.KeepTopN {
   380  			heap.Pop(h) // remove the element with the smallest duration
   381  		}
   382  		heap.Push(h, result.migrationDuration)
   383  	}
   384  }