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 }