@@ -8,6 +8,7 @@ import { pEvent } from 'p-event'
88import { stubInterface } from 'sinon-ts'
99import { Providers } from '../src/providers.js'
1010import { Reprovider } from '../src/reprovider.js'
11+ import { convertBuffer } from '../src/utils.js'
1112import { createPeerIdWithPrivateKey , createPeerIdsWithPrivateKey } from './utils/create-peer-id.js'
1213import type { PeerAndKey } from './utils/create-peer-id.js'
1314import type { ContentRouting } from '../src/content-routing/index.js'
@@ -158,6 +159,101 @@ describe('reprovider', () => {
158159 expect ( provsAfter [ 0 ] . toString ( ) ) . to . equal ( components . peerId . toString ( ) )
159160 } )
160161
162+ it ( 'should reprovide in Kademlia key order' , async function ( ) {
163+ this . timeout ( 5000 )
164+
165+ // five well-known IPFS CIDs — their Kademlia keys will be in some order
166+ // that is unlikely to match the insertion order below
167+ const cids = [
168+ CID . parse ( 'QmZ8eiDPqQqWR17EPxiwCDgrKPVhCHLcyn6xSCNpFAdAZb' ) ,
169+ CID . parse ( 'QmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n' ) ,
170+ CID . parse ( 'QmRgutAxd8t7oGkSm4wmeuByG6M51wcTso6cubDdQtuEfL' ) ,
171+ CID . parse ( 'QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB' ) ,
172+ CID . parse ( 'QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN' )
173+ ]
174+
175+ // compute expected Kademlia key order — use multihash bytes as canonical
176+ // identity since parseProviderKey always reconstructs CIDs as CIDv1/raw
177+ const kadKeys = await Promise . all ( cids . map ( cid => convertBuffer ( cid . multihash . bytes ) ) )
178+ const expectedMultihashes = cids
179+ . map ( ( cid , i ) => ( { multihash : cid . multihash . bytes , kadKey : kadKeys [ i ] } ) )
180+ . sort ( ( a , b ) => {
181+ for ( let i = 0 ; i < a . kadKey . length ; i ++ ) {
182+ if ( a . kadKey [ i ] !== b . kadKey [ i ] ) {
183+ return a . kadKey [ i ] - b . kadKey [ i ]
184+ }
185+ }
186+ return 0
187+ } )
188+ . map ( ( { multihash } ) => multihash )
189+
190+ // insert CIDs in REVERSE expected order to prove sorting overrides insertion order
191+ for ( const { multihash } of [ ...expectedMultihashes ] . reverse ( ) . map ( ( m , i ) => ( { multihash : m , i } ) ) ) {
192+ const cid = cids . find ( c => c . multihash . bytes === multihash ) ??
193+ cids . find ( c => c . multihash . bytes . every ( ( b , j ) => b === multihash [ j ] ) )
194+ if ( cid != null ) {
195+ await providers . addProvider ( cid , components . peerId )
196+ }
197+ }
198+
199+ // recreate reprovider with concurrency=1 so provides are strictly sequential
200+ reprovider = new Reprovider ( components , {
201+ logPrefix : 'libp2p' ,
202+ datastorePrefix : '/dht' ,
203+ metricsPrefix : '' ,
204+ contentRouting,
205+ threshold : 100 ,
206+ validity : 200 ,
207+ interval : 200 ,
208+ concurrency : 1 ,
209+ operationMetrics : { }
210+ } )
211+
212+ const provisionMultihashes : Uint8Array [ ] = [ ]
213+
214+ // resolve when all CIDs have been provided
215+ let resolveWhenDone ! : ( ) => void
216+ const whenAllDone = new Promise < void > ( resolve => { resolveWhenDone = resolve } )
217+ let provided = 0
218+
219+ contentRouting . provide . callsFake ( async function * ( cid : CID ) {
220+ provisionMultihashes . push ( cid . multihash . bytes )
221+ provided ++
222+ if ( provided === cids . length ) {
223+ resolveWhenDone ( )
224+ }
225+ yield * [ ]
226+ } )
227+
228+ await start ( reprovider )
229+ await pEvent ( reprovider , 'reprovide:start' )
230+ await pEvent ( reprovider , 'reprovide:end' )
231+
232+ // wait for the queue to finish processing all enqueued reprovides
233+ await whenAllDone
234+
235+ // verify CIDs were provided in Kademlia key order by checking each
236+ // adjacent pair maintains non-decreasing Kademlia key order
237+ expect ( provisionMultihashes ) . to . have . lengthOf ( cids . length )
238+
239+ for ( let i = 1 ; i < provisionMultihashes . length ; i ++ ) {
240+ const prevKey = await convertBuffer ( provisionMultihashes [ i - 1 ] )
241+ const currKey = await convertBuffer ( provisionMultihashes [ i ] )
242+
243+ let comparison = 0
244+ for ( let j = 0 ; j < prevKey . length ; j ++ ) {
245+ if ( prevKey [ j ] !== currKey [ j ] ) {
246+ comparison = prevKey [ j ] - currKey [ j ]
247+ break
248+ }
249+ }
250+
251+ expect ( comparison ) . to . be . lessThanOrEqual ( 0 ,
252+ `CID at position ${ i - 1 } should have a smaller or equal Kademlia key than position ${ i } `
253+ )
254+ }
255+ } )
256+
161257 describe ( 'shouldReprovide' , ( ) => {
162258 it ( 'should return false for non-self providers' , ( ) => {
163259 const expires = Date . now ( ) + 50
0 commit comments