33import { expect } from 'aegir/chai'
44import random from 'lodash.random'
55import sinon from 'sinon'
6- import { RoutingTable } from '../src/routing-table/index.js'
6+ import { KAD_CLOSE_TAG_NAME , KAD_CLOSE_TAG_VALUE , KBUCKET_SIZE , RoutingTable } from '../src/routing-table/index.js'
77import * as kadUtils from '../src/utils.js'
88import { createPeerId , createPeerIds } from './utils/create-peer-id.js'
99import { PROTOCOL_DHT } from '../src/constants.js'
1010import { peerIdFromString } from '@libp2p/peer-id'
1111import { Components } from '@libp2p/components'
1212import { mockConnectionManager } from '@libp2p/interface-mocks'
13+ import { PersistentPeerStore } from '@libp2p/peer-store'
14+ import { MemoryDatastore } from 'datastore-core'
15+ import { createEd25519PeerId } from '@libp2p/peer-id-factory'
16+ import { sortClosestPeers } from './utils/sort-closest-peers.js'
17+ import pWaitFor from 'p-wait-for'
18+ import { pipe } from 'it-pipe'
19+ import all from 'it-all'
20+ import { PeerSet } from '@libp2p/peer-collections'
1321
1422describe ( 'Routing Table' , ( ) => {
1523 let table : RoutingTable
@@ -20,7 +28,9 @@ describe('Routing Table', () => {
2028
2129 components = new Components ( {
2230 peerId : await createPeerId ( ) ,
23- connectionManager : mockConnectionManager ( )
31+ connectionManager : mockConnectionManager ( ) ,
32+ datastore : new MemoryDatastore ( ) ,
33+ peerStore : new PersistentPeerStore ( )
2434 } )
2535
2636 table = new RoutingTable ( {
@@ -207,4 +217,90 @@ describe('Routing Table', () => {
207217 // evicted the old peer
208218 expect ( table . kb . get ( oldPeer . id ) ) . to . be . null ( )
209219 } )
220+
221+ it ( 'tags newly found kad-close peers' , async ( ) => {
222+ const remotePeer = await createEd25519PeerId ( )
223+ const tagPeerSpy = sinon . spy ( components . getPeerStore ( ) , 'tagPeer' )
224+
225+ await table . add ( remotePeer )
226+
227+ expect ( tagPeerSpy . callCount ) . to . equal ( 0 , 'did not debounce call to peerStore.tagPeer' )
228+
229+ await pWaitFor ( ( ) => {
230+ return tagPeerSpy . callCount === 1
231+ } )
232+
233+ expect ( tagPeerSpy . callCount ) . to . equal ( 1 , 'did not tag kad-close peer' )
234+ expect ( tagPeerSpy . getCall ( 0 ) . args [ 0 ] . toString ( ) ) . to . equal ( remotePeer . toString ( ) )
235+ expect ( tagPeerSpy . getCall ( 0 ) . args [ 1 ] ) . to . equal ( KAD_CLOSE_TAG_NAME )
236+ expect ( tagPeerSpy . getCall ( 0 ) . args [ 2 ] ) . to . have . property ( 'value' , KAD_CLOSE_TAG_VALUE )
237+ } )
238+
239+ it ( 'removes tags from kad-close peers when closer peers are found' , async ( ) => {
240+ async function getTaggedPeers ( ) : Promise < PeerSet > {
241+ return new PeerSet ( await pipe (
242+ await components . getPeerStore ( ) . all ( ) ,
243+ async function * ( source ) {
244+ for await ( const peer of source ) {
245+ const tags = await components . getPeerStore ( ) . getTags ( peer . id )
246+ const kadCloseTags = tags . filter ( tag => tag . name === KAD_CLOSE_TAG_NAME )
247+
248+ if ( kadCloseTags . length > 0 ) {
249+ yield peer . id
250+ }
251+ }
252+ } ,
253+ async ( source ) => await all ( source )
254+ ) )
255+ }
256+
257+ const tagPeerSpy = sinon . spy ( components . getPeerStore ( ) , 'tagPeer' )
258+ const unTagPeerSpy = sinon . spy ( components . getPeerStore ( ) , 'unTagPeer' )
259+ const localNodeId = await kadUtils . convertPeerId ( components . getPeerId ( ) )
260+ const sortedPeerList = await sortClosestPeers (
261+ await Promise . all (
262+ new Array ( KBUCKET_SIZE + 1 ) . fill ( 0 ) . map ( async ( ) => await createEd25519PeerId ( ) )
263+ ) ,
264+ localNodeId
265+ )
266+
267+ // sort list furthest -> closest
268+ sortedPeerList . reverse ( )
269+
270+ // fill the table up to the first kbucket size
271+ for ( let i = 0 ; i < KBUCKET_SIZE ; i ++ ) {
272+ await table . add ( sortedPeerList [ i ] )
273+ }
274+
275+ // should have all added contacts in the root kbucket
276+ expect ( table . kb ?. count ( ) ) . to . equal ( KBUCKET_SIZE , 'did not fill kbuckets' )
277+ expect ( table . kb ?. root . contacts ) . to . have . lengthOf ( KBUCKET_SIZE , 'split root kbucket when we should not have' )
278+ expect ( table . kb ?. root . left ) . to . be . null ( 'split root kbucket when we should not have' )
279+ expect ( table . kb ?. root . right ) . to . be . null ( 'split root kbucket when we should not have' )
280+
281+ await pWaitFor ( ( ) => {
282+ return tagPeerSpy . callCount === KBUCKET_SIZE
283+ } )
284+
285+ // make sure we tagged all of the peers as kad-close
286+ const taggedPeers = await getTaggedPeers ( )
287+ expect ( taggedPeers . difference ( new PeerSet ( sortedPeerList . slice ( 0 , sortedPeerList . length - 1 ) ) ) ) . to . have . property ( 'size' , 0 )
288+ tagPeerSpy . resetHistory ( )
289+
290+ // add a node that is closer than any added so far
291+ await table . add ( sortedPeerList [ sortedPeerList . length - 1 ] )
292+
293+ expect ( table . kb ?. count ( ) ) . to . equal ( KBUCKET_SIZE + 1 , 'did not fill kbuckets' )
294+ expect ( table . kb ?. root . left ) . to . not . be . null ( 'did not split root kbucket when we should have' )
295+ expect ( table . kb ?. root . right ) . to . not . be . null ( 'did not split root kbucket when we should have' )
296+
297+ // wait for tag new peer and untag old peer
298+ await pWaitFor ( ( ) => {
299+ return tagPeerSpy . callCount === 1 && unTagPeerSpy . callCount === 1
300+ } )
301+
302+ // should have updated list of tagged peers
303+ const finalTaggedPeers = await getTaggedPeers ( )
304+ expect ( finalTaggedPeers . difference ( new PeerSet ( sortedPeerList . slice ( 1 ) ) ) ) . to . have . property ( 'size' , 0 )
305+ } )
210306} )
0 commit comments