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 }