carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package hostbox
2
3import (
4 "context"
5 "crypto/ed25519"
6 "fmt"
7
8 "github.com/carlosframework/platform/internal/genselect"
9 "github.com/carlosframework/platform/internal/hostclaim"
10)
11
12// LossAdmissionSources validates every frozen source against its remote baseline.
13// The writer verifies a signed declaration or the claim's consumed automatic fence.
14type LossAdmissionSources func(context.Context, hostclaim.Observed, hostclaim.LossRecord, hostclaim.Admission, *genselect.Plan) ([]hostclaim.ValidatedWriteSource, error)
15
16type LossAdmission struct {
17 PublicKey ed25519.PublicKey
18 Owner hostclaim.LossRecoveryOwner
19 Sources LossAdmissionSources
20}
21
22func (w Writer) admitLoss(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission, baseline *genselect.Plan, id string) (hostclaim.Observed, error) {
23 if w.Loss == nil || w.Loss.Owner == nil || w.Loss.Sources == nil || len(w.Loss.PublicKey) != ed25519.PublicKeySize {
24 return hostclaim.Observed{}, hostclaim.ErrLossOwnerRequired
25 }
26 if o.Claim.Op.Kind != hostclaim.KindRestoring || o.Claim.Op.Phase != hostclaim.PhaseProbing || in.Coverage != "" || in.KeepStamp || (in.LossID != "" && in.LossID != id) {
27 return hostclaim.Observed{}, hostclaim.ErrLossRecord
28 }
29 // Select validates the restore point; loss sources describe independent baselines.
30 if err := w.restorePoint(ctx, o, in, baseline); err != nil {
31 return hostclaim.Observed{}, err
32 }
33 loss, err := hostclaim.ReadLossRecord(ctx, w.Claims, w.Loss.PublicKey, id)
34 if err != nil {
35 return hostclaim.Observed{}, err
36 }
37 r := loss.Record
38 if r.State != "committed" || r.Host != o.Host || r.OpID != o.Claim.Op.ID || r.HolderIncarnation != o.Claim.Holder.Incarnation {
39 return hostclaim.Observed{}, hostclaim.ErrLossRecord
40 }
41 sources, err := w.Loss.Sources(ctx, o, r, in, baseline)
42 if err != nil {
43 return hostclaim.Observed{}, err
44 }
45 p := hostclaim.AdmissionRecord{Box: o.Claim.Holder.WriterBox, Session: o.Claim.Holder.Session, Generation: o.Claim.Holder.Generation}
46 if a := o.Claim.Holder.Admissions; len(a) > 0 {
47 p = a[len(a)-1]
48 }
49 in.AdditionalWritesLost = nil
50 for _, s := range sources {
51 if s.Box == p.Box && s.Session == p.Session && s.Generation == p.Generation {
52 in.RemoteMaxTXID = s.RemoteMaxTXID
53 } else {
54 in.AdditionalWritesLost = append(in.AdditionalWritesLost, hostclaim.WritesLost{Box: s.Box, Session: s.Session, Generation: s.Generation, RemoteMaxTXID: s.RemoteMaxTXID})
55 }
56 }
57 if err := w.AdmissionEvidence(ctx, o, in, baseline); err != nil {
58 return hostclaim.Observed{}, err
59 }
60 return hostclaim.AdmitWriterWithLoss(ctx, w.Claims, w.Loss.PublicKey, o, in, id, sources, w.Loss.Owner)
61}
62
63func (w Writer) admitFencedLoss(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission, baseline *genselect.Plan, record hostclaim.LossRecord) (hostclaim.Observed, error) {
64 if w.Loss == nil || w.Loss.Sources == nil {
65 return hostclaim.Observed{}, hostclaim.ErrLossOwnerRequired
66 }
67 if err := w.restorePoint(ctx, o, in, baseline); err != nil {
68 return hostclaim.Observed{}, err
69 }
70 sources, err := w.Loss.Sources(ctx, o, record, in, baseline)
71 if err != nil {
72 return hostclaim.Observed{}, err
73 }
74 if len(sources) != 1 {
75 return hostclaim.Observed{}, hostclaim.ErrFence
76 }
77 in.RemoteMaxTXID = sources[0].RemoteMaxTXID
78 if err = w.AdmissionEvidence(ctx, o, in, baseline); err != nil {
79 return hostclaim.Observed{}, err
80 }
81 return hostclaim.AdmitWriterWithFencedTargetLoss(ctx, w.Claims, o, in, sources)
82}
83
84// restorePoint is a loss admission's restore point: Select's plan, whose
85// maximum must be the admission's RemoteMaxTXID and, read from the plan's
86// own selected files by genselect.GenerationMax (the rule moves' loss
87// baseline validator reads, platform-1e on #236), the maximum of the plan's
88// own generation. It reads no bucket. The predecessor's loss boundary, which
89// replaces RemoteMaxTXID after the loss sources, is a separate value.
90func (w Writer) restorePoint(ctx context.Context, o hostclaim.Observed, in hostclaim.Admission, baseline *genselect.Plan) error {
91 if baseline == nil || baseline.MaxTXID != in.RemoteMaxTXID {
92 return ErrChanged
93 }
94 max, err := genselect.GenerationMax(baseline, o.Claim.Holder.Location, baseline.Generation)
95 if err != nil {
96 return fmt.Errorf("%w: the restore plan: %w", ErrChanged, err)
97 }
98 if max != in.RemoteMaxTXID {
99 return fmt.Errorf("%w: the restore plan's files reach %d, not its maximum", ErrChanged, max)
100 }
101 return nil
102}
103