github.com/koko1123/flow-go-1@v0.29.6/cmd/util/ledger/migrations/account_based_migration.go (about)

     1  package migrations
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/rs/zerolog/log"
     7  
     8  	"github.com/koko1123/flow-go-1/ledger"
     9  	"github.com/koko1123/flow-go-1/model/flow"
    10  	"github.com/koko1123/flow-go-1/module/util"
    11  )
    12  
    13  // PayloadToAccount takes a payload and return:
    14  // - (address, true, nil) if the payload is for an account, the account address is returned
    15  // - ("", false, nil) if the payload is not for an account
    16  // - ("", false, err) if running into any exception
    17  func PayloadToAccount(p ledger.Payload) (string, bool, error) {
    18  	k, err := p.Key()
    19  	if err != nil {
    20  		return "", false, fmt.Errorf("could not find key for payload: %w", err)
    21  	}
    22  	id, err := KeyToRegisterID(k)
    23  	if err != nil {
    24  		return "", false, fmt.Errorf("error converting key to register ID")
    25  	}
    26  	if len([]byte(id.Owner)) != flow.AddressLength {
    27  		return "", false, nil
    28  	}
    29  	return id.Owner, true, nil
    30  }
    31  
    32  // PayloadGroup groups payloads by account.
    33  // For global payloads, it's stored under NonAccountPayloads field
    34  type PayloadGroup struct {
    35  	NonAccountPayloads []ledger.Payload
    36  	Accounts           map[string][]ledger.Payload
    37  }
    38  
    39  // PayloadGrouping is a reducer function that adds the given payload to the corresponding
    40  // group under its account
    41  func PayloadGrouping(groups *PayloadGroup, payload ledger.Payload) (*PayloadGroup, error) {
    42  	address, isAccount, err := PayloadToAccount(payload)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	if isAccount {
    48  		groups.Accounts[address] = append(groups.Accounts[address], payload)
    49  	} else {
    50  		groups.NonAccountPayloads = append(groups.NonAccountPayloads, payload)
    51  	}
    52  
    53  	return groups, nil
    54  }
    55  
    56  // AccountMigrator takes all the payloads that belong to the given account
    57  // and return the migrated payloads
    58  type AccountMigrator interface {
    59  	MigratePayloads(account string, payloads []ledger.Payload) ([]ledger.Payload, error)
    60  }
    61  
    62  // MigrateByAccount teaks a migrator function and all the payloads, and return the migrated payloads
    63  func MigrateByAccount(migrator AccountMigrator, allPayloads []ledger.Payload, nWorker int) (
    64  	[]ledger.Payload, error) {
    65  	groups := &PayloadGroup{
    66  		NonAccountPayloads: make([]ledger.Payload, 0),
    67  		Accounts:           make(map[string][]ledger.Payload),
    68  	}
    69  
    70  	log.Info().Msgf("start grouping for a total of %v payloads", len(allPayloads))
    71  
    72  	var err error
    73  	logGrouping := util.LogProgress("grouping payload", len(allPayloads), &log.Logger)
    74  	for i, payload := range allPayloads {
    75  		groups, err = PayloadGrouping(groups, payload)
    76  		if err != nil {
    77  			return nil, err
    78  		}
    79  		logGrouping(i)
    80  	}
    81  
    82  	log.Info().Msgf("finish grouping for payloads by account: %v groups in total, %v NonAccountPayloads",
    83  		len(groups.Accounts), len(groups.NonAccountPayloads))
    84  
    85  	// migrate the payloads under accounts
    86  	migrated, err := MigrateGroupConcurrently(migrator, groups.Accounts, nWorker)
    87  
    88  	if err != nil {
    89  		return nil, fmt.Errorf("could not migrate group: %w", err)
    90  	}
    91  
    92  	log.Info().Msgf("finished migrating payloads for %v account", len(groups.Accounts))
    93  
    94  	// add the non accounts which don't need to be migrated
    95  	migrated = append(migrated, groups.NonAccountPayloads...)
    96  
    97  	log.Info().Msgf("finished migrating all account based payloads, total migrated payloads: %v", len(migrated))
    98  
    99  	return migrated, nil
   100  }
   101  
   102  // MigrateGroupSequentially migrate the payloads in the given payloadsByAccount map which
   103  // using the migrator
   104  func MigrateGroupSequentially(
   105  	migrator AccountMigrator,
   106  	payloadsByAccount map[string][]ledger.Payload,
   107  ) (
   108  	[]ledger.Payload, error) {
   109  
   110  	logAccount := util.LogProgress("processing account group", len(payloadsByAccount), &log.Logger)
   111  
   112  	i := 0
   113  	migrated := make([]ledger.Payload, 0)
   114  	for address, payloads := range payloadsByAccount {
   115  		accountMigrated, err := migrator.MigratePayloads(address, payloads)
   116  		if err != nil {
   117  			return nil, fmt.Errorf("could not migrate for account address %v: %w", address, err)
   118  		}
   119  
   120  		migrated = append(migrated, accountMigrated...)
   121  		logAccount(i)
   122  		i++
   123  	}
   124  
   125  	return migrated, nil
   126  }
   127  
   128  type jobMigrateAccountGroup struct {
   129  	Account  string
   130  	Payloads []ledger.Payload
   131  }
   132  
   133  type migrationResult struct {
   134  	Migrated []ledger.Payload
   135  	Err      error
   136  }
   137  
   138  // MigrateGroupConcurrently migrate the payloads in the given payloadsByAccount map which
   139  // using the migrator
   140  // It's similar to MigrateGroupSequentially, except it will migrate different groups concurrently
   141  func MigrateGroupConcurrently(
   142  	migrator AccountMigrator,
   143  	payloadsByAccount map[string][]ledger.Payload,
   144  	nWorker int,
   145  ) (
   146  	[]ledger.Payload, error) {
   147  
   148  	jobs := make(chan jobMigrateAccountGroup, len(payloadsByAccount))
   149  	go func() {
   150  		for account, payloads := range payloadsByAccount {
   151  			jobs <- jobMigrateAccountGroup{
   152  				Account:  account,
   153  				Payloads: payloads,
   154  			}
   155  		}
   156  		close(jobs)
   157  	}()
   158  
   159  	resultCh := make(chan *migrationResult)
   160  	for i := 0; i < int(nWorker); i++ {
   161  		go func() {
   162  			for job := range jobs {
   163  				accountMigrated, err := migrator.MigratePayloads(job.Account, job.Payloads)
   164  				resultCh <- &migrationResult{
   165  					Migrated: accountMigrated,
   166  					Err:      err,
   167  				}
   168  			}
   169  		}()
   170  	}
   171  
   172  	// read job results
   173  	logAccount := util.LogProgress("processing account group", len(payloadsByAccount), &log.Logger)
   174  
   175  	migrated := make([]ledger.Payload, 0)
   176  
   177  	for i := 0; i < len(payloadsByAccount); i++ {
   178  		result := <-resultCh
   179  		if result.Err != nil {
   180  			return nil, fmt.Errorf("fail to migrate payload: %w", result.Err)
   181  		}
   182  
   183  		accountMigrated := result.Migrated
   184  		migrated = append(migrated, accountMigrated...)
   185  		logAccount(i)
   186  	}
   187  
   188  	return migrated, nil
   189  }