diff --git a/commonspace/deletion_test.go b/commonspace/deletion_test.go new file mode 100644 index 00000000..3b5ad8b5 --- /dev/null +++ b/commonspace/deletion_test.go @@ -0,0 +1,184 @@ +package commonspace + +import ( + "context" + "fmt" + "github.com/anytypeio/any-sync/commonspace/object/accountdata" + "github.com/anytypeio/any-sync/commonspace/object/tree/objecttree" + "github.com/anytypeio/any-sync/commonspace/object/tree/treechangeproto" + "github.com/anytypeio/any-sync/commonspace/settings" + "github.com/anytypeio/any-sync/commonspace/settings/settingsstate" + "github.com/anytypeio/any-sync/commonspace/spacestorage" + "github.com/anytypeio/any-sync/util/crypto" + "github.com/stretchr/testify/require" + "math/rand" + "testing" + "time" +) + +func addIncorrectSnapshot(settingsObject settings.SettingsObject, acc *accountdata.AccountKeys, partialIds []string, newId string) (err error) { + factory := settingsstate.NewChangeFactory() + bytes, err := factory.CreateObjectDeleteChange(newId, &settingsstate.State{DeletedIds: partialIds}, true) + if err != nil { + return + } + ch, err := settingsObject.PrepareChange(objecttree.SignableChangeContent{ + Data: bytes, + Key: acc.SignKey, + IsSnapshot: true, + IsEncrypted: false, + Timestamp: time.Now().Unix(), + }) + if err != nil { + return + } + res, err := settingsObject.AddRawChanges(context.Background(), objecttree.RawChangesPayload{ + NewHeads: []string{ch.Id}, + RawChanges: []*treechangeproto.RawTreeChangeWithId{ch}, + }) + if err != nil { + return + } + if res.Mode != objecttree.Rebuild { + return fmt.Errorf("incorrect mode: %d", res.Mode) + } + return +} + +func TestSpaceDeleteIds(t *testing.T) { + fx := newFixture(t) + acc := fx.account.Account() + rk := crypto.NewAES() + ctx := context.Background() + totalObjs := 3000 + + // creating space + sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{ + SigningKey: acc.SignKey, + SpaceType: "type", + ReadKey: rk.Bytes(), + ReplicationKey: 10, + MasterKey: acc.PeerKey, + }) + require.NoError(t, err) + require.NotNil(t, sp) + + // initializing space + spc, err := fx.spaceService.NewSpace(ctx, sp) + require.NoError(t, err) + require.NotNil(t, spc) + err = spc.Init(ctx) + require.NoError(t, err) + // adding space to tree manager + fx.treeManager.space = spc + + var ids []string + for i := 0; i < totalObjs; i++ { + // creating a tree + bytes := make([]byte, 32) + rand.Read(bytes) + doc, err := spc.CreateTree(ctx, objecttree.ObjectTreeCreatePayload{ + PrivKey: acc.SignKey, + ChangeType: "some", + SpaceId: spc.Id(), + IsEncrypted: false, + Seed: bytes, + Timestamp: time.Now().Unix(), + }) + require.NoError(t, err) + tr, err := spc.PutTree(ctx, doc, nil) + require.NoError(t, err) + ids = append(ids, tr.Id()) + tr.Close() + } + // deleting trees + for _, id := range ids { + err = spc.DeleteTree(ctx, id) + require.NoError(t, err) + } + time.Sleep(3 * time.Second) + spc.Close() + require.Equal(t, len(ids), len(fx.treeManager.deletedIds)) +} + +func TestSpaceDeleteIdsIncorrectSnapshot(t *testing.T) { + fx := newFixture(t) + acc := fx.account.Account() + rk := crypto.NewAES() + ctx := context.Background() + totalObjs := 3000 + partialObjs := 300 + + // creating space + sp, err := fx.spaceService.CreateSpace(ctx, SpaceCreatePayload{ + SigningKey: acc.SignKey, + SpaceType: "type", + ReadKey: rk.Bytes(), + ReplicationKey: 10, + MasterKey: acc.PeerKey, + }) + require.NoError(t, err) + require.NotNil(t, sp) + + // initializing space + spc, err := fx.spaceService.NewSpace(ctx, sp) + require.NoError(t, err) + require.NotNil(t, spc) + err = spc.Init(ctx) + require.NoError(t, err) + // adding space to tree manager + fx.treeManager.space = spc + + settingsObject := spc.(*space).settingsObject + var ids []string + for i := 0; i < totalObjs; i++ { + // creating a tree + bytes := make([]byte, 32) + rand.Read(bytes) + doc, err := spc.CreateTree(ctx, objecttree.ObjectTreeCreatePayload{ + PrivKey: acc.SignKey, + ChangeType: "some", + SpaceId: spc.Id(), + IsEncrypted: false, + Seed: bytes, + Timestamp: time.Now().Unix(), + }) + require.NoError(t, err) + tr, err := spc.PutTree(ctx, doc, nil) + require.NoError(t, err) + ids = append(ids, tr.Id()) + tr.Close() + } + // copying storage, so we will have all the trees locally + inmemory := spc.Storage().(*commonStorage).SpaceStorage.(*spacestorage.InMemorySpaceStorage) + storageCopy := inmemory.CopyStorage() + treesCopy := inmemory.AllTrees() + + // deleting trees + for _, id := range ids { + err = spc.DeleteTree(ctx, id) + require.NoError(t, err) + } + // adding snapshot that breaks the state + err = addIncorrectSnapshot(settingsObject, acc, ids[:partialObjs], ids[partialObjs]) + require.NoError(t, err) + // copying the contents of the settings tree + treesCopy[settingsObject.Id()] = settingsObject.Storage() + storageCopy.SetTrees(treesCopy) + spc.Close() + time.Sleep(100 * time.Millisecond) + // now we replace the storage, so the trees are back, but the settings object says that they are deleted + fx.storageProvider.(*spacestorage.InMemorySpaceStorageProvider).SetStorage(storageCopy) + + spc, err = fx.spaceService.NewSpace(ctx, sp) + require.NoError(t, err) + require.NotNil(t, spc) + err = spc.Init(ctx) + require.NoError(t, err) + fx.treeManager.space = spc + fx.treeManager.deletedIds = nil + + // waiting until everything is deleted + time.Sleep(3 * time.Second) + require.Equal(t, len(ids), len(fx.treeManager.deletedIds)) +} diff --git a/commonspace/spacestorage/inmemoryprovider.go b/commonspace/spacestorage/inmemoryprovider.go index b1357962..02dd65ac 100644 --- a/commonspace/spacestorage/inmemoryprovider.go +++ b/commonspace/spacestorage/inmemoryprovider.go @@ -52,3 +52,9 @@ func (i *InMemorySpaceStorageProvider) CreateSpaceStorage(payload SpaceStorageCr i.storages[payload.SpaceHeaderWithId.Id] = spaceStorage return spaceStorage, nil } + +func (i *InMemorySpaceStorageProvider) SetStorage(storage SpaceStorage) { + i.Lock() + defer i.Unlock() + i.storages[storage.Id()] = storage +} diff --git a/commonspace/spacestorage/inmemorystorage.go b/commonspace/spacestorage/inmemorystorage.go index 9f9be094..f6071afb 100644 --- a/commonspace/spacestorage/inmemorystorage.go +++ b/commonspace/spacestorage/inmemorystorage.go @@ -151,3 +151,43 @@ func (i *InMemorySpaceStorage) ReadSpaceHash() (hash string, err error) { func (i *InMemorySpaceStorage) Close() error { return nil } + +func (i *InMemorySpaceStorage) AllTrees() map[string]treestorage.TreeStorage { + i.Lock() + defer i.Unlock() + cp := map[string]treestorage.TreeStorage{} + for id, store := range i.trees { + cp[id] = store + } + return cp +} + +func (i *InMemorySpaceStorage) SetTrees(trees map[string]treestorage.TreeStorage) { + i.Lock() + defer i.Unlock() + i.trees = trees +} + +func (i *InMemorySpaceStorage) CopyStorage() *InMemorySpaceStorage { + i.Lock() + defer i.Unlock() + copyTreeDeleted := map[string]string{} + for id, status := range i.treeDeleted { + copyTreeDeleted[id] = status + } + copyTrees := map[string]treestorage.TreeStorage{} + for id, store := range i.trees { + copyTrees[id] = store + } + return &InMemorySpaceStorage{ + id: i.id, + isDeleted: i.isDeleted, + spaceSettingsId: i.spaceSettingsId, + treeDeleted: copyTreeDeleted, + trees: copyTrees, + aclStorage: i.aclStorage, + spaceHeader: i.spaceHeader, + spaceHash: i.spaceHash, + Mutex: sync.Mutex{}, + } +} diff --git a/commonspace/spaceutils_test.go b/commonspace/spaceutils_test.go index 62403ad3..7b025f73 100644 --- a/commonspace/spaceutils_test.go +++ b/commonspace/spaceutils_test.go @@ -122,7 +122,7 @@ func (m *mockConf) NodeTypes(nodeId string) []nodeconf.NodeType { } // -// Mock PeerManager implementation +// Mock PeerManager // type mockPeerManager struct { @@ -141,7 +141,7 @@ func (p *mockPeerManager) GetResponsiblePeers(ctx context.Context) (peers []peer } // -// Mock PeerManagerProvider implementation +// Mock PeerManagerProvider // type mockPeerManagerProvider struct { @@ -160,7 +160,7 @@ func (m *mockPeerManagerProvider) NewPeerManager(ctx context.Context, spaceId st } // -// Mock Pool implementation +// Mock Pool // type mockPool struct { @@ -191,7 +191,7 @@ func (m *mockPool) DialOneOf(ctx context.Context, peerIds []string) (peer.Peer, } // -// Mock Config implementation +// Mock Config // type mockConfig struct { @@ -214,12 +214,13 @@ func (m *mockConfig) GetSpace() Config { } // -// Mock TreeManager implementation +// Mock TreeManager // type mockTreeManager struct { - space Space - cache ocache.OCache + space Space + cache ocache.OCache + deletedIds []string } func (t *mockTreeManager) Init(a *app.App) (err error) { @@ -260,6 +261,7 @@ func (t *mockTreeManager) DeleteTree(ctx context.Context, spaceId, treeId string if err != nil { return } + t.deletedIds = append(t.deletedIds, treeId) _, err = t.cache.Remove(ctx, treeId) return nil } @@ -311,7 +313,3 @@ func newFixture(t *testing.T) *spaceFixture { require.NoError(t, err) return fx } - -func TestSpace(t *testing.T) { - _ = newFixture(t) -}