diff --git a/pkg/acl/tree/changebuilder.go b/pkg/acl/tree/changebuilder.go new file mode 100644 index 00000000..b9f2dc9d --- /dev/null +++ b/pkg/acl/tree/changebuilder.go @@ -0,0 +1,119 @@ +package tree + +import ( + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/aclchanges/aclpb" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/asymmetric/signingkey" + "github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys/symmetric" + "github.com/gogo/protobuf/proto" + "time" +) + +const componentBuilder = "tree.changebuilder" + +type BuilderContent struct { + treeHeadIds []string + aclHeadId string + snapshotBaseId string + currentReadKeyHash uint64 + identity string + isSnapshot bool + signingKey signingkey.PrivKey + readKey *symmetric.Key + content proto.Marshaler +} + +type ChangeBuilder interface { + ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) + ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) + BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) +} + +type changeBuilder struct { + keys *keychain +} + +func newChangeBuilder(keys *keychain) *changeBuilder { + return &changeBuilder{keys: keys} +} + +func (c *changeBuilder) ConvertFromRaw(rawChange *aclpb.RawChange) (ch *Change, err error) { + unmarshalled := &aclpb.Change{} + err = proto.Unmarshal(rawChange.Payload, unmarshalled) + if err != nil { + return nil, err + } + + ch = NewChange(rawChange.Id, unmarshalled, rawChange.Signature) + return +} + +func (c *changeBuilder) ConvertFromRawAndVerify(rawChange *aclpb.RawChange) (ch *Change, err error) { + unmarshalled := &aclpb.Change{} + ch, err = c.ConvertFromRaw(rawChange) + if err != nil { + return nil, err + } + + identityKey, err := c.keys.getOrAdd(unmarshalled.Identity) + if err != nil { + return + } + + res, err := identityKey.Verify(rawChange.Payload, rawChange.Signature) + if err != nil { + return + } + if !res { + err = ErrIncorrectSignature + } + + return +} + +func (c *changeBuilder) BuildContent(payload BuilderContent) (ch *Change, raw *aclpb.RawChange, err error) { + aclChange := &aclpb.Change{ + TreeHeadIds: payload.treeHeadIds, + AclHeadId: payload.aclHeadId, + SnapshotBaseId: payload.snapshotBaseId, + CurrentReadKeyHash: payload.currentReadKeyHash, + Timestamp: int64(time.Now().Nanosecond()), + Identity: payload.identity, + IsSnapshot: payload.isSnapshot, + } + marshalledData, err := payload.content.Marshal() + if err != nil { + return + } + + encrypted, err := payload.readKey.Encrypt(marshalledData) + if err != nil { + return + } + aclChange.ChangesData = encrypted + + fullMarshalledChange, err := proto.Marshal(aclChange) + if err != nil { + return + } + + signature, err := payload.signingKey.Sign(fullMarshalledChange) + if err != nil { + return + } + + id, err := cid.NewCIDFromBytes(fullMarshalledChange) + if err != nil { + return + } + + ch = NewChange(id, aclChange, signature) + ch.ParsedModel = payload.content + + raw = &aclpb.RawChange{ + Payload: fullMarshalledChange, + Signature: signature, + Id: id, + } + return +} diff --git a/pkg/acl/tree/changevalidator.go b/pkg/acl/tree/changevalidator.go index 0c77d169..63c515f0 100644 --- a/pkg/acl/tree/changevalidator.go +++ b/pkg/acl/tree/changevalidator.go @@ -6,19 +6,19 @@ import ( "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/list" ) -type DocTreeValidator interface { +type ObjectTreeValidator interface { ValidateTree(tree *Tree, aclList list.ACLList) error } -type docTreeValidator struct{} +type objectTreeValidator struct{} -func newTreeValidator() DocTreeValidator { - return &docTreeValidator{} +func newTreeValidator() ObjectTreeValidator { + return &objectTreeValidator{} } -func (v *docTreeValidator) ValidateTree(tree *Tree, aclList list.ACLList) (err error) { - // 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 +func (v *objectTreeValidator) ValidateTree(tree *Tree, aclList list.ACLList) (err error) { + aclList.RLock() + defer aclList.RUnlock() var ( perm list.UserPermissionPair state = aclList.ACLState() diff --git a/pkg/acl/tree/keychain.go b/pkg/acl/tree/keychain.go index 2e6eca83..582a8102 100644 --- a/pkg/acl/tree/keychain.go +++ b/pkg/acl/tree/keychain.go @@ -13,6 +13,7 @@ type keychain struct { func newKeychain() *keychain { return &keychain{ decoder: signingkey.NewEDPubKeyDecoder(), + keys: make(map[string]signingkey.PubKey), } } diff --git a/pkg/acl/tree/objecttree.go b/pkg/acl/tree/objecttree.go index 474dd1ff..2eae9247 100644 --- a/pkg/acl/tree/objecttree.go +++ b/pkg/acl/tree/objecttree.go @@ -6,12 +6,9 @@ import ( "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/storage" - "github.com/anytypeio/go-anytype-infrastructure-experiments/util/cid" "github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice" - "github.com/gogo/protobuf/proto" "go.uber.org/zap" "sync" - "time" ) type ObjectTreeUpdateListener interface { @@ -64,24 +61,24 @@ type ObjectTree interface { Storage() storage.TreeStorage DebugDump() (string, error) - AddContent(ctx context.Context, aclList list.ACLList, content SignableChangeContent) (*aclpb.RawChange, error) - AddRawChanges(ctx context.Context, aclList list.ACLList, changes ...*aclpb.RawChange) (AddResult, error) + AddContent(ctx context.Context, content SignableChangeContent) (*aclpb.RawChange, error) + AddRawChanges(ctx context.Context, changes ...*aclpb.RawChange) (AddResult, error) Close() error } type objectTree struct { treeStorage storage.TreeStorage + changeBuilder ChangeBuilder updateListener ObjectTreeUpdateListener + validator ObjectTreeValidator + treeBuilder *treeBuilder + aclList list.ACLList id string header *aclpb.Header tree *Tree - treeBuilder *treeBuilder - validator DocTreeValidator - kch *keychain - // buffers difSnapshotBuf []*aclpb.RawChange tmpChangesBuf []*Change @@ -93,26 +90,52 @@ type objectTree struct { sync.RWMutex } -func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateListener, aclList list.ACLList) (ObjectTree, error) { - treeBuilder := newTreeBuilder(treeStorage) - validator := newTreeValidator() +type objectTreeDeps struct { + changeBuilder ChangeBuilder + treeBuilder *treeBuilder + treeStorage storage.TreeStorage + updateListener ObjectTreeUpdateListener + validator ObjectTreeValidator + aclList list.ACLList +} - objTree := &objectTree{ - treeStorage: treeStorage, - tree: nil, +func defaultObjectTreeDeps( + treeStorage storage.TreeStorage, + listener ObjectTreeUpdateListener, + aclList list.ACLList) objectTreeDeps { + + keychain := newKeychain() + changeBuilder := newChangeBuilder(keychain) + treeBuilder := newTreeBuilder(treeStorage, changeBuilder) + return objectTreeDeps{ + changeBuilder: changeBuilder, treeBuilder: treeBuilder, - validator: validator, + treeStorage: treeStorage, updateListener: listener, + validator: newTreeValidator(), + aclList: aclList, + } +} + +func buildObjectTree(deps objectTreeDeps) (ObjectTree, error) { + objTree := &objectTree{ + treeStorage: deps.treeStorage, + updateListener: deps.updateListener, + treeBuilder: deps.treeBuilder, + validator: deps.validator, + aclList: deps.aclList, + changeBuilder: deps.changeBuilder, + tree: nil, tmpChangesBuf: make([]*Change, 0, 10), difSnapshotBuf: make([]*aclpb.RawChange, 0, 10), notSeenIdxBuf: make([]int, 0, 10), - kch: newKeychain(), } - err := objTree.rebuildFromStorage(aclList, nil) + + err := objTree.rebuildFromStorage(nil) if err != nil { return nil, err } - storageHeads, err := treeStorage.Heads() + storageHeads, err := objTree.treeStorage.Heads() if err != nil { return nil, err } @@ -123,30 +146,35 @@ func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateL if !slice.UnsortedEquals(storageHeads, objTree.tree.Heads()) { log.With(zap.Strings("storage", storageHeads), zap.Strings("rebuilt", objTree.tree.Heads())). Errorf("the heads in storage and objTree are different") - err = treeStorage.SetHeads(objTree.tree.Heads()) + err = objTree.treeStorage.SetHeads(objTree.tree.Heads()) if err != nil { return nil, err } } - objTree.id, err = treeStorage.ID() + objTree.id, err = objTree.treeStorage.ID() if err != nil { return nil, err } - objTree.header, err = treeStorage.Header() + objTree.header, err = objTree.treeStorage.Header() if err != nil { return nil, err } - if listener != nil { - listener.Rebuild(objTree) + if objTree.updateListener != nil { + objTree.updateListener.Rebuild(objTree) } return objTree, nil } -func (ot *objectTree) rebuildFromStorage(aclList list.ACLList, newChanges []*Change) (err error) { - ot.treeBuilder.Init(ot.kch) +func BuildObjectTree(treeStorage storage.TreeStorage, listener ObjectTreeUpdateListener, aclList list.ACLList) (ObjectTree, error) { + deps := defaultObjectTreeDeps(treeStorage, listener, aclList) + return buildObjectTree(deps) +} + +func (ot *objectTree) rebuildFromStorage(newChanges []*Change) (err error) { + ot.treeBuilder.Reset() ot.tree, err = ot.treeBuilder.Build(newChanges) if err != nil { @@ -157,7 +185,7 @@ func (ot *objectTree) rebuildFromStorage(aclList list.ACLList, newChanges []*Cha // but obviously they are not roots, because of the way how we construct the tree ot.tree.clearPossibleRoots() - return ot.validator.ValidateTree(ot.tree, aclList) + return ot.validator.ValidateTree(ot.tree, ot.aclList) } func (ot *objectTree) ID() string { @@ -172,83 +200,54 @@ func (ot *objectTree) Storage() storage.TreeStorage { return ot.treeStorage } -func (ot *objectTree) AddContent(ctx context.Context, aclList list.ACLList, content SignableChangeContent) (rawChange *aclpb.RawChange, err error) { +func (ot *objectTree) AddContent(ctx context.Context, content SignableChangeContent) (rawChange *aclpb.RawChange, err error) { + ot.aclList.Lock() defer func() { + ot.aclList.Unlock() if ot.updateListener != nil { ot.updateListener.Update(ot) } }() - state := aclList.ACLState() // special method for own keys - aclChange := &aclpb.Change{ - TreeHeadIds: ot.tree.Heads(), - AclHeadId: aclList.Head().Id, - SnapshotBaseId: ot.tree.RootId(), - CurrentReadKeyHash: state.CurrentReadKeyHash(), - Timestamp: int64(time.Now().Nanosecond()), - Identity: content.Identity, - IsSnapshot: content.IsSnapshot, - } - - marshalledData, err := content.Proto.Marshal() - if err != nil { - return nil, err - } + state := ot.aclList.ACLState() // special method for own keys readKey, err := state.CurrentReadKey() if err != nil { return nil, err } - encrypted, err := readKey.Encrypt(marshalledData) - if err != nil { - return nil, err + payload := BuilderContent{ + treeHeadIds: ot.tree.Heads(), + aclHeadId: ot.aclList.Head().Id, + snapshotBaseId: ot.tree.RootId(), + currentReadKeyHash: state.CurrentReadKeyHash(), + identity: content.Identity, + isSnapshot: content.IsSnapshot, + signingKey: content.Key, + readKey: readKey, + content: content.Proto, } - aclChange.ChangesData = encrypted - - fullMarshalledChange, err := proto.Marshal(aclChange) - if err != nil { - return nil, err - } - - signature, err := content.Key.Sign(fullMarshalledChange) - if err != nil { - return nil, err - } - - id, err := cid.NewCIDFromBytes(fullMarshalledChange) - if err != nil { - return nil, err - } - - docChange := NewChange(id, aclChange, signature) - docChange.ParsedModel = content - + objChange, rawChange, err := ot.changeBuilder.BuildContent(payload) if content.IsSnapshot { // clearing tree, because we already fixed everything in the last snapshot ot.tree = &Tree{} } - err = ot.tree.AddMergedHead(docChange) + err = ot.tree.AddMergedHead(objChange) if err != nil { panic(err) } - rawChange = &aclpb.RawChange{ - Payload: fullMarshalledChange, - Signature: docChange.Signature(), - Id: docChange.Id, - } err = ot.treeStorage.AddRawChange(rawChange) if err != nil { return } - err = ot.treeStorage.SetHeads([]string{docChange.Id}) + err = ot.treeStorage.SetHeads([]string{objChange.Id}) return } -func (ot *objectTree) AddRawChanges(ctx context.Context, aclList list.ACLList, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) { +func (ot *objectTree) AddRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (addResult AddResult, err error) { var mode Mode - mode, addResult, err = ot.addRawChanges(ctx, aclList, rawChanges...) + mode, addResult, err = ot.addRawChanges(ctx, rawChanges...) if err != nil { return } @@ -285,14 +284,14 @@ func (ot *objectTree) AddRawChanges(ctx context.Context, aclList list.ACLList, r return } -func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, rawChanges ...*aclpb.RawChange) (mode Mode, addResult AddResult, err error) { +func (ot *objectTree) addRawChanges(ctx context.Context, rawChanges ...*aclpb.RawChange) (mode Mode, addResult AddResult, err error) { // resetting buffers ot.tmpChangesBuf = ot.tmpChangesBuf[:0] ot.notSeenIdxBuf = ot.notSeenIdxBuf[:0] ot.difSnapshotBuf = ot.difSnapshotBuf[:0] ot.newSnapshotsBuf = ot.newSnapshotsBuf[:0] - // this will be returned to client so we shouldn't use buffer here + // this will be returned to client, so we shouldn't use buffer here prevHeadsCopy := make([]string, 0, len(ot.tree.Heads())) copy(prevHeadsCopy, ot.tree.Heads()) @@ -303,7 +302,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, r } var change *Change - change, err = newVerifiedChangeFromRaw(ch, ot.kch) + change, err = ot.changeBuilder.ConvertFromRawAndVerify(ch) if err != nil { return } @@ -370,10 +369,10 @@ func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, r // checking if we have some changes with different snapshot and then rebuilding for _, ch := range ot.tmpChangesBuf { if isOldSnapshot(ch) { - err = ot.rebuildFromStorage(aclList, ot.tmpChangesBuf) + err = ot.rebuildFromStorage(ot.tmpChangesBuf) if err != nil { // rebuilding without new changes - ot.rebuildFromStorage(aclList, nil) + ot.rebuildFromStorage(nil) return } @@ -401,7 +400,7 @@ func (ot *objectTree) addRawChanges(ctx context.Context, aclList list.ACLList, r 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 - err = ot.validator.ValidateTree(ot.tree, aclList) + err = ot.validator.ValidateTree(ot.tree, ot.aclList) if err != nil { rollback() err = ErrHasInvalidChanges diff --git a/pkg/acl/tree/objecttree_test.go b/pkg/acl/tree/objecttree_test.go new file mode 100644 index 00000000..fbdd85d9 --- /dev/null +++ b/pkg/acl/tree/objecttree_test.go @@ -0,0 +1,21 @@ +package tree + +import ( + "context" + "github.com/anytypeio/go-anytype-infrastructure-experiments/app" + "github.com/anytypeio/go-anytype-infrastructure-experiments/pkg/acl/storage" + "testing" +) + +func TestObjectTree(t *testing.T) { + a := &app.App{} + inmemory := storage.NewInMemoryTreeStorage(...) + app.RegisterWithType[storage.TreeStorage](a, inmemory) + app.RegisterWithType[]() + + a.Start(context.Background()) + objectTree := app.MustComponentWithType[ObjectTree](a).(ObjectTree) + + + +} diff --git a/pkg/acl/tree/tree.go b/pkg/acl/tree/tree.go index e251d911..c15b15d1 100644 --- a/pkg/acl/tree/tree.go +++ b/pkg/acl/tree/tree.go @@ -115,6 +115,7 @@ func (t *Tree) Add(changes ...*Change) (mode Mode) { return Append } +// RemoveInvalidChange removes all the changes that are descendants of id func (t *Tree) RemoveInvalidChange(id string) { stack := []string{id} // removing all children of this id (either next or unattached) diff --git a/pkg/acl/tree/treebuilder.go b/pkg/acl/tree/treebuilder.go index f5af5800..e284336d 100644 --- a/pkg/acl/tree/treebuilder.go +++ b/pkg/acl/tree/treebuilder.go @@ -17,25 +17,26 @@ var ( ) type treeBuilder struct { - cache map[string]*Change - kch *keychain - tree *Tree treeStorage storage.TreeStorage + builder ChangeBuilder + + cache map[string]*Change + tree *Tree // buffers idStack []string loadBuffer []*Change } -func newTreeBuilder(t storage.TreeStorage) *treeBuilder { +func newTreeBuilder(storage storage.TreeStorage, builder ChangeBuilder) *treeBuilder { return &treeBuilder{ - treeStorage: t, + treeStorage: storage, + builder: builder, } } -func (tb *treeBuilder) Init(kch *keychain) { +func (tb *treeBuilder) Reset() { tb.cache = make(map[string]*Change) - tb.kch = kch tb.tree = &Tree{} } @@ -131,7 +132,7 @@ func (tb *treeBuilder) loadChange(id string) (ch *Change, err error) { return nil, err } - ch, err = newVerifiedChangeFromRaw(change, tb.kch) + ch, err = tb.builder.ConvertFromRawAndVerify(change) if err != nil { return nil, err }