diff --git a/pkg/acl/list/aclstatebuilder.go b/pkg/acl/list/aclstatebuilder.go index 51dd9139..3586dec9 100644 --- a/pkg/acl/list/aclstatebuilder.go +++ b/pkg/acl/list/aclstatebuilder.go @@ -7,7 +7,6 @@ import ( ) type aclStateBuilder struct { - log ACLList identity string key encryptionkey.PrivKey decoder keys.Decoder @@ -25,12 +24,7 @@ func newACLStateBuilder() *aclStateBuilder { return &aclStateBuilder{} } -func (sb *aclStateBuilder) Init(aclLog ACLList) error { - sb.log = aclLog - return nil -} - -func (sb *aclStateBuilder) Build() (*ACLState, error) { +func (sb *aclStateBuilder) Build(records []*Record) (*ACLState, error) { var ( err error state *ACLState @@ -41,11 +35,12 @@ func (sb *aclStateBuilder) Build() (*ACLState, error) { } else { state = newACLState() } - - sb.log.Iterate(func(c *Record) (isContinue bool) { - err = state.applyChangeAndUpdate(c) - return err == nil - }) + for _, rec := range records { + err = state.applyChangeAndUpdate(rec) + if err != nil { + return nil, err + } + } return state, err } diff --git a/pkg/acl/list/list.go b/pkg/acl/list/list.go index c7fa37d8..dc355f4c 100644 --- a/pkg/acl/list/list.go +++ b/pkg/acl/list/list.go @@ -1,8 +1,12 @@ package list import ( + "context" + "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/pkg/acl/tree" + "sync" ) type IterFunc = func(record *Record) (IsContinue bool) @@ -11,7 +15,7 @@ type ACLList interface { tree.RWLocker ID() string Header() *aclpb.Header - ACLState() ACLState + ACLState() *ACLState IsAfter(first string, second string) (bool, error) Head() *Record Get(id string) (*Record, error) @@ -19,43 +23,123 @@ type ACLList interface { IterateFrom(startId string, iterFunc IterFunc) } -//func (t *ACLListStorageBuilder) IsAfter(first string, second string) (bool, error) { -// firstRec, okFirst := t.indexes[first] -// secondRec, okSecond := t.indexes[second] -// if !okFirst || !okSecond { -// return false, fmt.Errorf("not all entries are there: first (%b), second (%b)", okFirst, okSecond) -// } -// return firstRec > secondRec, nil -//} -// -//func (t *ACLListStorageBuilder) Head() *list.Record { -// return t.records[len(t.records)-1] -//} -// -//func (t *ACLListStorageBuilder) Get(id string) (*list.Record, error) { -// recIdx, ok := t.indexes[id] -// if !ok { -// return nil, fmt.Errorf("no such record") -// } -// return t.records[recIdx], nil -//} -// -//func (t *ACLListStorageBuilder) Iterate(iterFunc list.IterFunc) { -// for _, rec := range t.records { -// if !iterFunc(rec) { -// return -// } -// } -//} -// -//func (t *ACLListStorageBuilder) IterateFrom(startId string, iterFunc list.IterFunc) { -// recIdx, ok := t.indexes[startId] -// if !ok { -// return -// } -// for i := recIdx; i < len(t.records); i++ { -// if !iterFunc(t.records[i]) { -// return -// } -// } -//} +type aclList struct { + header *aclpb.Header + records []*Record + indexes map[string]int + id string + + builder *aclStateBuilder + aclState *ACLState + + sync.RWMutex +} + +func BuildACLListWithIdentity(acc *account.AccountData, storage Storage) (ACLList, error) { + builder := newACLStateBuilderWithIdentity(acc.Decoder, acc) + header, err := storage.Header() + if err != nil { + return nil, err + } + + rawRecord, err := storage.Head() + if err != nil { + return nil, err + } + + record, err := NewFromRawRecord(rawRecord) + if err != nil { + return nil, err + } + records := []*Record{record} + + for record.Content.PrevId != "" { + rawRecord, err = storage.GetRecord(context.Background(), record.Content.PrevId) + if err != nil { + return nil, err + } + record, err = NewFromRawRecord(rawRecord) + if err != nil { + return nil, err + } + records = append(records, record) + } + + indexes := make(map[string]int) + for i, j := 0, len(records)-1; i < j; i, j = i+1, j-1 { + records[i], records[j] = records[j], records[i] + indexes[records[i].Id] = i + indexes[records[j].Id] = j + } + // adding missed index if needed + if len(records)%2 != 0 { + indexes[records[len(records)/2].Id] = len(records) / 2 + } + + state, err := builder.Build(records) + if err != nil { + return nil, err + } + + return &aclList{ + header: header, + records: records, + indexes: indexes, + builder: builder, + aclState: state, + RWMutex: sync.RWMutex{}, + }, nil +} + +func (a *aclList) ID() string { + return a.id +} + +func (a *aclList) Header() *aclpb.Header { + return a.header +} + +func (a *aclList) ACLState() *ACLState { + return a.aclState +} + +func (a *aclList) IsAfter(first string, second string) (bool, error) { + firstRec, okFirst := a.indexes[first] + secondRec, okSecond := a.indexes[second] + if !okFirst || !okSecond { + return false, fmt.Errorf("not all entries are there: first (%b), second (%b)", okFirst, okSecond) + } + return firstRec > secondRec, nil +} + +func (a *aclList) Head() *Record { + return a.records[len(a.records)-1] +} + +func (a *aclList) Get(id string) (*Record, error) { + recIdx, ok := a.indexes[id] + if !ok { + return nil, fmt.Errorf("no such record") + } + return a.records[recIdx], nil +} + +func (a *aclList) Iterate(iterFunc IterFunc) { + for _, rec := range a.records { + if !iterFunc(rec) { + return + } + } +} + +func (a *aclList) IterateFrom(startId string, iterFunc IterFunc) { + recIdx, ok := a.indexes[startId] + if !ok { + return + } + for i := recIdx; i < len(a.records); i++ { + if !iterFunc(a.records[i]) { + return + } + } +} diff --git a/pkg/acl/list/record.go b/pkg/acl/list/record.go index e20351d2..3227db27 100644 --- a/pkg/acl/list/record.go +++ b/pkg/acl/list/record.go @@ -1,6 +1,9 @@ package list -import "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" + "github.com/gogo/protobuf/proto" +) type Record struct { Id string @@ -15,3 +18,17 @@ func NewRecord(id string, aclRecord *aclpb.Record) *Record { Content: aclRecord, } } + +func NewFromRawRecord(rawRec *aclpb.RawRecord) (*Record, error) { + aclRec := &aclpb.Record{} + err := proto.Unmarshal(rawRec.Payload, aclRec) + if err != nil { + return nil, err + } + + return &Record{ + Id: rawRec.Id, + Content: aclRec, + Sign: rawRec.Signature, + }, nil +} diff --git a/pkg/acl/list/storage.go b/pkg/acl/list/storage.go index 22fcb732..d606aa94 100644 --- a/pkg/acl/list/storage.go +++ b/pkg/acl/list/storage.go @@ -6,7 +6,7 @@ import ( ) type Storage interface { - ID() string + ID() (string, error) Head() (*aclpb.RawRecord, error) Header() (*aclpb.Header, error) GetRecord(ctx context.Context, id string) (*aclpb.RawRecord, error) diff --git a/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go b/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go index 2116eac7..1037770e 100644 --- a/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go +++ b/pkg/acl/testutils/acllistbuilder/liststoragebuilder.go @@ -101,8 +101,8 @@ func (t *ACLListStorageBuilder) AddRecord(ctx context.Context, rec *aclpb.Record panic("implement me") } -func (t *ACLListStorageBuilder) ID() string { - return t.id +func (t *ACLListStorageBuilder) ID() (string, error) { + return t.id, nil } func (t *ACLListStorageBuilder) GetKeychain() *Keychain { diff --git a/pkg/acl/tree/doctree.go b/pkg/acl/tree/doctree.go index eeece7e6..8e1cfad5 100644 --- a/pkg/acl/tree/doctree.go +++ b/pkg/acl/tree/doctree.go @@ -10,6 +10,7 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" "github.com/gogo/protobuf/proto" "go.uber.org/zap" "sync" @@ -130,6 +131,22 @@ func BuildDocTree(t treestorage.TreeStorage, decoder keys.Decoder, listener Tree if err != nil { return nil, err } + storageHeads, err := t.Heads() + if err != nil { + return nil, err + } + // comparing rebuilt heads with heads in storage + // in theory it can happen that we didn't set heads because the process has crashed + // therefore we want to set them later + if !slice.UnsortedEquals(storageHeads, docTree.tree.Heads()) { + log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", docTree.tree.Heads())). + Errorf("the heads in storage and tree are different") + err = t.SetHeads(docTree.tree.Heads()) + if err != nil { + return nil, err + } + } + docTree.id, err = t.ID() if err != nil { return nil, err @@ -324,11 +341,22 @@ func (d *docTree) AddRawChanges(ctx context.Context, aclList list.ACLList, rawCh return added } + rollback := func() { + for _, ch := range d.tmpChangesBuf { + if _, exists := d.tree.attached[ch.Id]; exists { + delete(d.tree.attached, ch.Id) + } else if _, exists := d.tree.unAttached[ch.Id]; exists { + delete(d.tree.unAttached, ch.Id) + } + } + } + // checking if we have some changes with different snapshot and then rebuilding for _, ch := range d.tmpChangesBuf { if ch.SnapshotId != d.tree.RootId() && ch.SnapshotId != "" { err = d.rebuildFromStorage(aclList, d.tmpChangesBuf) if err != nil { + rollback() return AddResult{}, err } @@ -360,14 +388,7 @@ func (d *docTree) AddRawChanges(ctx context.Context, aclList list.ACLList, rawCh // as an optimization we could've started from current heads, but I didn't implement that err = d.validator.ValidateTree(d.tree, aclList) if err != nil { - // rolling back - for _, ch := range d.tmpChangesBuf { - if _, exists := d.tree.attached[ch.Id]; exists { - delete(d.tree.attached, ch.Id) - } else if _, exists := d.tree.unAttached[ch.Id]; exists { - delete(d.tree.unAttached, ch.Id) - } - } + rollback() return AddResult{}, ErrHasInvalidChanges } @@ -444,6 +465,7 @@ func (d *docTree) ChangesAfterCommonSnapshot(theirPath []string) ([]*aclpb.RawCh return nil, err } } + // TODO: if the snapshot is in the tree we probably can skip going to the DB var rawChanges []*aclpb.RawChange // using custom load function to skip verification step and save raw changes load := func(id string) (*Change, error) { diff --git a/service/account/service.go b/service/account/service.go index ed66fe1e..f617fdd4 100644 --- a/service/account/service.go +++ b/service/account/service.go @@ -61,7 +61,7 @@ func (s *service) Init(ctx context.Context, a *app.App) (err error) { Identity: identity, SignKey: signKey, EncKey: decodedEncryptionKey.(encryptionkey.PrivKey), - Decoder: signingkey.NewEd25519PubKeyDecoder(), + Decoder: signingkey.NewEDPubKeyDecoder(), } s.peerId = acc.PeerId diff --git a/service/treecache/service.go b/service/treecache/service.go index 37b58f21..f442b5ab 100644 --- a/service/treecache/service.go +++ b/service/treecache/service.go @@ -7,7 +7,6 @@ import ( "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/tree" - "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/treestorage/treepb" "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/ocache" "github.com/anytypeio/go-anytype-infrastructure-experiments/service/account" "github.com/anytypeio/go-anytype-infrastructure-experiments/service/storage" @@ -23,7 +22,7 @@ var log = logger.NewNamed("treecache") type Service interface { Do(ctx context.Context, treeId string, f TreeFunc) error - Add(ctx context.Context, treeId string, header *treepb.TreeHeader, changes []*aclpb.RawChange, f TreeFunc) error + Add(ctx context.Context, treeId string, header *aclpb.Header, changes []*aclpb.RawChange, f TreeFunc) error } type service struct { @@ -91,10 +90,10 @@ func (s *service) loadTree(ctx context.Context, id string) (ocache.Object, error return nil, err } - switch header.Type { - case treepb.TreeHeader_ACLTree: + switch header.DocType { + case aclpb.Header_ACL: return tree.BuildACLTreeWithIdentity(t, s.account.Account(), nil) - case treepb.TreeHeader_DocTree: + case aclpb.Header_DocTree: break default: return nil, fmt.Errorf("incorrect type")