···99 "github.com/bluesky-social/indigo/atproto/syntax"
1010 "github.com/cosmos/iavl"
1111 "github.com/did-method-plc/go-didplc"
1212- "github.com/ipfs/go-cid"
1312 "github.com/palantir/stacktrace"
1413 "github.com/samber/lo"
1414+ "github.com/samber/mo"
1515 "tangled.org/gbl08ma/didplcbft/store"
1616)
17171818type TreeProvider interface {
1919 MutableTree() (*iavl.MutableTree, error)
2020- ImmutableTree(version TreeVersion) (store.PossiblyMutableTree, error)
2020+ ImmutableTree(version TreeVersion) (store.ReadOnlyTree, error)
2121}
22222323type plcImpl struct {
···4343 plc.mu.Lock()
4444 defer plc.mu.Unlock()
45454646- timestamp := syntax.Datetime(at.Format(syntax.AtprotoDatetimeLayout))
4646+ timestamp := syntax.Datetime(at.Format(store.ActualAtprotoDatetimeLayout))
47474848 // TODO set true to false only while importing old ops
4949 _, err := plc.validator.Validate(atHeight, timestamp, did, opBytes, true)
···5454 return nil
5555}
56565757-func (plc *plcImpl) ExecuteOperation(ctx context.Context, t time.Time, did string, opBytes []byte) (cid.Cid, error) {
5757+func (plc *plcImpl) ExecuteOperation(ctx context.Context, t time.Time, did string, opBytes []byte) error {
5858 plc.mu.Lock()
5959 defer plc.mu.Unlock()
60606161- timestamp := syntax.Datetime(t.Format(syntax.AtprotoDatetimeLayout))
6161+ timestamp := syntax.Datetime(t.Format(store.ActualAtprotoDatetimeLayout))
62626363 // TODO set true to false only while importing old ops
6464 effects, err := plc.validator.Validate(WorkingTreeVersion, timestamp, did, opBytes, true)
6565 if err != nil {
6666- return cid.Undef, stacktrace.Propagate(err, "operation failed validation")
6666+ return stacktrace.Propagate(err, "operation failed validation")
6767 }
68686969 tree, err := plc.treeProvider.MutableTree()
7070 if err != nil {
7171- return cid.Undef, stacktrace.Propagate(err, "failed to obtain mutable tree")
7171+ return stacktrace.Propagate(err, "failed to obtain mutable tree")
7272+ }
7373+7474+ err = store.Tree.StoreOperation(tree, effects.NewLogEntry, effects.NullifiedEntriesStartingIndex)
7575+ if err != nil {
7676+ return stacktrace.Propagate(err, "failed to commit operation")
7777+ }
7878+7979+ return nil
8080+}
8181+8282+func (plc *plcImpl) ImportOperationFromAuthoritativeSource(ctx context.Context, newEntry didplc.LogEntry,
8383+ authoritativeAuditLogFetcher func() ([]didplc.LogEntry, error)) error {
8484+ plc.mu.Lock()
8585+ defer plc.mu.Unlock()
8686+8787+ tree, err := plc.treeProvider.MutableTree()
8888+ if err != nil {
8989+ return stacktrace.Propagate(err, "failed to obtain mutable tree")
9090+ }
9191+9292+ l, _, err := store.Tree.AuditLog(tree, newEntry.DID, false)
9393+ if err != nil {
9494+ return stacktrace.Propagate(err, "")
9595+ }
9696+9797+ newCID := newEntry.CID
9898+ newPrev := newEntry.Operation.AsOperation().PrevCIDStr()
9999+100100+ // TODO avoid redundant CreatedAt formating and parsing by using a specialized LogEntry type internally (i.e. between us and the store)
101101+ newCreatedAtDT, err := syntax.ParseDatetime(newEntry.CreatedAt)
102102+ if err != nil {
103103+ return stacktrace.Propagate(err, "")
72104 }
105105+ newCreatedAt := newCreatedAtDT.Time()
731067474- err = store.Tree.StoreOperation(tree, effects.NewLogEntry, effects.NewOperationIndex, effects.NullifiedEntriesStartingIndex)
107107+ mustFullyReplaceHistory := false
108108+ for _, entry := range l {
109109+ existingCreatedAt, err := syntax.ParseDatetime(entry.CreatedAt)
110110+ if err != nil {
111111+ return stacktrace.Propagate(err, "")
112112+ }
113113+ if existingCreatedAt.Time().After(newCreatedAt) {
114114+ // We're trying to import an operation whose timestamp precedes one of the timestamps for operations we already know about
115115+ // We'll need to discard all known history and import it anew using the authoritative source data (same as when dealing with sequence forks)
116116+ mustFullyReplaceHistory = true
117117+ break
118118+ }
119119+120120+ if entry.CID == newCID {
121121+ // If an operation with the same CID already exists -> easy-ish
122122+123123+ // this operation is already present, there is nothing to do
124124+ // 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
125125+ // (Though, the actually damaging cases of incorrect createdAt are already handled by the prior check)
126126+ return nil
127127+ }
128128+ }
129129+130130+ if len(l) == 0 || (!mustFullyReplaceHistory && l[len(l)-1].CID == newPrev) {
131131+ // If DID doesn't exist at all -> easy
132132+ // If prev matches CID of latest operation, and resulting timestamp sequence monotonically increases -> easy
133133+ err = store.Tree.StoreOperation(tree, newEntry, mo.None[int]())
134134+ return stacktrace.Propagate(err, "failed to commit operation")
135135+ }
136136+137137+ // if we get here then we're dealing with a DID that has "complicated" history
138138+ // 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"
139139+ // and which are caused by people purposefully submitting forking ops to the chain vs the authoritative source)
140140+ // fetch audit log for DID and replace the entire history with the one from the authoritative source
141141+142142+ auditLog, err := authoritativeAuditLogFetcher()
75143 if err != nil {
7676- return cid.Undef, stacktrace.Propagate(err, "failed to commit operation")
144144+ return stacktrace.Propagate(err, "")
77145 }
781467979- return effects.NewOperationCID, nil
147147+ err = store.Tree.ReplaceHistory(tree, auditLog)
148148+ return stacktrace.Propagate(err, "")
80149}
8115082151func (plc *plcImpl) Resolve(ctx context.Context, atHeight TreeVersion, did string) (didplc.Doc, error) {
-5
plc/operation_validator.go
···99 "github.com/bluesky-social/indigo/atproto/atcrypto"
1010 "github.com/bluesky-social/indigo/atproto/syntax"
1111 "github.com/did-method-plc/go-didplc"
1212- "github.com/ipfs/go-cid"
1312 "github.com/palantir/stacktrace"
1413 "github.com/samber/mo"
1514)
···3231type OperationEffects struct {
3332 NullifiedEntriesStartingIndex mo.Option[int]
3433 NewLogEntry didplc.LogEntry
3535- 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
3636- NewOperationIndex int
3734}
38353936// Validate returns the new complete AuditLog that the DID history would assume if validation passes, and an error if it doesn't pass
···198195 return OperationEffects{
199196 NullifiedEntriesStartingIndex: nullifiedEntriesStartingIndex,
200197 NewLogEntry: newEntry,
201201- NewOperationCID: newOperationCID,
202202- NewOperationIndex: mostRecentOpIndex + 1,
203198 }, nil
204199}
205200