Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,10 @@ Emitted when a new connection has been established with a peer.

Emitted when a peer's connection has been closed.

#### `core.on('peer-bitfield', peer)`

Emitted when a bitfield or range message from a peer has been processed, meaning the peer's remote bitfield (which blocks it has, `peer.remoteBitfield` and `peer.remoteContiguousLength`) may have been updated.

#### `core.on('upload', index, byteLength, peer)`

Emitted when a block is uploaded to a peer.
Expand Down
12 changes: 12 additions & 0 deletions lib/replicator.js
Original file line number Diff line number Diff line change
Expand Up @@ -1333,6 +1333,8 @@ class Peer {
this.missingBlocks.insert(start, bitfield)
this._clearLocalRange(start, bitfield.byteLength * 8)
this._update()

this.replicator._onpeerbitfield(this)
}

_clearLocalRange(start, length) {
Expand Down Expand Up @@ -1436,6 +1438,8 @@ class Peer {
if (drop === false) this._update()

this.core.emitRemoteContiguousLength()

this.replicator._onpeerbitfield(this)
}

onreorghint() {
Expand Down Expand Up @@ -3217,6 +3221,14 @@ module.exports = class Replicator {
}
}

_onpeerbitfield(peer) {
const sessions = this.core.monitors

for (let i = sessions.length - 1; i >= 0; i--) {
sessions[i].emit('peer-bitfield', peer)
}
}

_ondownload(index, byteLength, from, req) {
const sessions = this.core.monitors

Expand Down
48 changes: 48 additions & 0 deletions test/replicate.js
Original file line number Diff line number Diff line change
Expand Up @@ -2937,6 +2937,54 @@ test('delayed updateAll timer doesnt keep event loop alive', async function (t)
t.is(r._updateAllBump, null, 'timer reset to null')
})

test('peer-bitfield is emitted when a range message is processed', async function (t) {
const a = await create(t)
await a.append(['a', 'b', 'c'])

const b = await create(t, a.key)

// The contiguous range broadcast when the channel opens is the first (and,
// with no wants sent, only) message that updates the peer's remote
// bitfield state, so this exercises the onrange path.
// Read the state inside the listener: reading it off the peer after the
// await would resolve a tick late, by which point onrange has updated it
// regardless of when the event fired.
const bUpdated = new Promise((resolve) => {
b.once('peer-bitfield', (peer) => resolve(peer.remoteContiguousLength))
})

replicate(a, b, t)

t.is(await bUpdated, 3, 'remote bitfield state is updated when emitted')
})

test('peer-bitfield is emitted when a bitfield message is processed', async function (t) {
const writer = await create(t)
await writer.append(['a', 'b', 'c'])

// a holds only block 2: its haves are non-contiguous (contiguous length
// 0), so it broadcasts no contiguous range at channel open and replies to
// wants with bitfield pages — this exercises the onbitfield path
const a = await create(t, writer.key)
replicate(writer, a, t)
await a.download({ blocks: [2] }).done()

const b = await create(t, writer.key)
replicate(a, b, t)

// Learning that the peer has block 2 can only come from a bitfield message
const haves = await new Promise((resolve) => {
b.on('peer-bitfield', function onUpdate(peer) {
if (!peer.remoteBitfield.get(2)) return
b.off('peer-bitfield', onUpdate)
resolve([0, 1, 2].map((i) => peer.remoteBitfield.get(i)))
})
b.download({ blocks: [2] })
})

t.alike(haves, [false, false, true], 'remote has only block 2 when emitted')
})

async function createAndDownload(t, core) {
const b = await create(t, core.key)
replicate(core, b, t, { teardown: false })
Expand Down