diff --git a/commonspace/object/tree/objecttree/changebuilder.go b/commonspace/object/tree/objecttree/changebuilder.go index 28b71ec9..31653b34 100644 --- a/commonspace/object/tree/objecttree/changebuilder.go +++ b/commonspace/object/tree/objecttree/changebuilder.go @@ -6,7 +6,6 @@ import ( "github.com/anytypeio/any-sync/util/cidutil" "github.com/anytypeio/any-sync/util/crypto" "github.com/gogo/protobuf/proto" - "time" ) var ErrEmptyChange = errors.New("change payload should not be empty") @@ -20,6 +19,7 @@ type BuilderContent struct { PrivKey crypto.PrivKey ReadKey crypto.SymKey Content []byte + Timestamp int64 } type InitialContent struct { @@ -166,7 +166,7 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange * AclHeadId: payload.AclHeadId, SnapshotBaseId: payload.SnapshotBaseId, ReadKeyId: payload.ReadKeyId, - Timestamp: time.Now().Unix(), + Timestamp: payload.Timestamp, Identity: identity, IsSnapshot: payload.IsSnapshot, } diff --git a/commonspace/object/tree/objecttree/objecttree.go b/commonspace/object/tree/objecttree/objecttree.go index f81a9ddd..d0fdc1ec 100644 --- a/commonspace/object/tree/objecttree/objecttree.go +++ b/commonspace/object/tree/objecttree/objecttree.go @@ -251,6 +251,10 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt } readKey = ot.currentReadKey } + timestamp := content.Timestamp + if timestamp <= 0 { + timestamp = time.Now().Unix() + } cnt = BuilderContent{ TreeHeadIds: ot.tree.Heads(), AclHeadId: ot.aclList.Head().Id, @@ -260,6 +264,7 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt PrivKey: content.Key, ReadKey: readKey, Content: content.Data, + Timestamp: content.Timestamp, } return } diff --git a/commonspace/object/tree/objecttree/objecttree_test.go b/commonspace/object/tree/objecttree/objecttree_test.go index 4d531d9d..bde899e9 100644 --- a/commonspace/object/tree/objecttree/objecttree_test.go +++ b/commonspace/object/tree/objecttree/objecttree_test.go @@ -10,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "testing" + "time" ) type testTreeContext struct { @@ -19,13 +20,13 @@ type testTreeContext struct { objTree ObjectTree } -func prepareAclList(t *testing.T) list.AclList { +func prepareAclList(t *testing.T) (list.AclList, *accountdata.AccountKeys) { randKeys, err := accountdata.NewRandom() require.NoError(t, err) aclList, err := list.NewTestDerivedAcl("spaceId", randKeys) require.NoError(t, err, "building acl list should be without error") - return aclList + return aclList, randKeys } func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) { @@ -88,7 +89,54 @@ func prepareContext( } func TestObjectTree(t *testing.T) { - aclList := prepareAclList(t) + aclList, keys := prepareAclList(t) + ctx := context.Background() + + t.Run("add content", func(t *testing.T) { + root, err := CreateObjectTreeRoot(ObjectTreeCreatePayload{ + PrivKey: keys.SignKey, + ChangeType: "changeType", + ChangePayload: nil, + SpaceId: "spaceId", + IsEncrypted: true, + }, aclList) + require.NoError(t, err) + store, _ := treestorage.NewInMemoryTreeStorage(root, []string{root.Id}, []*treechangeproto.RawTreeChangeWithId{root}) + oTree, err := BuildObjectTree(store, aclList) + require.NoError(t, err) + + t.Run("0 timestamp is changed", func(t *testing.T) { + res, err := oTree.AddContent(ctx, SignableChangeContent{ + Data: []byte("some"), + Key: keys.SignKey, + IsSnapshot: false, + IsEncrypted: true, + Timestamp: 0, + }) + require.NoError(t, err) + require.Len(t, oTree.Heads(), 1) + require.Equal(t, res.Added[0].Id, oTree.Heads()[0]) + ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true) + require.NoError(t, err) + require.NotEqual(t, ch.Timestamp, 0) + }) + t.Run("timestamp is set correctly", func(t *testing.T) { + someTs := time.Now().Add(time.Hour).Unix() + res, err := oTree.AddContent(ctx, SignableChangeContent{ + Data: []byte("some"), + Key: keys.SignKey, + IsSnapshot: false, + IsEncrypted: true, + Timestamp: someTs, + }) + require.NoError(t, err) + require.Len(t, oTree.Heads(), 1) + require.Equal(t, res.Added[0].Id, oTree.Heads()[0]) + ch, err := oTree.(*objectTree).changeBuilder.Unmarshall(res.Added[0], true) + require.NoError(t, err) + require.Equal(t, ch.Timestamp, someTs) + }) + }) t.Run("add simple", func(t *testing.T) { ctx := prepareTreeContext(t, aclList) diff --git a/commonspace/object/tree/objecttree/signablecontent.go b/commonspace/object/tree/objecttree/signablecontent.go index e56ac9f1..a48fef5b 100644 --- a/commonspace/object/tree/objecttree/signablecontent.go +++ b/commonspace/object/tree/objecttree/signablecontent.go @@ -4,9 +4,16 @@ import ( "github.com/anytypeio/any-sync/util/crypto" ) +// SignableChangeContent is a payload to be passed when we are creating change type SignableChangeContent struct { - Data []byte - Key crypto.PrivKey - IsSnapshot bool + // Data is a data provided by the client + Data []byte + // Key is the key which will be used to sign the change + Key crypto.PrivKey + // IsSnapshot tells if the change has snapshot of all previous data + IsSnapshot bool + // IsEncrypted tells if we encrypt the data with the relevant symmetric key IsEncrypted bool + // Timestamp is a timestamp of change, if it is <= 0, then we use current timestamp + Timestamp int64 }