Create ACL list and update tree package

This commit is contained in:
mcrakhman 2022-08-18 23:03:27 +02:00 committed by Mikhail Iudin
parent 38209fc1fb
commit 70184e1554
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
14 changed files with 984 additions and 866 deletions

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,12 @@ message RawChange {
string id = 3; string id = 3;
} }
message RawRecord {
bytes payload = 1;
bytes signature = 2;
string id = 3;
}
// the element of change tree used to store and internal apply smartBlock history // the element of change tree used to store and internal apply smartBlock history
message ACLChange { message ACLChange {
repeated string treeHeadIds = 1; repeated string treeHeadIds = 1;
@ -112,7 +118,7 @@ message ACLChange {
message Change { message Change {
repeated string treeHeadIds = 1; repeated string treeHeadIds = 1;
repeated string aclHeadIds = 2; string aclHeadId = 2;
string snapshotBaseId = 3; // we will only have one base snapshot for both string snapshotBaseId = 3; // we will only have one base snapshot for both
bytes changesData = 4; bytes changesData = 4;
uint64 currentReadKeyHash = 5; uint64 currentReadKeyHash = 5;
@ -120,3 +126,11 @@ message Change {
string identity = 7; string identity = 7;
bool isSnapshot = 8; bool isSnapshot = 8;
} }
message Record {
string prevId = 1;
string identity = 2;
bytes data = 3;
uint64 currentReadKeyHash = 4;
int64 timestamp = 5;
}

View File

@ -1,17 +1,22 @@
package tree package list
import ( import (
"bytes" "bytes"
"errors" "errors"
"fmt" "fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"go.uber.org/zap"
"hash/fnv" "hash/fnv"
) )
var log = logger.NewNamed("acllist")
var ErrNoSuchUser = errors.New("no such user") var ErrNoSuchUser = errors.New("no such user")
var ErrFailedToDecrypt = errors.New("failed to decrypt key") var ErrFailedToDecrypt = errors.New("failed to decrypt key")
var ErrUserRemoved = errors.New("user was removed from the document") var ErrUserRemoved = errors.New("user was removed from the document")
@ -23,7 +28,7 @@ type ACLState struct {
userReadKeys map[uint64]*symmetric.Key userReadKeys map[uint64]*symmetric.Key
userStates map[string]*aclpb.ACLChangeUserState userStates map[string]*aclpb.ACLChangeUserState
userInvites map[string]*aclpb.ACLChangeUserInvite userInvites map[string]*aclpb.ACLChangeUserInvite
signingPubKeyDecoder signingkey.PubKeyDecoder signingPubKeyDecoder keys.Decoder
encryptionKey encryptionkey.PrivKey encryptionKey encryptionkey.PrivKey
identity string identity string
} }
@ -31,14 +36,14 @@ type ACLState struct {
func newACLStateWithIdentity( func newACLStateWithIdentity(
identity string, identity string,
encryptionKey encryptionkey.PrivKey, encryptionKey encryptionkey.PrivKey,
signingPubKeyDecoder signingkey.PubKeyDecoder) *ACLState { decoder keys.Decoder) *ACLState {
return &ACLState{ return &ACLState{
identity: identity, identity: identity,
encryptionKey: encryptionKey, encryptionKey: encryptionKey,
userReadKeys: make(map[uint64]*symmetric.Key), userReadKeys: make(map[uint64]*symmetric.Key),
userStates: make(map[string]*aclpb.ACLChangeUserState), userStates: make(map[string]*aclpb.ACLChangeUserState),
userInvites: make(map[string]*aclpb.ACLChangeUserInvite), userInvites: make(map[string]*aclpb.ACLChangeUserInvite),
signingPubKeyDecoder: signingPubKeyDecoder, signingPubKeyDecoder: decoder,
} }
} }
@ -50,10 +55,18 @@ func newACLState() *ACLState {
} }
} }
func (st *ACLState) applyChange(change *aclpb.Change) (err error) { func (st *ACLState) CurrentReadKeyHash() uint64 {
return st.currentReadKeyHash
}
func (st *ACLState) UserReadKeys() map[uint64]*symmetric.Key {
return st.userReadKeys
}
func (st *ACLState) applyRecord(record *aclpb.Record) (err error) {
aclData := &aclpb.ACLChangeACLData{} aclData := &aclpb.ACLChangeACLData{}
err = proto.Unmarshal(change.ChangesData, aclData) err = proto.Unmarshal(record.Data, aclData)
if err != nil { if err != nil {
return return
} }
@ -62,27 +75,27 @@ func (st *ACLState) applyChange(change *aclpb.Change) (err error) {
if err != nil { if err != nil {
return return
} }
st.currentReadKeyHash = change.CurrentReadKeyHash st.currentReadKeyHash = record.CurrentReadKeyHash
}() }()
return st.applyChangeData(aclData, change.CurrentReadKeyHash, change.Identity) return st.applyChangeData(aclData, record.CurrentReadKeyHash, record.Identity)
} }
func (st *ACLState) applyChangeAndUpdate(changeWrapper *Change) (err error) { func (st *ACLState) applyChangeAndUpdate(recordWrapper *Record) (err error) {
change := changeWrapper.Content change := recordWrapper.Content
aclData := &aclpb.ACLChangeACLData{} aclData := &aclpb.ACLChangeACLData{}
if changeWrapper.ParsedModel != nil { if recordWrapper.ParsedModel != nil {
aclData = changeWrapper.ParsedModel.(*aclpb.ACLChangeACLData) aclData = recordWrapper.ParsedModel.(*aclpb.ACLChangeACLData)
} else { } else {
err = proto.Unmarshal(change.ChangesData, aclData) err = proto.Unmarshal(change.Data, aclData)
if err != nil { if err != nil {
return return
} }
changeWrapper.ParsedModel = aclData recordWrapper.ParsedModel = aclData
} }
return st.applyChangeData(aclData, changeWrapper.Content.CurrentReadKeyHash, changeWrapper.Content.Identity) return st.applyChangeData(aclData, recordWrapper.Content.CurrentReadKeyHash, recordWrapper.Content.Identity)
} }
func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uint64, identity string) (err error) { func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uint64, identity string) (err error) {
@ -111,7 +124,7 @@ func (st *ACLState) applyChangeData(changeData *aclpb.ACLChangeACLData, hash uin
for _, ch := range changeData.GetAclContent() { for _, ch := range changeData.GetAclContent() {
if err = st.applyChangeContent(ch); err != nil { if err = st.applyChangeContent(ch); err != nil {
log.Infof("error while applying changes: %v; ignore", err) log.Info("error while applying changes: %v; ignore", zap.Error(err))
return err return err
} }
} }
@ -174,7 +187,7 @@ func (st *ACLState) applyUserJoin(ch *aclpb.ACLChangeUserJoin) error {
return fmt.Errorf("failed to decode signing identity as bytes") return fmt.Errorf("failed to decode signing identity as bytes")
} }
res, err := verificationKey.Verify(rawSignedId, signature) res, err := verificationKey.(signingkey.PubKey).Verify(rawSignedId, signature)
if err != nil { if err != nil {
return fmt.Errorf("verification returned error: %w", err) return fmt.Errorf("verification returned error: %w", err)
} }

View File

@ -0,0 +1,51 @@
package list
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
)
type aclStateBuilder struct {
log ACLList
identity string
key encryptionkey.PrivKey
decoder keys.Decoder
}
func newACLStateBuilderWithIdentity(decoder keys.Decoder, accountData *account.AccountData) *aclStateBuilder {
return &aclStateBuilder{
decoder: decoder,
identity: accountData.Identity,
key: accountData.EncKey,
}
}
func newACLStateBuilder() *aclStateBuilder {
return &aclStateBuilder{}
}
func (sb *aclStateBuilder) Init(aclLog ACLList) error {
sb.log = aclLog
return nil
}
func (sb *aclStateBuilder) Build() (*ACLState, error) {
var (
err error
state *ACLState
)
if sb.decoder != nil {
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
} else {
state = newACLState()
}
sb.log.Iterate(func(c *Record) (isContinue bool) {
err = state.applyChangeAndUpdate(c)
return err == nil
})
return state, err
}

View File

@ -1,4 +1,4 @@
package tree package list
import ( import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
@ -20,7 +20,7 @@ type ACLChangeBuilder interface {
type aclChangeBuilder struct { type aclChangeBuilder struct {
aclState *ACLState aclState *ACLState
tree *Tree list ACLList
acc *account.AccountData acc *account.AccountData
aclData *aclpb.ACLChangeACLData aclData *aclpb.ACLChangeACLData
@ -33,9 +33,9 @@ func newACLChangeBuilder() *aclChangeBuilder {
return &aclChangeBuilder{} return &aclChangeBuilder{}
} }
func (c *aclChangeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) { func (c *aclChangeBuilder) Init(state *ACLState, list ACLList, acc *account.AccountData) {
c.aclState = state c.aclState = state
c.tree = tree c.list = list
c.acc = acc c.acc = acc
c.aclData = &aclpb.ACLChangeACLData{} c.aclData = &aclpb.ACLChangeACLData{}
@ -93,30 +93,25 @@ func (c *aclChangeBuilder) UserAdd(identity string, encryptionKey encryptionkey.
return nil return nil
} }
func (c *aclChangeBuilder) BuildAndApply() (*Change, []byte, error) { func (c *aclChangeBuilder) BuildAndApply() (*Record, []byte, error) {
aclChange := &aclpb.Change{ aclRecord := &aclpb.Record{
TreeHeadIds: c.tree.Heads(), PrevId: c.list.Last().Id,
SnapshotBaseId: c.tree.RootId(),
CurrentReadKeyHash: c.readKeyHash, CurrentReadKeyHash: c.readKeyHash,
Timestamp: int64(time.Now().Nanosecond()), Timestamp: int64(time.Now().Nanosecond()),
Identity: c.acc.Identity, Identity: c.acc.Identity,
} }
if c.aclState.currentReadKeyHash == 0 {
// setting IsSnapshot for initial change
aclChange.IsSnapshot = true
}
marshalledData, err := proto.Marshal(c.aclData) marshalledData, err := proto.Marshal(c.aclData)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
aclChange.ChangesData = marshalledData aclRecord.Data = marshalledData
err = c.aclState.applyChange(aclChange) err = c.aclState.applyRecord(aclRecord)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
fullMarshalledChange, err := proto.Marshal(aclChange) fullMarshalledChange, err := proto.Marshal(aclRecord)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -128,7 +123,7 @@ func (c *aclChangeBuilder) BuildAndApply() (*Change, []byte, error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
ch := NewChange(id, aclChange) ch := NewRecord(id, aclRecord)
ch.ParsedModel = c.aclData ch.ParsedModel = c.aclData
ch.Sign = signature ch.Sign = signature

16
pkg/acl/list/list.go Normal file
View File

@ -0,0 +1,16 @@
package list
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
type IterFunc = func(record *Record) (IsContinue bool)
type ACLList interface {
tree.RWLocker
ID() string
ACLState() ACLState
IsAfter(first string, second string) (bool, error)
Last() *Record
Get(id string) (*Record, error)
Iterate(iterFunc IterFunc)
IterateFrom(startId string, iterFunc IterFunc)
}

17
pkg/acl/list/record.go Normal file
View File

@ -0,0 +1,17 @@
package list
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
type Record struct {
Id string
Content *aclpb.Record
ParsedModel interface{}
Sign []byte
}
func NewRecord(id string, aclRecord *aclpb.Record) *Record {
return &Record{
Id: id,
Content: aclRecord,
}
}

View File

@ -1,102 +0,0 @@
package tree
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/encryptionkey"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
)
type aclStateBuilder struct {
tree *Tree
identity string
key encryptionkey.PrivKey
decoder signingkey.PubKeyDecoder
}
func newACLStateBuilderWithIdentity(decoder signingkey.PubKeyDecoder, accountData *account.AccountData) *aclStateBuilder {
return &aclStateBuilder{
decoder: decoder,
identity: accountData.Identity,
key: accountData.EncKey,
}
}
func newACLStateBuilder() *aclStateBuilder {
return &aclStateBuilder{}
}
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) BuildBefore(beforeId string) (*ACLState, bool, error) {
var (
err error
startChange = sb.tree.root
state *ACLState
foundId = false
)
if sb.decoder != nil {
state = newACLStateWithIdentity(sb.identity, sb.key, sb.decoder)
} else {
state = newACLState()
}
if beforeId == startChange.Id {
return state, true, nil
}
iterFunc := func(c *Change) (isContinue bool) {
defer func() {
if err == nil {
startChange = c
} else if err != ErrDocumentForbidden {
log.Errorf("marking change %s as invalid: %v", c.Id, err)
sb.tree.RemoveInvalidChange(c.Id)
}
}()
err = state.applyChangeAndUpdate(c)
if err != nil {
return false
}
// the user can't make changes
if !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Writer) && !state.hasPermission(c.Content.Identity, aclpb.ACLChange_Admin) {
err = fmt.Errorf("user %s cannot make changes", c.Content.Identity)
return false
}
if c.Id == beforeId {
foundId = true
return false
}
return true
}
for {
sb.tree.IterateSkip(sb.tree.root.Id, startChange.Id, iterFunc)
if err == nil {
break
}
// the user is forbidden to access the document
if err == ErrDocumentForbidden {
return nil, foundId, err
}
// otherwise we have to continue from the change which we had
err = nil
}
return state, foundId, err
}

View File

@ -1,525 +0,0 @@
package tree
import (
"context"
"errors"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
"go.uber.org/zap"
"sync"
)
type AddResultSummary int
var ErrTreeWithoutIdentity = errors.New("acl tree is created without identity")
var ErrHasInvalidChanges = errors.New("the change is invalid")
const (
AddResultSummaryNothing AddResultSummary = iota
AddResultSummaryAppend
AddResultSummaryRebuild
)
type AddResult struct {
OldHeads []string
Heads []string
Added []*aclpb.RawChange
// TODO: add summary for changes
Summary AddResultSummary
}
type ACLTreeUpdateListener interface {
Update(tree ACLTree)
Rebuild(tree ACLTree)
}
type NoOpListener struct{}
func (n NoOpListener) Update(tree ACLTree) {}
func (n NoOpListener) Rebuild(tree ACLTree) {}
type RWLocker interface {
sync.Locker
RLock()
RUnlock()
}
var ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
type ACLTree interface {
RWLocker
CommonTree
ACLState() *ACLState
AddContent(ctx context.Context, f func(builder ACLChangeBuilder) error) (*aclpb.RawChange, error)
AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error)
}
type aclTree struct {
treeStorage treestorage.TreeStorage
accountData *account.AccountData
updateListener ACLTreeUpdateListener
id string
header *treepb.TreeHeader
tree *Tree
aclState *ACLState
treeBuilder *treeBuilder
aclStateBuilder *aclStateBuilder
changeBuilder *aclChangeBuilder
sync.RWMutex
}
func BuildACLTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountData, listener ACLTreeUpdateListener) (ACLTree, error) {
treeBuilder := newTreeBuilder(t, acc.Decoder)
aclStateBuilder := newACLStateBuilderWithIdentity(acc.Decoder, acc)
changeBuilder := newACLChangeBuilder()
aclTree := &aclTree{
treeStorage: t,
accountData: acc,
tree: nil,
aclState: nil,
treeBuilder: treeBuilder,
aclStateBuilder: aclStateBuilder,
changeBuilder: changeBuilder,
updateListener: listener,
}
err := aclTree.rebuildFromStorage()
if err != nil {
return nil, err
}
err = aclTree.removeOrphans()
if err != nil {
return nil, err
}
err = t.SetHeads(aclTree.Heads())
if err != nil {
return nil, err
}
aclTree.id, err = t.TreeID()
if err != nil {
return nil, err
}
aclTree.header, err = t.Header()
if err != nil {
return nil, err
}
if listener != nil {
listener.Rebuild(aclTree)
}
return aclTree, nil
}
func BuildACLTree(t treestorage.TreeStorage, decoder signingkey.PubKeyDecoder, listener ACLTreeUpdateListener) (ACLTree, error) {
treeBuilder := newTreeBuilder(t, decoder)
aclStateBuilder := newACLStateBuilder()
changeBuilder := newACLChangeBuilder()
aclTree := &aclTree{
treeStorage: t,
tree: nil,
aclState: nil,
treeBuilder: treeBuilder,
aclStateBuilder: aclStateBuilder,
changeBuilder: changeBuilder,
updateListener: listener,
}
err := aclTree.rebuildFromStorage()
if err != nil {
return nil, err
}
err = aclTree.removeOrphans()
if err != nil {
return nil, err
}
err = t.SetHeads(aclTree.Heads())
if err != nil {
return nil, err
}
aclTree.id, err = t.TreeID()
if err != nil {
return nil, err
}
aclTree.header, err = t.Header()
if err != nil {
return nil, err
}
if listener != nil {
listener.Rebuild(aclTree)
}
return aclTree, nil
}
func (a *aclTree) removeOrphans() error {
// removing attached or invalid orphans
var toRemove []string
orphans, err := a.treeStorage.Orphans()
if err != nil {
return err
}
for _, orphan := range orphans {
if _, exists := a.tree.attached[orphan]; exists {
toRemove = append(toRemove, orphan)
}
if _, exists := a.tree.invalidChanges[orphan]; exists {
toRemove = append(toRemove, orphan)
}
}
return a.treeStorage.RemoveOrphans(toRemove...)
}
func (a *aclTree) rebuildFromStorage() (err error) {
a.treeBuilder.Init()
a.tree, err = a.treeBuilder.Build(true)
if err != nil {
return err
}
err = a.aclStateBuilder.Init(a.tree)
if err != nil {
return err
}
a.aclState, err = a.aclStateBuilder.Build()
if err != nil {
return err
}
return nil
}
func (a *aclTree) ID() string {
return a.id
}
func (a *aclTree) Header() *treepb.TreeHeader {
return a.header
}
func (a *aclTree) ACLState() *ACLState {
return a.aclState
}
func (a *aclTree) Storage() treestorage.TreeStorage {
return a.treeStorage
}
func (a *aclTree) AddContent(ctx context.Context, build func(builder ACLChangeBuilder) error) (*aclpb.RawChange, error) {
if a.accountData == nil {
return nil, ErrTreeWithoutIdentity
}
defer func() {
// TODO: should this be called in a separate goroutine to prevent accidental cycles (tree->updater->tree)
if a.updateListener != nil {
a.updateListener.Update(a)
}
}()
a.changeBuilder.Init(a.aclState, a.tree, a.accountData)
err := build(a.changeBuilder)
if err != nil {
return nil, err
}
ch, marshalled, err := a.changeBuilder.BuildAndApply()
if err != nil {
return nil, err
}
a.tree.AddFast(ch)
rawCh := &aclpb.RawChange{
Payload: marshalled,
Signature: ch.Signature(),
Id: ch.Id,
}
err = a.treeStorage.AddRawChange(rawCh)
if err != nil {
return nil, err
}
err = a.treeStorage.SetHeads([]string{ch.Id})
if err != nil {
return nil, err
}
return rawCh, nil
}
func (a *aclTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (AddResult, error) {
// TODO: make proper error handling, because there are a lot of corner cases where this will break
var err error
var mode Mode
var changes []*Change // TODO: = addChangesBuf[:0] ...
var notSeenIdx []int
prevHeads := a.tree.Heads()
for idx, ch := range rawChanges {
if a.HasChange(ch.Id) {
continue
}
change, err := NewFromRawChange(ch)
// TODO: think what if we will have incorrect signatures on rawChanges, how everything will work
if err != nil {
continue
}
changes = append(changes, change)
notSeenIdx = append(notSeenIdx, idx)
}
if len(notSeenIdx) == 0 {
return AddResult{
OldHeads: prevHeads,
Heads: prevHeads,
Summary: AddResultSummaryNothing,
}, nil
}
defer func() {
if err != nil {
return
}
err = a.removeOrphans()
if err != nil {
return
}
err = a.treeStorage.SetHeads(a.tree.Heads())
if err != nil {
return
}
if a.updateListener == nil {
return
}
switch mode {
case Append:
a.updateListener.Update(a)
case Rebuild:
a.updateListener.Rebuild(a)
default:
break
}
}()
getAddedChanges := func() []*aclpb.RawChange {
var added []*aclpb.RawChange
for _, idx := range notSeenIdx {
rawChange := rawChanges[idx]
if _, exists := a.tree.attached[rawChange.Id]; exists {
added = append(added, rawChange)
}
}
return added
}
for _, ch := range changes {
err = a.treeStorage.AddChange(ch)
if err != nil {
return AddResult{}, err
}
err = a.treeStorage.AddOrphans(ch.Id)
if err != nil {
return AddResult{}, err
}
}
rebuild := func() (AddResult, error) {
err = a.rebuildFromStorage()
if err != nil {
return AddResult{}, err
}
return AddResult{
OldHeads: prevHeads,
Heads: a.tree.Heads(),
Added: getAddedChanges(),
Summary: AddResultSummaryRebuild,
}, nil
}
mode = a.tree.Add(changes...)
switch mode {
case Nothing:
for _, ch := range changes {
// rebuilding if the snapshot is different from the root
if ch.SnapshotId != a.tree.RootId() && ch.SnapshotId != "" {
return rebuild()
}
}
return AddResult{
OldHeads: prevHeads,
Heads: prevHeads,
Summary: AddResultSummaryNothing,
}, nil
case Rebuild:
return rebuild()
default:
// just rebuilding the state from start without reloading everything from tree storage
// as an optimization we could've started from current heads, but I didn't implement that
a.aclState, err = a.aclStateBuilder.Build()
if err != nil {
return AddResult{}, err
}
return AddResult{
OldHeads: prevHeads,
Heads: a.tree.Heads(),
Added: getAddedChanges(),
Summary: AddResultSummaryAppend,
}, nil
}
}
func (a *aclTree) Iterate(f func(change *Change) bool) {
a.tree.Iterate(a.tree.RootId(), f)
}
func (a *aclTree) IterateFrom(s string, f func(change *Change) bool) {
a.tree.Iterate(s, f)
}
func (a *aclTree) HasChange(s string) bool {
_, attachedExists := a.tree.attached[s]
_, unattachedExists := a.tree.unAttached[s]
_, invalidExists := a.tree.invalidChanges[s]
return attachedExists || unattachedExists || invalidExists
}
func (a *aclTree) Heads() []string {
return a.tree.Heads()
}
func (a *aclTree) Root() *Change {
return a.tree.Root()
}
func (a *aclTree) Close() error {
return nil
}
func (a *aclTree) SnapshotPath() []string {
// TODO: think about caching this
var path []string
// TODO: think that the user may have not all of the snapshots locally
currentSnapshotId := a.tree.RootId()
for currentSnapshotId != "" {
sn, err := a.treeBuilder.loadChange(currentSnapshotId)
if err != nil {
break
}
path = append(path, currentSnapshotId)
currentSnapshotId = sn.SnapshotId
}
return path
}
func (a *aclTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawChange, error) {
// TODO: think about when the clients will have their full acl tree and thus full snapshots
// but no changes after some of the snapshots
var (
isNewDocument = len(theirPath) == 0
ourPath = a.SnapshotPath()
// by default returning everything we have
commonSnapshot = ourPath[len(ourPath)-1] // TODO: root snapshot, probably it is better to have a specific method in treestorage
err error
)
// if this is non-empty request
if !isNewDocument {
commonSnapshot, err = commonSnapshotForTwoPaths(ourPath, theirPath)
if err != nil {
return nil, err
}
}
var rawChanges []*aclpb.RawChange
// using custom load function to skip verification step and save raw changes
load := func(id string) (*Change, error) {
raw, err := a.treeStorage.GetChange(context.Background(), id)
if err != nil {
return nil, err
}
ch, err := NewFromRawChange(raw)
if err != nil {
return nil, err
}
rawChanges = append(rawChanges, raw)
return ch, nil
}
// we presume that we have everything after the common snapshot, though this may not be the case in case of clients and only ACL tree changes
log.With(
zap.Strings("heads", a.tree.Heads()),
zap.String("breakpoint", commonSnapshot),
zap.String("id", a.id)).
Debug("getting all changes from common snapshot")
_, err = a.treeBuilder.dfs(a.tree.Heads(), commonSnapshot, load)
if err != nil {
return nil, err
}
if isNewDocument {
// adding snapshot to raw changes
_, err = load(commonSnapshot)
if err != nil {
return nil, err
}
}
log.With(
zap.Int("len(changes)", len(rawChanges)),
zap.String("id", a.id)).
Debug("returning all changes after common snapshot")
return rawChanges, nil
}
func (a *aclTree) DebugDump() (string, error) {
return a.tree.Graph(ACLDescriptionParser)
}
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
var i int
var j int
log.With(zap.Strings("our path", ourPath), zap.Strings("their path", theirPath)).
Debug("finding common snapshot for two paths")
OuterLoop:
// find starting point from the right
for i = len(ourPath) - 1; i >= 0; i-- {
for j = len(theirPath) - 1; j >= 0; j-- {
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
if ourPath[i] == theirPath[j] {
break OuterLoop
}
}
}
if i < 0 || j < 0 {
return "", ErrNoCommonSnapshot
}
// find last common element of the sequence moving from right to left
for i >= 0 && j >= 0 {
if ourPath[i] == theirPath[j] {
i--
j--
}
}
return ourPath[i+1], nil
}

View File

@ -1,7 +1,9 @@
package tree package tree
import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
type DocTreeValidator interface { type DocTreeValidator interface {
ValidateTree(tree *Tree, aclTree ACLTree) error ValidateTree(tree *Tree, aclList list.ACLList) error
} }
type docTreeValidator struct{} type docTreeValidator struct{}
@ -9,7 +11,7 @@ type docTreeValidator struct{}
func newTreeValidator() DocTreeValidator { func newTreeValidator() DocTreeValidator {
return &docTreeValidator{} return &docTreeValidator{}
} }
func (v *docTreeValidator) ValidateTree(tree *Tree, aclTree ACLTree) error { func (v *docTreeValidator) ValidateTree(tree *Tree, list list.ACLList) error {
// TODO: add validation logic where we check that the change refers to correct acl heads // TODO: add validation logic where we check that the change refers to correct acl heads
// that means that more recent changes should refer to more recent acl heads // that means that more recent changes should refer to more recent acl heads
return nil return nil

View File

@ -1,13 +1,5 @@
package tree package tree
import (
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/gogo/protobuf/proto"
"strings"
"unicode"
)
type DescriptionParser interface { type DescriptionParser interface {
ParseChange(*Change) ([]string, error) ParseChange(*Change) ([]string, error)
} }
@ -19,35 +11,3 @@ type noopDescriptionParser struct{}
func (n noopDescriptionParser) ParseChange(change *Change) ([]string, error) { func (n noopDescriptionParser) ParseChange(change *Change) ([]string, error) {
return []string{"DOC"}, nil return []string{"DOC"}, nil
} }
var ACLDescriptionParser = aclDescriptionParser{}
type aclDescriptionParser struct{}
func (a aclDescriptionParser) ParseChange(changeWrapper *Change) (res []string, err error) {
change := changeWrapper.Content
aclData := &aclpb.ACLChangeACLData{}
if changeWrapper.ParsedModel != nil {
aclData = changeWrapper.ParsedModel.(*aclpb.ACLChangeACLData)
} else {
err = proto.Unmarshal(change.ChangesData, aclData)
if err != nil {
return
}
}
var chSymbs []string
for _, chc := range aclData.AclContent {
tp := fmt.Sprintf("%T", chc.Value)
tp = strings.Replace(tp, "ACLChangeACLContentValueValueOf", "", 1)
res := ""
for _, ts := range tp {
if unicode.IsUpper(ts) {
res += string(ts)
}
}
chSymbs = append(chSymbs, res)
}
return chSymbs, nil
}

View File

@ -2,8 +2,10 @@ package tree
import ( import (
"context" "context"
"errors"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
@ -20,11 +22,37 @@ type TreeUpdateListener interface {
Rebuild(tree DocTree) Rebuild(tree DocTree)
} }
type RWLocker interface {
sync.Locker
RLock()
RUnlock()
}
var ErrHasInvalidChanges = errors.New("the change is invalid")
var ErrNoCommonSnapshot = errors.New("trees doesn't have a common snapshot")
var ErrTreeWithoutIdentity = errors.New("acl tree is created without identity")
type AddResultSummary int
const (
AddResultSummaryNothing AddResultSummary = iota
AddResultSummaryAppend
AddResultSummaryRebuild
)
type AddResult struct {
OldHeads []string
Heads []string
Added []*aclpb.RawChange
// TODO: add summary for changes
Summary AddResultSummary
}
type DocTree interface { type DocTree interface {
RWLocker RWLocker
CommonTree CommonTree
AddContent(ctx context.Context, aclTree ACLTree, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error) AddContent(ctx context.Context, aclList list.ACLList, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error)
AddRawChanges(ctx context.Context, aclTree ACLTree, changes ...*aclpb.RawChange) (AddResult, error) AddRawChanges(ctx context.Context, aclList list.ACLList, changes ...*aclpb.RawChange) (AddResult, error)
} }
type docTree struct { type docTree struct {
@ -48,7 +76,7 @@ type docTree struct {
sync.RWMutex sync.RWMutex
} }
func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountData, listener TreeUpdateListener, aclTree ACLTree) (DocTree, error) { func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountData, listener TreeUpdateListener, aclList list.ACLList) (DocTree, error) {
treeBuilder := newTreeBuilder(t, acc.Decoder) treeBuilder := newTreeBuilder(t, acc.Decoder)
validator := newTreeValidator() validator := newTreeValidator()
@ -64,7 +92,7 @@ func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountDat
notSeenIdxBuf: make([]int, 0, 10), notSeenIdxBuf: make([]int, 0, 10),
identityKeys: make(map[string]signingkey.PubKey), identityKeys: make(map[string]signingkey.PubKey),
} }
err := docTree.rebuildFromStorage(aclTree, nil) err := docTree.rebuildFromStorage(aclList, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -84,7 +112,7 @@ func BuildDocTreeWithIdentity(t treestorage.TreeStorage, acc *account.AccountDat
return docTree, nil return docTree, nil
} }
func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener TreeUpdateListener, aclTree ACLTree) (DocTree, error) { func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener TreeUpdateListener, aclList list.ACLList) (DocTree, error) {
treeBuilder := newTreeBuilder(t, decoder) treeBuilder := newTreeBuilder(t, decoder)
validator := newTreeValidator() validator := newTreeValidator()
@ -99,7 +127,7 @@ func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener Tree
notSeenIdxBuf: make([]int, 0, 10), notSeenIdxBuf: make([]int, 0, 10),
identityKeys: make(map[string]signingkey.PubKey), identityKeys: make(map[string]signingkey.PubKey),
} }
err := docTree.rebuildFromStorage(aclTree, nil) err := docTree.rebuildFromStorage(aclList, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -119,7 +147,7 @@ func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener Tree
return docTree, nil return docTree, nil
} }
func (d *docTree) rebuildFromStorage(aclTree ACLTree, newChanges []*Change) (err error) { func (d *docTree) rebuildFromStorage(aclList list.ACLList, newChanges []*Change) (err error) {
d.treeBuilder.Init(d.identityKeys) d.treeBuilder.Init(d.identityKeys)
d.tree, err = d.treeBuilder.Build(false, newChanges) d.tree, err = d.treeBuilder.Build(false, newChanges)
@ -127,7 +155,7 @@ func (d *docTree) rebuildFromStorage(aclTree ACLTree, newChanges []*Change) (err
return err return err
} }
return d.validator.ValidateTree(d.tree, aclTree) return d.validator.ValidateTree(d.tree, aclList)
} }
func (d *docTree) ID() string { func (d *docTree) ID() string {
@ -142,7 +170,7 @@ func (d *docTree) Storage() treestorage.TreeStorage {
return d.treeStorage return d.treeStorage
} }
func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error) { func (d *docTree) AddContent(ctx context.Context, aclList list.ACLList, content proto.Marshaler, isSnapshot bool) (*aclpb.RawChange, error) {
if d.accountData == nil { if d.accountData == nil {
return nil, ErrTreeWithoutIdentity return nil, ErrTreeWithoutIdentity
} }
@ -153,12 +181,12 @@ func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto
d.updateListener.Update(d) d.updateListener.Update(d)
} }
}() }()
state := aclTree.ACLState() state := aclList.ACLState()
change := &aclpb.Change{ change := &aclpb.Change{
TreeHeadIds: d.tree.Heads(), TreeHeadIds: d.tree.Heads(),
AclHeadIds: aclTree.Heads(), AclHeadId: aclList.Last().Id,
SnapshotBaseId: d.tree.RootId(), SnapshotBaseId: d.tree.RootId(),
CurrentReadKeyHash: state.currentReadKeyHash, CurrentReadKeyHash: state.CurrentReadKeyHash(),
Timestamp: int64(time.Now().Nanosecond()), Timestamp: int64(time.Now().Nanosecond()),
Identity: d.accountData.Identity, Identity: d.accountData.Identity,
IsSnapshot: isSnapshot, IsSnapshot: isSnapshot,
@ -168,7 +196,7 @@ func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto
if err != nil { if err != nil {
return nil, err return nil, err
} }
encrypted, err := state.userReadKeys[state.currentReadKeyHash].Encrypt(marshalledData) encrypted, err := state.UserReadKeys()[state.CurrentReadKeyHash()].Encrypt(marshalledData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -213,7 +241,7 @@ func (d *docTree) AddContent(ctx context.Context, aclTree ACLTree, content proto
return rawCh, nil return rawCh, nil
} }
func (d *docTree) AddRawChanges(ctx context.Context, aclTree ACLTree, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) { func (d *docTree) AddRawChanges(ctx context.Context, aclList list.ACLList, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) {
var mode Mode var mode Mode
// resetting buffers // resetting buffers
@ -300,7 +328,7 @@ func (d *docTree) AddRawChanges(ctx context.Context, aclTree ACLTree, rawChanges
// checking if we have some changes with different snapshot and then rebuilding // checking if we have some changes with different snapshot and then rebuilding
for _, ch := range d.tmpChangesBuf { for _, ch := range d.tmpChangesBuf {
if ch.SnapshotId != d.tree.RootId() && ch.SnapshotId != "" { if ch.SnapshotId != d.tree.RootId() && ch.SnapshotId != "" {
err = d.rebuildFromStorage(aclTree, d.tmpChangesBuf) err = d.rebuildFromStorage(aclList, d.tmpChangesBuf)
if err != nil { if err != nil {
return AddResult{}, err return AddResult{}, err
} }
@ -331,7 +359,7 @@ func (d *docTree) AddRawChanges(ctx context.Context, aclTree ACLTree, rawChanges
default: default:
// just rebuilding the state from start without reloading everything from tree storage // just rebuilding the state from start without reloading everything from tree storage
// as an optimization we could've started from current heads, but I didn't implement that // as an optimization we could've started from current heads, but I didn't implement that
err = d.validator.ValidateTree(d.tree, aclTree) err = d.validator.ValidateTree(d.tree, aclList)
if err != nil { if err != nil {
// rolling back // rolling back
for _, ch := range d.tmpChangesBuf { for _, ch := range d.tmpChangesBuf {
@ -366,8 +394,7 @@ func (d *docTree) IterateFrom(s string, f func(change *Change) bool) {
func (d *docTree) HasChange(s string) bool { func (d *docTree) HasChange(s string) bool {
_, attachedExists := d.tree.attached[s] _, attachedExists := d.tree.attached[s]
_, unattachedExists := d.tree.unAttached[s] _, unattachedExists := d.tree.unAttached[s]
_, invalidExists := d.tree.invalidChanges[s] return attachedExists || unattachedExists
return attachedExists || unattachedExists || invalidExists
} }
func (d *docTree) Heads() []string { func (d *docTree) Heads() []string {
@ -462,3 +489,31 @@ func (d *docTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawCh
func (d *docTree) DebugDump() (string, error) { func (d *docTree) DebugDump() (string, error) {
return d.tree.Graph(NoOpDescriptionParser) return d.tree.Graph(NoOpDescriptionParser)
} }
func commonSnapshotForTwoPaths(ourPath []string, theirPath []string) (string, error) {
var i int
var j int
log.With(zap.Strings("our path", ourPath), zap.Strings("their path", theirPath)).
Debug("finding common snapshot for two paths")
OuterLoop:
// find starting point from the right
for i = len(ourPath) - 1; i >= 0; i-- {
for j = len(theirPath) - 1; j >= 0; j-- {
// most likely there would be only one comparison, because mostly the snapshot path will start from the root for nodes
if ourPath[i] == theirPath[j] {
break OuterLoop
}
}
}
if i < 0 || j < 0 {
return "", ErrNoCommonSnapshot
}
// find last common element of the sequence moving from right to left
for i >= 0 && j >= 0 {
if ourPath[i] == theirPath[j] {
i--
j--
}
}
return ourPath[i+1], nil
}

View File

@ -3,65 +3,66 @@ package tree
import ( import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"time" "time"
) )
func CreateNewTreeStorageWithACL( //
acc *account.AccountData, //func CreateNewTreeStorageWithACL(
build func(builder ACLChangeBuilder) error, // acc *account.AccountData,
create treestorage.CreatorFunc) (treestorage.TreeStorage, error) { // build func(builder list.ACLChangeBuilder) error,
bld := newACLChangeBuilder() // create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
bld.Init( // bld := list.newACLChangeBuilder()
newACLStateWithIdentity(acc.Identity, acc.EncKey, signingkey.NewEd25519PubKeyDecoder()), // bld.Init(
&Tree{}, // list.newACLStateWithIdentity(acc.Identity, acc.EncKey, signingkey.NewEd25519PubKeyDecoder()),
acc) // &Tree{},
err := build(bld) // acc)
if err != nil { // err := build(bld)
return nil, err // if err != nil {
} // return nil, err
// }
change, payload, err := bld.BuildAndApply() //
if err != nil { // change, payload, err := bld.BuildAndApply()
return nil, err // if err != nil {
} // return nil, err
// }
rawChange := &aclpb.RawChange{ //
Payload: payload, // rawChange := &aclpb.RawChange{
Signature: change.Signature(), // Payload: payload,
Id: change.CID(), // Signature: change.Signature(),
} // Id: change.CID(),
header, id, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_ACLTree, "") // }
if err != nil { // header, id, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_ACLTree, "")
return nil, err // if err != nil {
} // return nil, err
// }
thr, err := create(id, header, []*aclpb.RawChange{rawChange}) //
if err != nil { // thr, err := create(id, header, []*aclpb.RawChange{rawChange})
return nil, err // if err != nil {
} // return nil, err
// }
err = thr.SetHeads([]string{change.CID()}) //
if err != nil { // err = thr.SetHeads([]string{change.CID()})
return nil, err // if err != nil {
} // return nil, err
return thr, nil // }
} // return thr, nil
//}
func CreateNewTreeStorage( func CreateNewTreeStorage(
acc *account.AccountData, acc *account.AccountData,
aclTree ACLTree, aclList list.ACLList,
content proto.Marshaler, content proto.Marshaler,
create treestorage.CreatorFunc) (treestorage.TreeStorage, error) { create treestorage.CreatorFunc) (treestorage.TreeStorage, error) {
state := aclTree.ACLState() state := aclList.ACLState()
change := &aclpb.Change{ change := &aclpb.Change{
AclHeadIds: aclTree.Heads(), AclHeadId: aclList.Last().Id,
CurrentReadKeyHash: state.currentReadKeyHash, CurrentReadKeyHash: state.CurrentReadKeyHash(),
Timestamp: int64(time.Now().Nanosecond()), Timestamp: int64(time.Now().Nanosecond()),
Identity: acc.Identity, Identity: acc.Identity,
IsSnapshot: true, IsSnapshot: true,
@ -71,7 +72,7 @@ func CreateNewTreeStorage(
if err != nil { if err != nil {
return nil, err return nil, err
} }
encrypted, err := state.userReadKeys[state.currentReadKeyHash].Encrypt(marshalledData) encrypted, err := state.UserReadKeys()[state.CurrentReadKeyHash()].Encrypt(marshalledData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -95,7 +96,7 @@ func CreateNewTreeStorage(
Signature: signature, Signature: signature,
Id: changeId, Id: changeId,
} }
header, treeId, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_DocTree, aclTree.ID()) header, treeId, err := createTreeHeaderAndId(rawChange, treepb.TreeHeader_DocTree, aclList.ID())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/app" "github.com/anytypeio/go-anytype-infrastructure-experiments/app"
"github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger" "github.com/anytypeio/go-anytype-infrastructure-experiments/app/logger"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/testutils/testchanges/testchangepb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/tree"
"github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb"
@ -131,7 +132,7 @@ func (s *service) CreateACLTree(ctx context.Context) (id string, err error) {
heads []string heads []string
) )
t, err := tree.CreateNewTreeStorageWithACL(acc, func(builder tree.ACLChangeBuilder) error { t, err := tree.CreateNewTreeStorageWithACL(acc, func(builder list.ACLChangeBuilder) error {
err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin) err := builder.UserAdd(acc.Identity, acc.EncKey.GetPublic(), aclpb.ACLChange_Admin)
if err != nil { if err != nil {
return err return err