gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/snapshot.go (about) 1 package renter 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "os" 10 "sort" 11 "sync" 12 "time" 13 14 "gitlab.com/NebulousLabs/encoding" 15 "gitlab.com/SkynetLabs/skyd/build" 16 "gitlab.com/SkynetLabs/skyd/skymodules" 17 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem" 18 "go.sia.tech/siad/crypto" 19 "go.sia.tech/siad/modules" 20 "go.sia.tech/siad/types" 21 22 "gitlab.com/NebulousLabs/errors" 23 "gitlab.com/NebulousLabs/fastrand" 24 ) 25 26 var ( 27 // snapshotKeySpecifier is the specifier used for deriving the secret used 28 // to encrypt a snapshot from the RenterSeed. 29 snapshotKeySpecifier = types.NewSpecifier("snapshot") 30 31 // snapshotTableSpecifier is the specifier used to identify a snapshot entry 32 // table stored in a sector. 33 snapshotTableSpecifier = types.NewSpecifier("SnapshotTable") 34 ) 35 36 var ( 37 // errEmptyContract is returned when we are trying to read from an empty 38 // contract, this can be the case when downloading a snapshot from a host 39 // that does not have one yet. 40 errEmptyContract = errors.New("empty contract") 41 42 // maxSnapshotUploadTime defines the total amount of time that the renter 43 // will allocate to complete an upload of a snapshot .sia file to all hosts. 44 // This is done with each host in parallel, and the .sia file is not 45 // expected to exceed a few megabytes even for very large renters. 46 maxSnapshotUploadTime = build.Select(build.Var{ 47 Standard: time.Minute * 15, 48 Dev: time.Minute * 3, 49 Testing: time.Minute, 50 }).(time.Duration) 51 ) 52 53 // A snapshotEntry is an entry within the snapshot table, identifying both the 54 // snapshot metadata and the other sectors on the host storing the snapshot 55 // data. 56 type snapshotEntry struct { 57 Name [96]byte 58 UID [16]byte 59 CreationDate types.Timestamp 60 Size uint64 // size of snapshot .sia file 61 DataSectors [4]crypto.Hash // pointers to sectors containing snapshot .sia file 62 } 63 64 // calcSnapshotUploadProgress calculates the upload progress of a snapshot. 65 func calcSnapshotUploadProgress(fileUploadProgress float64, dotSiaUploadProgress float64) float64 { 66 return 0.8*fileUploadProgress + 0.2*dotSiaUploadProgress 67 } 68 69 // UploadedBackups returns the backups that the renter can download, along with 70 // a list of which contracts are storing all known backups. 71 func (r *Renter) UploadedBackups() ([]skymodules.UploadedBackup, []types.SiaPublicKey, error) { 72 if err := r.tg.Add(); err != nil { 73 return nil, nil, err 74 } 75 defer r.tg.Done() 76 id := r.mu.RLock() 77 defer r.mu.RUnlock(id) 78 backups := append([]skymodules.UploadedBackup(nil), r.persist.UploadedBackups...) 79 hosts := make([]types.SiaPublicKey, 0, len(r.persist.SyncedContracts)) 80 for _, c := range r.staticHostContractor.Contracts() { 81 for _, id := range r.persist.SyncedContracts { 82 if c.ID == id { 83 hosts = append(hosts, c.HostPublicKey) 84 break 85 } 86 } 87 } 88 return backups, hosts, nil 89 } 90 91 // BackupsOnHost returns the backups stored on a particular host. This operation 92 // can take multiple minutes if the renter is performing many other operations 93 // on this host, however this operation is given high priority over other types 94 // of operations. 95 func (r *Renter) BackupsOnHost(hostKey types.SiaPublicKey) ([]skymodules.UploadedBackup, error) { 96 if err := r.tg.Add(); err != nil { 97 return nil, err 98 } 99 defer r.tg.Done() 100 101 // Find the relevant worker. 102 w, err := r.staticWorkerPool.callWorker(hostKey) 103 if err != nil { 104 return nil, errors.AddContext(err, "host not found in the worker table") 105 } 106 107 return w.FetchBackups(r.tg.StopCtx()) 108 } 109 110 // UploadBackup creates a backup of the renter which is uploaded to the sia 111 // network as a snapshot and can be retrieved using only the seed. 112 func (r *Renter) UploadBackup(src, name string) error { 113 if err := r.tg.Add(); err != nil { 114 return err 115 } 116 defer r.tg.Done() 117 return r.managedUploadBackup(src, name) 118 } 119 120 // managedUploadBackup creates a backup of the renter which is uploaded to the 121 // sia network as a snapshot and can be retrieved using only the seed. 122 func (r *Renter) managedUploadBackup(src, name string) error { 123 if len(name) > 96 { 124 return errors.New("name is too long") 125 } 126 127 // Check if snapshot already exists. 128 if r.managedSnapshotExists(name) { 129 s := fmt.Sprintf("snapshot with name '%s' already exists", name) 130 return errors.AddContext(filesystem.ErrExists, s) 131 } 132 133 // Open the backup for uploading. 134 backup, err := os.Open(src) 135 if err != nil { 136 return errors.AddContext(err, "failed to open backup for uploading") 137 } 138 defer func() { 139 err = errors.Compose(err, backup.Close()) 140 }() 141 142 // Prepare the siapath. 143 sp, err := skymodules.BackupFolder.Join(name) 144 if err != nil { 145 return err 146 } 147 // Create upload params with high redundancy. 148 allowance := r.staticHostContractor.Allowance() 149 dataPieces := allowance.Hosts / 10 150 if dataPieces == 0 { 151 dataPieces = 1 152 } 153 parityPieces := allowance.Hosts - dataPieces 154 ec, err := skymodules.NewRSSubCode(int(dataPieces), int(parityPieces), crypto.SegmentSize) 155 if err != nil { 156 return err 157 } 158 up := skymodules.FileUploadParams{ 159 SiaPath: sp, 160 ErasureCode: ec, 161 Force: false, 162 163 CipherType: crypto.TypeDefaultRenter, 164 } 165 // Begin uploading the backup. When the upload finishes, the backup .sia 166 // file will be uploaded by r.threadedSynchronizeSnapshots and then deleted. 167 fileNode, err := r.callUploadStreamFromReader(r.tg.StopCtx(), up, backup) 168 if err != nil { 169 return errors.AddContext(err, "failed to upload backup") 170 } 171 err = fileNode.Close() 172 if err != nil { 173 return errors.AddContext(err, "unable to close fileNode while uploading a backup") 174 } 175 // Save initial snapshot entry. 176 meta := skymodules.UploadedBackup{ 177 Name: name, 178 CreationDate: types.CurrentTimestamp(), 179 Size: 0, 180 UploadProgress: 0, 181 } 182 fastrand.Read(meta.UID[:]) 183 if err := r.managedSaveSnapshot(meta); err != nil { 184 return err 185 } 186 187 return nil 188 } 189 190 // DownloadBackup downloads the specified backup. 191 func (r *Renter) DownloadBackup(dst string, name string) (err error) { 192 if err := r.tg.Add(); err != nil { 193 return err 194 } 195 defer r.tg.Done() 196 // Open the destination. 197 dstFile, err := os.Create(dst) 198 if err != nil { 199 return err 200 } 201 defer func() { 202 err = errors.Compose(err, dstFile.Close()) 203 }() 204 // search for backup 205 if len(name) > 96 { 206 return errors.New("no record of a backup with that name") 207 } 208 var uid [16]byte 209 var found bool 210 id := r.mu.RLock() 211 for _, b := range r.persist.UploadedBackups { 212 if b.Name == name { 213 uid = b.UID 214 found = true 215 break 216 } 217 } 218 r.mu.RUnlock(id) 219 if !found { 220 return errors.New("no record of a backup with that name") 221 } 222 // Download snapshot's .sia file. 223 _, dotSia, err := r.managedDownloadSnapshot(uid) 224 if err != nil { 225 return err 226 } 227 // Store it in the backup file set. 228 backupSiaPath, err := skymodules.BackupFolder.Join(name) 229 if err != nil { 230 return err 231 } 232 if err := r.staticFileSystem.WriteFile(backupSiaPath, dotSia, 0666); err != nil { 233 return err 234 } 235 // Load the .sia file. 236 siaPath, err := skymodules.BackupFolder.Join(name) 237 if err != nil { 238 return err 239 } 240 entry, err := r.staticFileSystem.OpenSiaFile(siaPath) 241 if err != nil { 242 return err 243 } 244 defer func() { 245 err = errors.Compose(err, entry.Close()) 246 }() 247 // Use .sia file to download snapshot. 248 snap, err := entry.Snapshot(siaPath) 249 if err != nil { 250 return err 251 } 252 s := r.managedStreamer(snap, false) 253 _, err = io.Copy(dstFile, s) 254 return errors.Compose(err, s.Close()) 255 } 256 257 // managedSnapshotExists returns true if a snapshot with a given name already 258 // exists. 259 func (r *Renter) managedSnapshotExists(name string) bool { 260 id := r.mu.Lock() 261 defer r.mu.Unlock(id) 262 for _, ub := range r.persist.UploadedBackups { 263 if ub.Name == name { 264 return true 265 } 266 } 267 return false 268 } 269 270 // managedSaveSnapshot saves snapshot metadata to disk. 271 func (r *Renter) managedSaveSnapshot(meta skymodules.UploadedBackup) error { 272 id := r.mu.Lock() 273 defer r.mu.Unlock(id) 274 // Check whether we've already saved this snapshot. 275 for i, ub := range r.persist.UploadedBackups { 276 if ub.UID == meta.UID { 277 if ub == meta { 278 // nothing changed 279 return nil 280 } 281 // something changed; overwrite existing entry 282 r.persist.UploadedBackups[i] = meta 283 return r.saveSync() 284 } 285 } 286 // Append the new snapshot. 287 r.persist.UploadedBackups = append(r.persist.UploadedBackups, meta) 288 // Trim the set of snapshots if necessary. Hosts can only store a finite 289 // number of snapshots, so if we exceed that number, we kick out the oldest 290 // snapshot. We check the size by encoding a slice of snapshotEntrys and 291 // removing elements from the slice until the encoded size fits on the host. 292 entryTable := make([]snapshotEntry, len(r.persist.UploadedBackups)) 293 for len(encoding.Marshal(entryTable)) > int(modules.SectorSize) { 294 entryTable = entryTable[1:] 295 } 296 // Sort by CreationDate (youngest-to-oldest) and remove excess elements from 297 // the end. 298 sort.Slice(r.persist.UploadedBackups, func(i, j int) bool { 299 return r.persist.UploadedBackups[i].CreationDate > r.persist.UploadedBackups[j].CreationDate 300 }) 301 r.persist.UploadedBackups = r.persist.UploadedBackups[:len(entryTable)] 302 // Save the result. 303 if err := r.saveSync(); err != nil { 304 return err 305 } 306 return nil 307 } 308 309 // managedDownloadSnapshotTable will fetch the snapshot table from the host. 310 func (r *Renter) managedDownloadSnapshotTable(host *worker) ([]snapshotEntry, error) { 311 // Get the wallet seed. 312 ws, _, err := r.staticWallet.PrimarySeed() 313 if err != nil { 314 return nil, errors.AddContext(err, "failed to get wallet's primary seed") 315 } 316 // Derive the renter seed and wipe the memory once we are done using it. 317 rs := skymodules.DeriveRenterSeed(ws) 318 defer fastrand.Read(rs[:]) 319 // Derive the secret and wipe it afterwards. 320 secret := crypto.HashAll(rs, snapshotKeySpecifier) 321 defer fastrand.Read(secret[:]) 322 323 // Create an empty entryTable 324 var entryTable []snapshotEntry 325 326 // Fetch the contract and see if it's empty, if that is the case return an 327 // empty entryTable and appropriate error. 328 contract, ok := r.staticHostContractor.ContractByPublicKey(host.staticHostPubKey) 329 if !ok { 330 return nil, errors.New("failed to host contract") 331 } 332 if contract.Size() == 0 { 333 return entryTable, errEmptyContract 334 } 335 336 // Download the table of snapshots that the host is storing. 337 tableSector, err := host.ReadOffset(r.tg.StopCtx(), categorySnapshotDownload, 0, modules.SectorSize) 338 if err != nil { 339 return nil, errors.AddContext(err, "unable to perform a download by index on this contract") 340 } 341 342 // decrypt the table 343 c, _ := crypto.NewSiaKey(crypto.TypeThreefish, secret[:]) 344 encTable, err := c.DecryptBytesInPlace(tableSector, 0) 345 if err != nil || !bytes.Equal(encTable[:16], snapshotTableSpecifier[:]) { 346 // either the first sector was not an entry table, or it got corrupted 347 // somehow; either way, it's not retrievable, so we'll treat this as 348 // equivalent to having no entry table at all. This is not an error; it 349 // just means that when we upload a snapshot, we'll have to create a new 350 // table. 351 return nil, errors.AddContext(err, "error decrypting bytes") 352 } 353 354 if err := encoding.Unmarshal(encTable[16:], &entryTable); err != nil { 355 return nil, errors.AddContext(err, "error unmarshaling the entry table") 356 } 357 return entryTable, nil 358 } 359 360 // managedUploadSnapshot uploads a snapshot .sia file to all hosts. 361 func (r *Renter) managedUploadSnapshot(meta skymodules.UploadedBackup, dotSia []byte) error { 362 // Grab all of the workers that are good for upload. 363 var total int 364 workers := r.staticWorkerPool.callWorkers() 365 for i := 0; i < len(workers); i++ { 366 if workers[i].staticCache().staticContractUtility.GoodForUpload { 367 workers[total] = workers[i] 368 total++ 369 } 370 } 371 workers = workers[:total] 372 373 // Submit a job to each worker. Make sure the response channel has enough 374 // room in the buffer for all results, this way workers are not being 375 // blocked when returning their results. 376 maxWait, cancel := context.WithTimeout(r.tg.StopCtx(), maxSnapshotUploadTime) 377 defer cancel() 378 responseChan := make(chan *jobUploadSnapshotResponse, len(workers)) 379 queued := 0 380 for _, w := range workers { 381 job := &jobUploadSnapshot{ 382 staticSiaFileData: dotSia, 383 384 staticResponseChan: responseChan, 385 386 jobGeneric: newJobGeneric(maxWait, w.staticJobUploadSnapshotQueue, meta), 387 } 388 389 // If a job is not added correctly, count this as a failed response. 390 if w.staticJobUploadSnapshotQueue.callAdd(job) { 391 queued++ 392 } 393 } 394 395 // Iteratively grab the responses from the workers. 396 responses := 0 397 successes := 0 398 399 LOOP: 400 for responses < queued { 401 var resp *jobUploadSnapshotResponse 402 select { 403 case resp = <-responseChan: 404 case <-maxWait.Done(): 405 break LOOP 406 } 407 responses++ 408 409 // Update the progress. 410 pct := 100 * float64(responses) / float64(total) 411 meta.UploadProgress = calcSnapshotUploadProgress(100, pct) 412 err := r.managedSaveSnapshot(meta) 413 if err != nil { 414 r.staticLog.Println("Error saving snapshot during upload:", err) 415 continue 416 } 417 418 // Log any error. 419 if resp.staticErr != nil { 420 r.staticLog.Debugln("snapshot upload failed:", resp.staticErr) 421 continue 422 } 423 successes++ 424 } 425 426 // Check for shutdown. 427 select { 428 case <-r.tg.StopChan(): 429 return errors.New("renter is shutting down") 430 default: 431 } 432 433 // Check if there were too few successes to count this as a successful 434 // backup. A 1/3 success rate is really quite arbitrary, picked because it 435 // ~feels~ like that should be enough to give the user security, but really 436 // who knows. Like really we should probably be looking at the total number 437 // of hosts in the allowance and comparing against that. 438 if successes < total/3 { 439 r.staticLog.Printf("Unable to save snapshot effectively, wanted %v but only got %v successful snapshot backups", total, successes) 440 return fmt.Errorf("needed at least %v successes, only got %v", total/3, successes) 441 } 442 443 // Save the final version of the snapshot. Represent the progress at 100% 444 // even though not every host may have our snapshot. Really we only need 1 445 // working host to do a full recovery. 446 meta.UploadProgress = calcSnapshotUploadProgress(100, 100) 447 if err := r.managedSaveSnapshot(meta); err != nil { 448 return errors.AddContext(err, "error saving snapshot after upload completed") 449 } 450 return nil 451 } 452 453 // managedDownloadSnapshot downloads and returns the specified snapshot. 454 func (r *Renter) managedDownloadSnapshot(uid [16]byte) (ub skymodules.UploadedBackup, dotSia []byte, err error) { 455 if err := r.tg.Add(); err != nil { 456 return skymodules.UploadedBackup{}, nil, err 457 } 458 defer r.tg.Done() 459 460 // Get the wallet seed. 461 ws, _, err := r.staticWallet.PrimarySeed() 462 if err != nil { 463 return skymodules.UploadedBackup{}, nil, errors.AddContext(err, "failed to get wallet's primary seed") 464 } 465 // Derive the renter seed and wipe the memory once we are done using it. 466 rs := skymodules.DeriveRenterSeed(ws) 467 defer fastrand.Read(rs[:]) 468 // Derive the secret and wipe it afterwards. 469 secret := crypto.HashAll(rs, snapshotKeySpecifier) 470 defer fastrand.Read(secret[:]) 471 472 // try downloading from each host in serial, prioritizing the hosts that are 473 // GoodForUpload, then GoodForRenew 474 contracts := r.staticHostContractor.Contracts() 475 sort.Slice(contracts, func(i, j int) bool { 476 if contracts[i].Utility.GoodForUpload == contracts[j].Utility.GoodForUpload { 477 return contracts[i].Utility.GoodForRenew && !contracts[j].Utility.GoodForRenew 478 } 479 return contracts[i].Utility.GoodForUpload && !contracts[j].Utility.GoodForUpload 480 }) 481 for i := range contracts { 482 err := func() (err error) { 483 w, err := r.staticWorkerPool.callWorker(contracts[i].HostPublicKey) 484 if err != nil { 485 return err 486 } 487 // download the snapshot table 488 entryTable, err := w.DownloadSnapshotTable(r.tg.StopCtx()) 489 if err != nil { 490 return err 491 } 492 493 // search for the desired snapshot 494 var entry *snapshotEntry 495 for j := range entryTable { 496 if entryTable[j].UID == uid { 497 entry = &entryTable[j] 498 break 499 } 500 } 501 if entry == nil { 502 return errors.New("entry table does not contain snapshot") 503 } 504 // download the entry 505 dotSia = nil 506 for _, root := range entry.DataSectors { 507 data, err := w.ReadSector(r.tg.StopCtx(), categorySnapshotDownload, root, 0, modules.SectorSize) 508 if err != nil { 509 return err 510 } 511 dotSia = append(dotSia, data...) 512 if uint64(len(dotSia)) >= entry.Size { 513 dotSia = dotSia[:entry.Size] 514 break 515 } 516 } 517 ub = skymodules.UploadedBackup{ 518 Name: string(bytes.TrimRight(entry.Name[:], types.RuneToString(0))), 519 UID: entry.UID, 520 CreationDate: entry.CreationDate, 521 Size: entry.Size, 522 UploadProgress: 100, 523 } 524 return nil 525 }() 526 if err != nil { 527 r.staticLog.Printf("Downloading backup from host %v failed: %v", contracts[i].HostPublicKey, err) 528 continue 529 } 530 return ub, dotSia, nil 531 } 532 return skymodules.UploadedBackup{}, nil, errors.New("could not download backup from any host") 533 } 534 535 // threadedSynchronizeSnapshots continuously scans hosts to ensure that all 536 // current hosts are storing all known snapshots. 537 func (r *Renter) threadedSynchronizeSnapshots() { 538 if err := r.tg.Add(); err != nil { 539 return 540 } 541 defer r.tg.Done() 542 // calcOverlap takes a host's entry table and the set of known snapshots, 543 // and calculates which snapshots the host is missing and which snapshots it 544 // has that we don't. 545 calcOverlap := func(entryTable []snapshotEntry, known map[[16]byte]struct{}) (unknown []skymodules.UploadedBackup, missing [][16]byte) { 546 missingMap := make(map[[16]byte]struct{}, len(known)) 547 for uid := range known { 548 missingMap[uid] = struct{}{} 549 } 550 for _, e := range entryTable { 551 if _, ok := known[e.UID]; !ok { 552 unknown = append(unknown, skymodules.UploadedBackup{ 553 Name: string(bytes.TrimRight(e.Name[:], types.RuneToString(0))), 554 UID: e.UID, 555 CreationDate: e.CreationDate, 556 Size: e.Size, 557 UploadProgress: 100, 558 }) 559 } 560 delete(missingMap, e.UID) 561 } 562 for uid := range missingMap { 563 missing = append(missing, uid) 564 } 565 return 566 } 567 568 // Build a set of which contracts are synced. 569 syncedContracts := make(map[types.FileContractID]struct{}) 570 id := r.mu.RLock() 571 for _, fcid := range r.persist.SyncedContracts { 572 syncedContracts[fcid] = struct{}{} 573 } 574 r.mu.RUnlock(id) 575 576 for { 577 // Can't do anything if the wallet is locked. 578 if unlocked, _ := r.staticWallet.Unlocked(); !unlocked { 579 select { 580 case <-time.After(snapshotSyncSleepDuration): 581 case <-r.tg.StopChan(): 582 return 583 } 584 continue 585 } 586 r.staticWorkerPool.callUpdate(r) 587 588 // First, process any snapshot siafiles that may have finished uploading. 589 root := skymodules.BackupFolder 590 var mu sync.Mutex 591 flf := func(info skymodules.FileInfo) { 592 // Make sure we only look at a single info at a time. 593 mu.Lock() 594 defer mu.Unlock() 595 596 // locate corresponding entry 597 id = r.mu.RLock() 598 var meta skymodules.UploadedBackup 599 found := false 600 for _, meta = range r.persist.UploadedBackups { 601 sp, _ := info.SiaPath.Rebase(skymodules.BackupFolder, skymodules.RootSiaPath()) 602 if meta.Name == sp.String() { 603 found = true 604 break 605 } 606 } 607 r.mu.RUnlock(id) 608 if !found { 609 r.staticLog.Println("Could not locate entry for file in backup set") 610 return 611 } 612 613 // record current UploadProgress 614 meta.UploadProgress = calcSnapshotUploadProgress(info.UploadProgress, 0) 615 if err := r.managedSaveSnapshot(meta); err != nil { 616 r.staticLog.Println("Could not save upload progress:", err) 617 return 618 } 619 620 if skymodules.NeedsRepair(info.Health) { 621 // not ready for upload yet 622 return 623 } 624 r.staticLog.Println("Uploading snapshot", info.SiaPath) 625 err := func() error { 626 // Grab the entry for the uploaded backup's siafile. 627 entry, err := r.staticFileSystem.OpenSiaFile(info.SiaPath) 628 if err != nil { 629 return errors.AddContext(err, "failed to get entry for snapshot") 630 } 631 // Read the siafile from disk. 632 sr, err := entry.SnapshotReader() 633 if err != nil { 634 return errors.Compose(err, entry.Close()) 635 } 636 // NOTE: The snapshot reader needs to be closed before 637 // entry.Close is called, and unfortunately also before 638 // managedUploadSnapshot is called, because the snapshot reader 639 // holds a lock on the underlying dotsia, which is also actively 640 // a part of the repair system. The repair system locks the 641 // renter then tries to grab a lock on the siafile, while 642 // 'managedUploadSnapshot' can independently try to grab a lock 643 // on the renter while holding the snapshot. 644 645 // TODO: Calling ReadAll here is not really acceptable because 646 // for larger renters, the snapshot can be very large. For a 647 // user with 20 TB of data, the snapshot is going to be 2 GB 648 // large, which on its own exceeds our goal for the total memory 649 // consumption of the renter, and it's not even that much data. 650 // 651 // Need to work around the snapshot reader lock issue as well. 652 dotSia, err := ioutil.ReadAll(sr) 653 if err := sr.Close(); err != nil { 654 return errors.Compose(err, entry.Close()) 655 } 656 657 // Upload the snapshot to the network. 658 meta.UploadProgress = calcSnapshotUploadProgress(100, 0) 659 meta.Size = uint64(len(dotSia)) 660 if err := r.managedUploadSnapshot(meta, dotSia); err != nil { 661 return errors.Compose(err, entry.Close()) 662 } 663 664 // Close out the entry. 665 err = entry.Close() 666 if err != nil { 667 return err 668 } 669 670 // Delete the local siafile. 671 if err := r.staticFileSystem.DeleteFile(info.SiaPath); err != nil { 672 return err 673 } 674 return nil 675 }() 676 if err != nil { 677 r.staticLog.Println("Failed to upload snapshot .sia:", err) 678 } 679 } 680 offlineMap, goodForRenewMap, contractsMap, _ := r.callRenterContractsAndUtilities() 681 err := r.staticFileSystem.List(root, true, offlineMap, goodForRenewMap, contractsMap, flf, func(skymodules.DirectoryInfo) {}) 682 if err != nil { 683 r.staticLog.Println("Could not get un-uploaded snapshots:", err) 684 } 685 686 // Build a set of the snapshots we already have. 687 known := make(map[[16]byte]struct{}) 688 id := r.mu.RLock() 689 for _, ub := range r.persist.UploadedBackups { 690 if ub.UploadProgress == 100 { 691 known[ub.UID] = struct{}{} 692 } else { 693 // signal r.threadedUploadAndRepair to keep uploading the snapshot 694 select { 695 case r.staticUploadHeap.repairNeeded <- struct{}{}: 696 default: 697 } 698 } 699 } 700 r.mu.RUnlock(id) 701 702 // Select an unsynchronized host. 703 contracts := r.staticHostContractor.Contracts() 704 var found bool 705 var c skymodules.RenterContract 706 for _, c = range contracts { 707 // ignore bad contracts 708 if !c.Utility.GoodForRenew || !c.Utility.GoodForUpload { 709 continue 710 } 711 if _, ok := syncedContracts[c.ID]; !ok { 712 found = true 713 break 714 } 715 } 716 if !found { 717 // No unsychronized hosts; drop any irrelevant contracts, then sleep 718 // for a while before trying again 719 if len(contracts) != 0 { 720 syncedContracts = make(map[types.FileContractID]struct{}) 721 for _, c := range contracts { 722 if !c.Utility.GoodForRenew || !c.Utility.GoodForUpload { 723 continue 724 } 725 syncedContracts[c.ID] = struct{}{} 726 } 727 } 728 select { 729 case <-time.After(snapshotSyncSleepDuration): 730 case <-r.tg.StopChan(): 731 return 732 } 733 continue 734 } 735 736 // Synchronize the host. 737 r.staticLog.Debugln("Synchronizing snapshots on host", c.HostPublicKey) 738 err = func() (err error) { 739 // Get the right worker for the host. 740 w, err := r.staticWorkerPool.callWorker(c.HostPublicKey) 741 if err != nil { 742 return err 743 } 744 745 // download the snapshot table 746 entryTable, err := w.DownloadSnapshotTable(r.tg.StopCtx()) 747 if err != nil { 748 return err 749 } 750 751 // Calculate which snapshots the host doesn't have, and which 752 // snapshots it does have that we haven't seen before. 753 unknown, missing := calcOverlap(entryTable, known) 754 755 // If *any* snapshots are new, mark all other hosts as not 756 // synchronized. 757 if len(unknown) != 0 { 758 for fcid := range syncedContracts { 759 delete(syncedContracts, fcid) 760 } 761 // Record the new snapshots; they'll be replicated to the other 762 // hosts in subsequent iterations of the synchronization loop. 763 for _, ub := range unknown { 764 known[ub.UID] = struct{}{} 765 if err := r.managedSaveSnapshot(ub); err != nil { 766 return err 767 } 768 r.staticLog.Printf("Located new snapshot %q on host %v", ub.Name, c.HostPublicKey) 769 } 770 } 771 772 // Upload any snapshots that the host is missing. 773 // 774 // TODO: instead of returning immediately upon encountering an 775 // error, we should probably continue trying to upload the other 776 // snapshots. 777 for _, uid := range missing { 778 ub, dotSia, err := r.managedDownloadSnapshot(uid) 779 if err != nil { 780 // TODO: if snapshot can't be found on any host, delete it 781 return err 782 } 783 if err := w.UploadSnapshot(r.tg.StopCtx(), ub, dotSia); err != nil { 784 return err 785 } 786 r.staticLog.Printf("Replicated missing snapshot %q to host %v", ub.Name, c.HostPublicKey) 787 } 788 return nil 789 }() 790 if err != nil { 791 r.staticLog.Debugln("Failed to synchronize snapshots on host:", err) 792 // sleep for a bit to prevent retrying the same host repeatedly in a 793 // tight loop 794 select { 795 case <-time.After(snapshotSyncSleepDuration): 796 case <-r.tg.StopChan(): 797 return 798 } 799 continue 800 } 801 // Mark the contract as synchronized. 802 syncedContracts[c.ID] = struct{}{} 803 // Commit the set of synchronized hosts. 804 id = r.mu.Lock() 805 r.persist.SyncedContracts = r.persist.SyncedContracts[:0] 806 for fcid := range syncedContracts { 807 r.persist.SyncedContracts = append(r.persist.SyncedContracts, fcid) 808 } 809 if err := r.saveSync(); err != nil { 810 r.staticLog.Println("Failed to update set of synced hosts:", err) 811 } 812 r.mu.Unlock(id) 813 } 814 }