diff --git a/acltree/aclstatebuilder.go b/acltree/aclstatebuilder.go index 3eb62783..609ab793 100644 --- a/acltree/aclstatebuilder.go +++ b/acltree/aclstatebuilder.go @@ -10,7 +10,6 @@ import ( type aclStateBuilder struct { tree *Tree - aclState *ACLState identity string key keys.EncryptionPrivKey decoder keys.SigningPubKeyDecoder @@ -29,15 +28,28 @@ func newACLStateBuilder(decoder keys.SigningPubKeyDecoder, accountData *account. } } -func (sb *aclStateBuilder) build() (*ACLState, error) { - state, _, err := sb.buildBefore("") +func (sb *aclStateBuilder) Init(tree *Tree) error { + sb.tree = tree + return nil +} + +func (sb *aclStateBuilder) Build() (*ACLState, error) { + state, _, err := sb.BuildBefore("") return state, err } -func (sb *aclStateBuilder) init(tree *Tree) error { - root := tree.Root() +// TODO: we can probably have only one state builder, because we can Build both at the same time +func (sb *aclStateBuilder) BuildBefore(beforeId string) (*ACLState, bool, error) { + var ( + err error + startChange = sb.tree.root + foundId bool + idSeenMap = make(map[string][]*Change) + decreasedPermissions *decreasedPermissionsParameters + ) + root := sb.tree.Root() if !root.IsSnapshot { - return fmt.Errorf("root should always be a snapshot") + return nil, false, fmt.Errorf("root should always be a snapshot") } snapshot := root.Content.GetAclData().GetAclSnapshot() @@ -47,27 +59,13 @@ func (sb *aclStateBuilder) init(tree *Tree) error { sb.key, sb.decoder) if err != nil { - return fmt.Errorf("could not build ACLState from snapshot: %w", err) + return nil, false, fmt.Errorf("could not build ACLState from snapshot: %w", err) } - sb.tree = tree - sb.aclState = state - return nil -} - -// TODO: we can probably have only one state builder, because we can build both at the same time -func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) { - var ( - err error - startChange = sb.tree.root - foundId bool - idSeenMap = make(map[string][]*Change) - decreasedPermissions *decreasedPermissionsParameters - ) idSeenMap[startChange.Content.Identity] = append(idSeenMap[startChange.Content.Identity], startChange) if startChange.Content.GetChangesData() != nil { - key, exists := sb.aclState.userReadKeys[startChange.Content.CurrentReadKeyHash] + key, exists := state.userReadKeys[startChange.Content.CurrentReadKeyHash] if !exists { return nil, false, fmt.Errorf("no first snapshot") } @@ -79,7 +77,7 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) } if beforeId == startChange.Id { - return sb.aclState, true, nil + return state, true, nil } for { @@ -101,13 +99,13 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) idSeenMap[c.Content.Identity] = append(idSeenMap[c.Content.Identity], c) if c.Content.GetAclData() != nil { - err = sb.aclState.applyChange(c.Id, c.Content) + err = state.applyChange(c.Id, c.Content) if err != nil { return false } // if we have some users who have less permissions now - users := sb.aclState.getPermissionDecreasedUsers(c.Content) + users := state.getPermissionDecreasedUsers(c.Content) if len(users) > 0 { decreasedPermissions = &decreasedPermissionsParameters{ users: users, @@ -118,14 +116,14 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) } // the user can't make changes - if !sb.aclState.hasPermission(c.Content.Identity, pb.ACLChange_Writer) && !sb.aclState.hasPermission(c.Content.Identity, pb.ACLChange_Admin) { + if !state.hasPermission(c.Content.Identity, pb.ACLChange_Writer) && !state.hasPermission(c.Content.Identity, pb.ACLChange_Admin) { err = fmt.Errorf("user %s cannot make changes", c.Content.Identity) return false } // decrypting contents on the fly if c.Content.GetChangesData() != nil { - key, exists := sb.aclState.userReadKeys[c.Content.CurrentReadKeyHash] + key, exists := state.userReadKeys[c.Content.CurrentReadKeyHash] if !exists { err = fmt.Errorf("failed to find key with hash: %d", c.Content.CurrentReadKeyHash) return false @@ -169,7 +167,7 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) decreasedPermissions = nil if removed { // starting from the beginning but with updated Tree - return sb.buildBefore(beforeId) + return sb.BuildBefore(beforeId) } } else if err == nil { // we can finish the acl state building process @@ -185,5 +183,5 @@ func (sb *aclStateBuilder) buildBefore(beforeId string) (*ACLState, bool, error) err = nil } - return sb.aclState, foundId, err + return state, foundId, err } diff --git a/acltree/aclstatebuilder_test.go b/acltree/aclstatebuilder_test.go index 228744e5..c5ea0c79 100644 --- a/acltree/aclstatebuilder_test.go +++ b/acltree/aclstatebuilder_test.go @@ -19,8 +19,8 @@ type ACLContext struct { func createTreeFromThread(t thread.Thread, fromStart bool) (*Tree, error) { treeBuilder := newTreeBuilder(t, keys.NewEd25519Decoder()) - treeBuilder.init() - return treeBuilder.build(fromStart) + treeBuilder.Init() + return treeBuilder.Build(fromStart) } func createACLStateFromThread( @@ -40,16 +40,16 @@ func createACLStateFromThread( } aclTreeBuilder := newACLTreeBuilder(t, decoder) - aclTreeBuilder.init() - aclTree, err := aclTreeBuilder.build() + aclTreeBuilder.Init() + aclTree, err := aclTreeBuilder.Build() if err != nil { return nil, err } if !fromStart { snapshotValidator := newSnapshotValidator(decoder, accountData) - snapshotValidator.init(aclTree) - valid, err := snapshotValidator.validateSnapshot(tree.root) + snapshotValidator.Init(aclTree) + valid, err := snapshotValidator.ValidateSnapshot(tree.root) if err != nil { return nil, err } @@ -60,12 +60,12 @@ func createACLStateFromThread( } aclBuilder := newACLStateBuilder(decoder, accountData) - err = aclBuilder.init(tree) + err = aclBuilder.Init(tree) if err != nil { return nil, err } - aclState, err := aclBuilder.build() + aclState, err := aclBuilder.Build() if err != nil { return nil, err } @@ -88,7 +88,7 @@ func TestACLStateBuilder_UserJoinBuild(t *testing.T) { keys.NewEd25519Decoder(), false) if err != nil { - t.Fatalf("should build acl ACLState without err: %v", err) + t.Fatalf("should Build acl ACLState without err: %v", err) } aclState := ctx.ACLState //fmt.Println(ctx.Tree.Graph()) @@ -122,7 +122,7 @@ func TestACLStateBuilder_UserRemoveBuild(t *testing.T) { keys.NewEd25519Decoder(), false) if err != nil { - t.Fatalf("should build acl ACLState without err: %v", err) + t.Fatalf("should Build acl ACLState without err: %v", err) } aclState := ctx.ACLState //fmt.Println(ctx.Tree.Graph()) @@ -152,7 +152,7 @@ func TestACLStateBuilder_UserRemoveBeforeBuild(t *testing.T) { keys.NewEd25519Decoder(), false) if err != nil { - t.Fatalf("should build acl ACLState without err: %v", err) + t.Fatalf("should Build acl ACLState without err: %v", err) } aclState := ctx.ACLState //fmt.Println(ctx.Tree.Graph()) @@ -183,7 +183,7 @@ func TestACLStateBuilder_InvalidSnapshotBuild(t *testing.T) { keys.NewEd25519Decoder(), false) if err != nil { - t.Fatalf("should build acl ACLState without err: %v", err) + t.Fatalf("should Build acl ACLState without err: %v", err) } aclState := ctx.ACLState //fmt.Println(ctx.Tree.Graph()) @@ -213,7 +213,7 @@ func TestACLStateBuilder_ValidSnapshotBuild(t *testing.T) { keys.NewEd25519Decoder(), false) if err != nil { - t.Fatalf("should build acl ACLState without err: %v", err) + t.Fatalf("should Build acl ACLState without err: %v", err) } aclState := ctx.ACLState //fmt.Println(ctx.Tree.Graph()) diff --git a/acltree/acltree.go b/acltree/acltree.go index 1cb83da7..0162cd33 100644 --- a/acltree/acltree.go +++ b/acltree/acltree.go @@ -7,6 +7,7 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" "github.com/gogo/protobuf/proto" + "sync" ) type AddResultSummary int @@ -18,17 +19,17 @@ const ( ) type AddResult struct { - AttachedChanges []*Change - InvalidChanges []*Change - UnattachedChanges []*Change - + OldHeads []string + Heads []string + // TODO: add summary for changes Summary AddResultSummary } +// TODO: Change add change content to include ACLChangeBuilder type ACLTree interface { ACLState() *ACLState AddContent(changeContent *ChangeContent) (*Change, error) - AddChanges(changes ...*Change) (AddResult, error) // TODO: Make change as interface + AddChanges(changes ...*Change) (AddResult, error) Heads() []string Iterate(func(change *Change) bool) IterateFrom(string, func(change *Change) bool) @@ -40,13 +41,15 @@ type aclTree struct { accountData *account.AccountData fullTree *Tree - aclTree *Tree // this tree is built from start of the document + aclTree *Tree // TODO: right now we don't use it, we can probably have only local var for now. This tree is built from start of the document aclState *ACLState treeBuilder *treeBuilder aclTreeBuilder *aclTreeBuilder aclStateBuilder *aclStateBuilder snapshotValidator *snapshotValidator + + sync.Mutex } func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) { @@ -74,14 +77,15 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) { return aclTree, nil } +// TODO: this is not used for now, in future we should think about not making full tree rebuild func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) { if validateSnapshot { - err = a.snapshotValidator.init(a.aclTree) + err = a.snapshotValidator.Init(a.aclTree) if err != nil { return err } - valid, err := a.snapshotValidator.validateSnapshot(a.fullTree.root) + valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root) if err != nil { return err } @@ -90,12 +94,12 @@ func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) { } } - err = a.aclStateBuilder.init(a.fullTree) + err = a.aclStateBuilder.Init(a.fullTree) if err != nil { return err } - a.aclState, err = a.aclStateBuilder.build() + a.aclState, err = a.aclStateBuilder.Build() if err != nil { return err } @@ -104,28 +108,28 @@ func (a *aclTree) rebuildFromTree(validateSnapshot bool) (err error) { } func (a *aclTree) rebuildFromThread(fromStart bool) error { - a.treeBuilder.init() - a.aclTreeBuilder.init() + a.treeBuilder.Init() + a.aclTreeBuilder.Init() var err error - a.fullTree, err = a.treeBuilder.build(fromStart) + a.fullTree, err = a.treeBuilder.Build(fromStart) if err != nil { return err } // TODO: remove this from context as this is used only to validate snapshot - a.aclTree, err = a.aclTreeBuilder.build() + a.aclTree, err = a.aclTreeBuilder.Build() if err != nil { return err } if !fromStart { - err = a.snapshotValidator.init(a.aclTree) + err = a.snapshotValidator.Init(a.aclTree) if err != nil { return err } - valid, err := a.snapshotValidator.validateSnapshot(a.fullTree.root) + valid, err := a.snapshotValidator.ValidateSnapshot(a.fullTree.root) if err != nil { return err } @@ -133,12 +137,12 @@ func (a *aclTree) rebuildFromThread(fromStart bool) error { return a.rebuildFromThread(true) } } - err = a.aclStateBuilder.init(a.fullTree) + err = a.aclStateBuilder.Init(a.fullTree) if err != nil { return err } - a.aclState, err = a.aclStateBuilder.build() + a.aclState, err = a.aclStateBuilder.Build() if err != nil { return err } @@ -181,6 +185,8 @@ func (a *aclTree) ACLState() *ACLState { func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) { // TODO: add snapshot creation logic + a.Lock() + defer a.Unlock() marshalled, err := changeContent.ChangesData.Marshal() if err != nil { return nil, err @@ -239,40 +245,100 @@ func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) { } func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) { + a.Lock() + defer a.Unlock() + // TODO: make proper error handling, because there are a lot of corner cases where this will break var aclChanges []*Change + var err error + + defer func() { + if err != nil { + return + } + // removing attached or invalid orphans + var toRemove []string + + for _, orphan := range a.thread.Orphans() { + if _, exists := a.fullTree.attached[orphan]; exists { + toRemove = append(toRemove, orphan) + } + if _, exists := a.fullTree.invalidChanges[orphan]; exists { + toRemove = append(toRemove, orphan) + } + } + a.thread.RemoveOrphans(toRemove...) + }() + for _, ch := range changes { if ch.IsACLChange() { aclChanges = append(aclChanges, ch) break } - a.thread.A + err = a.thread.AddChange(ch) + if err != nil { + return AddResult{}, err + } + a.thread.AddOrphans(ch.Id) } - // TODO: understand the common snapshot problem prevHeads := a.fullTree.Heads() mode := a.fullTree.Add(changes...) switch mode { case Nothing: - return AddResult{Summary: AddResultSummaryNothing}, nil + return AddResult{ + OldHeads: prevHeads, + Heads: prevHeads, + Summary: AddResultSummaryNothing, + }, nil + case Rebuild: - res, err := d.Build() - return AddResult{Summary: Rebuild}, err + err = a.rebuildFromThread(false) + if err != nil { + return AddResult{}, err + } + + return AddResult{ + OldHeads: prevHeads, + Heads: a.fullTree.Heads(), + Summary: AddResultSummaryRebuild, + }, nil default: - break + a.aclState, err = a.aclStateBuilder.Build() + if err != nil { + return AddResult{}, err + } + + return AddResult{ + OldHeads: prevHeads, + Heads: a.fullTree.Heads(), + Summary: AddResultSummaryAppend, + }, nil } } func (a *aclTree) Iterate(f func(change *Change) bool) { - //TODO implement me - panic("implement me") + a.Lock() + defer a.Unlock() + a.fullTree.Iterate(a.fullTree.RootId(), f) } func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) { - //TODO implement me - panic("implement me") + a.Lock() + defer a.Unlock() + a.fullTree.Iterate(s, f) } func (a *aclTree) HasChange(s string) bool { - //TODO implement me - panic("implement me") + a.Lock() + defer a.Unlock() + _, attachedExists := a.fullTree.attached[s] + _, unattachedExists := a.fullTree.unAttached[s] + _, invalidExists := a.fullTree.invalidChanges[s] + return attachedExists || unattachedExists || invalidExists +} + +func (a *aclTree) Heads() []string { + a.Lock() + defer a.Unlock() + return a.fullTree.Heads() } diff --git a/acltree/acltreebuilder.go b/acltree/acltreebuilder.go index 83d7afd3..7c552407 100644 --- a/acltree/acltreebuilder.go +++ b/acltree/acltreebuilder.go @@ -30,16 +30,19 @@ func newACLTreeBuilder(t thread.Thread, decoder keys.SigningPubKeyDecoder) *aclT } } -func (tb *aclTreeBuilder) init() { +func (tb *aclTreeBuilder) Init() { tb.cache = make(map[string]*Change) tb.identityKeys = make(map[string]keys.SigningPubKey) tb.tree = &Tree{} - tb.changeLoader.init(tb.cache, tb.identityKeys) + tb.changeLoader.Init(tb.cache, tb.identityKeys) } -func (tb *aclTreeBuilder) build() (*Tree, error) { - heads := tb.thread.PossibleHeads() - aclHeads, err := tb.getACLHeads(heads) +func (tb *aclTreeBuilder) Build() (*Tree, error) { + var headsAndOrphans []string + headsAndOrphans = append(headsAndOrphans, tb.thread.Orphans()...) + headsAndOrphans = append(headsAndOrphans, tb.thread.Heads()...) + aclHeads, err := tb.getACLHeads(headsAndOrphans) + if err != nil { return nil, err } diff --git a/acltree/changeloader.go b/acltree/changeloader.go index 0dfe9e25..82c3df99 100644 --- a/acltree/changeloader.go +++ b/acltree/changeloader.go @@ -30,7 +30,7 @@ func newChangeLoader( } } -func (c *changeLoader) init(cache map[string]*Change, +func (c *changeLoader) Init(cache map[string]*Change, identityKeys map[string]keys.SigningPubKey) { c.cache = cache c.identityKeys = identityKeys diff --git a/acltree/snapshotvalidator.go b/acltree/snapshotvalidator.go index b64f3d4a..8bf19451 100644 --- a/acltree/snapshotvalidator.go +++ b/acltree/snapshotvalidator.go @@ -26,13 +26,13 @@ func newSnapshotValidator( } } -func (s *snapshotValidator) init(aclTree *Tree) error { +func (s *snapshotValidator) Init(aclTree *Tree) error { s.aclTree = aclTree - return s.stateBuilder.init(aclTree) + return s.stateBuilder.Init(aclTree) } -func (s *snapshotValidator) validateSnapshot(ch *Change) (bool, error) { - st, found, err := s.stateBuilder.buildBefore(ch.Id) +func (s *snapshotValidator) ValidateSnapshot(ch *Change) (bool, error) { + st, found, err := s.stateBuilder.BuildBefore(ch.Id) if err != nil { return false, err } diff --git a/acltree/treebuilder.go b/acltree/treebuilder.go index 8196046e..0e3bc79d 100644 --- a/acltree/treebuilder.go +++ b/acltree/treebuilder.go @@ -38,27 +38,29 @@ func newTreeBuilder(t thread.Thread, decoder keys.SigningPubKeyDecoder) *treeBui } } -func (tb *treeBuilder) init() { +func (tb *treeBuilder) Init() { tb.cache = make(map[string]*Change) tb.identityKeys = make(map[string]keys.SigningPubKey) tb.tree = &Tree{} - tb.changeLoader.init(tb.cache, tb.identityKeys) + tb.changeLoader.Init(tb.cache, tb.identityKeys) } -func (tb *treeBuilder) build(fromStart bool) (*Tree, error) { - heads := tb.thread.PossibleHeads() +func (tb *treeBuilder) Build(fromStart bool) (*Tree, error) { + var headsAndOrphans []string + headsAndOrphans = append(headsAndOrphans, tb.thread.Orphans()...) + headsAndOrphans = append(headsAndOrphans, tb.thread.Heads()...) if fromStart { - if err := tb.buildTreeFromStart(heads); err != nil { + if err := tb.buildTreeFromStart(headsAndOrphans); err != nil { return nil, fmt.Errorf("buildTree error: %v", err) } } else { - breakpoint, err := tb.findBreakpoint(heads) + breakpoint, err := tb.findBreakpoint(headsAndOrphans) if err != nil { return nil, fmt.Errorf("findBreakpoint error: %v", err) } - if err = tb.buildTree(heads, breakpoint); err != nil { + if err = tb.buildTree(headsAndOrphans, breakpoint); err != nil { return nil, fmt.Errorf("buildTree error: %v", err) } } diff --git a/testutils/threadbuilder/threadbuilder.go b/testutils/threadbuilder/threadbuilder.go index 9d695479..081a961f 100644 --- a/testutils/threadbuilder/threadbuilder.go +++ b/testutils/threadbuilder/threadbuilder.go @@ -109,7 +109,7 @@ func (t *ThreadBuilder) AddRawChange(change *thread.RawChange) error { return nil } -func (t *ThreadBuilder) AddPossibleHead(head string) { +func (t *ThreadBuilder) AddOrphans(head string) { t.maybeHeads = append(t.maybeHeads, head) } @@ -140,7 +140,7 @@ func (t *ThreadBuilder) AddChange(change aclchanges.Change) error { return nil } -func (t *ThreadBuilder) PossibleHeads() []string { +func (t *ThreadBuilder) Orphans() []string { return t.maybeHeads } diff --git a/thread/models.go b/thread/models.go index 89e0737c..1e57beb7 100644 --- a/thread/models.go +++ b/thread/models.go @@ -11,13 +11,14 @@ type Thread interface { ID() string Heads() []string - PossibleHeads() []string + Orphans() []string SetHeads(heads []string) - SetPossibleHeads(heads []string) - AddPossibleHead(head string) + RemoveOrphans(orphan ...string) + AddOrphans(orphan ...string) AddRawChange(change *RawChange) error AddChange(change aclchanges.Change) error + // TODO: have methods with raw changes also GetChange(ctx context.Context, recordID string) (*RawChange, error) }