-
Notifications
You must be signed in to change notification settings - Fork 1.5k
[r3.6] db/downloader: keep local snapshot data once the initial download is complete #23446
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: release/3.6
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,12 @@ | ||
| # Downloader Components | ||
|
|
||
| Main properties: | ||
|
|
||
| - initial download complete (`preverified.toml` on disk): the local `data-file` outranks the remote manifest and is never moved aside. A missing `data-file` is still downloaded. A local `.torrent` with a different infohash is dropped on this path (`addPreverifiedSnapshotForDownload` retains one that is already loaded and seeds under its hash), and only its sizes are used to check the file: on a mismatch the data backs neither manifest, so the download goes ahead under the preverified hash and the client completes the file by length, re-fetching only if the length disagrees. Otherwise the preverified download is skipped. | ||
| - initial download not complete: the remote manifest wins. Fetch the chain manifest (`chain.toml`) and align the datadir to it (remote `.torrent` and `data-file`s); data whose local `.torrent` infohash does not match the preverified one — including data with no readable `.torrent` at all — is renamed to `.part` and re-fetched. `preverified.toml` is written locally once the set is complete, never downloaded. | ||
|
|
||
| Deriving a `.torrent` from a bare `data-file` happens on the seeding path (`AddNewSeedableFile`), not on either path above. | ||
|
AskAlexSharov marked this conversation as resolved.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "Deriving a
An operator on a restored datadir (
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same — fixed on So the operator on a restored datadir is pointed at the complete path, not away from it. |
||
|
|
||
| The diagram below shows the components used to manage downloads between torrents and WebSeeds. | ||
|
|
||
|  | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,17 +39,24 @@ func (me *downloadBatch) taskWaiter() { | |
| } | ||
|
|
||
| func (me *downloadBatch) addDownload(item preverifiedSnapshot) error { | ||
| t, first, miOpt, err := me.d.addPreverifiedSnapshotForDownload(item.InfoHash, item.Name) | ||
| snapshotTorrent, first, localMetainfo, keptLocal, err := me.d.addPreverifiedSnapshotForDownload(item.InfoHash, item.Name) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if keptLocal { | ||
| me.all.Go(func() { me.d.seedKeptSnapshot(me.d.ctx, item.Name) }) | ||
|
AskAlexSharov marked this conversation as resolved.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking on a release branch. One unbounded, uncancellable whole-file-hashing goroutine per kept item.
What it waits on: Trigger: a datadir with My reviewers split on whether erigon nodes reach this or only
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed on the unbounded/uncancellable hashing — that is the same defect #23472 is written against, so it gets fixed on main. One correction on what that fix currently buys, from reviewing #23472 at its current head: the bound lands, but the uninterruptible wait does not go away. So your "on interrupt, |
||
| } | ||
| if !snapshotTorrent.Ok { | ||
| return nil | ||
| } | ||
| t := snapshotTorrent.Value | ||
| me.torrents = append(me.torrents, t) | ||
| if !first { | ||
| return nil | ||
| } | ||
| me.metainfoTasks.Go(func() { | ||
| me.doMetainfoTask(func() func() { | ||
| return me.d.addedFirstDownloader(me.d.ctx, t, miOpt, item.Name, item.InfoHash) | ||
| return me.d.addedFirstDownloader(me.d.ctx, t, localMetainfo, item.Name, item.InfoHash) | ||
| }) | ||
| }) | ||
| return nil | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,7 @@ import ( | |
|
|
||
| "github.com/erigontech/erigon/common" | ||
| "github.com/erigontech/erigon/common/dbg" | ||
| "github.com/erigontech/erigon/common/dir" | ||
| "github.com/erigontech/erigon/common/log/v3" | ||
| "github.com/erigontech/erigon/db/datadir" | ||
| "github.com/erigontech/erigon/db/downloader/downloadercfg" | ||
|
|
@@ -493,8 +494,18 @@ func (d *Downloader) StartTorrentPeerManager(ctx context.Context) { | |
| }) | ||
| } | ||
|
|
||
| // Check snapshot data looks right. | ||
| func (d *Downloader) snapshotDataLooksComplete(info *metainfo.Info) bool { | ||
| // preverified.toml is written once the initial snapshot set completes, pinning the local hash set. | ||
| // Read on every call: the snapshot stage writes the file mid-run, and from that moment local data | ||
| // must be kept. | ||
| func (d *Downloader) initialDownloadComplete() (bool, error) { | ||
| complete, err := dir.FileExist(d.cfg.Dirs.PreverifiedPath()) | ||
| if err != nil { | ||
| return false, fmt.Errorf("checking %v: %w", d.cfg.Dirs.PreverifiedPath(), err) | ||
| } | ||
| return complete, nil | ||
| } | ||
|
|
||
| func (d *Downloader) snapshotDataSizesMatch(info *metainfo.Info) bool { | ||
| for f := range info.UpvertedFilesIter() { | ||
| pathParts := append([]string{info.BestName()}, f.BestPath()...) | ||
| slashPath := path.Join(pathParts...) | ||
|
|
@@ -774,7 +785,7 @@ func (d *Downloader) loadMetainfoFromDisk(name string) (mi *metainfo.MetaInfo, e | |
|
|
||
| // Loads metainfo from disk, removing it if it's invalid. Returns Some metainfo if it's valid. Logs | ||
| // errors. | ||
| func (d *Downloader) maybeLoadMetainfoFromDisk(name string) (miOpt g.Option[*metainfo.MetaInfo], err error) { | ||
| func (d *Downloader) maybeLoadMetainfoFromDisk(name string) (localMetainfo g.Option[*metainfo.MetaInfo], err error) { | ||
| miPath := d.metainfoFilePathForName(name) | ||
| mi, err := metainfo.LoadFromFile(miPath) | ||
| if err != nil { | ||
|
|
@@ -783,7 +794,7 @@ func (d *Downloader) maybeLoadMetainfoFromDisk(name string) (miOpt g.Option[*met | |
| } | ||
| return | ||
| } | ||
| miOpt.Set(mi) | ||
| localMetainfo.Set(mi) | ||
| return | ||
| } | ||
|
|
||
|
|
@@ -1000,39 +1011,58 @@ func (d *Downloader) testStartSingleDownloadNoWait( | |
| return err | ||
| } | ||
|
|
||
| func (d *Downloader) invalidateData(name snapshotName, infoHash metainfo.Hash) (err error) { | ||
| _, ok := d.torrentClient.Torrent(infoHash) | ||
| // Moves data aside so a download can't reuse it. Only legal while the initial download is | ||
| // incomplete: after that the local files are what this node built or restored, and nothing may | ||
| // remove them. | ||
| func (d *Downloader) invalidateData(name snapshotName, preverifiedInfoHash metainfo.Hash) (err error) { | ||
| complete, err := d.initialDownloadComplete() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if complete { | ||
| return fmt.Errorf("refusing to invalidate %q: initial download is complete", name) | ||
| } | ||
| _, ok := d.torrentClient.Torrent(preverifiedInfoHash) | ||
| // Torrent in use, bad idea to proceed. This shouldn't happen since we should have found | ||
| // the existing name earlier. | ||
| panicif.True(ok) | ||
| // Ensure the data isn't reused. We're presuming the storage in use, but we can't afford | ||
| // to wait until another torrent is fetched, and then we mistake a non-partial file with | ||
| // the correct size as being complete. | ||
| err = os.Rename(d.filePathForName(name), d.filePathForName(name+".part")) | ||
| if err != nil && errors.Is(err, os.ErrNotExist) { | ||
| err = nil | ||
| from := d.filePathForName(name) | ||
| to := d.filePathForName(name + ".part") | ||
| err = os.Rename(from, to) | ||
| if err != nil { | ||
| if errors.Is(err, os.ErrNotExist) { | ||
| err = nil | ||
| } | ||
| return | ||
| } | ||
| d.log(log.LvlWarn, "invalidated local snapshot data, will re-download", | ||
| "name", name, "renamed_to", to, "preverified", preverifiedInfoHash) | ||
| return | ||
| } | ||
|
|
||
| // Download a preverified file. That means it has a published manifest (metainfo), and a known info | ||
| // hash. Caller is responsible for flushing missing metainfos to disk when complete. | ||
| func (d *Downloader) addPreverifiedSnapshotForDownload( | ||
| infoHash metainfo.Hash, | ||
| preverifiedInfoHash metainfo.Hash, | ||
| name string, | ||
| ) ( | ||
| t *torrent.Torrent, | ||
| // First add of this Torrent that asked to download. The caller is responsible for adding | ||
| // download tasks. | ||
| firstDownloader bool, | ||
| miOpt g.Option[*metainfo.MetaInfo], | ||
| // None when local data wins. | ||
| snapshotTorrent g.Option[*torrent.Torrent], | ||
| firstDownloader bool, // First add of this Torrent that asked to download. The caller is responsible for adding download tasks. | ||
| localMetainfo g.Option[*metainfo.MetaInfo], | ||
| // Local data was kept and no torrent is registered for it. The caller seeds it, off d.lock: | ||
| // deriving a metainfo hashes the whole file. | ||
| keptLocal bool, | ||
| err error, | ||
| ) { | ||
| // Prevent anyone else from trying to add a torrent in the meanwhile, so we can do data | ||
| // invalidation, and identify the first downloader. | ||
| d.lock.Lock() | ||
| defer d.lock.Unlock() | ||
| t, ok, err := d.getExistingSnapshotTorrent(name, infoHash) | ||
| t, ok, err := d.getExistingSnapshotTorrent(name, preverifiedInfoHash) | ||
| if err != nil { | ||
| // If a torrent for this name is already loaded with a different infohash, keep the | ||
| // existing local torrent and skip the preverified download. This handles the case where | ||
|
|
@@ -1043,87 +1073,159 @@ func (d *Downloader) addPreverifiedSnapshotForDownload( | |
| // by AddTorrentsFromDisk before initial sync runs) is tracked separately. The proper fix | ||
| // is to run initial sync before AddTorrentsFromDisk so the preverified TOML hashes can | ||
| // always take precedence. See: https://github.com/erigontech/erigon/issues/19435 | ||
| if existingT, nameOk := d.torrentsByName[name]; nameOk && existingT.InfoHash() != infoHash { | ||
| if existingT, nameOk := d.torrentsByName[name]; nameOk && existingT.InfoHash() != preverifiedInfoHash { | ||
| d.log(log.LvlWarn, "snapshot already loaded with different infohash, keeping existing local torrent (preverified skipped)", | ||
| "name", name, | ||
| "existing_infohash", existingT.InfoHash().HexString(), | ||
| "preverified_infohash", infoHash.HexString()) | ||
| t = existingT | ||
| "preverified_infohash", preverifiedInfoHash.HexString()) | ||
| snapshotTorrent.Set(existingT) | ||
| err = nil | ||
| return | ||
| } | ||
| return | ||
| } | ||
| // We can invalidate data if a torrent isn't yet loaded. | ||
| if !ok { | ||
| miOpt, err = d.loadMatchingMetainfoOrInvalidateData(infoHash, name) | ||
| if err != nil { | ||
| return | ||
| } | ||
| var new bool | ||
| t, new, err = d.addTorrent(name, infoHash) | ||
| if err != nil { | ||
| var isNew bool | ||
| var addedTorrent g.Option[*torrent.Torrent] | ||
| addedTorrent, isNew, localMetainfo, keptLocal, err = d.addTorrentForPreverifiedSnapshot(preverifiedInfoHash, name) | ||
| if err != nil || !addedTorrent.Ok { | ||
| return | ||
| } | ||
| panicif.False(new) | ||
| panicif.False(isNew) | ||
| t = addedTorrent.Value | ||
| } | ||
| g.MakeMapIfNil(&d.downloads) | ||
| firstDownloader = !g.MapInsert(d.downloads, t, struct{}{}).Ok | ||
| snapshotTorrent.Set(t) | ||
| return | ||
| } | ||
|
|
||
| func (d *Downloader) loadMatchingMetainfoOrInvalidateData( | ||
| infoHash metainfo.Hash, | ||
| func (d *Downloader) addTorrentForPreverifiedSnapshot( | ||
| preverifiedInfoHash metainfo.Hash, | ||
| name string, | ||
| ) ( | ||
| miOpt g.Option[*metainfo.MetaInfo], | ||
| // None when local data wins. | ||
| addedTorrent g.Option[*torrent.Torrent], | ||
| isNew bool, | ||
| localMetainfo g.Option[*metainfo.MetaInfo], | ||
| keptLocal bool, | ||
| err error, | ||
| ) { | ||
| miOpt, err = d.maybeLoadMetainfoFromDisk(name) | ||
| var download bool | ||
| localMetainfo, download, err = d.prepareLocalDataForDownload(preverifiedInfoHash, name) | ||
| if err != nil || !download { | ||
| keptLocal = err == nil | ||
| return | ||
| } | ||
| t, isNew, err := d.addTorrent(name, preverifiedInfoHash) | ||
| if err != nil { | ||
| d.log(log.LvlError, "error loading metainfo from disk", "err", err, "name", name) | ||
| err = nil | ||
| return | ||
| } | ||
| if miOpt.Ok { | ||
| loadedIh := miOpt.Value.HashInfoBytes() | ||
| if loadedIh == infoHash { | ||
| return | ||
| addedTorrent.Set(t) | ||
| return | ||
| } | ||
|
|
||
| // Reports whether the preverified download should go ahead. Data that backs no matching metainfo is | ||
| // invalidated, but only while the manifest is still authoritative. | ||
| func (d *Downloader) prepareLocalDataForDownload( | ||
| preverifiedInfoHash metainfo.Hash, | ||
| name string, | ||
| ) ( | ||
| localMetainfo g.Option[*metainfo.MetaInfo], | ||
| download bool, | ||
| err error, | ||
| ) { | ||
| // An unreadable metainfo is logged and then treated as missing: it says nothing about the data. | ||
| localMetainfo, loadErr := d.maybeLoadMetainfoFromDisk(name) | ||
| if loadErr != nil { | ||
| d.log(log.LvlWarn, "error loading metainfo from disk", "err", loadErr, "name", name) | ||
| } | ||
| localMetainfoUnbacked := false | ||
| if localMetainfo.Ok { | ||
| localInfoHash := localMetainfo.Value.HashInfoBytes() | ||
| if localInfoHash == preverifiedInfoHash { | ||
| return localMetainfo, true, nil | ||
| } | ||
| // This is fine if we're doing initial sync. If we're not we shouldn't be here. | ||
| d.log(log.LvlWarn, "preverified snapshot hash has changed", | ||
| "expected", infoHash, | ||
| "actual", loadedIh, | ||
| "preverified", preverifiedInfoHash, | ||
| "local", localInfoHash, | ||
| "name", name) | ||
| info, infoErr := localMetainfo.Value.UnmarshalInfo() | ||
| if infoErr != nil { | ||
| d.log(log.LvlWarn, "error unmarshalling local metainfo", "err", infoErr, "name", name) | ||
| } | ||
| localMetainfoUnbacked = infoErr == nil && !d.snapshotDataSizesMatch(&info) | ||
| // Forget the metainfo we loaded, it's wrong (probably changed hash but not name...) | ||
| miOpt.SetNone() | ||
| localMetainfo.SetNone() | ||
| } else { | ||
| d.log(log.LvlDebug, "snapshot metainfo missing", "name", name) | ||
| } | ||
| err = d.invalidateData(name, infoHash) | ||
|
|
||
| complete, err := d.initialDownloadComplete() | ||
| if err != nil { | ||
| err = fmt.Errorf("invalidating old snapshot data: %w", err) | ||
| return | ||
| return localMetainfo, false, err | ||
| } | ||
| return | ||
| if complete { | ||
| // Local data outranks the manifest from here on: keep whatever we have, download the rest. | ||
| exists, err := d.snapshotDataExists(name) | ||
| if err != nil { | ||
| return localMetainfo, false, err | ||
| } | ||
| if !exists { | ||
| return localMetainfo, true, nil | ||
| } | ||
| if localMetainfoUnbacked { | ||
| // The data backs neither manifest. Downloading hands it to the client, which | ||
| // completes the file by length, so a wrong length is re-fetched while | ||
| // same-length-different-bytes is not. | ||
| d.log(log.LvlWarn, "local snapshot does not match its own metainfo, downloading", | ||
| "name", name) | ||
| return localMetainfo, true, nil | ||
| } | ||
| d.log(log.LvlWarn, "keeping local snapshot, skipping preverified download", "name", name) | ||
| return localMetainfo, false, nil | ||
| } | ||
|
|
||
| if err := d.invalidateData(name, preverifiedInfoHash); err != nil { | ||
| return localMetainfo, false, fmt.Errorf("invalidating old snapshot data: %w", err) | ||
| } | ||
| return localMetainfo, true, nil | ||
| } | ||
|
|
||
| // seedKeptSnapshot registers a kept local snapshot so it is seeded, deriving the metainfo when | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cost isn't the binding constraint here, and the same wording is at downloader.go:1055-1056.
The failure a cost-based comment permits: someone decides a goroutine is overkill for a small
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, and fixed on |
||
| // none is on disk. Must run without d.lock: deriving it hashes the whole file. | ||
|
AskAlexSharov marked this conversation as resolved.
|
||
| func (d *Downloader) seedKeptSnapshot(ctx context.Context, name string) { | ||
| if err := d.AddNewSeedableFile(ctx, name); err != nil && ctx.Err() == nil { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kept torrents get registered without trackers, so nothing announces them.
Reachable in The omission inside Single-source (conf 93) — I confirmed the
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checked this, since you flagged it as single-source. Confirmed, and it is worse than "no trackers".
So it is not only that no peer can discover it — even a peer that finds it via DHT/PEX or a manual add gets nothing, because the torrent will not upload. The seeding is a no-op, and the Agreed the omission inside
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question rather than a defect claim — this is present identically on main in #23350, so it is not a backport artifact, but I could not find it adjudicated anywhere and the caller contract is release-branch-facing. When a completed datadir has local snapshot data but no Is the success return intended here, or should a seeding failure on a kept file fail the batch?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Intended for the node, and your seedbox worry is real but a failed batch is the wrong lever.
The seedbox gap is real, and I found it is worse than the metainfo-write case you describe. Fixed on
If a seedbox should refuse to come up with unservable files, I would rather it assert that at the seedbox level — every preverified name resolves to a torrent that announces — than couple it to the download RPC. Happy to do that as a separate change if you want it. |
||
| d.log(log.LvlWarn, "cannot seed kept local snapshot", "err", err, "name", name) | ||
| } | ||
|
AskAlexSharov marked this conversation as resolved.
|
||
| } | ||
|
|
||
| func (d *Downloader) snapshotDataExists(name string) (bool, error) { | ||
| exists, err := dir.FileExist(d.filePathForName(name)) | ||
| if err != nil { | ||
| return false, fmt.Errorf("checking snapshot data for %q: %w", name, err) | ||
| } | ||
| return exists, nil | ||
| } | ||
|
|
||
| func (d *Downloader) addedFirstDownloader( | ||
| ctx context.Context, | ||
| t *torrent.Torrent, | ||
| miOpt g.Option[*metainfo.MetaInfo], | ||
| localMetainfo g.Option[*metainfo.MetaInfo], | ||
| name string, | ||
| infoHash metainfo.Hash, | ||
| ) (afterAdd func()) { | ||
| // Try again, we would have invalidated data for changed infohashes now. | ||
| if !miOpt.Ok { | ||
| // Try the webseeds for the metainfo that wasn't on disk. Nothing here relies on the data having | ||
| // been moved aside: after the initial download it never is, and the client completes the file | ||
| // by length. | ||
| if !localMetainfo.Ok { | ||
| // Yes I mean for this error to be scoped here. | ||
| err := d.fetchMetainfoFromWebseeds(ctx, name, infoHash) | ||
| if err == nil { | ||
| // Always reuse code paths to ensure no surprises later. I.e. load the metainfo again | ||
| // through the same path that is used on a good run. No data invalidation here, at this | ||
| // point we've added the torrent, and already invalidated if the metainfo was missing | ||
| // the first time. | ||
| miOpt, err = d.maybeLoadMetainfoFromDisk(name) | ||
| // through the same path that is used on a good run. | ||
| localMetainfo, err = d.maybeLoadMetainfoFromDisk(name) | ||
| if err != nil { | ||
| // Should this error be returned instead? | ||
| d.log(log.LvlError, "error loading metainfo from disk", "err", err, "name", name) | ||
|
|
@@ -1133,10 +1235,10 @@ func (d *Downloader) addedFirstDownloader( | |
| } | ||
| } | ||
|
|
||
| if miOpt.Ok { | ||
| if localMetainfo.Ok { | ||
| // Good case: We have a metainfo with the right infohash, either just fetched from a | ||
| // webseed, or it was cached on disk. | ||
| err := d.applyMetainfo(miOpt.Value, t) | ||
| err := d.applyMetainfo(localMetainfo.Value, t) | ||
| if err != nil { | ||
| d.log(log.LvlError, "error applying metainfo", "err", err, "name", name) | ||
| } | ||
|
|
@@ -1285,7 +1387,7 @@ func (d *Downloader) addTorrentIfComplete( | |
| err = fmt.Errorf("unmarshalling info from metainfo: %w", err) | ||
| return | ||
| } | ||
| if !d.snapshotDataLooksComplete(&info) { | ||
| if !d.snapshotDataSizesMatch(&info) { | ||
| err = nil | ||
| return | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The parenthetical scopes seeding under the local infohash to a torrent that is already loaded. The not-loaded case reaches the same outcome by another route, and the README doesn't say so.
keptLocal=true->seedKeptSnapshot->AddNewSeedableFile->BuildTorrentIfNeed, which returns early becausetorrentFiles.Existsis a bare file-presence check with no infohash validation and finds the stale.torrent; thenaddCompleteTorrent->loadMetainfoFromDisk->addCompleteTorrentFromMetainfo->addTorrentFromMetainfo(mi, name, mi.HashInfoBytes())registers the snapshot under the stale local infohash. The name lands intorrentsByName, hence inallActiveSnapshotsandPublishLocalChainToml, advertised under a hash that is not in the manifest.Reachable state:
preverified.tomlpresent,accounts.0-1024.kvpresent with a staleaccounts.0-1024.kv.torrentbeside it whose sizes still match (solocalMetainfoUnbackedis false and the file is kept), nothing loaded yet, download requested under the preverified hash.An operator debugging why a seedbox advertises a hash absent from the manifest reads this line, concludes it's confined to already-loaded torrents, and looks in the wrong place.
"dropped" earlier in the sentence is fine — the clause continues "and only its sizes are used to check the file", which makes clear only the infohash is disregarded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, and this is fixed on
mainin #23459 (this backport is a faithful cherry-pick of #23350, so it still carries the old wording).The line there now reads: a local
.torrentwhose infohash differs from the preverified one "is ignored for the download decision but is left on disk, and only its sizes are used to check the file"; the sentence ends "the preverified download is skipped and the kept snapshot is seeded, under whatever infohash its local.torrentcarries". NoaddPreverifiedSnapshotForDownloadparenthetical, so nothing scopes it to an already-loaded torrent — which is the route you traced (BuildTorrentIfNeedreturns early on bare file presence, thenloadMetainfoFromDiskregisters under the stale hash).