Artifact a9fd3c32c31a1070864340281154d783a04044277ead74846d602c8657d7eeeb:
- File
internal/sync/horizon_seedrefuse_test.go
— part of check-in
[5d0ef566aa]
at
2026-10-24 14:34:00
on branch feat/horizon-dfloor-receive
— Merge feat/seed-receive-drop; re-pin horizon draft to zit caeef421ee
Re-pins draft-fossil-horizon-00 to zit trunk check-in caeef421ee (file artifact ab18fa7f89), which changes §5.2.2, §5.2.3 and §6.2.3 and adds two Appendix C bullets. This brings in the local feat/seed-receive-drop branch, which implements the receive rule, and resolves it against main (#477 chain-walk bounds, #478 seed binding).
§5.2.2, the seed's D (tracked in #479): - Mint dates a seed at max(tagxref.mtime) over every row in the repository, cancels and propagated rows included, as zit's latestTagEvent does. It was the latest of the rows the seed summarizes. - A cancel of the newest tag, or a reopened closed branch, therefore no longer mints a seed earlier than the one a clone holds, which the clone's D floor refused as a replay.
§5.2.3, the receive rule: - A pull, clone or sync drops a seed by this store's label or by a binding card when it can read it, and the link step refuses one completed later. - The draft also requires that a delta kept without its base, pushed or received, is not announced or served while it cannot be expanded. feat/seed-receive-drop only withheld pushed ones. The pending mark moves into storeDeltaContent, so a pull marks too, and a clone marks what is still unreadable when it ends (seedcheck.WithholdUnreadable). That avoids judging every dangling file delta of a clone a second time. - Since #478 minted seeds carry a binding card. The seed-shaped-ordinary cases therefore strip it, and a new test checks that a seed with the card is never linked.
Conformance rows: - HZ-5.2-2 takes the new D text. - HZ-5.2-4 loses the old ordinary-artifact sentence. - HZ-6.2-6 says the D is fixed by the repository's tag events. - New rows: HZ-5.2-14 (the receive-path drop; PARTIAL, since a seed completed in a later round is kept inert, and was counted when its delta arrived), HZ-5.2-15 (the dropped-hash record) and HZ-5.2-16 (withholding unexpandable deltas). - New cases: two D cases mirroring zit's "horizon seed date" regression, a cancel on C and a cancel at an ancestor below it, and five receive cases. - The deepen-floor case now replays an older seed, since an honest origin no longer mints one. (user: [email protected], size: 51539) [annotate] [blame] [check-ins using] [more...]
package sync import ( "context" "path/filepath" "slices" "strings" "testing" "time" "github.com/danmestas/go-libfossil/db" "github.com/danmestas/go-libfossil/internal/blob" "github.com/danmestas/go-libfossil/internal/content" "github.com/danmestas/go-libfossil/internal/deck" "github.com/danmestas/go-libfossil/internal/delta" libfossil "github.com/danmestas/go-libfossil/internal/fsltype" "github.com/danmestas/go-libfossil/internal/hash" "github.com/danmestas/go-libfossil/internal/manifest" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/internal/seedcheck" "github.com/danmestas/go-libfossil/internal/shun" "github.com/danmestas/go-libfossil/internal/tag" "github.com/danmestas/go-libfossil/internal/verify" "github.com/danmestas/go-libfossil/internal/xfer" ) // wantSeedRefused is the whole reply to a push carrying a seed (horizon // §5.2.3 as amended by the zit 0.7.1 erratum): the error card alone. const wantSeedRefused = "seed refused: a seed is private to the clone that holds it; it is never pushed" // labelled is a control artifact tagging p under user, as a seed minted // with that label is. func (o *pcOrigin) labelled(user, value string) (*xfer.FileCard, []byte) { o.t.Helper() d := &deck.Deck{Type: deck.Control, D: o.when.Add(4 * time.Hour), U: deck.User(user), T: []deck.TagCard{{Type: deck.TagSingleton, Name: "seedtest", UUID: o.p, Value: value}}} data, err := d.Marshal() if err != nil { o.t.Fatal(err) } return &xfer.FileCard{UUID: hash.SHA1(data), Content: data}, data } // deltaCard is target sent as a delta against base, named baseUUID. func deltaCard(t *testing.T, base []byte, baseUUID string, target []byte) *xfer.FileCard { t.Helper() d, err := delta.Create(base, target) if err != nil { t.Fatal(err) } return &xfer.FileCard{UUID: hash.SHA1(target), DeltaSrc: baseUUID, Content: d} } // assertSeedRefused checks that a reply is the refusal alone and that none // of the named artifacts was stored. func assertSeedRefused(t *testing.T, o *pcOrigin, resp *xfer.Message, uuids ...string) { t.Helper() if len(resp.Cards) != 1 { t.Fatalf("reply has %d cards %v, want the refusal alone", len(resp.Cards), resp.Cards) } if e, ok := resp.Cards[0].(*xfer.ErrorCard); !ok || e.Message != wantSeedRefused { t.Fatalf("reply = %#v, want error %q", resp.Cards[0], wantSeedRefused) } for _, u := range uuids { if o.held(u) { t.Fatalf("the refused request stored %s", u) } } } func TestHorizonPushSeedRefusedFullText(t *testing.T) { o := newPCOrigin(t) s, _ := o.labelled(seedLogin, "a") resp := o.push(o.blobCard(), s) assertSeedRefused(t, o, resp, s.UUID, o.fileUID) } func TestHorizonPushSeedRefusedDeltaHeldBase(t *testing.T) { o := newPCOrigin(t) rid, ok := blob.Exists(o.r.DB(), o.p) if !ok { t.Fatal("origin does not hold p") } base, err := content.Expand(o.r.DB(), rid) if err != nil { t.Fatal(err) } _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, o.p, seed) resp := o.push(o.blobCard(), s) assertSeedRefused(t, o, resp, s.UUID, o.fileUID) } func TestHorizonPushSeedRefusedLateBase(t *testing.T) { o := newPCOrigin(t) baseCard, base := o.labelled("u", "b") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, baseCard.UUID, seed) resp := o.push(s, o.blobCard(), baseCard) assertSeedRefused(t, o, resp, s.UUID, o.fileUID, baseCard.UUID) } // A delta whose base never arrives cannot be identified: it is stored as // any dangling delta is, under the transit-phantom rules. func TestHorizonPushSeedUnexpandableAdmitted(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "b") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA1(base), seed) if errs := errorsOf(o.push(o.blobCard(), s)); len(errs) != 0 { t.Fatalf("errors = %q", errs) } if !o.held(o.fileUID) { t.Fatal("the ordinary artifact was not stored") } } // A seed admitted as a dangling delta is judged when a later request makes // it readable (spec F): that request is innocent and stored whole with no // error, while the seed is recorded as refused, never crosslinked, announced // or sent. The label and the shape are both caught. func TestHorizonPushSeedBaseInLaterRequest(t *testing.T) { for _, shape := range []string{"label", "shape"} { o := newPCOrigin(t) baseCard, base := o.labelled("u", "b") var seed []byte if shape == "label" { _, seed = o.labelled(seedLogin, "a") } else { _, seed = o.bound("zit-seed") } s := deltaCard(t, base, baseCard.UUID, seed) if errs := errorsOf(o.push(s)); len(errs) != 0 { t.Fatalf("%s: the dangling delta: errors %q", shape, errs) } if errs := errorsOf(o.push(o.blobCard(), baseCard)); len(errs) != 0 { t.Fatalf("%s: the base's push: errors %q", shape, errs) } for _, u := range []string{baseCard.UUID, o.fileUID} { if phantom, err := isPhantomRow(o.r.DB(), u); err != nil || phantom || !o.held(u) { t.Fatalf("%s: %.10s not stored (%v)", shape, u, err) } } assertRefused(t, o.r, s.UUID, true) var linked int if err := o.r.DB().QueryRow(`SELECT (SELECT count(*) FROM tagxref JOIN blob ON blob.rid=tagxref.srcid WHERE blob.uuid=?1) + (SELECT count(*) FROM event JOIN blob ON blob.rid=event.objid WHERE blob.uuid=?1)`, s.UUID).Scan(&linked); err != nil { t.Fatal(err) } if linked != 0 { t.Fatalf("%s: the seed was crosslinked: %d rows", shape, linked) } if _, err := verify.Rebuild(o.r); err != nil { t.Fatal(err) } if err := o.r.DB().QueryRow(`SELECT count(*) FROM tagxref JOIN blob ON blob.rid=tagxref.srcid WHERE blob.uuid=?`, s.UUID).Scan(&linked); err != nil || linked != 0 { t.Fatalf("%s: rebuild crosslinked the seed: %d rows (%v)", shape, linked, err) } pull := o.push(&xfer.PullCard{ServerCode: "s", ProjectCode: testPC}, &xfer.GimmeCard{UUID: s.UUID}) for _, c := range pull.Cards { if f, ok := c.(*xfer.FileCard); ok && f.UUID == s.UUID { t.Fatalf("%s: the seed was served", shape) } if g, ok := c.(*xfer.IGotCard); ok && g.UUID == s.UUID { t.Fatalf("%s: the seed was announced", shape) } } } } // Every dependent the base grounds is judged: an ordinary one before the // seed is linked, and does not end the check. A forged dependent, whose // bytes are a seed but not the artifact its name names, is not refused, so // a forgery cannot bar the real artifact. func TestHorizonPushSeedLaterJudgesEveryDependent(t *testing.T) { o := newPCOrigin(t) baseCard, base := o.labelled("u", "b") ordinaryCard, ordinary := o.labelled("u", "ordinary") _, seed := o.labelled(seedLogin, "a") _, forgedSeed := o.bound("zit-seed") plain := deltaCard(t, base, baseCard.UUID, ordinary) s := deltaCard(t, base, baseCard.UUID, seed) forged := deltaCard(t, base, baseCard.UUID, forgedSeed) forged.UUID = hash.SHA1([]byte("a name the forged bytes do not hash to\n")) // A plain file after the forgery: no link step judges it, so it is // cleared only if the forgery does not end the fill's judging. fileBytes := append(slices.Clone(base), []byte("a plain file on the base\n")...) file := deltaCard(t, base, baseCard.UUID, fileBytes) if errs := errorsOf(o.push(plain, forged, file, s)); len(errs) != 0 { t.Fatalf("the dangling deltas: errors %q", errs) } if errs := errorsOf(o.push(baseCard)); len(errs) != 0 { t.Fatalf("the base's push: errors %q", errs) } for _, c := range []struct { uuid string want bool }{{ordinaryCard.UUID, false}, {s.UUID, true}, {forged.UUID, false}} { assertRefused(t, o.r, c.uuid, c.want) } frid, _ := blob.Exists(o.r.DB(), file.UUID) if withheld, err := seedcheck.IsWithheld(o.r.DB(), frid); err != nil || withheld { t.Fatalf("the file after the forgery is withheld (%v)", err) } var linked int if err := o.r.DB().QueryRow(`SELECT count(*) FROM event JOIN blob ON blob.rid=event.objid WHERE blob.uuid=?`, ordinaryCard.UUID).Scan(&linked); err != nil || linked == 0 { t.Fatalf("the ordinary dependent was not linked: %d rows (%v)", linked, err) } } // A delta that cannot be read does not end the check: a seed after it in // the same request is still refused. func TestHorizonPushSeedAfterUnexpandable(t *testing.T) { o := newPCOrigin(t) _, never := o.labelled("u", "never") _, other := o.labelled("u", "other") stuck := deltaCard(t, never, hash.SHA1(never), other) baseCard, base := o.labelled("u", "b") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, baseCard.UUID, seed) resp := o.push(stuck, s, o.blobCard(), baseCard) assertSeedRefused(t, o, resp, stuck.UUID, s.UUID, o.fileUID, baseCard.UUID) } // Only this origin's own label is refused: another implementation's seed // label, and any user's, is an ordinary control artifact. func TestHorizonPushForeignSeedLabelOrdinary(t *testing.T) { for _, user := range []string{"zit-seed", "zit-serve", "u", "Go-Libfossil"} { o := newPCOrigin(t) c, _ := o.labelled(user, "a") if errs := errorsOf(o.push(c)); len(errs) != 0 || !o.held(c.UUID) { t.Fatalf("U %s: errors %q, held %v", user, errs, o.held(c.UUID)) } } } // A check-in carrying the label is not a seed: only control artifacts are. func TestHorizonPushSeedLabelCheckinOrdinary(t *testing.T) { o := newPCOrigin(t) d := &deck.Deck{Type: deck.Checkin, C: "x", D: o.when.Add(2 * time.Hour), U: deck.User(seedLogin), P: []string{o.p}, F: []deck.FileCard{{Name: "c", UUID: o.fileUID}}} data, err := d.Marshal() if err != nil { t.Fatal(err) } c := &xfer.FileCard{UUID: hash.SHA1(data), Content: data} if errs := errorsOf(o.push(o.blobCard(), c)); len(errs) != 0 || !o.held(c.UUID) { t.Fatalf("errors %q, held %v", errs, o.held(c.UUID)) } } // §5.7 is judged on expanded content too: a stale cancellation sent as a // delta whose base arrives later in the same push is refused. func TestHorizonPushStaleCancelLateDelta(t *testing.T) { o := newPCOrigin(t) card, uuid := o.branchFrom("feat") d := deltaCard(t, o.file, o.fileUID, card.Content) errs := errorsOf(o.push(d, o.blobCard())) if len(errs) != 1 || !strings.HasPrefix(errs[0], "stale seed: ") { t.Fatalf("errors = %q, want one stale-seed refusal", errs) } if o.held(uuid) || o.held(o.fileUID) { t.Fatal("the refused push stored artifacts") } } // A pusher surfaces the refusal as its sync's error and stops. func TestHorizonPushSeedRefusedClient(t *testing.T) { when := time.Date(2026, 10, 1, 12, 0, 0, 0, time.UTC) origin := setupPushableTestRepo(t) client := setupSyncTestRepo(t) rid, _ := mbCommit(t, client, "root", 0, when) if _, err := tag.AddTag(client, tag.TagOpts{TargetRID: rid, TagName: "seedtest", TagType: tag.TagSingleton, Value: "a", User: seedLogin, Time: when.Add(time.Hour)}); err != nil { t.Fatal(err) } res, err := Sync(context.Background(), client, handlerTransport(t, origin), SyncOpts{Push: true, ServerCode: "s", ProjectCode: testPC}) if err != nil { t.Fatalf("Sync: %v", err) } // Round one announces, round two sends what the origin asks for and is // refused; nothing is sent again. if res.Rounds != 2 || res.FilesSent != 3 { t.Fatalf("rounds %d, files sent %d: want the push to stop at the refusal", res.Rounds, res.FilesSent) } if len(res.Errors) != 1 || res.Errors[0] != "error: "+wantSeedRefused { t.Fatalf("errors = %q", res.Errors) } var n int if err := origin.DB().QueryRow("SELECT count(*) FROM blob WHERE size>=0").Scan(&n); err != nil { t.Fatal(err) } if n != 0 { t.Fatalf("origin stored %d artifacts of a refused push", n) } } // bound is a control artifact under user carrying a horizon-seed T card, the // shape of a bound seed whatever its label (horizon §5.2.3, spec E.2). func (o *pcOrigin) bound(user string) (*xfer.FileCard, []byte) { o.t.Helper() d := &deck.Deck{Type: deck.Control, D: o.when.Add(4 * time.Hour), U: deck.User(user), T: []deck.TagCard{{Type: deck.TagSingleton, Name: "horizon-seed", UUID: o.p, Value: o.p}}} data, err := d.Marshal() if err != nil { o.t.Fatal(err) } return &xfer.FileCard{UUID: hash.SHA1(data), Content: data}, data } func TestHorizonPushSeedShapeRefusedForeignLabel(t *testing.T) { o := newPCOrigin(t) s, _ := o.bound("zit-seed") resp := o.push(o.blobCard(), s) assertSeedRefused(t, o, resp, s.UUID, o.fileUID) } func TestHorizonPushSeedShapeRefusedLateBase(t *testing.T) { o := newPCOrigin(t) baseCard, base := o.labelled("u", "b") _, seed := o.bound("zit-seed") s := deltaCard(t, base, baseCard.UUID, seed) resp := o.push(s, o.blobCard(), baseCard) assertSeedRefused(t, o, resp, s.UUID, o.fileUID, baseCard.UUID) } // isPhantomRow reports whether uuid names a row whose content the store // does not hold. func isPhantomRow(q db.Querier, uuid string) (bool, error) { var phantom bool err := q.QueryRow("SELECT EXISTS(SELECT 1 FROM blob WHERE uuid=? AND size<0)", uuid).Scan(&phantom) return phantom, err } // assertRefused checks whether r records uuid as a refused seed: refused by // its hash, kept stored, withheld from every reply and never linked. A // refusal never lands in the shun table, which peers pull. func assertRefused(t *testing.T, r *repo.Repo, uuid string, want bool) { t.Helper() refused, err := seedcheck.IsRefusedUUID(r.DB(), uuid) if err != nil || refused != want { t.Fatalf("%.10s refused = %v (%v), want %v", uuid, refused, err, want) } if shunned, err := shun.IsShunned(r.DB(), uuid); err != nil || shunned { t.Fatalf("%.10s is shunned (%v)", uuid, err) } if !want { return } rid, held := blob.Exists(r.DB(), uuid) if !held { t.Fatalf("the refused %.10s is not kept", uuid) } if withheld, err := seedcheck.IsWithheld(r.DB(), rid); err != nil || !withheld { t.Fatalf("the refused %.10s is not withheld (%v)", uuid, err) } if n := seedLinkRows(t, r, uuid); n != 0 { t.Fatalf("the refused %.10s was crosslinked: %d rows", uuid, n) } } // assertDropped checks that r refused uuid by its hash and never stored its // content: a seed dropped on receipt (F.2). func assertDropped(t *testing.T, r *repo.Repo, uuid string) { t.Helper() if refused, err := seedcheck.IsRefusedUUID(r.DB(), uuid); err != nil || !refused { t.Fatalf("%.10s is not refused (%v)", uuid, err) } if phantom, err := isPhantomRow(r.DB(), uuid); err != nil { t.Fatal(err) } else if _, held := blob.Exists(r.DB(), uuid); held && !phantom { t.Fatalf("the dropped %.10s was stored", uuid) } } // seedLinkRows counts the derived rows sourced from uuid: tag rows and its // event. func seedLinkRows(t *testing.T, r *repo.Repo, uuid string) int { t.Helper() var n int if err := r.DB().QueryRow(`SELECT (SELECT count(*) FROM tagxref JOIN blob ON blob.rid=tagxref.srcid WHERE blob.uuid=?1) + (SELECT count(*) FROM event JOIN blob ON blob.rid=event.objid WHERE blob.uuid=?1)`, uuid).Scan(&n); err != nil { t.Fatal(err) } return n } // danglingSeed pushes a seed to o as a delta on base, which o lacks, and // returns the seed's card. The base is named as a new repository names // what it stores itself, by SHA3. func danglingSeed(t *testing.T, o *pcOrigin, base []byte) *xfer.FileCard { t.Helper() _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA3(base), seed) if errs := errorsOf(o.push(s)); len(errs) != 0 { t.Fatalf("the dangling delta: errors %q", errs) } return s } // The origin pulling the base from an upstream fills it through the client // path: the seed is judged there too, refused and never linked. func TestHorizonPullFillsSeedBase(t *testing.T) { o := newPCOrigin(t) upstream := setupPushableTestRepo(t) _, base := o.labelled("u", "b") s := danglingSeed(t, o, base) storeAndLink(t, upstream, base) res, err := Sync(context.Background(), o.r, handlerTransport(t, upstream), SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}) if err != nil || len(res.Errors) != 0 { t.Fatalf("pull: %v %q", err, res.Errors) } if phantom, err := isPhantomRow(o.r.DB(), hash.SHA3(base)); err != nil || phantom { t.Fatalf("the base was not pulled (%v)", err) } assertRefused(t, o.r, s.UUID, true) if n := seedLinkRows(t, o.r, s.UUID); n != 0 { t.Fatalf("the seed was crosslinked: %d rows", n) } } // A local check-in that writes the base's bytes fills it too, and judges // the seed in its own transaction: with no sweep after it, the seed is // already refused, and neither a clone nor a pulling peer receives it. func TestHorizonCheckinFillsSeedBase(t *testing.T) { o := newPCOrigin(t) base := []byte("file content that is also the seed's base\n") s := danglingSeed(t, o, base) if _, _, err := manifest.Checkin(o.r, manifest.CheckinOpts{Files: []manifest.File{{Name: "f", Content: base}}, Comment: "f", User: "u", Parent: 0, Time: o.when.Add(5 * time.Hour)}); err != nil { t.Fatal(err) } assertRefused(t, o.r, s.UUID, true) assertNotServed(t, o, s.UUID) } // assertNotServed checks that neither a clone of o nor a peer pulling from it // receives uuid. func assertNotServed(t *testing.T, o *pcOrigin, uuid string) { t.Helper() clone, _, err := Clone(context.Background(), filepath.Join(t.TempDir(), "c.fossil"), handlerTransport(t, o.r), CloneOpts{}) if err != nil { t.Fatal(err) } defer clone.Close() if _, ok := blob.Exists(clone.DB(), uuid); ok { t.Fatalf("a clone received %.10s", uuid) } peer := setupSyncTestRepo(t) if _, err := Sync(context.Background(), peer, handlerTransport(t, o.r), SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } if _, ok := blob.Exists(peer.DB(), uuid); ok { t.Fatalf("a pulling peer received %.10s", uuid) } } // A seed still pending, its base not yet arrived, is withheld too: it has // not been judged. (A clone of a store holding a dangling delta stalls // whatever the delta is, so only a pull and a gimme are checked.) func TestHorizonPendingSeedNotServed(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "never sent") s := danglingSeed(t, o, base) resp, err := HandleSync(context.Background(), o.r, &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "s", ProjectCode: testPC}, &xfer.GimmeCard{UUID: s.UUID}}}) if err != nil { t.Fatal(err) } for _, c := range resp.Cards { switch c := c.(type) { case *xfer.FileCard: if c.UUID == s.UUID { t.Fatal("a gimme was answered with the pending seed") } case *xfer.IGotCard: if c.UUID == s.UUID { t.Fatal("an inventory announced the pending seed") } } } } // A pending row is cleared once judged, whatever the artifact: a plain file // pushed as a delta ahead of its basis is served once the basis arrives. func TestHorizonPendingFileClearedWhenJudged(t *testing.T) { o := newPCOrigin(t) base := []byte("the basis of a plain file\n") file := []byte("the basis of a plain file\nand a line more\n") d := deltaCard(t, base, hash.SHA3(base), file) if errs := errorsOf(o.push(d)); len(errs) != 0 { t.Fatalf("the dangling file: errors %q", errs) } rid, _ := blob.Exists(o.r.DB(), d.UUID) if withheld, err := seedcheck.IsWithheld(o.r.DB(), rid); err != nil || !withheld { t.Fatalf("the dangling file is not pending (%v)", err) } if errs := errorsOf(o.push(&xfer.FileCard{UUID: hash.SHA3(base), Content: base})); len(errs) != 0 { t.Fatalf("the base's push: errors %q", errs) } if withheld, err := seedcheck.IsWithheld(o.r.DB(), rid); err != nil || withheld { t.Fatalf("the judged file is still withheld (%v)", err) } peer := setupSyncTestRepo(t) if _, err := Sync(context.Background(), peer, handlerTransport(t, o.r), SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } if _, ok := blob.Exists(peer.DB(), d.UUID); !ok { t.Fatal("a pulling peer did not receive the file") } // And the origin announces it again when it pushes on. pushed := false for _, req := range pushOnRecording(t, o) { for _, c := range req.Cards { if g, ok := c.(*xfer.IGotCard); ok && g.UUID == d.UUID { pushed = true } } } if !pushed { t.Fatal("the origin did not announce the judged file when pushing on") } } // Purging a pending delta forgets its record, so an innocent artifact that // is later stored under the reused rid is served. func TestHorizonPurgedPendingForgotten(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "never sent") s := danglingSeed(t, o, base) pendingRID, _ := blob.Exists(o.r.DB(), s.UUID) var maxRID libfossil.FslID if err := o.r.DB().QueryRow("SELECT max(rid) FROM blob").Scan(&maxRID); err != nil { t.Fatal(err) } if pendingRID != maxRID { t.Fatalf("fixture: the pending delta is rid %d, not the last row %d", pendingRID, maxRID) } if err := shun.Add(o.r.DB(), s.UUID, "purge"); err != nil { t.Fatal(err) } if _, err := shun.Purge(o.r.DB()); err != nil { t.Fatal(err) } innocent := []byte("an innocent blob stored after the purge\n") rid, _, err := blob.Store(o.r.DB(), innocent) if err != nil { t.Fatal(err) } if rid != pendingRID { t.Fatalf("fixture: the innocent blob is rid %d, not the reused %d", rid, pendingRID) } if withheld, err := seedcheck.IsWithheld(o.r.DB(), rid); err != nil || withheld { t.Fatalf("the innocent blob inherited the pending record (%v)", err) } } // A private sync's inventory withholds a seed pushed privately. func TestHorizonSeedWithheldFromPrivateInventory(t *testing.T) { o := newPCOrigin(t) if _, err := o.r.DB().Exec("UPDATE user SET cap=cap||'x' WHERE login='nobody'"); err != nil { t.Fatal(err) } _, base := o.labelled("u", "never sent") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA3(base), seed) private := &xfer.PragmaCard{Name: "send-private"} push := func(cards ...xfer.Card) *xfer.Message { req := &xfer.Message{Cards: append([]xfer.Card{private, &xfer.PushCard{ServerCode: "s", ProjectCode: testPC}}, cards...)} resp, err := HandleSync(context.Background(), o.r, req) if err != nil { t.Fatal(err) } return resp } if errs := errorsOf(push(&xfer.PrivateCard{}, s)); len(errs) != 0 { t.Fatalf("the private dangling seed: errors %q", errs) } resp, err := HandleSync(context.Background(), o.r, &xfer.Message{Cards: []xfer.Card{private, &xfer.PullCard{ServerCode: "s", ProjectCode: testPC}}}) if err != nil { t.Fatal(err) } for _, c := range resp.Cards { if g, ok := c.(*xfer.IGotCard); ok && g.UUID == s.UUID { t.Fatal("a private inventory announced the seed") } } } // A delta built on a pending seed is an ordinary artifact: once the base // arrives the seed is refused but kept, so the delta on it is readable, // linked and served. Its chain runs through the seed, whether it arrived // after the seed (root the base) or before it (root the seed, moved to the // base when the seed arrives). func TestHorizonDeltaOnSeedServed(t *testing.T) { for _, order := range []string{"seed first", "dependent first"} { o := newPCOrigin(t) _, base := o.labelled("u", "b") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA3(base), seed) depCard, dep := o.labelled("u", "built on the seed") y := deltaCard(t, seed, s.UUID, dep) // A plain file on the seed too: no link step judges it, so only the // fill that grounds it clears it. file := append(slices.Clone(seed), []byte("a plain file built on the seed\n")...) f := deltaCard(t, seed, s.UUID, file) cards := []*xfer.FileCard{s, y, f} if order == "dependent first" { cards = []*xfer.FileCard{y, f, s} } for _, c := range cards { if errs := errorsOf(o.push(c)); len(errs) != 0 { t.Fatalf("%s: pushing %.10s: errors %q", order, c.UUID, errs) } } if errs := errorsOf(o.push(&xfer.FileCard{UUID: hash.SHA3(base), Content: base})); len(errs) != 0 { t.Fatalf("%s: the base's push: errors %q", order, errs) } assertRefused(t, o.r, s.UUID, true) rid, _ := blob.Exists(o.r.DB(), depCard.UUID) if !content.IsAvailable(o.r.DB(), rid) { t.Fatalf("%s: the delta on the seed is not readable", order) } if withheld, err := seedcheck.IsWithheld(o.r.DB(), rid); err != nil || withheld { t.Fatalf("%s: the delta on the seed is withheld (%v)", order, err) } if n := seedLinkRows(t, o.r, depCard.UUID); n == 0 { t.Fatalf("%s: the delta on the seed was not linked", order) } frid, _ := blob.Exists(o.r.DB(), f.UUID) if withheld, err := seedcheck.IsWithheld(o.r.DB(), frid); err != nil || withheld { t.Fatalf("%s: the file on the seed is withheld (%v)", order, err) } resp, err := HandleSync(context.Background(), o.r, &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "s", ProjectCode: testPC}, &xfer.GimmeCard{UUID: depCard.UUID}}}) if err != nil { t.Fatal(err) } served := false for _, c := range resp.Cards { if f, ok := c.(*xfer.FileCard); ok && f.UUID == depCard.UUID { served = true } } if !served { t.Fatalf("%s: the delta on the seed was not served", order) } } } // A reply that read the withheld set for a gimme before the request stored // a dangling seed still keeps the seed out of its inventory. func TestHorizonSeedWithheldAfterEarlierGimme(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "never sent") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA3(base), seed) resp, err := HandleSync(context.Background(), o.r, &xfer.Message{Cards: []xfer.Card{ &xfer.PushCard{ServerCode: "s", ProjectCode: testPC}, &xfer.PullCard{ServerCode: "s", ProjectCode: testPC}, &xfer.GimmeCard{UUID: o.p}, s}}) if err != nil { t.Fatal(err) } for _, c := range resp.Cards { if g, ok := c.(*xfer.IGotCard); ok && g.UUID == s.UUID { t.Fatal("the inventory announced the seed") } } } // A refused seed is served by no path: not cloned, and not pushed on by the // origin acting as a client. func TestHorizonRefusedSeedNotServed(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "b") s := danglingSeed(t, o, base) baseCard := &xfer.FileCard{UUID: hash.SHA3(base), Content: base} if errs := errorsOf(o.push(baseCard)); len(errs) != 0 { t.Fatalf("the base's push: errors %q", errs) } assertRefused(t, o.r, s.UUID, true) clone, _, err := Clone(context.Background(), filepath.Join(t.TempDir(), "c.fossil"), handlerTransport(t, o.r), CloneOpts{}) if err != nil { t.Fatal(err) } defer clone.Close() if _, ok := blob.Exists(clone.DB(), s.UUID); ok { t.Fatal("a clone received the refused seed") } if _, ok := blob.Exists(clone.DB(), baseCard.UUID); !ok { t.Fatal("a clone did not receive the base") } // The peer asks for the seed by name, as one that learned it elsewhere // would: the origin, as a client, still does not send it. peer := setupPushableTestRepo(t) asked := false tr := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message { for _, c := range req.Cards { if f, ok := c.(*xfer.FileCard); ok && f.UUID == s.UUID { t.Error("the origin pushed the refused seed on") } if g, ok := c.(*xfer.IGotCard); ok && g.UUID == s.UUID { t.Error("the origin announced the refused seed") } } resp, err := HandleSync(context.Background(), peer, req) if err != nil { t.Errorf("HandleSync: %v", err) return &xfer.Message{} } if !asked { asked = true resp.Cards = append(resp.Cards, &xfer.GimmeCard{UUID: s.UUID}) } return resp }} if _, err := Sync(context.Background(), o.r, tr, SyncOpts{Push: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } } // The refusal record is store-local: no configuration group carries it. func TestSeedcheckTableNotSynced(t *testing.T) { for _, tbl := range configTables { if strings.Contains(tbl.table, "seedcheck") { t.Fatalf("configuration group %s carries the seedcheck table", tbl.card) } } } // An origin that refused a seed by its hash refuses a re-push of it at // once, as full text or as a delta whose basis it lacks. func TestHorizonRePushRefusedByHash(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "b") s := danglingSeed(t, o, base) if errs := errorsOf(o.push(&xfer.FileCard{UUID: hash.SHA3(base), Content: base})); len(errs) != 0 { t.Fatalf("the base's push: errors %q", errs) } assertRefused(t, o.r, s.UUID, true) _, seed := o.labelled(seedLogin, "a") _, other := o.labelled("u", "elsewhere") for _, card := range []*xfer.FileCard{ {UUID: s.UUID, Content: seed}, deltaCard(t, other, hash.SHA3(other), seed), } { assertSeedRefused(t, o, o.push(o.blobCard(), card), o.fileUID) assertRefused(t, o.r, s.UUID, true) } // A pusher announcing the hash is not asked for it. for _, c := range o.push(&xfer.IGotCard{UUID: s.UUID}).Cards { if g, ok := c.(*xfer.GimmeCard); ok && g.UUID == s.UUID { t.Fatal("the origin asked for a refused seed a pusher announced") } } } // A tag whose control artifact is a pending seed's basis judges the seed in // its own transaction: it is refused and dropped. func TestHorizonTagFillsSeedBase(t *testing.T) { when := time.Date(2026, 10, 1, 20, 0, 0, 0, time.UTC) addTag := func(o *pcOrigin) libfossil.FslID { t.Helper() target, ok := blob.Exists(o.r.DB(), o.p) if !ok { t.Fatal("origin does not hold p") } rid, err := tag.AddTag(o.r, tag.TagOpts{TargetRID: target, TagName: "release", TagType: tag.TagSingleton, User: "u", Time: when}) if err != nil { t.Fatal(err) } return rid } // The same tag on an identical origin gives the control artifact's bytes. twin := newPCOrigin(t) base, err := content.Expand(twin.r.DB(), addTag(twin)) if err != nil { t.Fatal(err) } o := newPCOrigin(t) s := danglingSeed(t, o, base) addTag(o) assertRefused(t, o.r, s.UUID, true) } // A pending seed made readable by a path that judges nothing (a raw store // here) is still caught by the link step: it is refused and never linked. func TestHorizonLinkJudgesPendingSeed(t *testing.T) { o := newPCOrigin(t) base := []byte("a basis stored with no seed check\n") s := danglingSeed(t, o, base) if _, _, err := blob.Store(o.r.DB(), base); err != nil { t.Fatal(err) } if _, err := manifest.Crosslink(o.r); err != nil { t.Fatal(err) } if refused, err := seedcheck.IsRefusedUUID(o.r.DB(), s.UUID); err != nil || !refused { t.Fatalf("the link step did not refuse the seed (%v)", err) } if n := seedLinkRows(t, o.r, s.UUID); n != 0 { t.Fatalf("the seed was crosslinked: %d rows", n) } } // seedPeer serves upstream to a client and records every request. type seedPeer struct { t *testing.T upstream *repo.Repo requests []*xfer.Message } func (p *seedPeer) Exchange(ctx context.Context, req *xfer.Message) (*xfer.Message, error) { p.requests = append(p.requests, req) return HandleSync(ctx, p.upstream, req) } // asked reports whether any request p saw gimmes uuid. func (p *seedPeer) asked(uuid string) bool { for _, req := range p.requests { for _, c := range req.Cards { if g, ok := c.(*xfer.GimmeCard); ok && g.UUID == uuid { return true } } } return false } // Horizon F.2: a store never keeps a seed it did not receive through a // horizon-seed announcement. A pull receiving one by label, or by binding // card under another label, drops it: not stored, not counted, no phantom, // and a second sync does not ask for it again. func TestHorizonPullDropsSeed(t *testing.T) { for _, shape := range []string{"label", "card"} { src := newPCOrigin(t) var seed []byte if shape == "label" { _, seed = src.labelled(seedLogin, "a") } else { _, seed = src.bound("zit-seed") } // A peer that keeps seeds, as one predating F.2 does: stored raw, // since this store's own link step would refuse it. _, uuid, err := blob.Store(src.r.DB(), seed) if err != nil { t.Fatal(err) } client := setupSyncTestRepo(t) peer := &seedPeer{t: t, upstream: src.r} res, err := Sync(context.Background(), client, peer, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}) if err != nil || len(res.Errors) != 0 { t.Fatalf("%s: pull: %v %q", shape, err, res.Errors) } if !peer.asked(uuid) { t.Fatalf("%s: fixture: the client never asked for the seed", shape) } assertDropped(t, client, uuid) var held int if err := client.DB().QueryRow("SELECT count(*) FROM blob WHERE size>=0").Scan(&held); err != nil { t.Fatal(err) } if res.FilesRecvd != held { t.Fatalf("%s: %d files counted received, %d held", shape, res.FilesRecvd, held) } // A peer with no cursor for this client announces the seed again. other := setupPushableTestRepo(t) if _, _, err := blob.Store(other.DB(), seed); err != nil { t.Fatal(err) } peer = &seedPeer{t: t, upstream: other} if _, err := Sync(context.Background(), client, peer, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } if peer.asked(uuid) { t.Fatalf("%s: a second sync asked for the dropped seed again", shape) } } } // A seed that arrives as a delta whose basis comes in a later round is // dropped once the basis makes it readable. func TestHorizonPullDropsLateSeedDelta(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "b") _, seed := o.labelled(seedLogin, "a") baseUUID := hash.SHA3(base) s := deltaCard(t, base, baseUUID, seed) round := 0 tr := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message { round++ if round == 1 { return &xfer.Message{Cards: []xfer.Card{s}} } for _, c := range req.Cards { if g, ok := c.(*xfer.GimmeCard); ok && g.UUID == baseUUID { return &xfer.Message{Cards: []xfer.Card{&xfer.FileCard{UUID: baseUUID, Content: base}}} } } return &xfer.Message{} }} client := setupSyncTestRepo(t) if _, err := Sync(context.Background(), client, tr, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } if round < 2 { t.Fatal("fixture: the basis was never asked for") } assertRefused(t, client, s.UUID, true) if n := seedLinkRows(t, client, s.UUID); n != 0 { t.Fatalf("the seed was crosslinked: %d rows", n) } if phantom, err := isPhantomRow(client.DB(), baseUUID); err != nil || phantom { t.Fatalf("the basis was not stored (%v)", err) } // Kept but refused, it is not announced when this store pushes on. assertNeverNamed(t, pushOnFrom(t, client), s.UUID) } // A refused seed sent again unasked, as a delta whose basis the client // lacks, is dropped by its hash: not stored and not counted. func TestHorizonPullDropsResentSeed(t *testing.T) { o := newPCOrigin(t) _, seed := o.labelled(seedLogin, "a") _, base := o.labelled("u", "never sent") full := &xfer.FileCard{UUID: hash.SHA1(seed), Content: seed} resent := deltaCard(t, base, hash.SHA3(base), seed) client := setupSyncTestRepo(t) for i, card := range []*xfer.FileCard{full, resent} { sent := false tr := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message { if sent { return &xfer.Message{} } sent = true return &xfer.Message{Cards: []xfer.Card{card}} }} res, err := Sync(context.Background(), client, tr, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}) if err != nil { t.Fatal(err) } if res.FilesRecvd != 0 { t.Fatalf("send %d: %d files counted received", i, res.FilesRecvd) } assertDropped(t, client, full.UUID) } } // cloneScript answers a clone round by round from fixed replies; rounds past // the last get the final one. func cloneScript(replies ...[]xfer.Card) *MockTransport { round := 0 return &MockTransport{Handler: func(*xfer.Message) *xfer.Message { cards := replies[min(round, len(replies)-1)] round++ return &xfer.Message{Cards: cards} }} } // A clone drops a seed by label and by binding card, and a refused seed sent // again in another form, counting none of them. func TestHorizonCloneDropsSeed(t *testing.T) { o := newPCOrigin(t) _, labelled := o.labelled(seedLogin, "a") _, bound := o.bound("zit-seed") ordinary := []byte("an ordinary file the clone keeps\n") again := deltaCard(t, ordinary, hash.SHA1(ordinary), labelled) push := &xfer.PushCard{ServerCode: "s", ProjectCode: testPC} tr := cloneScript( []xfer.Card{push, &xfer.FileCard{UUID: hash.SHA1(labelled), Content: labelled}, &xfer.FileCard{UUID: hash.SHA1(bound), Content: bound}, again, &xfer.FileCard{UUID: hash.SHA1(ordinary), Content: ordinary}, &xfer.CloneSeqNoCard{SeqNo: 0}}, []xfer.Card{&xfer.CloneSeqNoCard{SeqNo: 0}}, ) clone, res, err := Clone(context.Background(), filepath.Join(t.TempDir(), "c.fossil"), tr, CloneOpts{}) if err != nil { t.Fatal(err) } defer clone.Close() if res.BlobsRecvd != 1 { t.Fatalf("%d blobs counted received, want the ordinary file alone", res.BlobsRecvd) } assertDropped(t, clone, hash.SHA1(labelled)) assertDropped(t, clone, hash.SHA1(bound)) } // A clone drops a seed sent as a delta whose basis comes in a later round. func TestHorizonCloneDropsLateSeedDelta(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "b") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA1(base), seed) push := &xfer.PushCard{ServerCode: "s", ProjectCode: testPC} tr := cloneScript( []xfer.Card{push, s, &xfer.CloneSeqNoCard{SeqNo: 1}}, []xfer.Card{&xfer.FileCard{UUID: hash.SHA1(base), Content: base}, &xfer.CloneSeqNoCard{SeqNo: 0}}, []xfer.Card{&xfer.CloneSeqNoCard{SeqNo: 0}}, ) clone, _, err := Clone(context.Background(), filepath.Join(t.TempDir(), "c.fossil"), tr, CloneOpts{}) if err != nil { t.Fatal(err) } defer clone.Close() assertRefused(t, clone, s.UUID, true) if n := seedLinkRows(t, clone, s.UUID); n != 0 { t.Fatalf("the seed was crosslinked: %d rows", n) } if _, ok := blob.Exists(clone.DB(), hash.SHA1(base)); !ok { t.Fatal("the basis was not kept") } } // A seed sent to a client as a delta on a basis it holds is readable on // arrival, and dropped then: never stored. func TestHorizonPullDropsSeedDeltaOnHeldBase(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "a basis the client holds") _, seed := o.labelled(seedLogin, "a") client := setupSyncTestRepo(t) if _, _, err := blob.Store(client.DB(), base); err != nil { t.Fatal(err) } s := deltaCard(t, base, hash.SHA3(base), seed) sent := false tr := &MockTransport{Handler: func(*xfer.Message) *xfer.Message { if sent { return &xfer.Message{} } sent = true return &xfer.Message{Cards: []xfer.Card{s}} }} res, err := Sync(context.Background(), client, tr, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}) if err != nil { t.Fatal(err) } if res.FilesRecvd != 0 { t.Fatalf("%d files counted received", res.FilesRecvd) } assertDropped(t, client, s.UUID) } // pushOnRecording pushes o's store to a fresh peer and returns every request // the peer saw, failing if the push does not converge. func pushOnRecording(t *testing.T, o *pcOrigin) []*xfer.Message { t.Helper() return pushOnFrom(t, o.r) } // pushOnFrom is pushOnRecording from any store. func pushOnFrom(t *testing.T, r *repo.Repo) []*xfer.Message { t.Helper() peer := &requestRecorder{upstream: setupPushableTestRepo(t)} if _, err := Sync(context.Background(), r, peer, SyncOpts{Push: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatalf("push: %v", err) } return peer.requests } // requestRecorder serves upstream and records every request. type requestRecorder struct { upstream *repo.Repo requests []*xfer.Message } func (p *requestRecorder) Exchange(ctx context.Context, req *xfer.Message) (*xfer.Message, error) { p.requests = append(p.requests, req) return HandleSync(ctx, p.upstream, req) } // assertNeverNamed checks that no request named uuid in any card. func assertNeverNamed(t *testing.T, requests []*xfer.Message, uuid string) { t.Helper() for _, req := range requests { for _, c := range req.Cards { switch c := c.(type) { case *xfer.IGotCard: if c.UUID == uuid { t.Fatalf("a push announced %.10s", uuid) } case *xfer.FileCard: if c.UUID == uuid { t.Fatalf("a push sent %.10s", uuid) } } } } } // A pending seed is not announced when the origin pushes on to a peer. func TestHorizonPendingSeedNotAnnouncedOnPush(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "never sent") s := danglingSeed(t, o, base) assertNeverNamed(t, pushOnRecording(t, o), s.UUID) } // After a rebuild, which marks every blob unclustered and unsent again, a // refused seed is still not announced: a push on to a peer converges and // the peer never learns its hash. func TestHorizonRefusedSeedNotAnnouncedAfterRebuild(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "b") s := danglingSeed(t, o, base) if errs := errorsOf(o.push(&xfer.FileCard{UUID: hash.SHA3(base), Content: base})); len(errs) != 0 { t.Fatalf("the base's push: errors %q", errs) } assertRefused(t, o.r, s.UUID, true) if _, err := verify.Rebuild(o.r); err != nil { t.Fatal(err) } assertNeverNamed(t, pushOnRecording(t, o), s.UUID) } // privateDanglingSeed pushes a seed to o privately, as a delta whose basis // o lacks, so it is pending and private. func privateDanglingSeed(t *testing.T, o *pcOrigin) *xfer.FileCard { t.Helper() if _, err := o.r.DB().Exec("UPDATE user SET cap=cap||'x' WHERE login='nobody'"); err != nil { t.Fatal(err) } _, base := o.labelled("u", "never sent") _, seed := o.labelled(seedLogin, "a") s := deltaCard(t, base, hash.SHA3(base), seed) resp, err := HandleSync(context.Background(), o.r, &xfer.Message{Cards: []xfer.Card{ &xfer.PragmaCard{Name: "send-private"}, &xfer.PushCard{ServerCode: "s", ProjectCode: testPC}, &xfer.PrivateCard{}, s}}) if err != nil { t.Fatal(err) } if errs := errorsOf(resp); len(errs) != 0 { t.Fatalf("the private dangling seed: errors %q", errs) } return s } // A private push on to a peer does not announce a privately pushed pending // seed. func TestHorizonPendingSeedNotAnnouncedPrivately(t *testing.T) { o := newPCOrigin(t) s := privateDanglingSeed(t, o) upstream := setupPushableTestRepo(t) if _, err := upstream.DB().Exec("UPDATE user SET cap=cap||'x' WHERE login='nobody'"); err != nil { t.Fatal(err) } peer := &requestRecorder{upstream: upstream} if _, err := Sync(context.Background(), o.r, peer, SyncOpts{Push: true, Private: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } assertNeverNamed(t, peer.requests, s.UUID) } // A peer asking for a privately pushed pending seed outside a private sync // is not told it exists. func TestHorizonPendingSeedNotNamedPrivate(t *testing.T) { o := newPCOrigin(t) s := privateDanglingSeed(t, o) peer := setupPushableTestRepo(t) asked := false var requests []*xfer.Message tr := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message { requests = append(requests, req) resp, err := HandleSync(context.Background(), peer, req) if err != nil { t.Errorf("HandleSync: %v", err) return &xfer.Message{} } if !asked { asked = true resp.Cards = append(resp.Cards, &xfer.GimmeCard{UUID: s.UUID}) } return resp }} if _, err := Sync(context.Background(), o.r, tr, SyncOpts{Push: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } assertNeverNamed(t, requests, s.UUID) } // Horizon F: a pushed artifact is judged when it first becomes readable, // whatever path supplies its basis. Here a pull supplies the basis as a // delta, first on a basis the origin lacks, then on a held one: the pushed // file and control artifact waiting on it are judged when the chain // resolves, then served, announced and, the control artifact, linked. func TestHorizonPulledDeltaFillsPendingBasis(t *testing.T) { for _, held := range []bool{false, true} { o := newPCOrigin(t) root := []byte("the root of the basis chain\n") basis := append(slices.Clone(root), []byte("the basis the pending artifacts need\n")...) fileBytes := append(slices.Clone(basis), []byte("a plain file pushed on the basis\n")...) ctlCard, ctl := o.labelled("u", "a control artifact pushed on the basis") file := deltaCard(t, basis, hash.SHA3(basis), fileBytes) control := deltaCard(t, basis, hash.SHA3(basis), ctl) if errs := errorsOf(o.push(file, control)); len(errs) != 0 { t.Fatalf("held=%v: pending pushes: errors %q", held, errs) } if held { if _, _, err := blob.Store(o.r.DB(), root); err != nil { t.Fatal(err) } } // The basis arrives by pull as a delta on root; root, unless held, // arrives in the next round. d, err := delta.Create(root, basis) if err != nil { t.Fatal(err) } basisCard := &xfer.FileCard{UUID: hash.SHA3(basis), DeltaSrc: hash.SHA3(root), Content: d} round := 0 tr := &MockTransport{Handler: func(req *xfer.Message) *xfer.Message { round++ if round == 1 { return &xfer.Message{Cards: []xfer.Card{basisCard}} } for _, c := range req.Cards { if g, ok := c.(*xfer.GimmeCard); ok && g.UUID == hash.SHA3(root) { return &xfer.Message{Cards: []xfer.Card{&xfer.FileCard{UUID: g.UUID, Content: root}}} } } return &xfer.Message{} }} if _, err := Sync(context.Background(), o.r, tr, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } for _, u := range []string{file.UUID, ctlCard.UUID} { rid, _ := blob.Exists(o.r.DB(), u) if !content.IsAvailable(o.r.DB(), rid) { t.Fatalf("held=%v: %.10s is not readable", held, u) } if withheld, err := seedcheck.IsWithheld(o.r.DB(), rid); err != nil || withheld { t.Fatalf("held=%v: %.10s is still withheld (%v)", held, u, err) } } if n := seedLinkRows(t, o.r, ctlCard.UUID); n == 0 { t.Fatalf("held=%v: the control artifact was not linked", held) } announced := map[string]bool{} for _, req := range pushOnRecording(t, o) { for _, c := range req.Cards { if g, ok := c.(*xfer.IGotCard); ok { announced[g.UUID] = true } } } if !announced[file.UUID] || !announced[ctlCard.UUID] { t.Fatalf("held=%v: announced file %v, control %v", held, announced[file.UUID], announced[ctlCard.UUID]) } } } // plantDeepChain stores, under root's phantom, a chain of n delta rows one // on the next, its top row pending on root: what a pusher can build by // re-rooting. Only the chain's length is read, so the rows carry no content // and, sized as phantoms, are no work for a crosslink sweep. func plantDeepChain(t *testing.T, r *repo.Repo, root libfossil.FslID, n int) libfossil.FslID { t.Helper() var top libfossil.FslID err := r.WithTx(func(tx *db.Tx) error { below := root for i := 0; i < n; i++ { res, err := tx.Exec("INSERT INTO blob(uuid, size, content, rcvid) VALUES(printf('%040x', ?), -1, NULL, 1)", i+1) if err != nil { return err } id, err := res.LastInsertId() if err != nil { return err } rid := libfossil.FslID(id) if _, err := tx.Exec("INSERT INTO delta(rid, srcid) VALUES(?, ?)", rid, below); err != nil { return err } below, top = rid, rid } if err := seedcheck.MarkPending(tx, top); err != nil { return err } _, err := tx.Exec("UPDATE seedpending SET root=? WHERE rid=?", root, top) return err }) if err != nil { t.Fatal(err) } return top } // A pending row whose chain is longer than any readable one cannot be // judged: it stays withheld, and the honest push that supplies its root as // a delta is stored as usual instead of failing on it. func TestHorizonOverDeepPendingChainDoesNotBlockFill(t *testing.T) { o := newPCOrigin(t) held := []byte("a basis the origin holds\n") if _, _, err := blob.Store(o.r.DB(), held); err != nil { t.Fatal(err) } basis := append(slices.Clone(held), []byte("the basis an honest pusher supplies\n")...) phantom, err := blob.StorePhantom(o.r.DB(), hash.SHA3(basis)) if err != nil { t.Fatal(err) } top := plantDeepChain(t, o.r, phantom, content.MaxDeltaChainDepth+1) honest := deltaCard(t, held, hash.SHA3(held), basis) honest.UUID = hash.SHA3(basis) if errs := errorsOf(o.push(honest)); len(errs) != 0 { t.Fatalf("the honest push failed: %q", errs) } if rid, ok := blob.Exists(o.r.DB(), honest.UUID); !ok || !content.IsAvailable(o.r.DB(), rid) { t.Fatal("the honest basis was not stored") } if withheld, err := seedcheck.IsWithheld(o.r.DB(), top); err != nil || !withheld { t.Fatalf("the over-deep row is not withheld (%v)", err) } } // assertNotOffered checks that a peer pulling from r is neither announced // uuid nor sent it when it asks. func assertNotOffered(t *testing.T, r *repo.Repo, uuid string) { t.Helper() resp, err := HandleSync(context.Background(), r, &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "s", ProjectCode: testPC}, &xfer.GimmeCard{UUID: uuid}}}) if err != nil { t.Fatal(err) } for _, c := range resp.Cards { switch c := c.(type) { case *xfer.IGotCard: if c.UUID == uuid { t.Fatalf("%.10s was announced", uuid) } case *xfer.FileCard: if c.UUID == uuid { t.Fatalf("%.10s was sent", uuid) } case *xfer.CFileCard: if c.UUID == uuid { t.Fatalf("%.10s was sent", uuid) } } } assertNeverNamed(t, pushOnFrom(t, r), uuid) } // §5.2.3: a store that keeps a received delta whose base it lacks does not // announce or serve it while it cannot be expanded. A pull keeps one here. func TestPulledDanglingDeltaWithheld(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "a base the peer never sends") _, target := o.labelled("u", "an ordinary artifact sent as a delta on it") d := deltaCard(t, base, hash.SHA3(base), target) sent := false tr := &MockTransport{Handler: func(*xfer.Message) *xfer.Message { if sent { return &xfer.Message{} } sent = true return &xfer.Message{Cards: []xfer.Card{d}} }} client := setupSyncTestRepo(t) if _, err := Sync(context.Background(), client, tr, SyncOpts{Pull: true, ServerCode: "s", ProjectCode: testPC}); err != nil { t.Fatal(err) } if _, ok := blob.Exists(client.DB(), d.UUID); !ok { t.Fatal("fixture: the dangling delta was not kept") } assertNotOffered(t, client, d.UUID) } // The same for a clone left holding a delta whose base its source never // sent. func TestClonedDanglingDeltaWithheld(t *testing.T) { o := newPCOrigin(t) _, base := o.labelled("u", "a base the source never sends") _, target := o.labelled("u", "an ordinary artifact sent as a delta on it") d := deltaCard(t, base, hash.SHA3(base), target) // And an ordinary artifact sent as a delta on a base sent too: readable, // so it is offered as any other. readableBase := []byte("a base the source does send\n") _, readable := o.labelled("u", "an ordinary artifact the clone can read") r := deltaCard(t, readableBase, hash.SHA1(readableBase), readable) push := &xfer.PushCard{ServerCode: "s", ProjectCode: testPC} tr := cloneScript( []xfer.Card{push, d, r, &xfer.FileCard{UUID: hash.SHA1(readableBase), Content: readableBase}, &xfer.CloneSeqNoCard{SeqNo: 0}}, []xfer.Card{&xfer.CloneSeqNoCard{SeqNo: 0}}, ) clone, _, err := Clone(context.Background(), filepath.Join(t.TempDir(), "c.fossil"), tr, CloneOpts{}) if err != nil { t.Fatal(err) } defer clone.Close() if _, ok := blob.Exists(clone.DB(), d.UUID); !ok { t.Fatal("fixture: the dangling delta was not kept") } assertNotOffered(t, clone, d.UUID) resp, err := HandleSync(context.Background(), clone, &xfer.Message{Cards: []xfer.Card{ &xfer.PullCard{ServerCode: "s", ProjectCode: testPC}}}) if err != nil { t.Fatal(err) } offered := false for _, c := range resp.Cards { if g, ok := c.(*xfer.IGotCard); ok && g.UUID == r.UUID { offered = true } } if !offered { t.Fatal("the readable delta is not offered") } }