Change ACLTree methods to include change builder

This commit is contained in:
mcrakhman 2022-07-10 17:14:15 +02:00 committed by Mikhail Iudin
parent 43797b1b84
commit 8372abb4e1
No known key found for this signature in database
GPG Key ID: FAAAA8BAABDFF1C0
3 changed files with 155 additions and 83 deletions

View File

@ -1,13 +1,11 @@
package acltree
import (
"sync"
"github.com/anytypeio/go-anytype-infrastructure-experiments/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/aclchanges/pb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/thread"
"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
@ -25,10 +23,9 @@ type AddResult struct {
Summary AddResultSummary
}
// TODO: Change add change content to include ACLChangeBuilder
type ACLTree interface {
ACLState() *ACLState
AddContent(changeContent *ChangeContent) (*Change, error)
AddContent(f func(builder ChangeBuilder)) (*Change, error)
AddChanges(changes ...*Change) (AddResult, error)
Heads() []string
Iterate(func(change *Change) bool)
@ -40,14 +37,15 @@ type aclTree struct {
thread thread.Thread
accountData *account.AccountData
fullTree *Tree
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
fullTree *Tree
aclTreeFromStart *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
changeBuilder *changeBuilder
sync.Mutex
}
@ -58,6 +56,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
treeBuilder := newTreeBuilder(t, decoder)
snapshotValidator := newSnapshotValidator(decoder, acc)
aclStateBuilder := newACLStateBuilder(decoder, acc)
changeBuilder := newChangeBuilder()
aclTree := &aclTree{
thread: t,
@ -68,6 +67,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
aclTreeBuilder: aclTreeBuilder,
aclStateBuilder: aclStateBuilder,
snapshotValidator: snapshotValidator,
changeBuilder: changeBuilder,
}
err := aclTree.rebuildFromThread(false)
if err != nil {
@ -80,7 +80,7 @@ func BuildACLTree(t thread.Thread, acc *account.AccountData) (ACLTree, error) {
// 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.aclTreeFromStart)
if err != nil {
return err
}
@ -118,13 +118,13 @@ func (a *aclTree) rebuildFromThread(fromStart bool) error {
}
// TODO: remove this from context as this is used only to validate snapshot
a.aclTree, err = a.aclTreeBuilder.Build()
a.aclTreeFromStart, err = a.aclTreeBuilder.Build()
if err != nil {
return err
}
if !fromStart {
err = a.snapshotValidator.Init(a.aclTree)
err = a.snapshotValidator.Init(a.aclTreeFromStart)
if err != nil {
return err
}
@ -150,98 +150,40 @@ func (a *aclTree) rebuildFromThread(fromStart bool) error {
return nil
}
// TODO: this should not be the responsibility of ACLTree, move it somewhere else after testing
func (a *aclTree) getACLHeads() []string {
var aclTreeHeads []string
for _, head := range a.fullTree.Heads() {
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
continue
}
precedingHeads := a.getPrecedingACLHeads(head)
for _, aclHead := range precedingHeads {
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
continue
}
aclTreeHeads = append(aclTreeHeads, aclHead)
}
}
return aclTreeHeads
}
func (a *aclTree) getPrecedingACLHeads(head string) []string {
headChange := a.fullTree.attached[head]
if headChange.Content.GetAclData() != nil {
return []string{head}
} else {
return headChange.Content.AclHeadIds
}
}
func (a *aclTree) ACLState() *ACLState {
return a.aclState
}
func (a *aclTree) AddContent(changeContent *ChangeContent) (*Change, error) {
func (a *aclTree) AddContent(build func(builder ChangeBuilder)) (*Change, error) {
// TODO: add snapshot creation logic
a.Lock()
defer a.Unlock()
marshalled, err := changeContent.ChangesData.Marshal()
a.changeBuilder.Init(a.aclState, a.fullTree, a.accountData)
build(a.changeBuilder)
ch, marshalled, err := a.changeBuilder.Build()
if err != nil {
return nil, err
}
err = a.aclState.applyChange(ch.Id, ch.Content)
if err != nil {
return nil, err
}
encrypted, err := a.aclState.userReadKeys[a.aclState.currentReadKeyHash].
Encrypt(marshalled)
if err != nil {
return nil, err
}
aclChange := &pb.ACLChange{
TreeHeadIds: a.fullTree.Heads(),
AclHeadIds: a.getACLHeads(),
SnapshotBaseId: a.fullTree.RootId(),
AclData: changeContent.ACLData,
ChangesData: encrypted,
CurrentReadKeyHash: a.aclState.currentReadKeyHash,
Timestamp: 0,
Identity: a.accountData.Identity,
}
// TODO: add CID creation logic based on content
ch := NewChange(changeContent.Id, aclChange)
ch.DecryptedDocumentChange = marshalled
fullMarshalledChange, err := proto.Marshal(aclChange)
if err != nil {
return nil, err
}
signature, err := a.accountData.SignKey.Sign(fullMarshalledChange)
if err != nil {
return nil, err
}
if aclChange.AclData != nil {
// we can apply change right away without going through builder, because
err = a.aclState.applyChange(changeContent.Id, aclChange)
if err != nil {
return nil, err
}
}
a.fullTree.AddFast(ch)
err = a.thread.AddRawChange(&thread.RawChange{
Payload: marshalled,
Signature: signature,
Id: changeContent.Id,
Signature: ch.Signature(),
Id: ch.Id,
})
if err != nil {
return nil, err
}
a.thread.SetHeads([]string{ch.Id})
return a.fullTree.attached[changeContent.Id], nil
return ch, nil
}
func (a *aclTree) AddChanges(changes ...*Change) (AddResult, error) {

101
acltree/changebuilder.go Normal file
View File

@ -0,0 +1,101 @@
package acltree
import (
"github.com/anytypeio/go-anytype-infrastructure-experiments/account"
"github.com/anytypeio/go-anytype-infrastructure-experiments/aclchanges/pb"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/keys"
"github.com/gogo/protobuf/proto"
)
type MarshalledChange = []byte
type ACLChangeBuilder interface {
UserAdd(identity string, encryptionKey keys.EncryptionPubKey)
AddId(id string) // TODO: this is only for testing
SetMakeSnapshot(bool) // TODO: who should decide this? probably ACLTree so we can delete it
}
type ChangeBuilder interface {
ACLChangeBuilder
AddChangeContent(marshaler proto.Marshaler) // user code should be responsible for making regular snapshots
}
type changeBuilder struct {
aclState *ACLState
tree *Tree
acc *account.AccountData
aclData *pb.ACLChangeACLData
changeContent proto.Marshaler
id string
makeSnapshot bool
}
func newChangeBuilder() *changeBuilder {
return &changeBuilder{}
}
func (c *changeBuilder) Init(state *ACLState, tree *Tree, acc *account.AccountData) {
c.aclState = state
c.tree = tree
c.acc = acc
c.aclData = &pb.ACLChangeACLData{}
}
func (c *changeBuilder) AddId(id string) {
c.id = id
}
func (c *changeBuilder) SetMakeSnapshot(b bool) {
c.makeSnapshot = b
}
func (c *changeBuilder) UserAdd(identity string, encryptionKey keys.EncryptionPubKey) {
//TODO implement me
panic("implement me")
}
func (c *changeBuilder) Build() (*Change, []byte, error) {
marshalled, err := c.changeContent.Marshal()
if err != nil {
return nil, nil, err
}
encrypted, err := c.aclState.userReadKeys[c.aclState.currentReadKeyHash].
Encrypt(marshalled)
if err != nil {
return nil, nil, err
}
aclChange := &pb.ACLChange{
TreeHeadIds: c.tree.Heads(),
AclHeadIds: c.tree.ACLHeads(),
SnapshotBaseId: c.tree.RootId(), // TODO: add logic for ACL snapshot
AclData: c.aclData,
ChangesData: encrypted,
CurrentReadKeyHash: c.aclState.currentReadKeyHash,
Timestamp: 0,
Identity: c.acc.Identity,
}
// TODO: add CID creation logic based on content
ch := NewChange(c.id, aclChange)
ch.DecryptedDocumentChange = marshalled
fullMarshalledChange, err := proto.Marshal(aclChange)
if err != nil {
return nil, nil, err
}
signature, err := c.acc.SignKey.Sign(fullMarshalledChange)
if err != nil {
return nil, nil, err
}
ch.Sign = signature
return ch, fullMarshalledChange, nil
}
func (c *changeBuilder) AddChangeContent(marshaler proto.Marshaler) {
c.changeContent = marshaler
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/md5"
"fmt"
"github.com/anytypeio/go-anytype-infrastructure-experiments/util/slice"
"sort"
)
@ -288,6 +289,34 @@ func (t *Tree) updateHeads() {
sort.Strings(t.metaHeadIds)
}
func (t *Tree) ACLHeads() []string {
var aclTreeHeads []string
for _, head := range t.Heads() {
if slice.FindPos(aclTreeHeads, head) != -1 { // do not scan known heads
continue
}
precedingHeads := t.getPrecedingACLHeads(head)
for _, aclHead := range precedingHeads {
if slice.FindPos(aclTreeHeads, aclHead) != -1 {
continue
}
aclTreeHeads = append(aclTreeHeads, aclHead)
}
}
return aclTreeHeads
}
func (t *Tree) getPrecedingACLHeads(head string) []string {
headChange := t.attached[head]
if headChange.Content.GetAclData() != nil {
return []string{head}
} else {
return headChange.Content.AclHeadIds
}
}
func (t *Tree) iterate(start *Change, f func(c *Change) (isContinue bool)) {
it := newIterator()
defer freeIterator(it)