github.com/filecoin-project/specs-actors/v4@v4.0.2/actors/migration/nv10/top.go (about) 1 package nv10 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 address "github.com/filecoin-project/go-address" 11 "github.com/filecoin-project/go-state-types/abi" 12 "github.com/filecoin-project/go-state-types/rt" 13 cid "github.com/ipfs/go-cid" 14 cbor "github.com/ipfs/go-ipld-cbor" 15 "golang.org/x/sync/errgroup" 16 "golang.org/x/xerrors" 17 18 builtin2 "github.com/filecoin-project/specs-actors/v2/actors/builtin" 19 states2 "github.com/filecoin-project/specs-actors/v2/actors/states" 20 21 builtin3 "github.com/filecoin-project/specs-actors/v4/actors/builtin" 22 states3 "github.com/filecoin-project/specs-actors/v4/actors/states" 23 adt3 "github.com/filecoin-project/specs-actors/v4/actors/util/adt" 24 ) 25 26 // Config parameterizes a state tree migration 27 type Config struct { 28 // Number of migration worker goroutines to run. 29 // More workers enables higher CPU utilization doing migration computations (including state encoding) 30 MaxWorkers uint 31 // Capacity of the queue of jobs available to workers (zero for unbuffered). 32 // A queue length of hundreds to thousands improves throughput at the cost of memory. 33 JobQueueSize uint 34 // Capacity of the queue receiving migration results from workers, for persisting (zero for unbuffered). 35 // A queue length of tens to hundreds improves throughput at the cost of memory. 36 ResultQueueSize uint 37 // Time between progress logs to emit. 38 // Zero (the default) results in no progress logs. 39 ProgressLogPeriod time.Duration 40 } 41 42 type Logger interface { 43 // This is the same logging interface provided by the Runtime. 44 Log(level rt.LogLevel, msg string, args ...interface{}) 45 } 46 47 func ActorHeadKey(addr address.Address, head cid.Cid) string { 48 return addr.String() + "-h-" + head.String() 49 } 50 51 func DeadlineKey(dlCid cid.Cid) string { 52 return "d-" + dlCid.String() 53 } 54 55 func SectorsRootKey(sCid cid.Cid) string { 56 return "s-" + sCid.String() 57 } 58 59 // MigrationCache stores and loads cached data. Its implementation must be threadsafe 60 type MigrationCache interface { 61 Write(key string, newCid cid.Cid) error 62 Read(key string) (bool, cid.Cid, error) 63 Load(key string, loadFunc func() (cid.Cid, error)) (cid.Cid, error) 64 } 65 66 // Migrates the filecoin state tree starting from the global state tree and upgrading all actor state. 67 // The store must support concurrent writes (even if the configured worker count is 1). 68 func MigrateStateTree(ctx context.Context, store cbor.IpldStore, actorsRootIn cid.Cid, priorEpoch abi.ChainEpoch, cfg Config, log Logger, cache MigrationCache) (cid.Cid, error) { 69 if cfg.MaxWorkers <= 0 { 70 return cid.Undef, xerrors.Errorf("invalid migration config with %d workers", cfg.MaxWorkers) 71 } 72 73 // Maps prior version code CIDs to migration functions. 74 var migrations = map[cid.Cid]actorMigration{ 75 builtin2.AccountActorCodeID: nilMigrator{builtin3.AccountActorCodeID}, 76 builtin2.CronActorCodeID: nilMigrator{builtin3.CronActorCodeID}, 77 builtin2.InitActorCodeID: cachedMigration(cache, initMigrator{}), 78 builtin2.MultisigActorCodeID: cachedMigration(cache, multisigMigrator{}), 79 builtin2.PaymentChannelActorCodeID: cachedMigration(cache, paychMigrator{}), 80 builtin2.RewardActorCodeID: nilMigrator{builtin3.RewardActorCodeID}, 81 builtin2.StorageMarketActorCodeID: cachedMigration(cache, marketMigrator{}), 82 builtin2.StorageMinerActorCodeID: cachedMigration(cache, minerMigrator{}), 83 builtin2.StoragePowerActorCodeID: cachedMigration(cache, powerMigrator{}), 84 builtin2.SystemActorCodeID: nilMigrator{builtin3.SystemActorCodeID}, 85 builtin2.VerifiedRegistryActorCodeID: cachedMigration(cache, verifregMigrator{}), 86 } 87 // Set of prior version code CIDs for actors to defer during iteration, for explicit migration afterwards. 88 var deferredCodeIDs = map[cid.Cid]struct{}{ 89 // None 90 } 91 if len(migrations)+len(deferredCodeIDs) != 11 { 92 panic(fmt.Sprintf("incomplete migration specification with %d code CIDs", len(migrations))) 93 } 94 startTime := time.Now() 95 96 // Load input and output state trees 97 adtStore := adt3.WrapStore(ctx, store) 98 actorsIn, err := states2.LoadTree(adtStore, actorsRootIn) 99 if err != nil { 100 return cid.Undef, err 101 } 102 actorsOut, err := states3.NewTree(adtStore) 103 if err != nil { 104 return cid.Undef, err 105 } 106 107 // Setup synchronization 108 grp, ctx := errgroup.WithContext(ctx) 109 // Input and output queues for workers. 110 jobCh := make(chan *migrationJob, cfg.JobQueueSize) 111 jobResultCh := make(chan *migrationJobResult, cfg.ResultQueueSize) 112 // Atomically-modified counters for logging progress 113 var jobCount uint32 114 var doneCount uint32 115 116 // Iterate all actors in old state root to create migration jobs for each non-deferred actor. 117 grp.Go(func() error { 118 defer close(jobCh) 119 log.Log(rt.INFO, "Creating migration jobs for tree %s", actorsRootIn) 120 if err = actorsIn.ForEach(func(addr address.Address, actorIn *states2.Actor) error { 121 if _, ok := deferredCodeIDs[actorIn.Code]; ok { 122 return nil // Deferred for explicit migration later. 123 } 124 nextInput := &migrationJob{ 125 Address: addr, 126 Actor: *actorIn, // Must take a copy, the pointer is not stable. 127 cache: cache, 128 actorMigration: migrations[actorIn.Code], 129 } 130 select { 131 case jobCh <- nextInput: 132 case <-ctx.Done(): 133 return ctx.Err() 134 } 135 atomic.AddUint32(&jobCount, 1) 136 return nil 137 }); err != nil { 138 return err 139 } 140 log.Log(rt.INFO, "Done creating %d migration jobs for tree %s after %v", jobCount, actorsRootIn, time.Since(startTime)) 141 return nil 142 }) 143 144 // Worker threads run jobs. 145 var workerWg sync.WaitGroup 146 for i := uint(0); i < cfg.MaxWorkers; i++ { 147 workerWg.Add(1) 148 workerId := i 149 grp.Go(func() error { 150 defer workerWg.Done() 151 for job := range jobCh { 152 result, err := job.run(ctx, store, priorEpoch) 153 if err != nil { 154 return err 155 } 156 select { 157 case jobResultCh <- result: 158 case <-ctx.Done(): 159 return ctx.Err() 160 } 161 atomic.AddUint32(&doneCount, 1) 162 } 163 log.Log(rt.INFO, "Worker %d done", workerId) 164 return nil 165 }) 166 } 167 log.Log(rt.INFO, "Started %d workers", cfg.MaxWorkers) 168 169 // Monitor the job queue. This non-critical goroutine is outside the errgroup and exits when 170 // workersFinished is closed, or the context done. 171 workersFinished := make(chan struct{}) // Closed when waitgroup is emptied. 172 if cfg.ProgressLogPeriod > 0 { 173 go func() { 174 defer log.Log(rt.DEBUG, "Job queue monitor done") 175 for { 176 select { 177 case <-time.After(cfg.ProgressLogPeriod): 178 jobsNow := jobCount // Snapshot values to avoid incorrect-looking arithmetic if they change. 179 doneNow := doneCount 180 pendingNow := jobsNow - doneNow 181 elapsed := time.Since(startTime) 182 rate := float64(doneNow) / elapsed.Seconds() 183 log.Log(rt.INFO, "%d jobs created, %d done, %d pending after %v (%.0f/s)", 184 jobsNow, doneNow, pendingNow, elapsed, rate) 185 case <-workersFinished: 186 return 187 case <-ctx.Done(): 188 return 189 } 190 } 191 }() 192 } 193 194 // Close result channel when workers are done sending to it. 195 grp.Go(func() error { 196 workerWg.Wait() 197 close(jobResultCh) 198 close(workersFinished) 199 log.Log(rt.INFO, "All workers done after %v", time.Since(startTime)) 200 return nil 201 }) 202 203 // Insert migrated records in output state tree and accumulators. 204 grp.Go(func() error { 205 log.Log(rt.INFO, "Result writer started") 206 resultCount := 0 207 for result := range jobResultCh { 208 if err := actorsOut.SetActor(result.Address, &result.Actor); err != nil { 209 return err 210 } 211 resultCount++ 212 } 213 log.Log(rt.INFO, "Result writer wrote %d results to state tree after %v", resultCount, time.Since(startTime)) 214 return nil 215 }) 216 217 if err := grp.Wait(); err != nil { 218 return cid.Undef, err 219 } 220 221 // Perform any deferred migrations explicitly here. 222 // Deferred migrations might depend on values accumulated through migration of other actors. 223 224 elapsed := time.Since(startTime) 225 rate := float64(doneCount) / elapsed.Seconds() 226 log.Log(rt.INFO, "All %d done after %v (%.0f/s). Flushing state tree root.", doneCount, elapsed, rate) 227 return actorsOut.Flush() 228 } 229 230 type actorMigrationInput struct { 231 address address.Address // actor's address 232 balance abi.TokenAmount // actor's balance 233 head cid.Cid // actor's state head CID 234 priorEpoch abi.ChainEpoch // epoch of last state transition prior to migration 235 cache MigrationCache // cache of existing cid -> cid migrations for this actor 236 } 237 238 type actorMigrationResult struct { 239 newCodeCID cid.Cid 240 newHead cid.Cid 241 } 242 243 type actorMigration interface { 244 // Loads an actor's state from an input store and writes new state to an output store. 245 // Returns the new state head CID. 246 migrateState(ctx context.Context, store cbor.IpldStore, input actorMigrationInput) (result *actorMigrationResult, err error) 247 migratedCodeCID() cid.Cid 248 } 249 250 type migrationJob struct { 251 address.Address 252 states2.Actor 253 actorMigration 254 cache MigrationCache 255 } 256 type migrationJobResult struct { 257 address.Address 258 states3.Actor 259 } 260 261 func (job *migrationJob) run(ctx context.Context, store cbor.IpldStore, priorEpoch abi.ChainEpoch) (*migrationJobResult, error) { 262 result, err := job.migrateState(ctx, store, actorMigrationInput{ 263 address: job.Address, 264 balance: job.Actor.Balance, 265 head: job.Actor.Head, 266 priorEpoch: priorEpoch, 267 cache: job.cache, 268 }) 269 if err != nil { 270 return nil, xerrors.Errorf("state migration failed for %s actor, addr %s: %w", 271 builtin2.ActorNameByCode(job.Actor.Code), job.Address, err) 272 } 273 274 // Set up new actor record with the migrated state. 275 return &migrationJobResult{ 276 job.Address, // Unchanged 277 states3.Actor{ 278 Code: result.newCodeCID, 279 Head: result.newHead, 280 CallSeqNum: job.Actor.CallSeqNum, // Unchanged 281 Balance: job.Actor.Balance, // Unchanged 282 }, 283 }, nil 284 } 285 286 // Migrator which preserves the head CID and provides a fixed result code CID. 287 type nilMigrator struct { 288 OutCodeCID cid.Cid 289 } 290 291 func (n nilMigrator) migrateState(_ context.Context, _ cbor.IpldStore, in actorMigrationInput) (*actorMigrationResult, error) { 292 return &actorMigrationResult{ 293 newCodeCID: n.OutCodeCID, 294 newHead: in.head, 295 }, nil 296 } 297 298 func (n nilMigrator) migratedCodeCID() cid.Cid { 299 return n.OutCodeCID 300 } 301 302 // Migrator that uses cached transformation if it exists 303 type cachedMigrator struct { 304 cache MigrationCache 305 actorMigration 306 } 307 308 func (c cachedMigrator) migrateState(ctx context.Context, store cbor.IpldStore, in actorMigrationInput) (*actorMigrationResult, error) { 309 newHead, err := c.cache.Load(ActorHeadKey(in.address, in.head), func() (cid.Cid, error) { 310 result, err := c.actorMigration.migrateState(ctx, store, in) 311 if err != nil { 312 return cid.Undef, err 313 } 314 return result.newHead, nil 315 }) 316 if err != nil { 317 return nil, err 318 } 319 return &actorMigrationResult{ 320 newCodeCID: c.migratedCodeCID(), 321 newHead: newHead, 322 }, nil 323 } 324 325 func cachedMigration(cache MigrationCache, m actorMigration) actorMigration { 326 return cachedMigrator{ 327 actorMigration: m, 328 cache: cache, 329 } 330 }