From 45f5f852b71a63c142bca8b05efe53eebf142594 Mon Sep 17 00:00:00 2001 From: Michael Muré Date: Sat, 13 Aug 2022 12:08:48 +0200 Subject: core: generalized resolvers to resolve any entity time when unmarshalling an operation --- entity/dag/entity.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 9 deletions(-) (limited to 'entity/dag/entity.go') diff --git a/entity/dag/entity.go b/entity/dag/entity.go index 4ccf0e0e..09f37246 100644 --- a/entity/dag/entity.go +++ b/entity/dag/entity.go @@ -26,7 +26,7 @@ type Definition struct { // the Namespace in git references (bugs, prs, ...) Namespace string // a function decoding a JSON message into an Operation - OperationUnmarshaler func(raw json.RawMessage, resolver identity.Resolver) (Operation, error) + OperationUnmarshaler func(raw json.RawMessage, resolver entity.Resolvers) (Operation, error) // the expected format version number, that can be used for data migration/upgrade FormatVersion uint } @@ -57,29 +57,29 @@ func New(definition Definition) *Entity { } // Read will read and decode a stored local Entity from a repository -func Read(def Definition, repo repository.ClockedRepo, resolver identity.Resolver, id entity.Id) (*Entity, error) { +func Read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, id entity.Id) (*Entity, error) { if err := id.Validate(); err != nil { return nil, errors.Wrap(err, "invalid id") } ref := fmt.Sprintf("refs/%s/%s", def.Namespace, id.String()) - return read(def, repo, resolver, ref) + return read(def, repo, resolvers, ref) } // readRemote will read and decode a stored remote Entity from a repository -func readRemote(def Definition, repo repository.ClockedRepo, resolver identity.Resolver, remote string, id entity.Id) (*Entity, error) { +func readRemote(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, remote string, id entity.Id) (*Entity, error) { if err := id.Validate(); err != nil { return nil, errors.Wrap(err, "invalid id") } ref := fmt.Sprintf("refs/remotes/%s/%s/%s", def.Namespace, remote, id.String()) - return read(def, repo, resolver, ref) + return read(def, repo, resolvers, ref) } // read fetch from git and decode an Entity at an arbitrary git reference. -func read(def Definition, repo repository.ClockedRepo, resolver identity.Resolver, ref string) (*Entity, error) { +func read(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers, ref string) (*Entity, error) { rootHash, err := repo.ResolveRef(ref) if err != nil { return nil, err @@ -138,7 +138,7 @@ func read(def Definition, repo repository.ClockedRepo, resolver identity.Resolve return nil, fmt.Errorf("multiple leafs in the entity DAG") } - opp, err := readOperationPack(def, repo, resolver, commit) + opp, err := readOperationPack(def, repo, resolvers, commit) if err != nil { return nil, err } @@ -239,13 +239,65 @@ func read(def Definition, repo repository.ClockedRepo, resolver identity.Resolve }, nil } +// readClockNoCheck fetch from git, read and witness the clocks of an Entity at an arbitrary git reference. +// Note: readClockNoCheck does not verify the integrity of the Entity and could witness incorrect or incomplete +// clocks if so. If data integrity check is a requirement, a flow similar to read without actually reading/decoding +// operation blobs can be implemented instead. +func readClockNoCheck(def Definition, repo repository.ClockedRepo, ref string) error { + rootHash, err := repo.ResolveRef(ref) + if err != nil { + return err + } + + commit, err := repo.ReadCommit(rootHash) + if err != nil { + return err + } + + createTime, editTime, err := readOperationPackClock(repo, commit) + if err != nil { + return err + } + + // if we have more than one commit, we need to find the root to have the create time + if len(commit.Parents) > 0 { + for len(commit.Parents) > 0 { + // The path to the root is irrelevant. + commit, err = repo.ReadCommit(commit.Parents[0]) + if err != nil { + return err + } + } + createTime, _, err = readOperationPackClock(repo, commit) + if err != nil { + return err + } + } + + if createTime <= 0 { + return fmt.Errorf("creation lamport time not set") + } + if editTime <= 0 { + return fmt.Errorf("creation lamport time not set") + } + err = repo.Witness(fmt.Sprintf(creationClockPattern, def.Namespace), createTime) + if err != nil { + return err + } + err = repo.Witness(fmt.Sprintf(editClockPattern, def.Namespace), editTime) + if err != nil { + return err + } + return nil +} + type StreamedEntity struct { Entity *Entity Err error } // ReadAll read and parse all local Entity -func ReadAll(def Definition, repo repository.ClockedRepo, resolver identity.Resolver) <-chan StreamedEntity { +func ReadAll(def Definition, repo repository.ClockedRepo, resolvers entity.Resolvers) <-chan StreamedEntity { out := make(chan StreamedEntity) go func() { @@ -260,7 +312,7 @@ func ReadAll(def Definition, repo repository.ClockedRepo, resolver identity.Reso } for _, ref := range refs { - e, err := read(def, repo, resolver, ref) + e, err := read(def, repo, resolvers, ref) if err != nil { out <- StreamedEntity{Err: err} @@ -274,6 +326,26 @@ func ReadAll(def Definition, repo repository.ClockedRepo, resolver identity.Reso return out } +// ReadAllClocksNoCheck goes over all entities matching Definition and read/witness the corresponding clocks so that the +// repo end up with correct clocks for the next write. +func ReadAllClocksNoCheck(def Definition, repo repository.ClockedRepo) error { + refPrefix := fmt.Sprintf("refs/%s/", def.Namespace) + + refs, err := repo.ListRefs(refPrefix) + if err != nil { + return err + } + + for _, ref := range refs { + err = readClockNoCheck(def, repo, ref) + if err != nil { + return err + } + } + + return nil +} + // Id return the Entity identifier func (e *Entity) Id() entity.Id { // id is the id of the first operation -- cgit