Seamless Integration of Mix Protocol with Existing libp2p Protocols#15
Seamless Integration of Mix Protocol with Existing libp2p Protocols#15AkshayaMani wants to merge 10 commits intomainfrom
Conversation
- Added NoRespPing codec - Added protocol handler callback
- Tests end-to-end functionality of mix protocol using custom protocol NoRespPing
- Added callbacks for entry and exit mix interface
|
Nice, but it looks like there is a dependency on other protocols Lines 8 to 13 in 1ed184b Or is this code supposed to be part of user of mix e.g waku? |
| GossipSub12 = GossipSubCodec_12 | ||
| GossipSub11 = GossipSubCodec_11 | ||
| GossipSub10 = GossipSubCodec_10 | ||
| NoRespPing = NoRespPingCodec |
There was a problem hiding this comment.
Is ProtocolType still needed, or would it be possible to just pass the codec string in callHandler without having to do a conversion to this type? The advantage of it would be that it would not be necessary to modify this enum when we want to add an additional protocol.
| NoRespPing = NoRespPingCodec | ||
| OtherProtocol = "other" # Placeholder for other protocols | ||
|
|
||
| type ProtocolHandler* = |
There was a problem hiding this comment.
This is not introduced in this PR but it would be ideal if the {.async.} tag had a raises to indicate which exceptions can be thrown
|
|
||
| method callHandler*( | ||
| switch: Switch, conn: Connection, proto: ProtocolType | ||
| ): Future[void] {.base, async.} = |
There was a problem hiding this comment.
Same here:
| ): Future[void] {.base, async.} = | |
| ): Future[void] {.base, async: (raises[]).} = |
It's likely this would throw an LPError or CancelledError, but not sure
| type | ||
| NoRespPingHandler* {.public.} = | ||
| proc(peer: PeerId): Future[void] {.gcsafe, raises: [].} |
There was a problem hiding this comment.
Maybe let's try this?
| type | |
| NoRespPingHandler* {.public.} = | |
| proc(peer: PeerId): Future[void] {.gcsafe, raises: [].} | |
| type | |
| NoRespPingHandler* {.public.} = | |
| proc(peer: PeerId): Future[void] {.async:(raises: [CancelledError]).} |
| else: | ||
| self.message = @[] | ||
|
|
||
| # ToDo: Check readLine, readVarint, readLp implementations |
There was a problem hiding this comment.
These implementations seem very similar to those in libp2p LpStream (libp2p/stream/connection.nim), Do you think we could inherit from those instead of duplicating its implementation?
| let paddedMsg = padMessage(mixMsg, peerID) | ||
| let paddedMsg = padMessage(serialized, peerID) | ||
|
|
||
| info "# Sent: ", sender = multiAddr, message = msg |
There was a problem hiding this comment.
Should probably be trace or debug
| let parts = multiAddrs[0].split("/p2p/") | ||
| if parts.len != 2: | ||
| error "Invalid multiaddress format", parts = parts | ||
| return | ||
|
|
||
| let firstMixAddr = MultiAddress.init(parts[0]).valueOr: | ||
| error "Failed to initialize MultiAddress", err = error | ||
| return | ||
|
|
||
| let firstMixPeerId = PeerId.init(parts[1]).valueOr: | ||
| error "Failed to initialize PeerId", err = error | ||
| return |
There was a problem hiding this comment.
I see we try to split multiaddresses by "p2p" in diff areas of the code. Maybe we can use some utils functions to extract the peerId and the address from a multiaddress:
func stripPeerId(multiaddr: MultiAddress): MultiAddress =
if not multiaddr.contains(multiCodec("p2p")).get():
return multiaddr
var cleanAddr = MultiAddress.init()
for item in multiaddr.items:
if item.value.protoName().get() != "p2p":
# Add all parts except p2p peerId
discard cleanAddr.append(item.value)
return cleanAddr
proc peerID*(ma: MultiAddress): Result[PeerId, string] =
let p2pPart = ?ma[^1]
if ?p2pPart.protoCode != multiCodec("p2p"):
return err("Missing p2p part from multiaddress!")
ok(?PeerId.init(?p2pPart.protoArgument()).orErr("invalid peerid"))Then we can use them like this
let ma = MultiAddress.init(parts[0]).valueOr:
error "Failed to initialize MultiAddress", err = error
return
let firstMixPeerId = ma.peerID().valueOr:
error "Failed to initialize PeerId", err = error
return
let firstMixAddr = ma.stripPeerId()| let multiAddr = MultiAddress.init(multiAddrStr.split("/p2p/")[0]).valueOr: | ||
| error "Failed to initialize MultiAddress", err = error | ||
| return |
There was a problem hiding this comment.
Using the function from prev comment, it's likely we can do some quick changes to avoid having to initialize a multiaddress here:
MixNodeInfomultiAddrfield should be aMultiaddressinstead of astring. The multiaddress initialization can happen when you are loading the node info from a file.getMixNodeInfowould then return aMultiaddressinstead of a string.
That would allow you to do something like:
let multiAddr = mixNodeMultiAddr.stripPeerId()
| proc noRespPing*(p: NoRespPing, conn: Connection): Future[seq[byte]] {.async, public.} = | ||
| trace "initiating ping" | ||
| var randomBuf: array[NoRespPingSize, byte] | ||
| hmacDrbgGenerate(p.rng[], randomBuf) | ||
| trace "sending ping" | ||
| await conn.write(@randomBuf) | ||
| trace "sent ping: ", ping = @randomBuf | ||
| return @randomBuf |
There was a problem hiding this comment.
Does this mean that protocols that want to use mix would need to implement a function that would allow them to use a provided conn instead of use one obtained from the switch?
There was a problem hiding this comment.
Do you foresee any issues with this approach? I don't believe there are any. The mix and the top-level protocol are part of the same switch, so ideally, they can communicate directly.
We’re establishing a virtual connection here to allow us to intercept messages from the top-level protocol easily. As a result, creating an MixEntryConnection is necessary for this to work as intended.
- Simplify with valueOr Co-authored-by: richΛrd <info@richardramos.me>
Summary
This pull request introduces seamless integration of existing libp2p protocols with the Mix Protocol. By abstracting the entry and exit connections to the Mix protocol, this implementation allows existing protocols to anonymize messages without requiring any modifications to their code.
Key Abstractions
To achieve this functionality, we introduce two abstractions:
MixEntryConnection:
MixDialer) to route them through the mix network.NoRespPing.noRespPingis called,MixEntryConnectionensures that the message is anonymized and routed through the mix network.MixExitConnection:
NoRespPing) to process these messages seamlessly.Implementation Details
MixDialer Callback:
MixDialercallback is defined to route outgoing messages throughanonymizeLocalProtocolSendin theMixProtocol.MixEntryConnection, which invokes it whenever an outgoing message is written.Protocol Handler Callback:
Simulation:
NoRespPingprotocol is used to test end-to-end functionality.MixEntryConnectionat the sender side and processed byMixExitConnectionat the receiver side.Run PoC
Benefits