Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

protocols/kad: Add Kademlia::get_closest_local_peers #2436

Merged
merged 15 commits into from
Jan 22, 2022

Conversation

folex
Copy link
Contributor

@folex folex commented Jan 14, 2022

Hi! I'm developing a project where a need came for a network discovery on top of the Kademlia. Here's an example fluencelabs/quickstart/oracle.

For this, I expose API to gather Kademlia's k-closest peers. However, exposing get_closest_peers to users isn't secure: it may (it sure does, I've tested 🥲) lead to amplification attack for obvious reasons. So, I ended up using Kademlia's local routing table for that, and it's working great for my purposes.

However, maintaining a GitHub fork of libp2p is tedious. So it would be great to have this change upstream so I can use libp2p from crates.io instead of a GitHub fork. And I hope that may be useful for other people out there.

And that's what this PR is about: it adds a simple single-line method that runs ClosestBucketsIter over local kbuckets and returns it as iterator.

Thanks in advance!

Copy link
Member

@mxinden mxinden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the late review.

Do I understand correctly that Kademlia::kbucket is not enough for your use-case?

protocols/kad/src/behaviour.rs Outdated Show resolved Hide resolved
/// Returns closest peers to the given key; takes peers from local routing table
pub fn local_closest_peers<'s, 'k: 's, K: 's>(
&'s mut self,
key: &'k kbucket::Key<K>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
key: &'k kbucket::Key<K>,
key: K,
// ...
where
K: Into<kbucket::Key<K>> + Into<Vec<u8>> + Clone,

Why not mirror the signature of get_closest_peers?

Copy link
Contributor Author

@folex folex Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we have to pass key as a reference, so function can return an impl Iterator that would use that reference.

We can't pass key by value, because then fn get_local_closest_peers will own it and drop, thus invalidating impl Iterator. Borrow checker would be unhappy about that :)

Copy link
Contributor Author

@folex folex Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible to do it this way:

    pub fn get_local_closest_peers<'s, 'k: 's, K: 's>(
        &'s mut self,
        key: &'k K,
    ) -> impl ...
    where
        K: AsRef<kbucket::Key<K>> + Clone,
    {
        self.kbuckets.closest_keys(key.as_ref())
    }

Do you find that better than the current version?

P.S. Into<Vec<u8>> isn't required for get_local_closest_peers

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a second thought, K: AsRef<kbucket::Key<K>> + Clone doesn't make sense. It does make sense to do K: Into<Key<K>>, cuz then if refers to the following From impls:

impl From<Multihash> for Key<Multihash>
impl From<PeerId> for Key<PeerId>
impl From<Vec<u8>> for Key<Vec<u8>>

But since there aren't any analogous AsRef<Key<_>> impls, the bound K: AsRef<kbucket::Key<K>> doesn't make sense.

Copy link
Contributor Author

@folex folex Jan 18, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I see best to stay with key: &'k kbucket::Key<K> and rename K to T for naming consistency. WDYT?

Did that. Now it looks like this:

pub fn get_local_closest_peers<'s, 't: 's, T: 's>(
    &'s mut self,
    key: &'t kbucket::Key<T>,
) -> impl ...
where
    T: Clone,
{
    self.kbuckets.closest_keys(key)
}

protocols/kad/src/behaviour.rs Outdated Show resolved Hide resolved
@folex
Copy link
Contributor Author

folex commented Jan 18, 2022

Do I understand correctly that Kademlia::kbucket is not enough for your use-case?

That's right. It doesn't follow the XOR-metric sorting, so I can't have a consistent set of peers for a key.

@folex folex requested a review from mxinden January 19, 2022 11:02
@mxinden
Copy link
Member

mxinden commented Jan 20, 2022

Thanks for the explanation above on key needs to be borrowed.

How about the following?

    /// Returns closest peers to the given key; takes peers from local routing table only.
    pub fn get_closest_local_peers<'a, K: Clone>(
        &'a mut self,
        key: &'a kbucket::Key<K>,
    ) -> impl Iterator<Item = kbucket::Key<PeerId>> + 'a {
        self.kbuckets.closest_keys(key)
    }
  • I think it is fine to keep referring to the generic key as K.
  • Let's not mix trait bound listing in the initial <T: XXX> and a where clause.
  • I don't think the lifetime on T (now K) is needed. In other words, the lifetime only needs to be mentioned on the reference (&'a)
  • Let's rename it to get_closest_local_peers
  • What does the lifetime parameter s stand for? If it is just a random letter, I would prefer 'a as that seems to be the Rust convention and mirrors the lifetime parameters used in closest_keys.

Let me know what you think @folex.

@folex
Copy link
Contributor Author

folex commented Jan 21, 2022

Thanks for the thorough comments!

What does the lifetime parameter s stand for

Lifetime name 's was a short for self. Though I think 'a suits great as well 👍

Updated to reflect all your comments.

Copy link
Member

@mxinden mxinden left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. Final ask (I promise) would be to add a changelog entry in protocols/kad/CHANGELOG.md. Sorry for the many back and forths.

@folex
Copy link
Contributor Author

folex commented Jan 21, 2022

Sorry for the many back and forths.

It took me almost a year to submit this PR, so I'm really happy with review timings 😅

@mxinden mxinden changed the title Kademlia: add method to gather closest peers locally protocols/kad: Add Kademlia::get_closest_local_peers Jan 22, 2022
@mxinden mxinden merged commit 320a1cd into libp2p:master Jan 22, 2022
@folex folex deleted the local_closest_peers branch February 1, 2023 05:37
mergify bot pushed a commit that referenced this pull request Oct 24, 2024
## Description

Fixes #5626

## Notes & open questions

This is the nicest way I came up with, I decided to leave
`get_closest_local_peers` as is since it does return all peers, not just
`replication_factor` peers.

Looking at #2436 it is not
clear if @folex really needed all peers returned or it just happened
that way. I'm also happy to change proposed API to return all peers if
that is preferred by others.

It is very unfortunate that `&mut self` is needed for this, I created
#5644 that if resolved will
allow to have `&self` instead.

## Change checklist

- [x] I have performed a self-review of my own code
- [x] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] A changelog entry has been made in the appropriate crates

Co-authored-by: João Oliveira <hello@jxs.pt>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants