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

feat(libp2p): direct connection through relay protocol (DCUtR) #1928

Merged
merged 17 commits into from
Aug 16, 2023

Conversation

achingbrain
Copy link
Member

Implements the DCUtR protocol.

Only supports TCP Simultaneous Connect since there is no QUIC support yet

Implements the [DCUtR protocol](https://github.com/libp2p/specs/blob/master/relay/DCUtR.md).

Only supports TCP Simultaneous Connect since there is no QUIC support yet
@achingbrain achingbrain changed the title fix: add gh credentials to publish docs feat: direct connection through relay protocol (DCUtR) Aug 3, 2023
@p-shahi p-shahi linked an issue Aug 4, 2023 that may be closed by this pull request
SgtPooki added a commit to SgtPooki/helia-playground that referenced this pull request Aug 8, 2023
Copy link
Member

@SgtPooki SgtPooki left a comment

Choose a reason for hiding this comment

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

the updates with forced connections are great, and I think what was preventing my demo from succeeding.

@@ -37,7 +41,7 @@ export interface ConnectionManager {
* const connection = await libp2p.connectionManager.openConnection(peerId)
* ```
*/
openConnection: (peer: PeerId | Multiaddr | Multiaddr[], options?: AbortOptions) => Promise<Connection>
openConnection: (peer: PeerId | Multiaddr | Multiaddr[], options?: OpenConnectionOptions) => Promise<Connection>
Copy link
Member

Choose a reason for hiding this comment

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

we could probably do this as a separate PR

Copy link
Member Author

Choose a reason for hiding this comment

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

It's needed to pass the force option to openConnection when we have an existing transient connection.

Copy link
Member

Choose a reason for hiding this comment

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

I mean, we could make this change in a smaller PR and get the code out prior to the whole DCUtR. it's beneficial in it's own right.

packages/libp2p/src/dcutr/dcutr.ts Outdated Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Outdated Show resolved Hide resolved
SgtPooki added a commit to SgtPooki/helia-playground that referenced this pull request Aug 10, 2023
@SgtPooki
Copy link
Member

I put the latest of your changes up at https://github.com/SgtPooki/helia-playground/tree/feat/half-dcutr-poc and it still seems like dial-backs aren't quite enough for unblocking the golden-path for helia-authored content being accessible from ipfs.io.

Am I missing something? I was thinking this PR (or at least the unilateral connection upgrade) would be enough to unblock the golden path scenario for Helia?

@SgtPooki
Copy link
Member

SgtPooki commented Aug 10, 2023

one thing i'm noticing is that isPublicAndDialable isn't always filtering out p2p-circuit addresses:

Here are some that i can see in my testing of helia-playground:

dcutr.ts:355 libp2p:dcutr:B:trace unilateral publicAddresses left +1ms 
 
"/ip4/5.161.55.227/tcp/4001/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit"
"/ip4/5.161.55.227/udp/4001/quic-v1/webtransport/certhash/uEiAVFKPNfMZ-n9o4QEUIoDKM3UrXRlrfCBwhflJAkO6fdA/certhash/uEiCAaSv97aZJ8eA7o41dza9EoLMHtv51dSdntNwYKRdu0g/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit"
"/ip4/5.161.55.227/udp/4001/quic/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit"
"/ip4/5.161.92.43/tcp/4001/p2p/12D3KooWFFhc8fPYnQXdWBCowxSV21EFYin3rU27p3NVgSMjN41k/p2p-circuit"
"/ip4/5.161.92.43/udp/4001/quic-v1/webtransport/certhash/uEiDfxpe8sEFZ2k4BRjku6zhmfMLrig2EtuydiK9UxpPpIw/certhash/uEiCx-6CWdlNH5f1qfOwhFfCqYWxUpzoVirpd9R0cIfkwqA/p2p/12D3KooWFFhc8fPYnQXdWBCowxSV21EFYin3rU27p3NVgSMjN41k/p2p-circuit"
"/ip4/5.161.92.43/udp/4001/quic/p2p/12D3KooWFFhc8fPYnQXdWBCowxSV21EFYin3rU27p3NVgSMjN41k/p2p-circuit"
"/ip6/2a01:4ff:f0:1e5a::1/tcp/4001/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit"
"/ip6/2a01:4ff:f0:1e5a::1/udp/4001/quic-v1/webtransport/certhash/uEiAVFKPNfMZ-n9o4QEUIoDKM3UrXRlrfCBwhflJAkO6fdA/certhash/uEiCAaSv97aZJ8eA7o41dza9EoLMHtv51dSdntNwYKRdu0g/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit"
"/ip6/2a01:4ff:f0:1e5a::1/udp/4001/quic/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit"
"/ip6/2a01:4ff:f0:3b1e::1/tcp/4001/p2p/12D3KooWFFhc8fPYnQXdWBCowxSV21EFYin3rU27p3NVgSMjN41k/p2p-circuit"
"/ip6/2a01:4ff:f0:3b1e::1/udp/4001/quic-v1/webtransport/certhash/uEiDfxpe8sEFZ2k4BRjku6zhmfMLrig2EtuydiK9UxpPpIw/certhash/uEiCx-6CWdlNH5f1qfOwhFfCqYWxUpzoVirpd9R0cIfkwqA/p2p/12D3KooWFFhc8fPYnQXdWBCowxSV21EFYin3rU27p3NVgSMjN41k/p2p-circuit"
"/ip6/2a01:4ff:f0:3b1e::1/udp/4001/quic/p2p/12D3KooWFFhc8fPYnQXdWBCowxSV21EFYin3rU27p3NVgSMjN41k/p2p-circuit"

Opened multiformats/js-multiaddr-matcher#2 to address

@achingbrain achingbrain changed the title feat: direct connection through relay protocol (DCUtR) feat(libp2p): direct connection through relay protocol (DCUtR) Aug 10, 2023
@achingbrain
Copy link
Member Author

one thing i'm noticing is that isPublicAndDialable isn't always filtering out p2p-circuit addresses

It's a bit of a weird one, network peers usually return their multiaddrs without a peer id, I guess the intention is including the peer id creates a lot of redundancy on the wire but it means we can't treat multiaddrs as opaque values.

Taking the first address as an example:

/ip4/5.161.55.227/tcp/4001/p2p/12D3KooWSW4hoHmDXmY5rW7nCi9XmGTy3foFt72u86jNP53LTNBJ/p2p-circuit

We can't actually dial this address as it needs to be: {relay-address}/p2p-circuit/{destination-peer-id} but it's just {relay-address}/p2p-circuit - we need {destination-peer-id} to be included otherwise we can't tell the relay which peer we are trying to dial in the Hop CONNECT message.

So we need to append the destination peer id ourselves, I've added this, hopefully it improves things.

Copy link

@whizzzkid whizzzkid left a comment

Choose a reason for hiding this comment

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

nits

packages/libp2p/src/dcutr/dcutr.ts Outdated Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Show resolved Hide resolved
packages/libp2p/src/dcutr/dcutr.ts Show resolved Hide resolved
Comment on lines +351 to +369
const output = []

for (const addr of addrs) {
if (addr == null || addr.length === 0) {
continue
}

try {
const ma = multiaddr(addr)

if (!this.isPublicAndDialable(ma)) {
continue
}

output.push(ma)
} catch {}
}

return output

Choose a reason for hiding this comment

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

why not just filter?

Suggested change
const output = []
for (const addr of addrs) {
if (addr == null || addr.length === 0) {
continue
}
try {
const ma = multiaddr(addr)
if (!this.isPublicAndDialable(ma)) {
continue
}
output.push(ma)
} catch {}
}
return output
return addrs
.filter(addr => addr != null)
.filter(addr => addr.length !== 0)
.filter(addr => {
try {
const ma = multiaddr(addr)
return this.isPublicAndDialable(ma))
} catch {}
})

Copy link
Member Author

Choose a reason for hiding this comment

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

The suggestion iterates over the list three times. Doing for..of means we only iterate over the list once.

Copy link

@whizzzkid whizzzkid Aug 12, 2023

Choose a reason for hiding this comment

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

nice, what I meant was, it can be filtered out in a single iteration instead of pushing into an output array.

Suggested change
const output = []
for (const addr of addrs) {
if (addr == null || addr.length === 0) {
continue
}
try {
const ma = multiaddr(addr)
if (!this.isPublicAndDialable(ma)) {
continue
}
output.push(ma)
} catch {}
}
return output
return addrs
.filter(addr => {
if (addr == null) return false
if (addr.length === 0) return false
try {
const ma = multiaddr(addr)
return this.isPublicAndDialable(ma))
} catch {
// swallow
} finally {
return false
}
})

Copy link
Member Author

Choose a reason for hiding this comment

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

The return type of the method is Multiaddr[] but the input type is Array<Uint8Array | string | null | undefined> so as implemented the suggestion returns Array<Uint8Array | string | null | undefined> - e.g. it's missing a .map step to turnUint8Array | string | null | undefined into Multiaddr but adding that will involve iterating over the list twice.

Copy link
Member Author

@achingbrain achingbrain Aug 13, 2023

Choose a reason for hiding this comment

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

It's also worth noting that for..of is faster than JS array methods since we're not creating new function contexts/stacks/etc for each iteration - I haven't measured anything so it might not be a bottleneck here but .forEach, .filter, .map etc are certainly not free.

Copy link
Member

@SgtPooki SgtPooki Aug 15, 2023

Choose a reason for hiding this comment

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

for loops are usually faster in all languages. forEach/map/etc are convenience methods that may improve readability, but for the majority of code we maintain, we should probably default to using for loops.

NOTE: some optimizations in v8 have made forEach/map/etc more efficient, but for loops are the "lowest level" loop we have access to and are faster by default

achingbrain and others added 4 commits August 11, 2023 14:21
Co-authored-by: Russell Dempsey <1173416+SgtPooki@users.noreply.github.com>
Co-authored-by: Marco Munizaga <git@marcopolo.io>
Co-authored-by: Chad Nehemiah <chad.nehemiah94@gmail.com>
@achingbrain
Copy link
Member Author

@SgtPooki @whizzzkid any further objections or can we merge this now?

@achingbrain achingbrain merged commit 87dc7e9 into master Aug 16, 2023
17 checks passed
@achingbrain achingbrain deleted the feat/dcutr branch August 16, 2023 06:52
This was referenced Jan 18, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

Use DCUtR to synchronize hole punching
4 participants