diff --git a/data/change.go b/data/change.go index eabc4128..906eb7c8 100644 --- a/data/change.go +++ b/data/change.go @@ -38,7 +38,7 @@ func (ch *Change) IsACLChange() bool { return ch.Content.GetAclData() != nil } -func NewChange(id string, ch *pb.ACLChange) (*Change, error) { +func NewChange(id string, ch *pb.ACLChange) *Change { return &Change{ Next: nil, PreviousIds: ch.TreeHeadIds, @@ -46,10 +46,10 @@ func NewChange(id string, ch *pb.ACLChange) (*Change, error) { Content: ch, SnapshotId: ch.SnapshotBaseId, IsSnapshot: ch.GetAclData().GetAclSnapshot() != nil, - }, nil + } } -func NewACLChange(id string, ch *pb.ACLChange) (*Change, error) { +func NewACLChange(id string, ch *pb.ACLChange) *Change { return &Change{ Next: nil, PreviousIds: ch.AclHeadIds, @@ -57,5 +57,5 @@ func NewACLChange(id string, ch *pb.ACLChange) (*Change, error) { Content: ch, SnapshotId: ch.SnapshotBaseId, IsSnapshot: ch.GetAclData().GetAclSnapshot() != nil, - }, nil + } } diff --git a/data/changeloader.go b/data/changeloader.go index 700fd0d6..5382aada 100644 --- a/data/changeloader.go +++ b/data/changeloader.go @@ -14,13 +14,13 @@ type changeLoader struct { identityKeys map[string]threadmodels.SigningPubKey signingPubKeyDecoder threadmodels.SigningPubKeyDecoder thread threadmodels.Thread - changeCreator func(id string, ch *pb.ACLChange) (*Change, error) + changeCreator func(id string, ch *pb.ACLChange) *Change } func newChangeLoader( thread threadmodels.Thread, signingPubKeyDecoder threadmodels.SigningPubKeyDecoder, - changeCreator func(id string, ch *pb.ACLChange) (*Change, error)) *changeLoader { + changeCreator func(id string, ch *pb.ACLChange) *Change) *changeLoader { return &changeLoader{ signingPubKeyDecoder: signingPubKeyDecoder, thread: thread, @@ -48,23 +48,12 @@ func (c *changeLoader) loadChange(id string) (ch *Change, err error) { return nil, err } - aclChange := new(pb.ACLChange) - - // TODO: think what should we do with such cases, because this can be used by attacker to break our tree - if err = proto.Unmarshal(change.Payload, aclChange); err != nil { - return - } - var verified bool - verified, err = c.verify(aclChange.Identity, change.Payload, change.Signature) + aclChange, err := c.makeVerifiedACLChange(change) if err != nil { - return - } - if !verified { - err = fmt.Errorf("the signature of the payload cannot be verified") - return + return nil, err } - ch, err = c.changeCreator(id, aclChange) + ch = c.changeCreator(id, aclChange) c.cache[id] = ch return ch, nil @@ -81,3 +70,22 @@ func (c *changeLoader) verify(identity string, payload, signature []byte) (isVer } return identityKey.Verify(payload, signature) } + +func (c *changeLoader) makeVerifiedACLChange(change *threadmodels.RawChange) (aclChange *pb.ACLChange, err error) { + aclChange = new(pb.ACLChange) + + // TODO: think what should we do with such cases, because this can be used by attacker to break our tree + if err = proto.Unmarshal(change.Payload, aclChange); err != nil { + return + } + var verified bool + verified, err = c.verify(aclChange.Identity, change.Payload, change.Signature) + if err != nil { + return + } + if !verified { + err = fmt.Errorf("the signature of the payload cannot be verified") + return + } + return +} diff --git a/data/document.go b/data/document.go index ebad98dc..45eaa6a4 100644 --- a/data/document.go +++ b/data/document.go @@ -1,7 +1,7 @@ package data import ( - "github.com/anytypeio/go-anytype-infrastructure-experiments/data/pb" + "fmt" "github.com/anytypeio/go-anytype-infrastructure-experiments/data/threadmodels" ) @@ -28,10 +28,9 @@ type Document struct { type UpdateResult int const ( - UpdateResultAppend = iota + UpdateResultNoAction = iota + UpdateResultAppend UpdateResultRebuild - UpdateResultExists - UpdateResultNoAction ) func NewDocument( @@ -53,8 +52,54 @@ func NewDocument( } } -func (d *Document) Update(changes []*pb.ACLChange) (DocumentState, UpdateResult, error) { - return nil, 0, nil +func (d *Document) Update(changes ...*threadmodels.RawChange) (DocumentState, UpdateResult, error) { + var treeChanges []*Change + + for _, ch := range changes { + aclChange, err := d.treeBuilder.makeVerifiedACLChange(ch) + if err != nil { + return nil, UpdateResultNoAction, fmt.Errorf("change with id %s is incorrect: %w", ch.Id, err) + } + + treeChange := d.treeBuilder.changeCreator(ch.Id, aclChange) + treeChanges = append(treeChanges, treeChange) + + err = d.thread.AddChange(ch) + if err != nil { + return nil, UpdateResultNoAction, fmt.Errorf("change with id %s cannot be added: %w", ch.Id, err) + } + } + + for _, ch := range treeChanges { + if ch.IsACLChange() { + res, err := d.Build() + return res, UpdateResultRebuild, err + } + } + + prevHeads := d.docContext.fullTree.Heads() + mode := d.docContext.fullTree.Add(treeChanges...) + switch mode { + case Nothing: + return d.docContext.docState, UpdateResultNoAction, nil + case Rebuild: + res, err := d.Build() + return res, UpdateResultRebuild, err + default: + break + } + + // because for every new change we know it was after any of the previous heads + // each of previous heads must have same "Next" nodes + // so it doesn't matter which one we choose + // so we choose first one + newState, err := d.docStateBuilder.appendFrom(prevHeads[0]) + if err != nil { + res, _ := d.Build() + return res, UpdateResultRebuild, fmt.Errorf("could not add changes to state, rebuilded") + } + + return newState, UpdateResultAppend, nil } func (d *Document) Build() (DocumentState, error) { @@ -77,7 +122,11 @@ func (d *Document) build(fromStart bool) (DocumentState, error) { } if !fromStart { - d.snapshotValidator.Init(d.docContext.aclTree) + err = d.snapshotValidator.Init(d.docContext.aclTree) + if err != nil { + return nil, err + } + valid, err := d.snapshotValidator.ValidateSnapshot(d.docContext.fullTree.root) if err != nil { return nil, err diff --git a/data/documentstatebuilder.go b/data/documentstatebuilder.go index 7bf74ffc..c0ca38e9 100644 --- a/data/documentstatebuilder.go +++ b/data/documentstatebuilder.go @@ -62,3 +62,19 @@ func (d *documentStateBuilder) build() (s DocumentState, err error) { } return s, err } + +func (d *documentStateBuilder) appendFrom(fromId string) (s DocumentState, err error) { + d.tree.Iterate(fromId, func(c *Change) (isContinue bool) { + if c.DecryptedDocumentChange != nil { + s, err = s.ApplyChange(c.DecryptedDocumentChange, c.Id) + if err != nil { + return false + } + } + return true + }) + if err != nil { + return + } + return s, err +} diff --git a/data/threadbuilder/threadbuilder.go b/data/threadbuilder/threadbuilder.go index 991a5ae1..92424ab1 100644 --- a/data/threadbuilder/threadbuilder.go +++ b/data/threadbuilder/threadbuilder.go @@ -30,6 +30,26 @@ type ThreadBuilder struct { keychain *Keychain } +func (t *ThreadBuilder) AddChange(change *threadmodels.RawChange) error { + //TODO implement me + panic("implement me") + return nil +} + +func (t *ThreadBuilder) MaybeHeads() []string { + //TODO implement me + panic("implement me") +} + +func (t *ThreadBuilder) SetMaybeHeads(heads []string) { + //TODO implement me + panic("implement me") +} + +func (t *ThreadBuilder) SetHeads(heads []string) { + //TODO implement me +} + func NewThreadBuilder(keychain *Keychain) *ThreadBuilder { return &ThreadBuilder{ allChanges: make(map[string]*threadChange), diff --git a/data/threadmodels/models.go b/data/threadmodels/models.go index 63367f28..71b6d433 100644 --- a/data/threadmodels/models.go +++ b/data/threadmodels/models.go @@ -2,16 +2,16 @@ package threadmodels import ( "context" - "github.com/gogo/protobuf/proto" ) type Thread interface { ID() string Heads() []string + MaybeHeads() []string GetChange(ctx context.Context, recordID string) (*RawChange, error) - //SetHeads(heads []string) - //AddChanges(*pb.ACLChange) - PushChange(payload proto.Marshaler) (id string, err error) + SetHeads(heads []string) + SetMaybeHeads(heads []string) + AddChange(change *RawChange) error } type RawChange struct {