diff options
| author | Fuwn <[email protected]> | 2026-02-26 14:46:02 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-26 14:48:52 -0800 |
| commit | 0099d621e97b6048971fadb5c71918cc9f2b5a09 (patch) | |
| tree | a38ba31585200bacd61f453ef7158de7f0aaf7a3 /internal/state | |
| parent | Initial commit (diff) | |
| download | plutia-test-0099d621e97b6048971fadb5c71918cc9f2b5a09.tar.xz plutia-test-0099d621e97b6048971fadb5c71918cc9f2b5a09.zip | |
feat: initial Plutia release — verifiable high-performance PLC mirror (mirror + resolver modes)
Diffstat (limited to 'internal/state')
| -rw-r--r-- | internal/state/engine.go | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/internal/state/engine.go b/internal/state/engine.go new file mode 100644 index 0000000..4ecdaca --- /dev/null +++ b/internal/state/engine.go @@ -0,0 +1,172 @@ +package state + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/Fuwn/plutia/internal/config" + "github.com/Fuwn/plutia/internal/storage" + "github.com/Fuwn/plutia/internal/types" +) + +type Engine struct { + store storage.Store + mode string +} + +func New(store storage.Store, mode string) *Engine { + return &Engine{store: store, mode: mode} +} + +func (e *Engine) Apply(op types.ParsedOperation, blockRef *types.BlockRefV1) error { + existing, ok, err := e.store.GetState(op.DID) + if err != nil { + return fmt.Errorf("load state: %w", err) + } + var prior *types.StateV1 + if ok { + prior = &existing + } + next, err := ComputeNextState(op, prior) + if err != nil { + return err + } + if pebbleStore, ok := e.store.(*storage.PebbleStore); ok { + includeOpRef := e.mode == config.ModeMirror && blockRef != nil + if err := pebbleStore.ApplyOperationBatch(next, blockRef, includeOpRef); err != nil { + return fmt.Errorf("apply operation batch: %w", err) + } + return nil + } + if err := e.store.PutState(next); err != nil { + return fmt.Errorf("put state: %w", err) + } + if err := e.store.SetChainHead(op.DID, op.Sequence); err != nil { + return fmt.Errorf("set chain head: %w", err) + } + if err := e.store.AddDIDSequence(op.DID, op.Sequence); err != nil { + return fmt.Errorf("append did sequence: %w", err) + } + if e.mode == config.ModeMirror && blockRef != nil { + if err := e.store.PutOpSeqRef(op.Sequence, *blockRef); err != nil { + return fmt.Errorf("put opseq ref: %w", err) + } + } + if err := e.store.SetGlobalSeq(op.Sequence); err != nil { + return fmt.Errorf("set global seq: %w", err) + } + return nil +} + +func ComputeNextState(op types.ParsedOperation, existing *types.StateV1) (types.StateV1, error) { + if existing != nil && op.Sequence <= existing.LatestOpSeq { + if existing.ChainTipHash == op.CID { + return *existing, nil + } + return types.StateV1{}, fmt.Errorf("non-monotonic sequence for %s: got %d <= %d", op.DID, op.Sequence, existing.LatestOpSeq) + } + doc, err := materializeDIDDocument(op.DID, op.Payload) + if err != nil { + return types.StateV1{}, err + } + next := types.StateV1{ + Version: 1, + DID: op.DID, + DIDDocument: doc, + ChainTipHash: op.CID, + LatestOpSeq: op.Sequence, + UpdatedAt: time.Now().UTC(), + } + next.RotationKeys = extractRotationKeys(op.Payload) + if len(next.RotationKeys) == 0 && existing != nil { + next.RotationKeys = append([]string(nil), existing.RotationKeys...) + } + return next, nil +} + +func materializeDIDDocument(did string, payload map[string]any) ([]byte, error) { + for _, k := range []string{"did_document", "didDoc", "document", "didDocument"} { + if v, ok := payload[k]; ok { + b, err := json.Marshal(v) + if err != nil { + return nil, fmt.Errorf("marshal did document field %s: %w", k, err) + } + return types.CanonicalizeJSON(b) + } + } + if v, ok := payload["alsoKnownAs"]; ok { + doc := map[string]any{ + "id": did, + "alsoKnownAs": v, + } + b, err := json.Marshal(doc) + if err != nil { + return nil, fmt.Errorf("marshal derived did document: %w", err) + } + return types.CanonicalizeJSON(b) + } + + doc := map[string]any{"id": did} + if typ, _ := payload["type"].(string); typ == "plc_tombstone" || typ == "tombstone" { + doc["deactivated"] = true + } + if handle, ok := payload["handle"].(string); ok && handle != "" { + doc["alsoKnownAs"] = []string{"at://" + handle} + } + if serviceEndpoint, ok := payload["service"].(string); ok && serviceEndpoint != "" { + doc["service"] = []map[string]any{ + { + "id": "#atproto_pds", + "type": "AtprotoPersonalDataServer", + "serviceEndpoint": serviceEndpoint, + }, + } + } + if signingKey, ok := payload["signingKey"].(string); ok && signingKey != "" { + doc["verificationMethod"] = []map[string]any{ + { + "id": "#atproto", + "type": "Multikey", + "controller": did, + "publicKeyMultibase": signingKey, + }, + } + doc["authentication"] = []string{"#atproto"} + } + b, err := json.Marshal(doc) + if err != nil { + return nil, fmt.Errorf("marshal synthesized did document: %w", err) + } + return types.CanonicalizeJSON(b) +} + +func extractRotationKeys(payload map[string]any) []string { + keys := make([]string, 0, 4) + seen := map[string]struct{}{} + add := func(v string) { + if v == "" { + return + } + if _, ok := seen[v]; ok { + return + } + seen[v] = struct{}{} + keys = append(keys, v) + } + + if arr, ok := payload["rotationKeys"].([]any); ok { + for _, v := range arr { + if s, ok := v.(string); ok { + add(s) + } + } + } + if recovery, ok := payload["recoveryKey"].(string); ok { + add(recovery) + } + if signing, ok := payload["signingKey"].(string); ok { + add(signing) + } + return keys +} |