@@ -881,6 +881,150 @@ fn get_record() {
881881 } ) )
882882}
883883
884+ /// `get_record_from` retrieves the record from a seeded peer that holds it.
885+ ///
886+ /// node 0 knows both node 1 and node 2, but the query is seeded with node 2
887+ /// only. Because `FixedPeersIter` never expands the peer set, the record is
888+ /// returned from node 2.
889+ #[ test]
890+ fn get_record_from_seeded_peer ( ) {
891+ let mut swarms = build_nodes ( 3 ) ;
892+
893+ // node 0 learns the addresses of node 1 and node 2.
894+ let node1_id = * swarms[ 1 ] . 1 . local_peer_id ( ) ;
895+ let node2_id = * swarms[ 2 ] . 1 . local_peer_id ( ) ;
896+ let node1_addr = swarms[ 1 ] . 0 . clone ( ) ;
897+ let node2_addr = swarms[ 2 ] . 0 . clone ( ) ;
898+ swarms[ 0 ]
899+ . 1
900+ . behaviour_mut ( )
901+ . add_address ( & node1_id, node1_addr) ;
902+ swarms[ 0 ]
903+ . 1
904+ . behaviour_mut ( )
905+ . add_address ( & node2_id, node2_addr) ;
906+
907+ // Drop the swarm addresses.
908+ let mut swarms = swarms
909+ . into_iter ( )
910+ . map ( |( _addr, swarm) | swarm)
911+ . collect :: < Vec < _ > > ( ) ;
912+
913+ let record = Record :: new ( random_multihash ( ) , vec ! [ 4 , 5 , 6 ] ) ;
914+ swarms[ 2 ] . behaviour_mut ( ) . store . put ( record. clone ( ) ) . unwrap ( ) ;
915+
916+ // Seed the GET to node 2 only.
917+ let qid = swarms[ 0 ]
918+ . behaviour_mut ( )
919+ . get_record_from ( record. key . clone ( ) , [ node2_id] ) ;
920+
921+ let rt = Runtime :: new ( ) . unwrap ( ) ;
922+ rt. block_on ( poll_fn ( move |ctx| {
923+ for swarm in & mut swarms {
924+ loop {
925+ match swarm. poll_next_unpin ( ctx) {
926+ Poll :: Ready ( Some ( SwarmEvent :: Behaviour ( Event :: OutboundQueryProgressed {
927+ id,
928+ result : QueryResult :: GetRecord ( Ok ( GetRecordOk :: FoundRecord ( r) ) ) ,
929+ ..
930+ } ) ) ) => {
931+ assert_eq ! ( id, qid) ;
932+ assert_eq ! ( r. record, record) ;
933+ return Poll :: Ready ( ( ) ) ;
934+ }
935+ // Ignore any other event.
936+ Poll :: Ready ( Some ( _) ) => ( ) ,
937+ e @ Poll :: Ready ( _) => panic ! ( "Unexpected return value: {e:?}" ) ,
938+ Poll :: Pending => break ,
939+ }
940+ }
941+ }
942+
943+ Poll :: Pending
944+ } ) )
945+ }
946+
947+ /// `get_record_from` never expands beyond the seeded peer set.
948+ ///
949+ /// node 0 knows both node 1 and node 2 and the record lives on node 2, but the
950+ /// query is seeded with node 1 only. An iterative `get_record` would reach
951+ /// node 2 (node 0 knows it directly) and find the record; `get_record_from`
952+ /// seeded with node 1 must instead finish with `NotFound`, never contacting
953+ /// node 2 — the property S/Kademlia node-disjoint lookups rely on.
954+ #[ test]
955+ fn get_record_from_does_not_expand_beyond_seeded_peers ( ) {
956+ let mut swarms = build_nodes ( 3 ) ;
957+
958+ let node1_id = * swarms[ 1 ] . 1 . local_peer_id ( ) ;
959+ let node2_id = * swarms[ 2 ] . 1 . local_peer_id ( ) ;
960+ let node1_addr = swarms[ 1 ] . 0 . clone ( ) ;
961+ let node2_addr = swarms[ 2 ] . 0 . clone ( ) ;
962+ swarms[ 0 ]
963+ . 1
964+ . behaviour_mut ( )
965+ . add_address ( & node1_id, node1_addr) ;
966+ swarms[ 0 ]
967+ . 1
968+ . behaviour_mut ( )
969+ . add_address ( & node2_id, node2_addr) ;
970+
971+ // Drop the swarm addresses.
972+ let mut swarms = swarms
973+ . into_iter ( )
974+ . map ( |( _addr, swarm) | swarm)
975+ . collect :: < Vec < _ > > ( ) ;
976+
977+ // The record lives on node 2, which is NOT in the seeded set.
978+ let record = Record :: new ( random_multihash ( ) , vec ! [ 7 , 8 , 9 ] ) ;
979+ let key = record. key . clone ( ) ;
980+ swarms[ 2 ] . behaviour_mut ( ) . store . put ( record) . unwrap ( ) ;
981+
982+ // Seed the GET to node 1 only — node 1 does not hold the record.
983+ let qid = swarms[ 0 ]
984+ . behaviour_mut ( )
985+ . get_record_from ( key. clone ( ) , [ node1_id] ) ;
986+
987+ let rt = Runtime :: new ( ) . unwrap ( ) ;
988+ rt. block_on ( poll_fn ( move |ctx| {
989+ for swarm in & mut swarms {
990+ loop {
991+ match swarm. poll_next_unpin ( ctx) {
992+ Poll :: Ready ( Some ( SwarmEvent :: Behaviour ( Event :: OutboundQueryProgressed {
993+ id,
994+ result :
995+ QueryResult :: GetRecord ( Err ( GetRecordError :: NotFound {
996+ key : k,
997+ closest_peers,
998+ } ) ) ,
999+ ..
1000+ } ) ) ) => {
1001+ assert_eq ! ( id, qid) ;
1002+ assert_eq ! ( k, key) ;
1003+ // The query must never have expanded to node 2.
1004+ assert ! (
1005+ !closest_peers. contains( & node2_id) ,
1006+ "fixed-peer GET must not contact peers outside the seeded set"
1007+ ) ;
1008+ return Poll :: Ready ( ( ) ) ;
1009+ }
1010+ Poll :: Ready ( Some ( SwarmEvent :: Behaviour ( Event :: OutboundQueryProgressed {
1011+ result : QueryResult :: GetRecord ( Ok ( GetRecordOk :: FoundRecord ( _) ) ) ,
1012+ ..
1013+ } ) ) ) => {
1014+ panic ! ( "record lives only on the un-seeded node 2 and must not be found" )
1015+ }
1016+ // Ignore any other event.
1017+ Poll :: Ready ( Some ( _) ) => ( ) ,
1018+ e @ Poll :: Ready ( _) => panic ! ( "Unexpected return value: {e:?}" ) ,
1019+ Poll :: Pending => break ,
1020+ }
1021+ }
1022+ }
1023+
1024+ Poll :: Pending
1025+ } ) )
1026+ }
1027+
8841028#[ test]
8851029fn get_record_many ( ) {
8861030 // TODO: Randomise
0 commit comments