diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index a21068d7d..000000000 --- a/TODO.txt +++ /dev/null @@ -1,7 +0,0 @@ -- can we remove `have`? -- can we remove `missing`? -- can we remove `dataToSync`? -- docs -- maybe clean up `reduceSyncState` -- change names? -- all remaining TODOs diff --git a/package-lock.json b/package-lock.json index 414802999..fbb2851b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,7 +82,6 @@ "cpy-cli": "^5.0.0", "drizzle-kit": "^0.20.14", "eslint": "^8.57.0", - "filter-obj": "^6.0.0", "husky": "^8.0.0", "iterpal": "^0.4.0", "light-my-request": "^5.10.0", @@ -4129,18 +4128,6 @@ "node": ">=8" } }, - "node_modules/filter-obj": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-6.0.0.tgz", - "integrity": "sha512-lscJ1kdwOyW2Nb3wcoU/LnNLoHg+Weoe7r8NT8xjNAdIZPm/JQuKoMcu3FnWrlKOjtAcf98pewuUyjeD6GoKig==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/find-my-way": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.1.0.tgz", diff --git a/package.json b/package.json index bb8767db7..c367d20f7 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,6 @@ "cpy-cli": "^5.0.0", "drizzle-kit": "^0.20.14", "eslint": "^8.57.0", - "filter-obj": "^6.0.0", "husky": "^8.0.0", "iterpal": "^0.4.0", "light-my-request": "^5.10.0", diff --git a/src/sync/core-sync-state.js b/src/sync/core-sync-state.js index ff9517418..d12eb5aa4 100644 --- a/src/sync/core-sync-state.js +++ b/src/sync/core-sync-state.js @@ -18,19 +18,22 @@ import RemoteBitfield, { * @property {import('../core-manager/index.js').Namespace} namespace */ /** - * @typedef {object} CoreState - * @property {number} have blocks the peer has locally - * @property {number} want blocks the peer wants, and at least one peer has - * @property {number} wanted blocks the peer has that at least one peer wants - * @property {number} missing blocks the peer wants but no peer has + * @typedef {object} LocalCoreState + * @property {number} have blocks we have + * @property {number} want unique blocks we want from any other peer + * @property {number} wanted blocks we want from this peer */ /** - * @typedef {CoreState & { status: 'disconnected' | 'connecting' | 'connected' }} PeerCoreState + * @typedef {object} PeerCoreState + * @property {number} have blocks the peer has locally + * @property {number} want blocks this peer wants from us + * @property {number} wanted blocks we want from this peer + * @property {'disconnected' | 'connecting' | 'connected'} status */ /** * @typedef {object} DerivedState * @property {number} coreLength known (sparse) length of the core - * @property {CoreState} localState local state + * @property {LocalCoreState} localState local state * @property {{ [peerId in PeerId]: PeerCoreState }} remoteStates map of state of all known peers */ @@ -45,11 +48,16 @@ import RemoteBitfield, { * "pull" the state when it wants it via `coreSyncState.getState()`. * * Each peer (including the local peer) has a state of: - * 1. `have` - number of blocks the peer has locally - * 2. `want` - number of blocks the peer wants, and at least one peer has - * 3. `wanted` - number of blocks the peer has that at least one peer wants - * 4. `missing` - number of blocks the peer wants but no peer has * + * 1. `have` - number of blocks the peer has locally + * + * 2. `want` - number of blocks this peer wants. For local state, this is the + * number of unique blocks we want from anyone else. For remote peers, it is + * the number of blocks this peer wants from us. + * + * 3. `wanted` - number of blocks this peer has that's wanted by others. For + * local state, this is the number of unique blocks any of our peers want. + * For remote peers, it is the number of blocks we want from them. */ export class CoreSyncState { /** @type {import('hypercore')<'binary', Buffer> | undefined} */ @@ -339,9 +347,7 @@ export class PeerState { } /** - * Derive count for each peer: "want"; "have"; "wanted". There is definitely a - * more performant and clever way of doing this, but at least with this - * implementation I can understand what I am doing. + * Derive count for each peer: "want"; "have"; "wanted". * * @param {InternalState} coreState * @@ -349,9 +355,14 @@ export class PeerState { * Only exporteed for testing */ export function deriveState(coreState) { - const peerIds = ['local'] - const peers = [coreState.localState] + const length = coreState.length || 0 + /** @type {LocalCoreState} */ + const localState = { have: 0, want: 0, wanted: 0 } + /** @type {Record} */ + const remoteStates = {} + /** @type {Map} */ + const peers = new Map() for (const [peerId, peerState] of coreState.remoteStates.entries()) { const psc = coreState.peerSyncControllers.get(peerId) const isBlocked = psc?.syncCapability[coreState.namespace] === 'blocked' @@ -359,65 +370,46 @@ export function deriveState(coreState) { // how to expose this state in a meaningful way for considering sync // completion, because blocked peers do not sync. if (isBlocked) continue - peerIds.push(peerId) - peers.push(peerState) + peers.set(peerId, peerState) + remoteStates[peerId] = { + have: 0, + want: 0, + wanted: 0, + status: peerState.status, + } } - /** @type {CoreState[]} */ - const peerStates = new Array(peers.length) - const length = coreState.length || 0 - for (let i = 0; i < peerStates.length; i++) { - peerStates[i] = { want: 0, have: 0, wanted: 0, missing: 0 } - } - const haves = new Array(peerStates.length) - let want = 0 for (let i = 0; i < length; i += 32) { const truncate = 2 ** Math.min(32, length - i) - 1 - let someoneHasIt = 0 - for (let j = 0; j < peers.length; j++) { - haves[j] = peers[j].haveWord(i) & truncate - someoneHasIt |= haves[j] - peerStates[j].have += bitCount32(haves[j]) - } - let someoneWantsIt = 0 - for (let j = 0; j < peers.length; j++) { - // A block is a want if: - // 1. The peer wants it - // 2. They don't have it - // 3. Someone does have it - const wouldLikeIt = peers[j].wantWord(i) & ~haves[j] - want = wouldLikeIt & someoneHasIt - someoneWantsIt |= want - peerStates[j].want += bitCount32(want) - // A block is missing if: - // 1. The peer wants it - // 2. The peer doesn't have it - // 3. No other peer has it - // Need to truncate to the core length, since otherwise we would get - // missing values beyond core length - const missing = wouldLikeIt & ~someoneHasIt & truncate - peerStates[j].missing += bitCount32(missing) - } - for (let j = 0; j < peerStates.length; j++) { - // A block is wanted if: - // 1. Someone wants it - // 2. The peer has it - const wanted = someoneWantsIt & haves[j] - peerStates[j].wanted += bitCount32(wanted) + + const localHaves = coreState.localState.haveWord(i) & truncate + localState.have += bitCount32(localHaves) + + let someoneElseWantsFromMe = 0 + let iWantFromSomeoneElse = 0 + + for (const [peerId, peer] of peers.entries()) { + const peerHaves = peer.haveWord(i) & truncate + remoteStates[peerId].have += bitCount32(peerHaves) + + const theyWantFromMe = peer.wantWord(i) & ~peerHaves & localHaves + remoteStates[peerId].want += bitCount32(theyWantFromMe) + someoneElseWantsFromMe |= theyWantFromMe + + const iWantFromThem = peerHaves & ~localHaves + remoteStates[peerId].wanted += bitCount32(iWantFromThem) + iWantFromSomeoneElse |= iWantFromThem } + + localState.wanted += bitCount32(someoneElseWantsFromMe) + localState.want += bitCount32(iWantFromSomeoneElse) } - /** @type {DerivedState} */ - const derivedState = { + + return { coreLength: length, - localState: peerStates[0], - remoteStates: {}, - } - for (let j = 1; j < peerStates.length; j++) { - const peerState = /** @type {PeerCoreState} */ (peerStates[j]) - peerState.status = peers[j].status - derivedState.remoteStates[peerIds[j]] = peerState + localState, + remoteStates, } - return derivedState } /** diff --git a/src/sync/namespace-sync-state.js b/src/sync/namespace-sync-state.js index e1a7a86ec..01fa4d373 100644 --- a/src/sync/namespace-sync-state.js +++ b/src/sync/namespace-sync-state.js @@ -148,9 +148,9 @@ export class NamespaceSyncState { */ export function createState(status) { if (status) { - return { want: 0, have: 0, wanted: 0, missing: 0, status } + return { want: 0, have: 0, wanted: 0, status } } else { - return { want: 0, have: 0, wanted: 0, missing: 0 } + return { want: 0, have: 0, wanted: 0 } } } @@ -178,7 +178,6 @@ function mutatingAddPeerState(accumulator, currentValue) { accumulator.have += currentValue.have accumulator.want += currentValue.want accumulator.wanted += currentValue.wanted - accumulator.missing += currentValue.missing if ('status' in accumulator && accumulator.status !== currentValue.status) { if (currentValue.status === 'disconnected') { accumulator.status === 'disconnected' diff --git a/src/sync/peer-sync-controller.js b/src/sync/peer-sync-controller.js index 2393329f6..c669199ce 100644 --- a/src/sync/peer-sync-controller.js +++ b/src/sync/peer-sync-controller.js @@ -29,7 +29,7 @@ export class PeerSyncController { #syncCapability = createNamespaceMap('unknown') /** @type {SyncEnabledState} */ #syncEnabledState = 'none' - /** @type {Record} */ + /** @type {Record} */ #prevLocalState = createNamespaceMap(null) /** @type {SyncStatus} */ #syncStatus = createNamespaceMap('unknown') diff --git a/src/sync/sync-api.js b/src/sync/sync-api.js index ba854f742..8854238b6 100644 --- a/src/sync/sync-api.js +++ b/src/sync/sync-api.js @@ -22,23 +22,25 @@ export const kRescindFullStopRequest = Symbol('foreground') */ /** - * @typedef {object} DeviceNamespaceGroupSyncState + * @internal + * @typedef {object} RemoteDeviceNamespaceGroupSyncState * @property {boolean} isSyncEnabled this device in a 'connected' state * @property {number} want number of docs wanted by this device * @property {number} wanted number of docs that other devices want from this device */ /** - * @typedef {object} DeviceSyncState state of sync for remote peer - * @property {DeviceNamespaceGroupSyncState} initial state of auth, metadata and project config - * @property {DeviceNamespaceGroupSyncState} data state of observations, map data, media attachments + * @internal + * @typedef {object} RemoteDeviceSyncState state of sync for remote peer + * @property {RemoteDeviceNamespaceGroupSyncState} initial state of auth, metadata and project config + * @property {RemoteDeviceNamespaceGroupSyncState} data state of observations, map data, media attachments */ /** * @typedef {object} State * @property {{ isSyncEnabled: boolean }} initial State of initial sync (sync of auth, metadata and project config) for local device * @property {{ isSyncEnabled: boolean }} data State of data sync (observations, map data, photos, audio, video etc.) for local device - * @property {Record} remoteDeviceSyncState map of peerId to DeviceSyncState. + * @property {Record} remoteDeviceSyncState map of peerId to DeviceSyncState. */ /** @@ -439,11 +441,6 @@ function getRemoteDevicesSyncState(namespaceSyncState, peerSyncControllers) { for (const psc of peerSyncControllers) { const { peerId } = psc - result[peerId] = { - initial: { isSyncEnabled: false, want: 0, wanted: 0 }, - data: { isSyncEnabled: false, want: 0, wanted: 0 }, - } - for (const namespace of NAMESPACES) { const isBlocked = psc.syncCapability[namespace] === 'blocked' if (isBlocked) continue @@ -465,6 +462,13 @@ function getRemoteDevicesSyncState(namespaceSyncState, peerSyncControllers) { throw new ExhaustivenessError(peerCoreState.status) } + if (!Object.hasOwn(result, peerId)) { + result[peerId] = { + initial: { isSyncEnabled: false, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 0, wanted: 0 }, + } + } + const namespaceGroup = PRESYNC_NAMESPACES.includes(namespace) ? 'initial' : 'data' diff --git a/test-e2e/sync.js b/test-e2e/sync.js index e8bd022ca..31cf77319 100644 --- a/test-e2e/sync.js +++ b/test-e2e/sync.js @@ -4,7 +4,7 @@ import * as fs from 'node:fs/promises' import { pEvent } from 'p-event' import { setTimeout as delay } from 'timers/promises' import { request } from 'undici' -import { excludeKeys } from 'filter-obj' +import { isDeepStrictEqual } from 'node:util' import FakeTimers from '@sinonjs/fake-timers' import Fastify from 'fastify' import { map } from 'iterpal' @@ -648,17 +648,7 @@ test('no sync capabilities === no namespaces sync apart from auth', async (t) => assert.equal(inviteeState[ns].coreCount, 2) assert.equal(blockedState[ns].coreCount, 1) } - - // "Invitor" knows blocked peer is blocked from the start, so never connects - // and never creates a local copy of the blocked peer cores, but "Invitee" - // does connect initially, before it realized the peer is blocked, and - // creates a local copy of the blocked peer's cores, but never downloads - // data, so it considers data to be "missing" which the Invitor does not - // register as missing. - assert.deepEqual( - excludeKeys(invitorState[ns].localState, ['missing']), - excludeKeys(inviteeState[ns].localState, ['missing']) - ) + assert.deepEqual(invitorState[ns].localState, inviteeState[ns].localState) } await disconnect1() @@ -805,11 +795,15 @@ test('Correct sync state prior to data sync', async function (t) { } }) - // Wait for initial sharing of sync state - await delay(200) - - const syncState = await Promise.all(projects.map((p) => p.$sync.getState())) + await Promise.all( + projects.map((project, i) => + pEvent(project.$sync, 'sync-state', (syncState) => + isDeepStrictEqual(syncState, expected[i]) + ) + ) + ) + const syncState = projects.map((p) => p.$sync.getState()) assert.deepEqual(syncState, expected) await disconnect2() @@ -883,7 +877,6 @@ test('pre-haves are updated', async (t) => { ) }) -// TODO: remove timeout test('data sync state is properly updated as data sync is enabled and disabled', async (t) => { const managers = await createManagers(3, t) const [invitor, ...invitees] = managers @@ -895,28 +888,43 @@ test('data sync state is properly updated as data sync is enabled and disabled', const invitorProject = await invitor.getProject(projectId) invitorProject.observation.create(valueOf(generate('observation')[0])) assert.ok( - invitorProject.$sync.getState().initial, + invitorProject.$sync.getState().initial.isSyncEnabled, 'initial sync is enabled for local device' ) await invite({ invitor, invitees, projectId }) - const [_, ...inviteesProjects] = await Promise.all( - managers.map((m) => m.getProject(projectId)) + const inviteesProjects = await Promise.all( + invitees.map((m) => m.getProject(projectId)) ) await waitForSync([invitorProject, ...inviteesProjects], 'initial') - Object.values(invitorProject.$sync.getState().remoteDeviceSyncState).forEach( - (syncState) => { - assert.ok( - syncState.initial.isSyncEnabled, - 'initial sync is also enabled for all remote devices' - ) - assert.equal( - syncState.data.want, - 1, - 'remote peers want one document from local peer' - ) - } + assert.deepEqual( + invitorProject.$sync.getState().remoteDeviceSyncState, + { + [invitees[0].deviceId]: { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 1, wanted: 0 }, + }, + [invitees[1].deviceId]: { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 1, wanted: 0 }, + }, + }, + "from the invitor's perspective, remote peers want one document and data sync is disabled" + ) + assert.deepEqual( + inviteesProjects[0].$sync.getState().remoteDeviceSyncState, + { + [invitorProject.deviceId]: { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 0, wanted: 1 }, + }, + [invitees[1].deviceId]: { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 0, wanted: 0 }, + }, + }, + "from one invitee's perspective, one remote peer has a document and data sync is disabled" ) invitorProject.$sync.start() @@ -925,13 +933,41 @@ test('data sync state is properly updated as data sync is enabled and disabled', invitorProject.$sync.getState().data.isSyncEnabled, 'after enabled sync, data sync is enabled for local device' ) + assert.deepEqual( + invitorProject.$sync.getState().remoteDeviceSyncState, + { + [invitees[0].deviceId]: { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 1, wanted: 0 }, + }, + [invitees[1].deviceId]: { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 1, wanted: 0 }, + }, + }, + 'data sync is still disabled for remote peers' + ) - Object.values(invitorProject.$sync.getState().remoteDeviceSyncState).forEach( - (syncState) => { - assert.ok( - !syncState.data.isSyncEnabled, - 'data sync is still disabled for all remote devices' - ) + const inviteeAppearsEnabledPromise = pEvent( + invitorProject.$sync, + 'sync-state', + ({ remoteDeviceSyncState }) => + remoteDeviceSyncState[invitees[0].deviceId]?.data.isSyncEnabled + ) + const invitorProjectSyncedWithFirstInviteePromise = pEvent( + invitorProject.$sync, + 'sync-state', + ({ remoteDeviceSyncState }) => { + const remoteData = remoteDeviceSyncState[invitees[0].deviceId]?.data ?? {} + return remoteData.want + remoteData.wanted === 0 + } + ) + const firstInviteeProjectSyncedWithInvitorPromise = pEvent( + inviteesProjects[0].$sync, + 'sync-state', + ({ remoteDeviceSyncState }) => { + const remoteData = remoteDeviceSyncState[invitor.deviceId]?.data ?? {} + return remoteData.want + remoteData.wanted === 0 } ) @@ -939,37 +975,54 @@ test('data sync state is properly updated as data sync is enabled and disabled', assert.ok( inviteesProjects[0].$sync.getState().data.isSyncEnabled, - 'remote peer has data sync enabled after starting sync' + 'invitee has data sync enabled after starting sync' ) - await delay(2000) - assert.ok( - Object.values(invitorProject.$sync.getState().remoteDeviceSyncState).some( - (remoteState) => { - return remoteState.data.isSyncEnabled - } - ), - 'at least one remote peer has enabled data sync' + await inviteeAppearsEnabledPromise + + assert( + invitorProject.$sync.getState().remoteDeviceSyncState[invitees[0].deviceId] + ?.data.isSyncEnabled, + 'one invitee has enabled data sync' + ) + assert( + !invitorProject.$sync.getState().remoteDeviceSyncState[invitees[1].deviceId] + ?.data.isSyncEnabled, + 'other invitee has not enabled data sync' + ) + + await Promise.all([ + invitorProjectSyncedWithFirstInviteePromise, + firstInviteeProjectSyncedWithInvitorPromise, + ]) + + const inviteeAppearsDisabledPromise = pEvent( + invitorProject.$sync, + 'sync-state', + ({ remoteDeviceSyncState }) => + !remoteDeviceSyncState[invitees[0].deviceId]?.data.isSyncEnabled ) inviteesProjects[0].$sync.stop() - const wantedDocs = Object.values( + + await inviteeAppearsDisabledPromise + + const finalRemoteDeviceSyncState = invitorProject.$sync.getState().remoteDeviceSyncState - ).reduce((finalWanted, remoteState) => { - return finalWanted + remoteState.data.want - }, 0) - assert.equal( - wantedDocs, - 1, - 'after one peer enabled data sync, only the other peer remains wanting a doc' - ) - await delay(2000) - assert.ok( - !Object.values(invitorProject.$sync.getState().remoteDeviceSyncState).some( - (remoteState) => { - return remoteState.data.isSyncEnabled - } - ), - 'no remote peer has enabled data sync now' + assert.deepEqual( + finalRemoteDeviceSyncState[invitees[0].deviceId], + { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 0, wanted: 0 }, + }, + 'one invitee is disabled but settled' + ) + assert.deepEqual( + finalRemoteDeviceSyncState[invitees[1].deviceId], + { + initial: { isSyncEnabled: true, want: 0, wanted: 0 }, + data: { isSyncEnabled: false, want: 1, wanted: 0 }, + }, + 'other invitee is still disabled, still wants something' ) }) diff --git a/tests/sync/core-sync-state.js b/tests/sync/core-sync-state.js index 4c1ccbbd1..0854899f4 100644 --- a/tests/sync/core-sync-state.js +++ b/tests/sync/core-sync-state.js @@ -29,7 +29,7 @@ import { EventEmitter } from 'node:events' */ const scenarios = [ { - message: '3 peers, start with haves, test want, have, wanted and missing', + message: '3 peers, start with haves, test want, have, and wanted', state: { length: 4, localState: { have: 0b0111 }, @@ -37,27 +37,24 @@ const scenarios = [ }, expected: { coreLength: 4, - localState: { want: 0, have: 3, wanted: 2, missing: 1 }, + localState: { want: 0, have: 3, wanted: 2 }, remoteStates: { peer0: { want: 1, have: 2, - wanted: 1, - missing: 1, + wanted: 0, status: 'disconnected', }, peer1: { want: 1, have: 2, - wanted: 1, - missing: 1, + wanted: 0, status: 'disconnected', }, peer2: { want: 2, have: 1, wanted: 0, - missing: 1, status: 'disconnected', }, }, @@ -72,20 +69,18 @@ const scenarios = [ }, expected: { coreLength: 4, - localState: { want: 0, have: 0, wanted: 0, missing: 4 }, + localState: { want: 0, have: 0, wanted: 0 }, remoteStates: { peer0: { want: 0, have: 0, wanted: 0, - missing: 4, status: 'disconnected', }, peer1: { want: 0, have: 0, wanted: 0, - missing: 4, status: 'disconnected', }, }, @@ -100,9 +95,9 @@ const scenarios = [ }, expected: { coreLength: 3, - localState: { want: 0, have: 3, wanted: 1, missing: 0 }, + localState: { want: 0, have: 3, wanted: 1 }, remoteStates: { - peer0: { want: 1, have: 1, wanted: 0, missing: 0, status: 'connected' }, + peer0: { want: 1, have: 1, wanted: 0, status: 'connected' }, }, }, }, @@ -115,13 +110,12 @@ const scenarios = [ }, expected: { coreLength: 3, - localState: { want: 0, have: 3, wanted: 1, missing: 0 }, + localState: { want: 0, have: 3, wanted: 1 }, remoteStates: { peer0: { want: 1, have: 1, wanted: 0, - missing: 0, status: 'disconnected', }, }, @@ -136,13 +130,12 @@ const scenarios = [ }, expected: { coreLength: 3, - localState: { want: 0, have: 3, wanted: 1, missing: 0 }, + localState: { want: 0, have: 3, wanted: 1 }, remoteStates: { peer0: { want: 1, have: 2, wanted: 0, - missing: 0, status: 'disconnected', }, }, @@ -157,13 +150,12 @@ const scenarios = [ }, expected: { coreLength: 3, - localState: { want: 0, have: 3, wanted: 0, missing: 0 }, + localState: { want: 0, have: 3, wanted: 0 }, remoteStates: { peer0: { want: 0, have: 3, wanted: 0, - missing: 0, status: 'disconnected', }, }, @@ -182,27 +174,24 @@ const scenarios = [ }, expected: { coreLength: 72, - localState: { want: 0, have: 50, wanted: 15, missing: 22 }, + localState: { want: 0, have: 50, wanted: 15 }, remoteStates: { peer0: { want: 10, have: 40, - wanted: 5, - missing: 22, + wanted: 0, status: 'disconnected', }, peer1: { want: 5, have: 40, - wanted: 10, - missing: 0, + wanted: 0, status: 'disconnected', }, peer2: { want: 5, have: 40, - wanted: 10, - missing: 0, + wanted: 0, status: 'disconnected', }, }, @@ -217,20 +206,18 @@ const scenarios = [ }, expected: { coreLength: 2, - localState: { want: 0, have: 2, wanted: 2, missing: 0 }, + localState: { want: 0, have: 2, wanted: 2 }, remoteStates: { peer0: { want: 1, have: 0, wanted: 0, - missing: 0, status: 'disconnected', }, peer1: { want: 2, have: 0, wanted: 0, - missing: 0, status: 'disconnected', }, }, @@ -272,14 +259,12 @@ test('deriveState() have at index beyond bitfield page size', () => { want: 1, have: 10, wanted: 10, - missing: BITS_PER_PAGE - 1, }, remoteStates: { peer0: { want: 10, have: 1, wanted: 1, - missing: BITS_PER_PAGE - 1, status: 'disconnected', }, }, diff --git a/tests/sync/namespace-sync-state.js b/tests/sync/namespace-sync-state.js index ad7545bc7..8551afe21 100644 --- a/tests/sync/namespace-sync-state.js +++ b/tests/sync/namespace-sync-state.js @@ -49,8 +49,7 @@ test('sync cores in a namespace', async () => { if ( state.localState.want === 0 && state.localState.wanted === 0 && - state.localState.have === 30 && - state.localState.missing === 10 + state.localState.have === 30 ) { syncState1Sync.resolve(state.remoteStates) } @@ -66,8 +65,7 @@ test('sync cores in a namespace', async () => { if ( state.localState.want === 0 && state.localState.wanted === 0 && - state.localState.have === 30 && - state.localState.missing === 10 + state.localState.have === 30 ) { syncState2Sync.resolve(state.remoteStates) } @@ -104,7 +102,6 @@ test('sync cores in a namespace', async () => { want: 0, wanted: 0, have: 30, - missing: 10, status: 'connected', }, }, @@ -118,7 +115,6 @@ test('sync cores in a namespace', async () => { want: 0, wanted: 0, have: 30, - missing: 10, status: 'connected', }, },