diff --git a/archive/adopter/adopter.go b/archive/adopter/adopter.go index 118efbdb..f1644f47 100644 --- a/archive/adopter/adopter.go +++ b/archive/adopter/adopter.go @@ -152,10 +152,10 @@ func (ad *adopter) AdoptArchive(ctx context.Context, req *nodesyncproto.AdoptArc if !ok { return nil, nodesyncproto.ErrArchiveObjectMissing } - if err = ad.storage.IndexStorage().MarkArchivedRemote(ctx, req.SpaceId, req.OldHash, req.NewHash, req.CompressedSize, req.UncompressedSize); err != nil { + if err = ad.storage.IndexStorage().MarkArchivedRemote(ctx, req.SpaceId, req.NewHash, req.CompressedSize, req.UncompressedSize); err != nil { return nil, err } - if _, err = ad.nodeHead.SetHead(req.SpaceId, req.OldHash, req.NewHash); err != nil { + if _, err = ad.nodeHead.SetHead(req.SpaceId, req.NewHash); err != nil { log.Warn("can't set nodehead after adoption", zap.String("spaceId", req.SpaceId), zap.Error(err)) err = nil } @@ -173,7 +173,7 @@ func (ad *adopter) AdoptArchive(ctx context.Context, req *nodesyncproto.AdoptArc // diverged copy could be older than the sender's and must not justify its // deletion. func alreadyHaveResponse(entry nodestorage.SpaceStatusEntry, req *nodesyncproto.AdoptArchiveRequest) *nodesyncproto.AdoptArchiveResponse { - if entry.NewHash == req.NewHash && entry.OldHash == req.OldHash { + if entry.NewHash == req.NewHash { return &nodesyncproto.AdoptArchiveResponse{ Result: nodesyncproto.AdoptArchiveResult_AdoptArchiveAlreadyHaveSame, } diff --git a/archive/adopter/adopter_test.go b/archive/adopter/adopter_test.go index f3e04000..c59e16d8 100644 --- a/archive/adopter/adopter_test.go +++ b/archive/adopter/adopter_test.go @@ -60,8 +60,8 @@ func TestAdopter_AdoptArchive(t *testing.T) { fx.storage.EXPECT().SpaceExists(req.SpaceId).Return(false) fx.archiveStore.EXPECT().CopyFrom(ctx, req.SrcKey, req.SpaceId).Return(nil) fx.archiveStore.EXPECT().Exists(ctx, req.SpaceId).Return(true, nil) - fx.indexStorage.EXPECT().MarkArchivedRemote(ctx, req.SpaceId, "oldHash", "newHash", int64(10), int64(20)).Return(nil) - fx.nodeHead.EXPECT().SetHead(req.SpaceId, "oldHash", "newHash").Return(0, nil) + fx.indexStorage.EXPECT().MarkArchivedRemote(ctx, req.SpaceId, "newHash", int64(10), int64(20)).Return(nil) + fx.nodeHead.EXPECT().SetHead(req.SpaceId, "newHash").Return(0, nil) resp, err := fx.AdoptArchive(ctx, req) require.NoError(t, err) @@ -76,8 +76,8 @@ func TestAdopter_AdoptArchive(t *testing.T) { fx.storage.EXPECT().SpaceExists(req.SpaceId).Return(false) fx.archiveStore.EXPECT().CopyFrom(ctx, req.SrcKey, req.SpaceId).Return(nil) fx.archiveStore.EXPECT().Exists(ctx, req.SpaceId).Return(true, nil) - fx.indexStorage.EXPECT().MarkArchivedRemote(ctx, req.SpaceId, "oldHash", "newHash", int64(10), int64(20)).Return(nil) - fx.nodeHead.EXPECT().SetHead(req.SpaceId, "oldHash", "newHash").Return(0, nil) + fx.indexStorage.EXPECT().MarkArchivedRemote(ctx, req.SpaceId, "newHash", int64(10), int64(20)).Return(nil) + fx.nodeHead.EXPECT().SetHead(req.SpaceId, "newHash").Return(0, nil) fx.archive.EXPECT().QueueRestore(req.SpaceId) resp, err := fx.AdoptArchive(ctx, req) @@ -88,7 +88,7 @@ func TestAdopter_AdoptArchive(t *testing.T) { fx := newFixture(t) req := adoptReq() fx.expectNodePeer() - fx.indexStorage.EXPECT().SpaceStatusEntry(ctx, req.SpaceId).Return(nodestorage.SpaceStatusEntry{Status: nodestorage.SpaceStatusOk, OldHash: "oldHash", NewHash: "newHash"}, nil) + fx.indexStorage.EXPECT().SpaceStatusEntry(ctx, req.SpaceId).Return(nodestorage.SpaceStatusEntry{Status: nodestorage.SpaceStatusOk, NewHash: "newHash"}, nil) fx.storage.EXPECT().SpaceExists(req.SpaceId).Return(true) resp, err := fx.AdoptArchive(ctx, req) @@ -99,7 +99,7 @@ func TestAdopter_AdoptArchive(t *testing.T) { fx := newFixture(t) req := adoptReq() fx.expectNodePeer() - fx.indexStorage.EXPECT().SpaceStatusEntry(ctx, req.SpaceId).Return(nodestorage.SpaceStatusEntry{Status: nodestorage.SpaceStatusArchived, OldHash: "oldHash", NewHash: "newHash"}, nil) + fx.indexStorage.EXPECT().SpaceStatusEntry(ctx, req.SpaceId).Return(nodestorage.SpaceStatusEntry{Status: nodestorage.SpaceStatusArchived, NewHash: "newHash"}, nil) fx.archiveStore.EXPECT().Exists(ctx, req.SpaceId).Return(true, nil) resp, err := fx.AdoptArchive(ctx, req) @@ -114,8 +114,8 @@ func TestAdopter_AdoptArchive(t *testing.T) { fx.archiveStore.EXPECT().Exists(ctx, req.SpaceId).Return(false, nil) fx.archiveStore.EXPECT().CopyFrom(ctx, req.SrcKey, req.SpaceId).Return(nil) fx.archiveStore.EXPECT().Exists(ctx, req.SpaceId).Return(true, nil) - fx.indexStorage.EXPECT().MarkArchivedRemote(ctx, req.SpaceId, "oldHash", "newHash", int64(10), int64(20)).Return(nil) - fx.nodeHead.EXPECT().SetHead(req.SpaceId, "oldHash", "newHash").Return(0, nil) + fx.indexStorage.EXPECT().MarkArchivedRemote(ctx, req.SpaceId, "newHash", int64(10), int64(20)).Return(nil) + fx.nodeHead.EXPECT().SetHead(req.SpaceId, "newHash").Return(0, nil) resp, err := fx.AdoptArchive(ctx, req) require.NoError(t, err) diff --git a/archive/archive.go b/archive/archive.go index 246d1cbd..4bc24e52 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -47,12 +47,12 @@ type Archive interface { // keeping the local DB and its status intact. Used to ship live spaces // through the shared archive store during migration. The caller must ensure // the space is not already archived: opening an archived space triggers a - // restore. Returns the heads read from the snapshot itself (they exactly - // describe the uploaded object, unlike the asynchronously updated index) + // restore. Returns the heads hash read from the snapshot itself (it exactly + // describes the uploaded object, unlike the asynchronously updated index) // and the snapshot sizes. // The return types are primitives on purpose: a struct would make the // generated mock import this package and create test-only import cycles. - ForceArchive(ctx context.Context, spaceId string) (oldHash, newHash string, compressedSize, uncompressedSize int64, err error) + ForceArchive(ctx context.Context, spaceId string) (hash string, compressedSize, uncompressedSize int64, err error) // QueueRestore schedules a background restore of an archived space // (used for eager adoption of migrated spaces). QueueRestore(spaceId string) @@ -213,12 +213,12 @@ func (a *archive) Archive(ctx context.Context, spaceId string) (err error) { return } -func (a *archive) ForceArchive(ctx context.Context, spaceId string) (oldHash, newHash string, compressedSize, uncompressedSize int64, err error) { +func (a *archive) ForceArchive(ctx context.Context, spaceId string) (hash string, compressedSize, uncompressedSize int64, err error) { // DumpStorage backups the db into a temp dir; it works for open spaces too err = a.storageProvider.DumpStorage(ctx, spaceId, func(path string) error { // read the heads from the snapshot: they describe exactly what the // uploaded object contains - oldHash, newHash, err = readSnapshotHeads(ctx, spaceId, filepath.Join(path, "store.db")) + hash, err = readSnapshotHead(ctx, spaceId, filepath.Join(path, "store.db")) if err != nil { return err } @@ -243,10 +243,10 @@ func (a *archive) ForceArchive(ctx context.Context, spaceId string) (oldHash, ne return } -// readSnapshotHeads reads the space state directly from the snapshot db +// readSnapshotHead reads the space state directly from the snapshot db // (schema of commonspace/headsync/statestorage, including the legacy -// single-hash fallback). -func readSnapshotHeads(ctx context.Context, spaceId, dbPath string) (oldHash, newHash string, err error) { +// "h" key fallback). +func readSnapshotHead(ctx context.Context, spaceId, dbPath string) (hash string, err error) { db, err := anystore.Open(ctx, dbPath, nil) if err != nil { return @@ -262,13 +262,11 @@ func readSnapshotHeads(ctx context.Context, spaceId, dbPath string) (oldHash, ne if err != nil { return } - oldHash = doc.Value().GetString("oh") - newHash = doc.Value().GetString("nh") - if oldHash == "" || newHash == "" { - oldHash = doc.Value().GetString("h") - newHash = oldHash + hash = doc.Value().GetString("nh") + if hash == "" { + hash = doc.Value().GetString("h") } - return oldHash, newHash, nil + return hash, nil } // createGzipFromStore creates store.gz from store.db inside spaceDir. diff --git a/archive/archive_test.go b/archive/archive_test.go index 614af156..d8a72bb6 100644 --- a/archive/archive_test.go +++ b/archive/archive_test.go @@ -173,10 +173,9 @@ func TestArchive_ForceArchive(t *testing.T) { }) // no MarkArchived, no local deletion: the space stays live - oldHash, newHash, compressedSize, uncompressedSize, err := fx.ForceArchive(ctx, spaceId) + hash, compressedSize, uncompressedSize, err := fx.ForceArchive(ctx, spaceId) require.NoError(t, err) - assert.Equal(t, "snap-old", oldHash) - assert.Equal(t, "snap-new", newHash) + assert.Equal(t, "snap-new", hash) assert.Greater(t, compressedSize, int64(0)) assert.Greater(t, uncompressedSize, int64(0)) assert.Equal(t, int(compressedSize), uploaded) diff --git a/archive/mock_archive/mock_archive.go b/archive/mock_archive/mock_archive.go index b687db09..b386ed86 100644 --- a/archive/mock_archive/mock_archive.go +++ b/archive/mock_archive/mock_archive.go @@ -56,15 +56,14 @@ func (mr *MockArchiveMockRecorder) Close(ctx any) *gomock.Call { } // ForceArchive mocks base method. -func (m *MockArchive) ForceArchive(ctx context.Context, spaceId string) (string, string, int64, int64, error) { +func (m *MockArchive) ForceArchive(ctx context.Context, spaceId string) (string, int64, int64, error) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "ForceArchive", ctx, spaceId) ret0, _ := ret[0].(string) - ret1, _ := ret[1].(string) + ret1, _ := ret[1].(int64) ret2, _ := ret[2].(int64) - ret3, _ := ret[3].(int64) - ret4, _ := ret[4].(error) - return ret0, ret1, ret2, ret3, ret4 + ret3, _ := ret[3].(error) + return ret0, ret1, ret2, ret3 } // ForceArchive indicates an expected call of ForceArchive. diff --git a/nodehead/mock_nodehead/mock_nodehead.go b/nodehead/mock_nodehead/mock_nodehead.go index ce1e6f78..7606bcc1 100644 --- a/nodehead/mock_nodehead/mock_nodehead.go +++ b/nodehead/mock_nodehead/mock_nodehead.go @@ -85,21 +85,6 @@ func (mr *MockNodeHeadMockRecorder) GetHead(spaceId any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHead", reflect.TypeOf((*MockNodeHead)(nil).GetHead), spaceId) } -// GetOldHead mocks base method. -func (m *MockNodeHead) GetOldHead(spaceId string) (string, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetOldHead", spaceId) - ret0, _ := ret[0].(string) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetOldHead indicates an expected call of GetOldHead. -func (mr *MockNodeHeadMockRecorder) GetOldHead(spaceId any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetOldHead", reflect.TypeOf((*MockNodeHead)(nil).GetOldHead), spaceId) -} - // Init mocks base method. func (m *MockNodeHead) Init(a *app.App) error { m.ctrl.T.Helper() @@ -186,16 +171,16 @@ func (mr *MockNodeHeadMockRecorder) Run(ctx any) *gomock.Call { } // SetHead mocks base method. -func (m *MockNodeHead) SetHead(spaceId, oldHead, newHead string) (int, error) { +func (m *MockNodeHead) SetHead(spaceId, head string) (int, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetHead", spaceId, oldHead, newHead) + ret := m.ctrl.Call(m, "SetHead", spaceId, head) ret0, _ := ret[0].(int) ret1, _ := ret[1].(error) return ret0, ret1 } // SetHead indicates an expected call of SetHead. -func (mr *MockNodeHeadMockRecorder) SetHead(spaceId, oldHead, newHead any) *gomock.Call { +func (mr *MockNodeHeadMockRecorder) SetHead(spaceId, head any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHead", reflect.TypeOf((*MockNodeHead)(nil).SetHead), spaceId, oldHead, newHead) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetHead", reflect.TypeOf((*MockNodeHead)(nil).SetHead), spaceId, head) } diff --git a/nodehead/nodehead.go b/nodehead/nodehead.go index 88bf0742..9b771a9d 100644 --- a/nodehead/nodehead.go +++ b/nodehead/nodehead.go @@ -35,9 +35,8 @@ func New() NodeHead { // NodeHead keeps current state of all spaces by partitions type NodeHead interface { - SetHead(spaceId, oldHead, newHead string) (part int, err error) + SetHead(spaceId, head string) (part int, err error) GetHead(spaceId string) (head string, err error) - GetOldHead(spaceId string) (head string, err error) DeleteHeads(spaceId string) error ReloadHeadFromStore(ctx context.Context, spaceId string) error LDiff(partId int) ldiff.Diff @@ -53,18 +52,16 @@ type nodeStorage interface { type nodeHead struct { mu sync.Mutex partitions map[int]ldiff.Diff - oldHashes map[string]string nodeconf nodeconf.NodeConf spaceStore nodeStorage } func (n *nodeHead) Init(a *app.App) (err error) { n.partitions = map[int]ldiff.Diff{} - n.oldHashes = map[string]string{} n.nodeconf = a.MustComponent(nodeconf.CName).(nodeconf.NodeConf) n.spaceStore = a.MustComponent(spacestorage.CName).(nodeStorage) - n.spaceStore.OnWriteHash(func(_ context.Context, spaceId, oldHash, newHash string) { - if _, e := n.SetHead(spaceId, oldHash, newHash); e != nil { + n.spaceStore.OnWriteHash(func(_ context.Context, spaceId, hash string) { + if _, e := n.SetHead(spaceId, hash); e != nil { log.Error("can't set head", zap.Error(e)) } }) @@ -88,7 +85,7 @@ func (n *nodeHead) Run(ctx context.Context) (err error) { var total int err = n.spaceStore.IndexStorage().ReadHashes(ctx, func(update nodestorage.SpaceUpdate) (bool, error) { total++ - if _, e := n.SetHead(update.SpaceId, update.OldHash, update.NewHash); e != nil { + if _, e := n.SetHead(update.SpaceId, update.NewHash); e != nil { log.Error("can't set head", zap.Error(e)) return false, e } @@ -104,7 +101,6 @@ func (n *nodeHead) Run(ctx context.Context) (err error) { func (n *nodeHead) DeleteHeads(spaceId string) error { n.mu.Lock() defer n.mu.Unlock() - delete(n.oldHashes, spaceId) part := n.nodeconf.Partition(spaceId) if ld, ok := n.partitions[part]; ok { return ld.RemoveId(spaceId) @@ -112,7 +108,7 @@ func (n *nodeHead) DeleteHeads(spaceId string) error { return nil } -func (n *nodeHead) SetHead(spaceId, oldHead, newHead string) (part int, err error) { +func (n *nodeHead) SetHead(spaceId, head string) (part int, err error) { part = n.nodeconf.Partition(spaceId) n.mu.Lock() defer n.mu.Unlock() @@ -121,8 +117,7 @@ func (n *nodeHead) SetHead(spaceId, oldHead, newHead string) (part int, err erro ld = ldiff.New(16, 16) n.partitions[part] = ld } - ld.Set(ldiff.Element{Id: spaceId, Head: newHead}) - n.oldHashes[spaceId] = oldHead + ld.Set(ldiff.Element{Id: spaceId, Head: head}) return } @@ -160,16 +155,6 @@ func (n *nodeHead) GetHead(spaceId string) (hash string, err error) { return el.Head, nil } -func (n *nodeHead) GetOldHead(spaceId string) (hash string, err error) { - n.mu.Lock() - defer n.mu.Unlock() - hash, ok := n.oldHashes[spaceId] - if !ok { - err = ErrSpaceNotFound - } - return -} - func (n *nodeHead) ReloadHeadFromStore(ctx context.Context, spaceId string) error { ss, err := n.spaceStore.IndexSpace(ctx, spaceId, true) if err != nil { diff --git a/nodehead/nodehead_test.go b/nodehead/nodehead_test.go index 6a758b20..26c618d6 100644 --- a/nodehead/nodehead_test.go +++ b/nodehead/nodehead_test.go @@ -60,7 +60,6 @@ func TestNodeHead_Run(t *testing.T) { fx = newFixture(t, tmpDir) defer fx.Finish(t) assert.Len(t, fx.NodeHead.(*nodeHead).partitions, 1) - assert.Len(t, fx.NodeHead.(*nodeHead).oldHashes, 10) }) } @@ -80,10 +79,10 @@ func TestNodeHead_SetHead(t *testing.T) { } t.Run("set head", func(t *testing.T) { - part, err := fx.SetHead("2.2", "oldHead", "newhead") + part, err := fx.SetHead("2.2", "newhead") require.NoError(t, err) h1 := getHash(part) - part2, err := fx.SetHead("3.2", "oldHead", "newhead") + part2, err := fx.SetHead("3.2", "newhead") assert.Equal(t, part, part2) h2 := getHash(part) assert.NotEqual(t, h1, h2) @@ -109,19 +108,14 @@ func TestNodeHead_Ranges(t *testing.T) { func TestNodeHead_GetSpaceHash(t *testing.T) { fx := newFixture(t, "") defer fx.Finish(t) - oldHash := "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262" newHash := "bf1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262" - _, err := fx.SetHead("space1", oldHash, newHash) + _, err := fx.SetHead("space1", newHash) require.NoError(t, err) head, err := fx.GetHead("space1") require.NoError(t, err) assert.Equal(t, newHash, head) - head, err = fx.GetOldHead("space1") - require.NoError(t, err) - assert.Equal(t, oldHash, head) - _, err = fx.GetHead("not found") assert.Equal(t, ErrSpaceNotFound, err) } @@ -130,7 +124,7 @@ func TestNodeHead_DeleteHeads(t *testing.T) { fx := newFixture(t, "") defer fx.Finish(t) hash := "af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262" - _, err := fx.SetHead("space1", hash, hash) + _, err := fx.SetHead("space1", hash) require.NoError(t, err) err = fx.NodeHead.(*nodeHead).DeleteHeads("space1") diff --git a/nodespace/migrator/migrator.go b/nodespace/migrator/migrator.go index 1383552d..bb7e98c9 100644 --- a/nodespace/migrator/migrator.go +++ b/nodespace/migrator/migrator.go @@ -130,9 +130,6 @@ func (m *migrator) Run(ctx context.Context) (err error) { } err = m.newStorage.IndexStorage().UpdateHash(ctx, nodestorage.SpaceUpdate{ SpaceId: id, - // statestorage.State no longer carries a distinct old hash after the - // diffsync-V2 removal; use NewHash for both (matches the "oh" == "nh" mirror). - OldHash: state.NewHash, NewHash: state.NewHash, }) if err != nil { diff --git a/nodespace/rpchandler.go b/nodespace/rpchandler.go index 4c821516..04b37c33 100644 --- a/nodespace/rpchandler.go +++ b/nodespace/rpchandler.go @@ -265,6 +265,11 @@ func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncR zap.String("accountId", accountIdentity.Account())) return nil, spacesyncproto.ErrPeerIsNotResponsible } + // reject unsupported diff types before the deep-sync fallback, so a crafted + // request can't force a full space load only to be rejected there + if req.DiffType != spacesyncproto.DiffType_V3 { + return nil, spacesyncproto.ErrUnexpected + } if resp = r.tryNodeHeadSync(req); resp != nil { return } @@ -280,47 +285,24 @@ func (r *rpcHandler) HeadSync(ctx context.Context, req *spacesyncproto.HeadSyncR func (r *rpcHandler) tryNodeHeadSync(req *spacesyncproto.HeadSyncRequest) (resp *spacesyncproto.HeadSyncResponse) { if len(req.Ranges) == 1 && !req.Ranges[0].Elements && (req.Ranges[0].From == 0 && req.Ranges[0].To == math.MaxUint64) { - switch req.DiffType { - case spacesyncproto.DiffType_V3: - hash, err := r.s.nodeHead.GetHead(req.SpaceId) - if err != nil { - return - } - hashB, err := hex.DecodeString(hash) - if err != nil { - return - } - log.Debug("got head sync with nodehead", zap.String("spaceId", req.SpaceId)) - return &spacesyncproto.HeadSyncResponse{ - DiffType: spacesyncproto.DiffType_V3, - Results: []*spacesyncproto.HeadSyncResult{ - { - Hash: hashB, - // this makes diff not compareResults and create new batch directly (see (d *diff) Diff) - Count: 1, - }, - }, - } - default: - hash, err := r.s.nodeHead.GetOldHead(req.SpaceId) - if err != nil { - return - } - hashB, err := hex.DecodeString(hash) - if err != nil { - return - } - log.Debug("got head sync with old nodehead", zap.String("spaceId", req.SpaceId)) - return &spacesyncproto.HeadSyncResponse{ - DiffType: spacesyncproto.DiffType_V2, - Results: []*spacesyncproto.HeadSyncResult{ - { - Hash: hashB, - // this makes diff not compareResults and create new batch directly (see (d *diff) Diff) - Count: 1, - }, + hash, err := r.s.nodeHead.GetHead(req.SpaceId) + if err != nil { + return + } + hashB, err := hex.DecodeString(hash) + if err != nil { + return + } + log.Debug("got head sync with nodehead", zap.String("spaceId", req.SpaceId)) + return &spacesyncproto.HeadSyncResponse{ + DiffType: spacesyncproto.DiffType_V3, + Results: []*spacesyncproto.HeadSyncResult{ + { + Hash: hashB, + // this makes diff not compareResults and create new batch directly (see (d *diff) Diff) + Count: 1, }, - } + }, } } return nil diff --git a/nodestorage/indexstorage.go b/nodestorage/indexstorage.go index e8ac6ee8..da7134f7 100644 --- a/nodestorage/indexstorage.go +++ b/nodestorage/indexstorage.go @@ -22,7 +22,6 @@ type SpaceStatusEntry struct { Status SpaceStatus Error string NewHash string - OldHash string LastAccess time.Time ArchiveSizeCompressed int64 ArchiveSizeUncompressed int64 @@ -46,11 +45,11 @@ var ( ) const ( - IndexStorageName = ".index" - migrationStateCollName = "migrationState" - spaceCollName = "space" - settingsCollName = "settings" - newHashKey = "nh" + IndexStorageName = ".index" + spaceCollName = "space" + settingsCollName = "settings" + newHashKey = "nh" + // oldHashKey is the legacy diff hash key: never written, only cleared oldHashKey = "oh" statusKey = "s" lastAccessKey = "la" @@ -58,8 +57,6 @@ const ( archiveSizeCompressedKey = "asc" archiveSizeUncompressedKey = "asu" errorKey = "err" - diffMigrationKey = "diffState" - diffVersionKey = "diffVersion" lastDeletionIdKey = "lastDeletionId" ) @@ -68,22 +65,18 @@ type IndexStorage interface { UpdateHash(ctx context.Context, updates ...SpaceUpdate) (err error) ReadHashes(ctx context.Context, iterFunc func(update SpaceUpdate) (bool, error)) (err error) ReadSpacesByStatus(ctx context.Context, status SpaceStatus, iterFunc func(spaceId string) (bool, error)) (err error) - UpdateHashes(ctx context.Context, updateFunc func(spaceId, newHash, oldHash string) (newNewHash, newOldHash string, shouldUpdate bool)) (err error) SetSpaceStatus(ctx context.Context, spaceId string, status SpaceStatus, recId string) (err error) SpaceStatus(ctx context.Context, spaceId string) (status SpaceStatus, err error) SpaceStatusEntry(ctx context.Context, spaceId string) (entry SpaceStatusEntry, err error) MarkArchived(ctx context.Context, spaceId string, compressedSize, uncompressedSize int64) (err error) DeleteSpaceEntry(ctx context.Context, spaceId string) (err error) - MarkArchivedRemote(ctx context.Context, spaceId, oldHash, newHash string, compressedSize, uncompressedSize int64) (err error) + MarkArchivedRemote(ctx context.Context, spaceId, hash string, compressedSize, uncompressedSize int64) (err error) MarkError(ctx context.Context, spaceId string, errString string) (err error) DeletionLogId(ctx context.Context) (id string, err error) SetDeletionLogId(ctx context.Context, id string) (err error) FindOldestInactiveSpace(ctx context.Context, olderThan time.Duration, skip int) (spaceId string, err error) UpdateLastAccess(ctx context.Context, spaceId string) (err error) - GetDiffMigrationVersion(ctx context.Context) (version int, err error) - SetDiffMigrationVersion(ctx context.Context, version int) (err error) - RunMigrations(ctx context.Context) (err error) Close() (err error) } @@ -110,7 +103,6 @@ func (d *indexStorage) UpdateHash(ctx context.Context, updates ...SpaceUpdate) ( if update.Updated.IsZero() { update.Updated = time.Now() } - v.Set(oldHashKey, a.NewString(update.OldHash)) v.Set(newHashKey, a.NewString(update.NewHash)) v.Set(lastAccessKey, a.NewNumberFloat64(float64(update.Updated.Unix()))) if v.Get(statusKey) == nil { @@ -146,7 +138,6 @@ func (d *indexStorage) ReadHashes(ctx context.Context, iterFunc func(update Spac } cont, err := iterFunc(SpaceUpdate{ SpaceId: doc.Value().GetString("id"), - OldHash: doc.Value().GetString(oldHashKey), NewHash: doc.Value().GetString(newHashKey), Updated: time.Unix(int64(doc.Value().GetInt(lastAccessKey)), 0), }) @@ -204,7 +195,6 @@ func (d *indexStorage) SpaceStatusEntry(ctx context.Context, spaceId string) (en Status: SpaceStatus(v.GetInt(statusKey)), Error: v.GetString(errorKey), NewHash: v.GetString(newHashKey), - OldHash: v.GetString(oldHashKey), LastAccess: time.Unix(int64(v.GetInt(lastAccessKey)), 0), ArchiveSizeCompressed: int64(v.GetInt(archiveSizeCompressedKey)), ArchiveSizeUncompressed: int64(v.GetInt(archiveSizeUncompressedKey)), @@ -281,11 +271,10 @@ func (d *indexStorage) MarkArchived(ctx context.Context, spaceId string, compres // MarkArchivedRemote registers a space adopted from another node as archived: // unlike MarkArchived the space has never been opened locally, so the heads -// hashes come from the sender and the index entry may not exist yet. -func (d *indexStorage) MarkArchivedRemote(ctx context.Context, spaceId, oldHash, newHash string, compressedSize, uncompressedSize int64) (err error) { +// hash comes from the sender and the index entry may not exist yet. +func (d *indexStorage) MarkArchivedRemote(ctx context.Context, spaceId, hash string, compressedSize, uncompressedSize int64) (err error) { _, err = d.spaceColl.UpsertId(ctx, spaceId, query.ModifyFunc(func(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error) { - v.Set(oldHashKey, a.NewString(oldHash)) - v.Set(newHashKey, a.NewString(newHash)) + v.Set(newHashKey, a.NewString(hash)) v.Set(lastAccessKey, a.NewNumberInt(int(time.Now().Unix()))) v.Set(archiveSizeCompressedKey, a.NewNumberInt(int(compressedSize))) v.Set(archiveSizeUncompressedKey, a.NewNumberInt(int(uncompressedSize))) @@ -381,76 +370,6 @@ func (d *indexStorage) FindOldestInactiveSpace(ctx context.Context, olderThan ti return spaceId, nil } -func (d *indexStorage) RunMigrations(ctx context.Context) (err error) { - diffMigration, err := newDiffMigration(d, log) - if err != nil { - return fmt.Errorf("failed to create diff migration: %w", err) - } - - if err := diffMigration.Run(ctx); err != nil { - return fmt.Errorf("diff migration failed: %w", err) - } - - return nil -} - -func (d *indexStorage) UpdateHashes(ctx context.Context, updateFunc func(spaceId, newHash, oldHash string) (newNewHash, newOldHash string, shouldUpdate bool)) (err error) { - _, err = d.spaceColl.Find(filterStatusHashOk).Update(ctx, query.ModifyFunc(func(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error) { - spaceId := v.GetString("id") - newHash := v.GetString(newHashKey) - oldHash := v.GetString(oldHashKey) - - newNewHash, newOldHash, shouldUpdate := updateFunc(spaceId, newHash, oldHash) - if !shouldUpdate { - return v, false, nil - } - - v.Set(newHashKey, a.NewString(newNewHash)) - v.Set(oldHashKey, a.NewString(newOldHash)) - if v.Get(statusKey) == nil { - v.Set(statusKey, a.NewNumberInt(int(SpaceStatusOk))) - } - return v, true, nil - })) - return -} - -func (d *indexStorage) GetDiffMigrationVersion(ctx context.Context) (version int, err error) { - migrationColl, err := d.db.Collection(ctx, migrationStateCollName) - if err != nil { - return 0, err - } - - doc, err := migrationColl.FindId(ctx, diffMigrationKey) - if err != nil { - if errors.Is(err, anystore.ErrDocNotFound) { - return 0, nil - } - return 0, err - } - - return int(doc.Value().GetFloat64(diffVersionKey)), nil -} - -func (d *indexStorage) SetDiffMigrationVersion(ctx context.Context, version int) (err error) { - migrationColl, err := d.db.Collection(ctx, migrationStateCollName) - if err != nil { - return err - } - - mod := query.ModifyFunc(func(a *anyenc.Arena, v *anyenc.Value) (result *anyenc.Value, modified bool, err error) { - if v == nil { - v = a.NewObject() - v.Set("id", a.NewString(diffMigrationKey)) - } - v.Set(diffVersionKey, a.NewNumberFloat64(float64(version))) - return v, true, nil - }) - - _, err = migrationColl.UpsertId(ctx, diffMigrationKey, mod) - return err -} - func (d *indexStorage) Close() (err error) { return d.db.Close() } diff --git a/nodestorage/indexstorage_test.go b/nodestorage/indexstorage_test.go index ae33b641..bf829b68 100644 --- a/nodestorage/indexstorage_test.go +++ b/nodestorage/indexstorage_test.go @@ -1,6 +1,7 @@ package nodestorage import ( + "context" "os" "path/filepath" "testing" @@ -22,7 +23,6 @@ func TestIndexStorage_UpdateLastAccess(t *testing.T) { require.NoError(t, fx.UpdateHash(ctx, SpaceUpdate{ SpaceId: "space2", - OldHash: "old", NewHash: "new", Updated: time.Now(), })) @@ -32,7 +32,6 @@ func TestIndexStorage_UpdateLastAccess(t *testing.T) { assert.True(t, update.Updated.Before(time.Now())) assert.True(t, update.SpaceId == "space1" || update.SpaceId == "space2") if update.SpaceId == "space2" { - assert.Equal(t, "old", update.OldHash) assert.Equal(t, "new", update.NewHash) } return true, nil @@ -134,7 +133,6 @@ func TestIndexStorage_SpaceStatusEntry(t *testing.T) { require.NoError(t, fx.SetSpaceStatus(ctx, "space1", SpaceStatusOk, "")) require.NoError(t, fx.UpdateHash(ctx, SpaceUpdate{ SpaceId: "space1", - OldHash: "old", NewHash: "new", })) require.NoError(t, fx.MarkArchived(ctx, "space1", 100, 200)) @@ -144,7 +142,6 @@ func TestIndexStorage_SpaceStatusEntry(t *testing.T) { assert.Equal(t, "space1", entry.SpaceId) assert.Equal(t, SpaceStatusArchived, entry.Status) assert.Equal(t, "new", entry.NewHash) - assert.Equal(t, "old", entry.OldHash) assert.Equal(t, int64(100), entry.ArchiveSizeCompressed) assert.Equal(t, int64(200), entry.ArchiveSizeUncompressed) assert.False(t, entry.LastAccess.IsZero()) @@ -178,3 +175,12 @@ func TestIndexStorage_MarkError(t *testing.T) { require.NoError(t, err) assert.Equal(t, SpaceStatusError, status) } + +func createTestIndexStorage(ctx context.Context, tempDir string) (IndexStorage, error) { + dbPath := filepath.Join(tempDir, "test_index") + err := os.MkdirAll(dbPath, 0755) + if err != nil { + return nil, err + } + return OpenIndexStorage(ctx, dbPath) +} diff --git a/nodestorage/migration.go b/nodestorage/migration.go deleted file mode 100644 index 8df223a4..00000000 --- a/nodestorage/migration.go +++ /dev/null @@ -1,72 +0,0 @@ -package nodestorage - -import ( - "context" - "fmt" - - "github.com/anyproto/any-sync/app/logger" - "go.uber.org/zap" -) - -const ( - targetMigrationVersion = 3 -) - -type Migration interface { - Run(ctx context.Context) error -} - -type diffMigration struct { - indexStorage IndexStorage - logger logger.CtxLogger -} - -func newDiffMigration(is IndexStorage, log logger.CtxLogger) (*diffMigration, error) { - return &diffMigration{ - indexStorage: is, - logger: log, - }, nil -} - -func (m *diffMigration) Run(ctx context.Context) error { - m.logger.Info("starting diff migration v2 to v3") - - currentVersion, err := m.indexStorage.GetDiffMigrationVersion(ctx) - if err != nil { - return fmt.Errorf("failed to get migration version: %w", err) - } - - if currentVersion >= targetMigrationVersion { - m.logger.Info("diff migration already completed", zap.Int("version", currentVersion)) - return nil - } - - err = m.migrateHashIndex(ctx) - if err != nil { - return fmt.Errorf("failed to migrate hash index: %w", err) - } - - err = m.indexStorage.SetDiffMigrationVersion(ctx, targetMigrationVersion) - if err != nil { - return fmt.Errorf("failed to set migration version: %w", err) - } - - m.logger.Info("diff migration completed successfully") - return nil -} - -func (m *diffMigration) migrateHashIndex(ctx context.Context) error { - migratedCount := 0 - - err := m.indexStorage.UpdateHashes(ctx, func(spaceId, newHash, oldHash string) (newNewHash, newOldHash string, shouldUpdate bool) { - migratedCount++ - return newHash, newHash, true - }) - - if err != nil { - return fmt.Errorf("failed to update hashes: %w", err) - } - - m.logger.Info("hash index migration completed", zap.Int("migrated_count", migratedCount)) - return nil -} diff --git a/nodestorage/migration_test.go b/nodestorage/migration_test.go deleted file mode 100644 index 53d89170..00000000 --- a/nodestorage/migration_test.go +++ /dev/null @@ -1,282 +0,0 @@ -package nodestorage - -import ( - "context" - "os" - "path/filepath" - "testing" - - "github.com/anyproto/any-sync/app/logger" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDiffMigration(t *testing.T) { - ctx := context.Background() - - testLogger := logger.NewNamed("test") - - t.Run("migration_skipped_when_already_completed", func(t *testing.T) { - tempDir := t.TempDir() - // Create index storage - is, err := createTestIndexStorage(ctx, tempDir) - require.NoError(t, err) - defer is.Close() - - // Set migration state to version 3 - err = setTestMigrationVersion(ctx, is, targetMigrationVersion) - require.NoError(t, err) - - // Run migration - migration, err := newDiffMigration(is, testLogger) - require.NoError(t, err) - - err = migration.Run(ctx) - require.NoError(t, err) - - // Verify migration state is still 3 - version, err := getTestMigrationVersion(ctx, is) - require.NoError(t, err) - assert.Equal(t, targetMigrationVersion, version) - }) - - t.Run("migration_copies_nh_to_oh", func(t *testing.T) { - tempDir := t.TempDir() - // Create index storage - is, err := createTestIndexStorage(ctx, tempDir) - require.NoError(t, err) - defer is.Close() - - // Add test data with only 'nh' field - testData := []SpaceUpdate{ - {SpaceId: "space1", NewHash: "hash1", OldHash: ""}, - {SpaceId: "space2", NewHash: "hash2", OldHash: ""}, - {SpaceId: "space3", NewHash: "hash3", OldHash: ""}, - } - - for _, update := range testData { - err := addTestHashEntry(ctx, is.(*indexStorage), update.SpaceId, update.NewHash, "") - require.NoError(t, err) - } - - // Run migration - migration, err := newDiffMigration(is, testLogger) - require.NoError(t, err) - - err = migration.Run(ctx) - require.NoError(t, err) - - // Verify all entries have 'oh' copied from 'nh' - err = is.ReadHashes(ctx, func(update SpaceUpdate) (bool, error) { - for _, expected := range testData { - if update.SpaceId == expected.SpaceId { - assert.Equal(t, expected.NewHash, update.NewHash) - assert.Equal(t, expected.NewHash, update.OldHash) // oh should be copied from nh - break - } - } - return true, nil - }) - require.NoError(t, err) - - // Verify migration state is updated - version, err := getTestMigrationVersion(ctx, is) - require.NoError(t, err) - assert.Equal(t, targetMigrationVersion, version) - }) - - t.Run("migration_always_copies_nh_to_oh", func(t *testing.T) { - tempDir := t.TempDir() - // Create index storage - is, err := createTestIndexStorage(ctx, tempDir) - require.NoError(t, err) - defer is.Close() - - // Add test data with mixed entries - testData := []SpaceUpdate{ - {SpaceId: "space1", NewHash: "hash1", OldHash: ""}, // Should be migrated - {SpaceId: "space2", NewHash: "hash2new", OldHash: "hash2old"}, // Should be overwritten - {SpaceId: "space3", NewHash: "hash3", OldHash: ""}, // Should be migrated - } - - for _, update := range testData { - err := addTestHashEntry(ctx, is.(*indexStorage), update.SpaceId, update.NewHash, update.OldHash) - require.NoError(t, err) - } - - // Run migration - migration, err := newDiffMigration(is, testLogger) - require.NoError(t, err) - - err = migration.Run(ctx) - require.NoError(t, err) - - // Verify results - results := make(map[string]SpaceUpdate) - err = is.ReadHashes(ctx, func(update SpaceUpdate) (bool, error) { - results[update.SpaceId] = update - return true, nil - }) - require.NoError(t, err) - - // space1: oh should be copied from nh - assert.Equal(t, "hash1", results["space1"].NewHash) - assert.Equal(t, "hash1", results["space1"].OldHash) - - // space2: oh should be overwritten with nh - assert.Equal(t, "hash2new", results["space2"].NewHash) - assert.Equal(t, "hash2new", results["space2"].OldHash) - - // space3: oh should be copied from nh - assert.Equal(t, "hash3", results["space3"].NewHash) - assert.Equal(t, "hash3", results["space3"].OldHash) - }) - - t.Run("migration_handles_empty_hashesIndex", func(t *testing.T) { - tempDir := t.TempDir() - // Create index storage - is, err := createTestIndexStorage(ctx, tempDir) - require.NoError(t, err) - defer is.Close() - - // Run migration on empty storage - migration, err := newDiffMigration(is, testLogger) - require.NoError(t, err) - - err = migration.Run(ctx) - require.NoError(t, err) - - // Verify migration state is updated - version, err := getTestMigrationVersion(ctx, is) - require.NoError(t, err) - assert.Equal(t, targetMigrationVersion, version) - }) - - t.Run("migration_runs_only_once", func(t *testing.T) { - tempDir := t.TempDir() - // Create index storage - is, err := createTestIndexStorage(ctx, tempDir) - require.NoError(t, err) - defer is.Close() - - // Add test data - err = addTestHashEntry(ctx, is.(*indexStorage), "space1", "hash1", "") - require.NoError(t, err) - - // Run migration first time - migration, err := newDiffMigration(is, testLogger) - require.NoError(t, err) - - err = migration.Run(ctx) - require.NoError(t, err) - - // Modify data after migration - err = is.UpdateHash(ctx, SpaceUpdate{ - SpaceId: "space1", - NewHash: "hash1_new", - OldHash: "hash1", - }) - require.NoError(t, err) - - // Run migration second time - err = migration.Run(ctx) - require.NoError(t, err) - - // Verify data wasn't changed by second migration - var result SpaceUpdate - err = is.ReadHashes(ctx, func(update SpaceUpdate) (bool, error) { - if update.SpaceId == "space1" { - result = update - return false, nil - } - return true, nil - }) - require.NoError(t, err) - - assert.Equal(t, "hash1_new", result.NewHash) - assert.Equal(t, "hash1", result.OldHash) - }) -} - -// Helper functions - -func createTestIndexStorage(ctx context.Context, tempDir string) (IndexStorage, error) { - dbPath := filepath.Join(tempDir, "test_index") - err := os.MkdirAll(dbPath, 0755) - if err != nil { - return nil, err - } - return OpenIndexStorage(ctx, dbPath) -} - -func addTestHashEntry(ctx context.Context, is *indexStorage, spaceId, newHash, oldHash string) error { - tx, err := is.spaceColl.WriteTx(ctx) - if err != nil { - return err - } - defer tx.Rollback() - - arena := is.arenaPool.Get() - defer is.arenaPool.Put(arena) - - doc := arena.NewObject() - doc.Set("id", arena.NewString(spaceId)) - doc.Set(newHashKey, arena.NewString(newHash)) - if oldHash != "" { - doc.Set(oldHashKey, arena.NewString(oldHash)) - } - doc.Set(statusKey, arena.NewNumberInt(int(SpaceStatusOk))) - - err = is.spaceColl.UpsertOne(tx.Context(), doc) - if err != nil { - return err - } - - return tx.Commit() -} - -func setTestMigrationVersion(ctx context.Context, is IndexStorage, version int) error { - return is.SetDiffMigrationVersion(ctx, version) -} - -func getTestMigrationVersion(ctx context.Context, is IndexStorage) (int, error) { - return is.GetDiffMigrationVersion(ctx) -} - -func TestRunMigrations(t *testing.T) { - ctx := context.Background() - tempDir := t.TempDir() - - t.Run("RunMigrations_calls_diff_migration", func(t *testing.T) { - // Create index storage - is, err := OpenIndexStorage(ctx, tempDir) - require.NoError(t, err) - defer is.Close() - - // Add test data - err = is.UpdateHash(ctx, SpaceUpdate{ - SpaceId: "space1", - NewHash: "hash1", - OldHash: "", - }) - require.NoError(t, err) - - // Run migrations through the interface - err = is.RunMigrations(ctx) - require.NoError(t, err) - - // Verify migration was executed - var result SpaceUpdate - err = is.ReadHashes(ctx, func(update SpaceUpdate) (bool, error) { - if update.SpaceId == "space1" { - result = update - return false, nil - } - return true, nil - }) - require.NoError(t, err) - - assert.Equal(t, "hash1", result.NewHash) - assert.Equal(t, "hash1", result.OldHash) - }) -} diff --git a/nodestorage/mock_nodestorage/mock_nodestorage.go b/nodestorage/mock_nodestorage/mock_nodestorage.go index abbc442e..b592ad82 100644 --- a/nodestorage/mock_nodestorage/mock_nodestorage.go +++ b/nodestorage/mock_nodestorage/mock_nodestorage.go @@ -215,7 +215,7 @@ func (mr *MockNodeStorageMockRecorder) OnDeleteStorage(onDelete any) *gomock.Cal } // OnWriteHash mocks base method. -func (m *MockNodeStorage) OnWriteHash(onWrite func(context.Context, string, string, string)) { +func (m *MockNodeStorage) OnWriteHash(onWrite func(context.Context, string, string)) { m.ctrl.T.Helper() m.ctrl.Call(m, "OnWriteHash", onWrite) } @@ -409,21 +409,6 @@ func (mr *MockIndexStorageMockRecorder) FindOldestInactiveSpace(ctx, olderThan, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOldestInactiveSpace", reflect.TypeOf((*MockIndexStorage)(nil).FindOldestInactiveSpace), ctx, olderThan, skip) } -// GetDiffMigrationVersion mocks base method. -func (m *MockIndexStorage) GetDiffMigrationVersion(ctx context.Context) (int, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetDiffMigrationVersion", ctx) - ret0, _ := ret[0].(int) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetDiffMigrationVersion indicates an expected call of GetDiffMigrationVersion. -func (mr *MockIndexStorageMockRecorder) GetDiffMigrationVersion(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetDiffMigrationVersion", reflect.TypeOf((*MockIndexStorage)(nil).GetDiffMigrationVersion), ctx) -} - // MarkArchived mocks base method. func (m *MockIndexStorage) MarkArchived(ctx context.Context, spaceId string, compressedSize, uncompressedSize int64) error { m.ctrl.T.Helper() @@ -439,17 +424,17 @@ func (mr *MockIndexStorageMockRecorder) MarkArchived(ctx, spaceId, compressedSiz } // MarkArchivedRemote mocks base method. -func (m *MockIndexStorage) MarkArchivedRemote(ctx context.Context, spaceId, oldHash, newHash string, compressedSize, uncompressedSize int64) error { +func (m *MockIndexStorage) MarkArchivedRemote(ctx context.Context, spaceId, hash string, compressedSize, uncompressedSize int64) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "MarkArchivedRemote", ctx, spaceId, oldHash, newHash, compressedSize, uncompressedSize) + ret := m.ctrl.Call(m, "MarkArchivedRemote", ctx, spaceId, hash, compressedSize, uncompressedSize) ret0, _ := ret[0].(error) return ret0 } // MarkArchivedRemote indicates an expected call of MarkArchivedRemote. -func (mr *MockIndexStorageMockRecorder) MarkArchivedRemote(ctx, spaceId, oldHash, newHash, compressedSize, uncompressedSize any) *gomock.Call { +func (mr *MockIndexStorageMockRecorder) MarkArchivedRemote(ctx, spaceId, hash, compressedSize, uncompressedSize any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkArchivedRemote", reflect.TypeOf((*MockIndexStorage)(nil).MarkArchivedRemote), ctx, spaceId, oldHash, newHash, compressedSize, uncompressedSize) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MarkArchivedRemote", reflect.TypeOf((*MockIndexStorage)(nil).MarkArchivedRemote), ctx, spaceId, hash, compressedSize, uncompressedSize) } // MarkError mocks base method. @@ -494,20 +479,6 @@ func (mr *MockIndexStorageMockRecorder) ReadSpacesByStatus(ctx, status, iterFunc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadSpacesByStatus", reflect.TypeOf((*MockIndexStorage)(nil).ReadSpacesByStatus), ctx, status, iterFunc) } -// RunMigrations mocks base method. -func (m *MockIndexStorage) RunMigrations(ctx context.Context) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "RunMigrations", ctx) - ret0, _ := ret[0].(error) - return ret0 -} - -// RunMigrations indicates an expected call of RunMigrations. -func (mr *MockIndexStorageMockRecorder) RunMigrations(ctx any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RunMigrations", reflect.TypeOf((*MockIndexStorage)(nil).RunMigrations), ctx) -} - // SetDeletionLogId mocks base method. func (m *MockIndexStorage) SetDeletionLogId(ctx context.Context, id string) error { m.ctrl.T.Helper() @@ -522,20 +493,6 @@ func (mr *MockIndexStorageMockRecorder) SetDeletionLogId(ctx, id any) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeletionLogId", reflect.TypeOf((*MockIndexStorage)(nil).SetDeletionLogId), ctx, id) } -// SetDiffMigrationVersion mocks base method. -func (m *MockIndexStorage) SetDiffMigrationVersion(ctx context.Context, version int) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetDiffMigrationVersion", ctx, version) - ret0, _ := ret[0].(error) - return ret0 -} - -// SetDiffMigrationVersion indicates an expected call of SetDiffMigrationVersion. -func (mr *MockIndexStorageMockRecorder) SetDiffMigrationVersion(ctx, version any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDiffMigrationVersion", reflect.TypeOf((*MockIndexStorage)(nil).SetDiffMigrationVersion), ctx, version) -} - // SetSpaceStatus mocks base method. func (m *MockIndexStorage) SetSpaceStatus(ctx context.Context, spaceId string, status nodestorage.SpaceStatus, recId string) error { m.ctrl.T.Helper() @@ -599,20 +556,6 @@ func (mr *MockIndexStorageMockRecorder) UpdateHash(ctx any, updates ...any) *gom return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHash", reflect.TypeOf((*MockIndexStorage)(nil).UpdateHash), varargs...) } -// UpdateHashes mocks base method. -func (m *MockIndexStorage) UpdateHashes(ctx context.Context, updateFunc func(string, string, string) (string, string, bool)) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "UpdateHashes", ctx, updateFunc) - ret0, _ := ret[0].(error) - return ret0 -} - -// UpdateHashes indicates an expected call of UpdateHashes. -func (mr *MockIndexStorageMockRecorder) UpdateHashes(ctx, updateFunc any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateHashes", reflect.TypeOf((*MockIndexStorage)(nil).UpdateHashes), ctx, updateFunc) -} - // UpdateLastAccess mocks base method. func (m *MockIndexStorage) UpdateLastAccess(ctx context.Context, spaceId string) error { m.ctrl.T.Helper() diff --git a/nodestorage/spacestorage.go b/nodestorage/spacestorage.go index 4ae84e82..0af9c47b 100644 --- a/nodestorage/spacestorage.go +++ b/nodestorage/spacestorage.go @@ -71,14 +71,11 @@ type nodeStorage struct { observer hashObserver } -func (st *nodeStorage) OnHashChange(newHash string) { - // any-sync's statestorage no longer tracks a distinct old hash after the - // diffsync-V2 removal (it mirrors "oh" == "nh" == newHash); the node keeps - // its oldHash plumbing, so we pass newHash for both. - st.observer(st.Id(), newHash, newHash) +func (st *nodeStorage) OnHashChange(hash string) { + st.observer(st.Id(), hash) } -type hashObserver = func(spaceId, oldHash, newHash string) +type hashObserver = func(spaceId, hash string) func newNodeStorage(spaceStorage spacestorage.SpaceStorage, cont *storageContainer, observer hashObserver) *nodeStorage { st := &nodeStorage{ diff --git a/nodestorage/storageservice.go b/nodestorage/storageservice.go index 838448e0..e427e8c7 100644 --- a/nodestorage/storageservice.go +++ b/nodestorage/storageservice.go @@ -72,7 +72,7 @@ type NodeStorage interface { DumpStorage(ctx context.Context, id string, do func(path string) error) (err error) AllSpaceIds() (ids []string, err error) OnDeleteStorage(onDelete func(ctx context.Context, spaceId string)) - OnWriteHash(onWrite func(ctx context.Context, spaceId, oldHash, newHash string)) + OnWriteHash(onWrite func(ctx context.Context, spaceId, hash string)) StoreDir(spaceId string) (path string) DeleteSpaceStorage(ctx context.Context, spaceId string) error ForceRemove(id string) (err error) @@ -110,7 +110,7 @@ type storageService struct { cache ocache.OCache indexStorage IndexStorage updater *spaceUpdater - onWriteHash func(ctx context.Context, spaceId, oldHash, newHash string) + onWriteHash func(ctx context.Context, spaceId, hash string) onDeleteStorage func(ctx context.Context, spaceId string) currentSpaces map[string]*storageContainer mu sync.Mutex @@ -131,7 +131,7 @@ func (s *storageService) Init(a *app.App) (err error) { } if s.onWriteHash != nil { for _, update := range updates { - s.onWriteHash(context.Background(), update.SpaceId, update.OldHash, update.NewHash) + s.onWriteHash(context.Background(), update.SpaceId, update.NewHash) } } }) @@ -175,12 +175,6 @@ func (s *storageService) Run(ctx context.Context) (err error) { log.Error("failed to open index storage", zap.Error(err)) return err } - - // Run migrations - if err := s.indexStorage.RunMigrations(ctx); err != nil { - log.Error("failed to run migrations", zap.Error(err)) - return err - } allIds, err := s.AllSpaceIds() if err != nil { log.Error("failed to get all space ids", zap.Error(err)) @@ -239,11 +233,10 @@ func (s *storageService) StatType() string { return CName } -func (s *storageService) onHashChange(spaceId, oldHash, newHash string) { +func (s *storageService) onHashChange(spaceId, hash string) { _ = s.updater.Add(SpaceUpdate{ SpaceId: spaceId, - OldHash: oldHash, - NewHash: newHash, + NewHash: hash, Updated: time.Now(), }) } @@ -412,9 +405,6 @@ func (s *storageService) IndexSpace(ctx context.Context, spaceId string, setHead } err = s.indexStorage.UpdateHash(ctx, SpaceUpdate{ SpaceId: spaceId, - // statestorage.State no longer carries a distinct old hash after the - // diffsync-V2 removal; use NewHash for both (matches the "oh" == "nh" mirror). - OldHash: state.NewHash, NewHash: state.NewHash, }) if err != nil { @@ -422,7 +412,7 @@ func (s *storageService) IndexSpace(ctx context.Context, spaceId string, setHead return } if setHead && s.onWriteHash != nil { - s.onWriteHash(ctx, spaceId, state.NewHash, state.NewHash) + s.onWriteHash(ctx, spaceId, state.NewHash) } return } @@ -611,7 +601,7 @@ func (s *storageService) StoreDir(spaceId string) (path string) { return filepath.Join(s.rootPath, spaceId) } -func (s *storageService) OnWriteHash(onWrite func(ctx context.Context, spaceId string, oldHash, newHash string)) { +func (s *storageService) OnWriteHash(onWrite func(ctx context.Context, spaceId string, hash string)) { s.onWriteHash = onWrite } diff --git a/nodestorage/updater.go b/nodestorage/updater.go index ced27b3b..736a164b 100644 --- a/nodestorage/updater.go +++ b/nodestorage/updater.go @@ -11,7 +11,6 @@ import ( type SpaceUpdate struct { SpaceId string - OldHash string NewHash string Updated time.Time } diff --git a/nodestorage/updater_test.go b/nodestorage/updater_test.go index cfb96d85..138e4d50 100644 --- a/nodestorage/updater_test.go +++ b/nodestorage/updater_test.go @@ -20,14 +20,14 @@ func TestSpaceUpdater(t *testing.T) { updater := newSpaceUpdater(updateFunc) now := time.Now() updates := []SpaceUpdate{ - {SpaceId: "space1", OldHash: "old1", NewHash: "new1", Updated: now.Add(-2 * time.Hour)}, - {SpaceId: "space1", OldHash: "old2", NewHash: "new2", Updated: now.Add(-1 * time.Hour)}, - {SpaceId: "space1", OldHash: "old3", NewHash: "new3", Updated: now}, + {SpaceId: "space1", NewHash: "new1", Updated: now.Add(-2 * time.Hour)}, + {SpaceId: "space1", NewHash: "new2", Updated: now.Add(-1 * time.Hour)}, + {SpaceId: "space1", NewHash: "new3", Updated: now}, - {SpaceId: "space2", OldHash: "old1", NewHash: "new1", Updated: now.Add(-1 * time.Hour)}, - {SpaceId: "space2", OldHash: "old2", NewHash: "new2", Updated: now}, + {SpaceId: "space2", NewHash: "new1", Updated: now.Add(-1 * time.Hour)}, + {SpaceId: "space2", NewHash: "new2", Updated: now}, - {SpaceId: "space3", OldHash: "old1", NewHash: "new1", Updated: now}, + {SpaceId: "space3", NewHash: "new1", Updated: now}, } for _, update := range updates { err := updater.Add(update) @@ -46,15 +46,12 @@ func TestSpaceUpdater(t *testing.T) { update, exists := spaceUpdates["space1"] require.True(t, exists, "Missing update for space1") require.Equal(t, "new3", update.NewHash, "For space1, should have the latest update") - require.Equal(t, "old3", update.OldHash, "For space1, should have the latest update") update, exists = spaceUpdates["space2"] require.True(t, exists, "Missing update for space2") require.Equal(t, "new2", update.NewHash, "For space2, should have the latest update") - require.Equal(t, "old2", update.OldHash, "For space2, should have the latest update") update, exists = spaceUpdates["space3"] require.True(t, exists, "Missing update for space3") require.Equal(t, "new1", update.NewHash, "For space3, should have the latest update") - require.Equal(t, "old1", update.OldHash, "For space3, should have the latest update") } diff --git a/oldstorage/storageservice.go b/oldstorage/storageservice.go index d5a66c62..7fbeeb34 100644 --- a/oldstorage/storageservice.go +++ b/oldstorage/storageservice.go @@ -50,7 +50,6 @@ type storageService struct { delStorage DeletionStorage onWriteHash func(ctx context.Context, spaceId, hash string) onDeleteStorage func(ctx context.Context, spaceId string) - onWriteOldHash func(ctx context.Context, spaceId, hash string) lockedSpaces map[string]*lockSpace mu sync.Mutex } @@ -232,10 +231,6 @@ func (s *storageService) OnWriteHash(onWrite func(ctx context.Context, spaceId s s.onWriteHash = onWrite } -func (s *storageService) OnWriteOldHash(onWrite func(ctx context.Context, spaceId string, hash string)) { - s.onWriteOldHash = onWrite -} - func (s *storageService) OnDeleteStorage(onDelete func(ctx context.Context, spaceId string)) { s.onDeleteStorage = onDelete } diff --git a/repairer/integration_test.go b/repairer/integration_test.go index e106f799..cfb9803b 100644 --- a/repairer/integration_test.go +++ b/repairer/integration_test.go @@ -6,6 +6,7 @@ import ( "path/filepath" "testing" + anystore "github.com/anyproto/any-store" "github.com/anyproto/any-sync/app" "github.com/anyproto/any-sync/net/peer" "github.com/anyproto/any-sync/net/rpc" @@ -15,7 +16,6 @@ import ( "github.com/anyproto/any-sync/nodeconf/mock_nodeconf" "github.com/anyproto/any-sync/testutil/accounttest" "github.com/anyproto/any-sync/testutil/anymock" - anystore "github.com/anyproto/any-store" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -52,7 +52,7 @@ func TestIntegration_RepairFlows(t *testing.T) { spaceId := fx.genSpaceOnB(t) // node A: an index entry with heads, but no data on disk at all require.NoError(t, fx.storageA.IndexStorage().UpdateHash(ctx, nodestorage.SpaceUpdate{ - SpaceId: spaceId, OldHash: "lost", NewHash: "lost", + SpaceId: spaceId, NewHash: "lost", })) require.NoError(t, fx.storageA.IndexStorage().MarkError(ctx, spaceId, "db file is missing")) @@ -92,7 +92,7 @@ func (fx *intFixture) corruptSpaceOnA(t *testing.T, spaceId string) { require.NoError(t, os.MkdirAll(spaceDir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(spaceDir, "store.db"), []byte("this is not a sqlite database"), 0o644)) require.NoError(t, fx.storageA.IndexStorage().UpdateHash(ctx, nodestorage.SpaceUpdate{ - SpaceId: spaceId, OldHash: "corrupt", NewHash: "corrupt", + SpaceId: spaceId, NewHash: "corrupt", })) require.NoError(t, fx.storageA.IndexStorage().MarkError(ctx, spaceId, "malformed database")) } diff --git a/resharder/integration_test.go b/resharder/integration_test.go index ce2e816d..49cdba5e 100644 --- a/resharder/integration_test.go +++ b/resharder/integration_test.go @@ -68,7 +68,7 @@ func TestIntegration_DrainAdoptRestore(t *testing.T) { require.NoError(t, stateColl.Insert(ctx, stateDoc)) require.NoError(t, db.Close()) require.NoError(t, nodeA.storage.IndexStorage().UpdateHash(ctx, nodestorage.SpaceUpdate{ - SpaceId: spaceId, OldHash: "h-old", NewHash: "h-new", + SpaceId: spaceId, NewHash: "h-new", })) // node A is no longer responsible; node B is the only owner (minAcks=1) @@ -77,7 +77,7 @@ func TestIntegration_DrainAdoptRestore(t *testing.T) { // node B accepts adoption from node A (a tree node) and registers heads nodeB.nodeConf.EXPECT().NodeTypes("nodeA").Return([]nodeconf.NodeType{nodeconf.NodeTypeTree}) nodeB.nodeConf.EXPECT().IsResponsible(spaceId).Return(true) - nodeB.nodeHead.EXPECT().SetHead(spaceId, "h-old", "h-new").Return(0, nil) + nodeB.nodeHead.EXPECT().SetHead(spaceId, "h-new").Return(0, nil) // wire the drainer's adopt call straight into node B's adopter nodeA.resharder.adoptFn = func(_ context.Context, peerId string, req *nodesyncproto.AdoptArchiveRequest) (*nodesyncproto.AdoptArchiveResponse, error) { diff --git a/resharder/resharder.go b/resharder/resharder.go index 64a42824..6279a79a 100644 --- a/resharder/resharder.go +++ b/resharder/resharder.go @@ -308,7 +308,7 @@ func (r *resharder) drainSpace(spaceId string) (ok bool, err error) { // heads advertised to the owners; for live spaces they are read from the // snapshot itself so they exactly describe the uploaded object - oldHash, newHash := entry.OldHash, entry.NewHash + newHash := entry.NewHash compressedSize, uncompressedSize := entry.ArchiveSizeCompressed, entry.ArchiveSizeUncompressed eager := false if status == nodestorage.SpaceStatusOk { @@ -316,7 +316,7 @@ func (r *resharder) drainSpace(spaceId string) (ok bool, err error) { // index entry without local data: nothing to hand off return true, index.SetSpaceStatus(ctx, spaceId, nodestorage.SpaceStatusMoved, "") } - if oldHash, newHash, compressedSize, uncompressedSize, err = r.archive.ForceArchive(ctx, spaceId); err != nil { + if newHash, compressedSize, uncompressedSize, err = r.archive.ForceArchive(ctx, spaceId); err != nil { return false, err } // writes may have landed while the snapshot was taken/uploaded: park @@ -333,9 +333,11 @@ func (r *resharder) drainSpace(spaceId string) (ok bool, err error) { } req := &nodesyncproto.AdoptArchiveRequest{ - SpaceId: spaceId, - SrcKey: r.archiveStore.Key(spaceId), - OldHash: oldHash, + SpaceId: spaceId, + SrcKey: r.archiveStore.Key(spaceId), + // OldHash is a legacy wire field: mirror NewHash for receivers that + // still compare both + OldHash: newHash, NewHash: newHash, Eager: eager, CompressedSize: compressedSize, @@ -388,7 +390,7 @@ func (r *resharder) drainSpace(spaceId string) (ok bool, err error) { if err != nil { return false, err } - if cur.NewHash != newHash || cur.OldHash != oldHash { + if cur.NewHash != newHash { log.Info("drain: heads changed during handoff, retrying next cycle", zap.String("spaceId", spaceId)) return false, nil } diff --git a/resharder/resharder_test.go b/resharder/resharder_test.go index fbbcd19e..3048d9e9 100644 --- a/resharder/resharder_test.go +++ b/resharder/resharder_test.go @@ -37,7 +37,6 @@ func okEntry() nodestorage.SpaceStatusEntry { return nodestorage.SpaceStatusEntry{ SpaceId: spaceId, Status: nodestorage.SpaceStatusOk, - OldHash: "oldHash", NewHash: "newHash", } } @@ -91,7 +90,7 @@ func TestResharder_DrainSpace(t *testing.T) { fx.nodeConf.EXPECT().IsResponsible(spaceId).Return(false) fx.indexStorage.EXPECT().SpaceStatusEntry(gomock.Any(), spaceId).Return(okEntry(), nil) fx.storage.EXPECT().SpaceExists(spaceId).Return(true) - fx.archive.EXPECT().ForceArchive(gomock.Any(), spaceId).Return("oldHash", "newHash", int64(11), int64(22), nil) + fx.archive.EXPECT().ForceArchive(gomock.Any(), spaceId).Return("newHash", int64(11), int64(22), nil) // post-snapshot check: index still matches the snapshot heads fx.indexStorage.EXPECT().SpaceStatusEntry(gomock.Any(), spaceId).Return(okEntry(), nil) fx.archiveStore.EXPECT().Key(spaceId).Return("me/" + spaceId) diff --git a/resharder/scenarios_test.go b/resharder/scenarios_test.go index 260193fe..bd60fc18 100644 --- a/resharder/scenarios_test.go +++ b/resharder/scenarios_test.go @@ -227,7 +227,7 @@ func (net *scenarioNet) createSpace(t *testing.T, nodeName, spaceId string) { require.NoError(t, stateColl.Insert(ctx, stateDoc)) require.NoError(t, db.Close()) require.NoError(t, node.storage.IndexStorage().UpdateHash(ctx, nodestorage.SpaceUpdate{ - SpaceId: spaceId, OldHash: "old-" + spaceId, NewHash: "new-" + spaceId, + SpaceId: spaceId, NewHash: "new-" + spaceId, })) } @@ -246,7 +246,7 @@ func newScenarioNode(t *testing.T, name string, bucket *memBucket, conf *ringCon hotSync := mock_hotsync.NewMockHotSync(ctrl) anymock.ExpectComp(nodeHead.EXPECT(), nodehead.CName) anymock.ExpectComp(hotSync.EXPECT(), hotsync.CName) - nodeHead.EXPECT().SetHead(gomock.Any(), gomock.Any(), gomock.Any()).AnyTimes().Return(0, nil) + nodeHead.EXPECT().SetHead(gomock.Any(), gomock.Any()).AnyTimes().Return(0, nil) nodeHead.EXPECT().DeleteHeads(gomock.Any()).AnyTimes().Return(nil) hotSync.EXPECT().SetMetric(gomock.Any(), gomock.Any()).AnyTimes() hotSync.EXPECT().UpdateQueue(gomock.Any()).AnyTimes()