carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package moves
2
3import (
4 "bytes"
5 "encoding/json"
6 "errors"
7 "fmt"
8 "io"
9 "time"
10
11 "github.com/carlosframework/platform/internal/provision"
12)
13
14// The return lane (moves spec §7.8 returning, Plan 4 Task 4): a dirty
15// rollback's target sends its stopped state back to the source through a
16// staging lane named by the rollback id, moves/<host>/<rb id>/, in the
17// SOURCE's bucket (M17: the target can put into the source's moves/*). It is
18// a staging lane like the cut's (round-2 finding 6): every object goes in
19// through the guarded staging writer under the staging-work lease for
20// (source region, host), and the lease's bound is recorded in the seal, so
21// the lane's park (Plan 5) acknowledges only after the last request a paused
22// writer signed has expired.
23//
24// The return seal is journalled in the claim under JournalReturnSeal, the
25// baseline the return restores to under JournalReturnBaseline. The source's
26// install (Task 3's installSource) reads the first as its Seal and the
27// second as its Baseline.
28
29// The claim journal keys of a dirty return, written in one claim write
30// before the record says returned.
31const (
32 JournalReturnSeal = "return_seal"
33 JournalReturnBaseline = "return_baseline"
34)
35
36// ReturnLane is the return lane's prefix: a staging prefix of the rollback
37// id, in the source's bucket.
38func ReturnLane(host, rbID string) string { return StagingPrefix(host, rbID) }
39
40// ReturnSeal is the seal of a return lane. replicas are the lane's replica
41// objects: one snapshot [1, n] of the stopped database for a dirty return,
42// or every level file of the staged seal copied as given (J22). The lane is
43// built from the target's stopped state and covers it exactly, so FromDB
44// and SrcCovered are both true. leaseUntil is the staging lease's bound
45// when the last lane object was written. state is the lane's build state,
46// nil when the target had none. The caller's slices are copied.
47func ReturnSeal(n uint64, replicas []SealedObject, files, timers, state []SealedObject, now time.Time, leaseUntil time.Time) Seal {
48 return Seal{V: SealVersion, N: n, Replica: sortObjects(replicas), Files: sortObjects(files), Timers: sortObjects(timers), State: sortObjects(state),
49 SrcCovered: true, FromDB: true, SealedAt: now.UTC(), LeaseUntil: leaseUntil.UTC()}
50}
51
52// errBadBaseline is a return baseline that does not name what a return
53// restores to.
54var errBadBaseline = errors.New("moves: the return baseline is not usable")
55
56// validBaseline requires what the source's install compares against: the
57// final TXID, both files digests, the timers digest and the evidence it was
58// taken under.
59func validBaseline(b provision.MoveSource) error {
60 if b.FinalTXID == 0 || b.Files == nil || b.Files.Plain == "" || b.Files.Sealed == "" || b.TimersSHA256 == "" || b.Evidence == "" {
61 return fmt.Errorf("%w: %+v", errBadBaseline, b)
62 }
63 return nil
64}
65
66// EncodeReturnBaseline is the return's baseline as the claim journal
67// carries it (Task 3's installSource.Baseline: the final TXID N', the files
68// and timers digests, the carried replication and next_due, and the target's
69// stop evidence).
70func EncodeReturnBaseline(b provision.MoveSource) (string, error) {
71 if err := validBaseline(b); err != nil {
72 return "", err
73 }
74 raw, err := json.Marshal(b)
75 if err != nil {
76 return "", err
77 }
78 return string(raw), nil
79}
80
81// DecodeReturnBaseline reads a journalled baseline strictly (no unknown
82// field, no trailing data) and validates it.
83func DecodeReturnBaseline(raw string) (provision.MoveSource, error) {
84 dec := json.NewDecoder(bytes.NewReader([]byte(raw)))
85 dec.DisallowUnknownFields()
86 var b provision.MoveSource
87 if err := dec.Decode(&b); err != nil {
88 return provision.MoveSource{}, fmt.Errorf("%w: %v", errBadBaseline, err)
89 }
90 if _, err := dec.Token(); err != io.EOF {
91 return provision.MoveSource{}, fmt.Errorf("%w: trailing data", errBadBaseline)
92 }
93 if err := validBaseline(b); err != nil {
94 return provision.MoveSource{}, err
95 }
96 return b, nil
97}
98