-
Notifications
You must be signed in to change notification settings - Fork 446
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: implement circuit v2 #1533
Conversation
2fe7457
to
2bd733a
Compare
src/circuit/relay.ts
Outdated
active?: boolean | ||
} | ||
|
||
export interface AutoRelayConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we still use this? We should either remove or rename it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use it to automatically initiate a reservation on newly connected peers or if the circuit relay protocol is added to an existing peer.
Is there anything blocking the PR review? |
I think todo's are:
But I think this shouldn't block reviews^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First pass of reviewing
- The
RelayConfig
,HopConfig
RelayAdvertiseConfig
andAutoRelayConfig
interface fields all need jsdoc comments to explain what they do - The
doc/CONFIGURATION.md
sections detailing relay config needs updating
Circuit Relay v2 is being implemented in libp2p/js-libp2p#1533 so enable the tests
I'm curious how this PR relates to AutoNAT (#1005). Isn't AutoNAT generally a prerequisite prior to doing NAT hole punching? |
@2color Yes AutoNAT is a requirement. You can see our roadmap issue for what's all needed: #1461 |
94d4d46
to
99cd723
Compare
package.json
Outdated
@@ -91,7 +92,8 @@ | |||
"test:firefox": "aegir test -t browser -f \"./dist/test/**/*.spec.js\" -- --browser firefox", | |||
"test:firefox-webworker": "aegir test -t webworker -f \"./dist/test/**/*.spec.js\" -- --browser firefox", | |||
"test:examples": "cd examples && npm run test:all", | |||
"test:interop": "aegir test -t node -f dist/test/interop.js" | |||
"test:interop": "aegir test -t node -f dist/test/interop.js", | |||
"test:relay": "aegir test -t node -f \"./dist/test/**/relay.{node,spec}.js\"" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: Remove this before merging. This is in place to ease testing.
test/relay/relay.node.ts
Outdated
const stream = await connection.newStream('/libp2p/circuit/relay/0.1.0') | ||
|
||
/* eslint-disable-next-line no-console */ | ||
console.log('>>>>>> connection established') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOTE: If the connection is established, connection.newStream
fails because of a reset.
src/circuit/transport.ts
Outdated
return this.components.connectionManager.getConnections(dstPeer)[0] ?? undefined | ||
} | ||
|
||
async _onProtocolV1 (data: IncomingStreamData) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mpetrunic Should we disable circuit v1 relaying entirely? There seems to be no reason to register this handler as it will not relay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, sure! I was just copying logic that go-libp2p had but I'm pretty sure nowadays, there is no need for that!
src/circuit/index.ts
Outdated
addressSorter?: AddressSorter | ||
export interface HopConfig { | ||
enabled?: boolean | ||
active?: boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How are active and enabled different? Also, we only use enabled
in Relay
to decide whether or not to advertise the node as a relay.
src/circuit/relay.ts
Outdated
ttl?: number | ||
} | ||
|
||
export interface HopConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is duplicated.
src/circuit/transport.ts
Outdated
|
||
await this.components.registrar.handle(RELAY_CODEC, (data) => { | ||
void this._onProtocol(data).catch(err => { | ||
await this.components.registrar.handle(RELAY_V2_HOP_CODEC, (data) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should adding this listener be conditional on HopConfig.enabled | HopConfig.active === true
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since circuit v2 kinda changed interaction, maybe it makes sense to make up new configs and names that are more dev friendly
src/circuit/v2/reservation-store.ts
Outdated
* @param limit - maximum number of reservations to store | ||
* @param reservationClearInterval - interval to check for expired reservations in millisecons | ||
*/ | ||
constructor (private readonly limit = 15, private readonly reservationClearInterval = 300 * 1000) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move these values to constants.ts
.
a2d9a6e
to
625753a
Compare
Having talked with @Jorropo and @mxinden this isn't the correct behaviour - I've opened libp2p/specs#526 to document what should happen - if everyone agrees with it then the stream should be reset. |
private readonly autoRelay?: AutoRelay | ||
private timeout?: any | ||
private started: boolean | ||
enabled?: boolean |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Under what circumstances would a user want to disable the reservation manager?
Are there tests for when it's disabled to ensure libp2p can run without it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We would want to disable the reservation manager when we do not want to automatically listen on relays. It replaces the old name AutoRelay
. #1533 (comment).
The relay tests disable this by default https://github.com/libp2p/js-libp2p/pull/1533/files#diff-81971332c1841fd8324647bd37da55f446e332af9e07d4f28632d5768302dac9R27
src/circuit/transport.ts
Outdated
async _onProtocol (data: IncomingStreamData) { | ||
const { connection, stream } = data | ||
async onHop ({ connection, stream }: IncomingStreamData) { | ||
log('received circuit v2 hop protocol stream from %s', connection.remotePeer) | ||
const controller = new TimeoutController(this._init.hop.timeout) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the benefit of having a hop timeout separate from the underlying transport timeout?
Abortable streams are expensive because they race each buffer against the abort controller so if it's not necessary this could be simplified.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the time in which the hop protocol must be completed. The hop timeout is much lower than the transport timeout limit, and is only used while 1) a new reservation is being created, or 2) a client is attempting to connect over the relay. Once the operations are complete, the timeout is cleared.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I can avoid the abortableDuplex though.
…am from it-pb-stream
src/circuit/v2/util.ts
Outdated
const dataLimitSource = (source: Source<Uint8ArrayList>, abort: (err: Error) => void, limit: bigint): Source<Uint8ArrayList> => { | ||
if (limit === BigInt(0)) { | ||
return source | ||
const dataLimitSource = (stream: Stream, limit: bigint): Stream => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this safe? While the implementation of it-pb-stream
returns the underlying stream, the return type only guarantees returning a Duplex
. The stream functions could be erased from the returned value in a future release without changing the API, which would cause this to break.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3.0.1
changes the unwrap
return type to be the type of the wrapped stream, so it should be safe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nearly there, comments inline.
test/circuit/v2/hop.spec.ts
Outdated
// source to dest, write 4 bytes | ||
const sender = pushable() | ||
void pipe(sender, sourceStream) | ||
sender.push(uint8arrayFromString('01234')) | ||
// source to dest, exceed stream limit | ||
sender.push(uint8arrayFromString('extra')) | ||
const data = await all(destStream.source) | ||
expect(data).to.have.length(1) | ||
expect(data[0]).to.have.length(5) | ||
expect(srcServerAbort.callCount).to.equal(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sends five bytes, not four. When four bytes are sent the test fails.
The dialler should send 0123
then extra
, then the relay should relay five bytes before reset the stream.
Also, the test needs to assert that both streams were reset - from the dialler to the relay and from the relay to the listener.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. I was resetting the stream if the sender attempted to send more than the limit.
Also, the test needs to assert that both streams were reset - from the dialler to the relay and from the relay to the listener.
The relay enforces the limit, so the abort is called from the relay to the dialler and from the relay to the listener. This is checked.
src/circuit/v2/util.ts
Outdated
const dataLimitSource = (source: Source<Uint8ArrayList>, abort: (err: Error) => void, limit: bigint): Source<Uint8ArrayList> => { | ||
if (limit === BigInt(0)) { | ||
return source | ||
const dataLimitSource = (stream: Stream, limit: bigint): Stream => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
3.0.1
changes the unwrap
return type to be the type of the wrapped stream, so it should be safe.
…move references to v2 in the code
@@ -38,8 +46,10 @@ export class ReservationStore implements IReservationStore, Startable { | |||
this.init = { | |||
maxReservations: options?.maxReservations ?? 15, | |||
reservationClearInterval: options?.reservationClearInterval ?? 300 * 1000, | |||
applyDefaultLimit: options?.applyDefaultLimit === false, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fun, it would have turned every js-libp2p node with the relay server enabled into an unlimited relay 😱
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am going to merge this which will publish an RC to unblock other efforts as it is functional though it still needs some work in a follow-up before being released.
- Split the code into two parts - the transport and the relay server
- At the moment the transport handles some of the server responsibilities
- The hop/stop implementations accept a load of context as arguments that might be easier to reason about as a class
- Configure the transport and relay server separately - at the moment the server is partially configured under the
config.relay.hop
key andconfig.relay.advertise
- the fact that the docs say "Does not make you a relay" in several places mean it's really not obvious - Maybe even have a separate CircuitRelay transport that you can configure
- More tests
Reopen the earlier circuit v2 implementation by @mpetrunic with a few fixes to ensure it is up to date with libp2p.
@mpetrunic looks mostly okay, but could you do a rough pass to make sure I haven't missed anything?
Todo: