Merge pull request #85 from anytypeio/GO-1227-add-custom-timestamps

This commit is contained in:
Mikhail Rakhmanov 2023-05-04 16:30:11 +02:00 committed by GitHub
commit ba5ca3d6be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 91 deletions

View File

@ -6,7 +6,6 @@ import (
"github.com/anytypeio/any-sync/util/cidutil" "github.com/anytypeio/any-sync/util/cidutil"
"github.com/anytypeio/any-sync/util/crypto" "github.com/anytypeio/any-sync/util/crypto"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"time"
) )
var ErrEmptyChange = errors.New("change payload should not be empty") var ErrEmptyChange = errors.New("change payload should not be empty")
@ -20,6 +19,7 @@ type BuilderContent struct {
PrivKey crypto.PrivKey PrivKey crypto.PrivKey
ReadKey crypto.SymKey ReadKey crypto.SymKey
Content []byte Content []byte
Timestamp int64
} }
type InitialContent struct { type InitialContent struct {
@ -166,7 +166,7 @@ func (c *changeBuilder) Build(payload BuilderContent) (ch *Change, rawIdChange *
AclHeadId: payload.AclHeadId, AclHeadId: payload.AclHeadId,
SnapshotBaseId: payload.SnapshotBaseId, SnapshotBaseId: payload.SnapshotBaseId,
ReadKeyId: payload.ReadKeyId, ReadKeyId: payload.ReadKeyId,
Timestamp: time.Now().Unix(), Timestamp: payload.Timestamp,
Identity: identity, Identity: identity,
IsSnapshot: payload.IsSnapshot, IsSnapshot: payload.IsSnapshot,
} }

View File

@ -251,6 +251,10 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
} }
readKey = ot.currentReadKey readKey = ot.currentReadKey
} }
timestamp := content.Timestamp
if timestamp <= 0 {
timestamp = time.Now().Unix()
}
cnt = BuilderContent{ cnt = BuilderContent{
TreeHeadIds: ot.tree.Heads(), TreeHeadIds: ot.tree.Heads(),
AclHeadId: ot.aclList.Head().Id, AclHeadId: ot.aclList.Head().Id,
@ -260,6 +264,7 @@ func (ot *objectTree) prepareBuilderContent(content SignableChangeContent) (cnt
PrivKey: content.Key, PrivKey: content.Key,
ReadKey: readKey, ReadKey: readKey,
Content: content.Data, Content: content.Data,
Timestamp: timestamp,
} }
return return
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"testing" "testing"
"time"
) )
type testTreeContext struct { type testTreeContext struct {
@ -19,13 +20,13 @@ type testTreeContext struct {
objTree ObjectTree objTree ObjectTree
} }
func prepareAclList(t *testing.T) list.AclList { func prepareAclList(t *testing.T) (list.AclList, *accountdata.AccountKeys) {
randKeys, err := accountdata.NewRandom() randKeys, err := accountdata.NewRandom()
require.NoError(t, err) require.NoError(t, err)
aclList, err := list.NewTestDerivedAcl("spaceId", randKeys) aclList, err := list.NewTestDerivedAcl("spaceId", randKeys)
require.NoError(t, err, "building acl list should be without error") require.NoError(t, err, "building acl list should be without error")
return aclList return aclList, randKeys
} }
func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) { func prepareHistoryTreeDeps(aclList list.AclList) (*MockChangeCreator, objectTreeDeps) {
@ -88,7 +89,57 @@ func prepareContext(
} }
func TestObjectTree(t *testing.T) { 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 to current", func(t *testing.T) {
start := time.Now()
res, err := oTree.AddContent(ctx, SignableChangeContent{
Data: []byte("some"),
Key: keys.SignKey,
IsSnapshot: false,
IsEncrypted: true,
Timestamp: 0,
})
end := time.Now()
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.GreaterOrEqual(t, start.Unix(), ch.Timestamp)
require.LessOrEqual(t, end.Unix(), ch.Timestamp)
})
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) { t.Run("add simple", func(t *testing.T) {
ctx := prepareTreeContext(t, aclList) ctx := prepareTreeContext(t, aclList)

View File

@ -5,8 +5,6 @@ import (
"github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto" "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto"
"github.com/anytypeio/any-sync/commonspace/object/tree/treestorage" "github.com/anytypeio/any-sync/commonspace/object/tree/treestorage"
"github.com/anytypeio/any-sync/util/crypto" "github.com/anytypeio/any-sync/util/crypto"
"math/rand"
"time"
) )
type ObjectTreeCreatePayload struct { type ObjectTreeCreatePayload struct {
@ -15,6 +13,8 @@ type ObjectTreeCreatePayload struct {
ChangePayload []byte ChangePayload []byte
SpaceId string SpaceId string
IsEncrypted bool IsEncrypted bool
Seed []byte
Timestamp int64
} }
type HistoryTreeParams struct { type HistoryTreeParams struct {
@ -85,19 +85,6 @@ func nonVerifiableTreeDeps(
} }
} }
func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
if err != nil {
return
}
return createObjectTreeRoot(payload, time.Now().Unix(), bytes, aclList)
}
func DeriveObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
return createObjectTreeRoot(payload, 0, nil, aclList)
}
func BuildEmptyDataObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) { func BuildEmptyDataObjectTree(treeStorage treestorage.TreeStorage, aclList list.AclList) (ObjectTree, error) {
rootChange, err := treeStorage.Root() rootChange, err := treeStorage.Root()
if err != nil { if err != nil {
@ -168,54 +155,7 @@ func BuildHistoryTree(params HistoryTreeParams) (HistoryTree, error) {
return buildHistoryTree(deps, params) return buildHistoryTree(deps, params)
} }
func CreateDerivedObjectTree( func CreateObjectTreeRoot(payload ObjectTreeCreatePayload, aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
payload ObjectTreeCreatePayload,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
return createObjectTree(payload, 0, nil, aclList, createStorage)
}
func CreateObjectTree(
payload ObjectTreeCreatePayload,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
bytes := make([]byte, 32)
_, err = rand.Read(bytes)
if err != nil {
return
}
return createObjectTree(payload, time.Now().Unix(), bytes, aclList, createStorage)
}
func createObjectTree(
payload ObjectTreeCreatePayload,
timestamp int64,
seed []byte,
aclList list.AclList,
createStorage treestorage.TreeStorageCreatorFunc) (objTree ObjectTree, err error) {
raw, err := createObjectTreeRoot(payload, timestamp, seed, aclList)
if err != nil {
return
}
// create storage
st, err := createStorage(treestorage.TreeStorageCreatePayload{
RootRawChange: raw,
Changes: []*treechangeproto.RawTreeChangeWithId{raw},
Heads: []string{raw.Id},
})
if err != nil {
return
}
return BuildObjectTree(st, aclList)
}
func createObjectTreeRoot(
payload ObjectTreeCreatePayload,
timestamp int64,
seed []byte,
aclList list.AclList) (root *treechangeproto.RawTreeChangeWithId, err error) {
aclList.RLock() aclList.RLock()
aclHeadId := aclList.Head().Id aclHeadId := aclList.Head().Id
aclList.RUnlock() aclList.RUnlock()
@ -229,8 +169,8 @@ func createObjectTreeRoot(
SpaceId: payload.SpaceId, SpaceId: payload.SpaceId,
ChangeType: payload.ChangeType, ChangeType: payload.ChangeType,
ChangePayload: payload.ChangePayload, ChangePayload: payload.ChangePayload,
Timestamp: timestamp, Timestamp: payload.Timestamp,
Seed: seed, Seed: payload.Seed,
} }
_, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt) _, root, err = NewChangeBuilder(crypto.NewKeyStorage(), nil).BuildRoot(cnt)

View File

@ -4,9 +4,16 @@ import (
"github.com/anytypeio/any-sync/util/crypto" "github.com/anytypeio/any-sync/util/crypto"
) )
// SignableChangeContent is a payload to be passed when we are creating change
type SignableChangeContent struct { type SignableChangeContent struct {
// Data is a data provided by the client
Data []byte Data []byte
// Key is the key which will be used to sign the change
Key crypto.PrivKey Key crypto.PrivKey
// IsSnapshot tells if the change has snapshot of all previous data
IsSnapshot bool IsSnapshot bool
// IsEncrypted tells if we encrypt the data with the relevant symmetric key
IsEncrypted bool IsEncrypted bool
// Timestamp is a timestamp of change, if it is <= 0, then we use current timestamp
Timestamp int64
} }

View File

@ -88,7 +88,6 @@ type Space interface {
DebugAllHeads() []headsync.TreeHeads DebugAllHeads() []headsync.TreeHeads
Description() (SpaceDescription, error) Description() (SpaceDescription, error)
DeriveTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error)
CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error)
PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error) PutTree(ctx context.Context, payload treestorage.TreeStorageCreatePayload, listener updatelistener.UpdateListener) (t objecttree.ObjectTree, err error)
BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error) BuildTree(ctx context.Context, id string, opts BuildTreeOpts) (t objecttree.ObjectTree, err error)
@ -242,23 +241,6 @@ func (s *space) DebugAllHeads() []headsync.TreeHeads {
return s.headSync.DebugAllHeads() return s.headSync.DebugAllHeads()
} }
func (s *space) DeriveTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) {
if s.isClosed.Load() {
err = ErrSpaceClosed
return
}
root, err := objecttree.DeriveObjectTreeRoot(payload, s.aclList)
if err != nil {
return
}
res = treestorage.TreeStorageCreatePayload{
RootRawChange: root,
Changes: []*treechangeproto.RawTreeChangeWithId{root},
Heads: []string{root.Id},
}
return
}
func (s *space) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) { func (s *space) CreateTree(ctx context.Context, payload objecttree.ObjectTreeCreatePayload) (res treestorage.TreeStorageCreatePayload, err error) {
if s.isClosed.Load() { if s.isClosed.Load() {
err = ErrSpaceClosed err = ErrSpaceClosed