···80 if err != nil {
81 return nil, stacktrace.Propagate(err, "")
82 }
83- for _, c := range result.TreeChanges {
84- _, err := d.tree.Set(c.Key, c.Value)
85- if err != nil {
86- return nil, stacktrace.Propagate(err, "")
87- }
88- }
89 // when preparing a proposal, invalid transactions should have been discarded
90 // so, if something doesn't succeed now, something has gone wrong and we should not vote in agreement of the proposal
91 if result.Code != 0 {
···141 result, err := processTx(ctx, d.plc, tx, req.Time, true)
142 if err != nil {
143 return nil, stacktrace.Propagate(err, "")
144- }
145- for _, c := range result.TreeChanges {
146- _, err := d.tree.Set(c.Key, c.Value)
147- if err != nil {
148- return nil, stacktrace.Propagate(err, "")
149- }
150 }
151 txResults[i] = &abcitypes.ExecTxResult{
152 Code: result.Code,
···80 if err != nil {
81 return nil, stacktrace.Propagate(err, "")
82 }
00000083 // when preparing a proposal, invalid transactions should have been discarded
84 // so, if something doesn't succeed now, something has gone wrong and we should not vote in agreement of the proposal
85 if result.Code != 0 {
···135 result, err := processTx(ctx, d.plc, tx, req.Time, true)
136 if err != nil {
137 return nil, stacktrace.Propagate(err, "")
000000138 }
139 txResults[i] = &abcitypes.ExecTxResult{
140 Code: result.Code,
···9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "github.com/cosmos/iavl"
11 "github.com/did-method-plc/go-didplc"
12- "github.com/ipfs/go-cid"
13 "github.com/palantir/stacktrace"
14 "github.com/samber/lo"
015 "tangled.org/gbl08ma/didplcbft/store"
16)
1718type TreeProvider interface {
19 MutableTree() (*iavl.MutableTree, error)
20- ImmutableTree(version TreeVersion) (store.PossiblyMutableTree, error)
21}
2223type plcImpl struct {
···43 plc.mu.Lock()
44 defer plc.mu.Unlock()
4546- timestamp := syntax.Datetime(at.Format(syntax.AtprotoDatetimeLayout))
4748 // TODO set true to false only while importing old ops
49 _, err := plc.validator.Validate(atHeight, timestamp, did, opBytes, true)
···54 return nil
55}
5657-func (plc *plcImpl) ExecuteOperation(ctx context.Context, t time.Time, did string, opBytes []byte) (cid.Cid, error) {
58 plc.mu.Lock()
59 defer plc.mu.Unlock()
6061- timestamp := syntax.Datetime(t.Format(syntax.AtprotoDatetimeLayout))
6263 // TODO set true to false only while importing old ops
64 effects, err := plc.validator.Validate(WorkingTreeVersion, timestamp, did, opBytes, true)
65 if err != nil {
66- return cid.Undef, stacktrace.Propagate(err, "operation failed validation")
67 }
6869 tree, err := plc.treeProvider.MutableTree()
70 if err != nil {
71- return cid.Undef, stacktrace.Propagate(err, "failed to obtain mutable tree")
0000000000000000000000000000000072 }
07374- err = store.Tree.StoreOperation(tree, effects.NewLogEntry, effects.NewOperationIndex, effects.NullifiedEntriesStartingIndex)
0000000000000000000000000000000000075 if err != nil {
76- return cid.Undef, stacktrace.Propagate(err, "failed to commit operation")
77 }
7879- return effects.NewOperationCID, nil
080}
8182func (plc *plcImpl) Resolve(ctx context.Context, atHeight TreeVersion, did string) (didplc.Doc, error) {
···9 "github.com/bluesky-social/indigo/atproto/syntax"
10 "github.com/cosmos/iavl"
11 "github.com/did-method-plc/go-didplc"
012 "github.com/palantir/stacktrace"
13 "github.com/samber/lo"
14+ "github.com/samber/mo"
15 "tangled.org/gbl08ma/didplcbft/store"
16)
1718type TreeProvider interface {
19 MutableTree() (*iavl.MutableTree, error)
20+ ImmutableTree(version TreeVersion) (store.ReadOnlyTree, error)
21}
2223type plcImpl struct {
···43 plc.mu.Lock()
44 defer plc.mu.Unlock()
4546+ timestamp := syntax.Datetime(at.Format(store.ActualAtprotoDatetimeLayout))
4748 // TODO set true to false only while importing old ops
49 _, err := plc.validator.Validate(atHeight, timestamp, did, opBytes, true)
···54 return nil
55}
5657+func (plc *plcImpl) ExecuteOperation(ctx context.Context, t time.Time, did string, opBytes []byte) error {
58 plc.mu.Lock()
59 defer plc.mu.Unlock()
6061+ timestamp := syntax.Datetime(t.Format(store.ActualAtprotoDatetimeLayout))
6263 // TODO set true to false only while importing old ops
64 effects, err := plc.validator.Validate(WorkingTreeVersion, timestamp, did, opBytes, true)
65 if err != nil {
66+ return stacktrace.Propagate(err, "operation failed validation")
67 }
6869 tree, err := plc.treeProvider.MutableTree()
70 if err != nil {
71+ return stacktrace.Propagate(err, "failed to obtain mutable tree")
72+ }
73+74+ err = store.Tree.StoreOperation(tree, effects.NewLogEntry, effects.NullifiedEntriesStartingIndex)
75+ if err != nil {
76+ return stacktrace.Propagate(err, "failed to commit operation")
77+ }
78+79+ return nil
80+}
81+82+func (plc *plcImpl) ImportOperationFromAuthoritativeSource(ctx context.Context, newEntry didplc.LogEntry,
83+ authoritativeAuditLogFetcher func() ([]didplc.LogEntry, error)) error {
84+ plc.mu.Lock()
85+ defer plc.mu.Unlock()
86+87+ tree, err := plc.treeProvider.MutableTree()
88+ if err != nil {
89+ return stacktrace.Propagate(err, "failed to obtain mutable tree")
90+ }
91+92+ l, _, err := store.Tree.AuditLog(tree, newEntry.DID, false)
93+ if err != nil {
94+ return stacktrace.Propagate(err, "")
95+ }
96+97+ newCID := newEntry.CID
98+ newPrev := newEntry.Operation.AsOperation().PrevCIDStr()
99+100+ // TODO avoid redundant CreatedAt formating and parsing by using a specialized LogEntry type internally (i.e. between us and the store)
101+ newCreatedAtDT, err := syntax.ParseDatetime(newEntry.CreatedAt)
102+ if err != nil {
103+ return stacktrace.Propagate(err, "")
104 }
105+ newCreatedAt := newCreatedAtDT.Time()
106107+ mustFullyReplaceHistory := false
108+ for _, entry := range l {
109+ existingCreatedAt, err := syntax.ParseDatetime(entry.CreatedAt)
110+ if err != nil {
111+ return stacktrace.Propagate(err, "")
112+ }
113+ if existingCreatedAt.Time().After(newCreatedAt) {
114+ // We're trying to import an operation whose timestamp precedes one of the timestamps for operations we already know about
115+ // We'll need to discard all known history and import it anew using the authoritative source data (same as when dealing with sequence forks)
116+ mustFullyReplaceHistory = true
117+ break
118+ }
119+120+ if entry.CID == newCID {
121+ // If an operation with the same CID already exists -> easy-ish
122+123+ // this operation is already present, there is nothing to do
124+ // TODO re-evaluate whether we want to still update the timestamp on the existing operation, as not doing this will cause the export from our impl to definitely not match the authoritative source
125+ // (Though, the actually damaging cases of incorrect createdAt are already handled by the prior check)
126+ return nil
127+ }
128+ }
129+130+ if len(l) == 0 || (!mustFullyReplaceHistory && l[len(l)-1].CID == newPrev) {
131+ // If DID doesn't exist at all -> easy
132+ // If prev matches CID of latest operation, and resulting timestamp sequence monotonically increases -> easy
133+ err = store.Tree.StoreOperation(tree, newEntry, mo.None[int]())
134+ return stacktrace.Propagate(err, "failed to commit operation")
135+ }
136+137+ // if we get here then we're dealing with a DID that has "complicated" history
138+ // to avoid dealing with nullification (which is made complicated here since we don't know which nullified ops are part of the "canonical audit log"
139+ // and which are caused by people purposefully submitting forking ops to the chain vs the authoritative source)
140+ // fetch audit log for DID and replace the entire history with the one from the authoritative source
141+142+ auditLog, err := authoritativeAuditLogFetcher()
143 if err != nil {
144+ return stacktrace.Propagate(err, "")
145 }
146147+ err = store.Tree.ReplaceHistory(tree, auditLog)
148+ return stacktrace.Propagate(err, "")
149}
150151func (plc *plcImpl) Resolve(ctx context.Context, atHeight TreeVersion, did string) (didplc.Doc, error) {
-5
plc/operation_validator.go
···9 "github.com/bluesky-social/indigo/atproto/atcrypto"
10 "github.com/bluesky-social/indigo/atproto/syntax"
11 "github.com/did-method-plc/go-didplc"
12- "github.com/ipfs/go-cid"
13 "github.com/palantir/stacktrace"
14 "github.com/samber/mo"
15)
···32type OperationEffects struct {
33 NullifiedEntriesStartingIndex mo.Option[int]
34 NewLogEntry didplc.LogEntry
35- NewOperationCID cid.Cid // should be equivalent to the CID field inside NewLogEntry, but that's a string and we need the strongly typed Cid sometimes
36- NewOperationIndex int
37}
3839// Validate returns the new complete AuditLog that the DID history would assume if validation passes, and an error if it doesn't pass
···198 return OperationEffects{
199 NullifiedEntriesStartingIndex: nullifiedEntriesStartingIndex,
200 NewLogEntry: newEntry,
201- NewOperationCID: newOperationCID,
202- NewOperationIndex: mostRecentOpIndex + 1,
203 }, nil
204}
205
···9 "github.com/bluesky-social/indigo/atproto/atcrypto"
10 "github.com/bluesky-social/indigo/atproto/syntax"
11 "github.com/did-method-plc/go-didplc"
012 "github.com/palantir/stacktrace"
13 "github.com/samber/mo"
14)
···31type OperationEffects struct {
32 NullifiedEntriesStartingIndex mo.Option[int]
33 NewLogEntry didplc.LogEntry
0034}
3536// Validate returns the new complete AuditLog that the DID history would assume if validation passes, and an error if it doesn't pass
···195 return OperationEffects{
196 NullifiedEntriesStartingIndex: nullifiedEntriesStartingIndex,
197 NewLogEntry: newEntry,
00198 }, nil
199}
200