Files
mix/mix/mix_protocol.nim
2025-09-08 14:30:14 -04:00

753 lines
25 KiB
Nim

import chronicles, chronos, sequtils, strutils, os, results
import std/[strformat, sysrand, tables], metrics
import
./[
config, curve25519, fragmentation, mix_message, mix_node, sphinx, serialization,
tag_manager, utils, mix_metrics, exit_layer,
]
import libp2p
import
libp2p/
[protocols/ping, protocols/protocol, stream/connection, stream/lpstream, switch]
when defined(enable_mix_benchmarks):
import stew/endians2
const MixProtocolID* = "/mix/1.0.0"
type ConnCreds = object
incoming: AsyncQueue[seq[byte]]
surbSKSeq: seq[(secret, key)]
## Mix Protocol defines a decentralized anonymous message routing layer for libp2p networks.
## It enables sender anonymity by routing each message through a decentralized mix overlay
## network composed of participating libp2p nodes, known as mix nodes. Each message is
## routed independently in a stateless manner, allowing other libp2p protocols to selectively
## anonymize messages without modifying their core protocol behavior.
type MixProtocol* = ref object of LPProtocol
mixNodeInfo: MixNodeInfo
pubNodeInfo: Table[PeerId, MixPubInfo]
switch: Switch
tagManager: TagManager
exitLayer: ExitLayer
rng: ref HmacDrbgContext
# TODO: verify if this requires cleanup for cases in which response never arrives (and connection is closed)
connCreds: Table[I, ConnCreds]
destReadBehavior: TableRef[string, destReadBehaviorCb]
proc benchmarkLog*(
eventName: static[string],
myPeerId: PeerId,
startTime: Moment,
msgId: uint64,
orig: uint64,
fromPeerId: Opt[PeerId],
toPeerId: Opt[PeerId],
) =
let endTime = Moment.now()
let procDelay = (endTime - startTime).milliseconds()
let fromPeerId =
if fromPeerId.isNone:
"None"
else:
fromPeerId.get().shortLog()
let toPeerId =
if toPeerId.isNone:
"None"
else:
toPeerId.get().shortLog()
info eventName,
msgId, fromPeerId, toPeerId, myPeerId, orig, current = startTime, procDelay
proc hasDestReadBehavior*(mixProto: MixProtocol, codec: string): bool =
return mixProto.destReadBehavior.hasKey(codec)
proc registerDestReadBehavior*(
mixProto: MixProtocol, codec: string, fwdBehavior: destReadBehaviorCb
) =
mixProto.destReadBehavior[codec] = fwdBehavior
proc loadMixNodeInfo*(
index: int, nodeFolderInfoPath: string = "./nodeInfo"
): Result[MixNodeInfo, string] =
let readNode = readMixNodeInfoFromFile(index, nodeFolderInfoPath).valueOr:
return err("Failed to load node info from file: " & error)
ok(readNode)
proc loadAllButIndexMixPubInfo*(
index, numNodes: int, pubInfoFolderPath: string = "./pubInfo"
): Result[Table[PeerId, MixPubInfo], string] =
var pubInfoTable = initTable[PeerId, MixPubInfo]()
for i in 0 ..< numNodes:
if i != index:
let pubInfo = readMixPubInfoFromFile(i, pubInfoFolderPath).valueOr:
return err("Failed to load pub info from file: " & error)
let (multiAddr, _, _) = getMixPubInfo(pubInfo)
let peerId = getPeerIdFromMultiAddr(multiAddr).valueOr:
return err("Failed to get peer id from multiaddress: " & error)
pubInfoTable[peerId] = pubInfo
return ok(pubInfoTable)
# ToDo: Change to a more secure random number generator for production.
proc cryptoRandomInt(max: int): Result[int, string] =
if max == 0:
return err("Max cannot be zero.")
var bytes: array[8, byte]
discard urandom(bytes)
let value = cast[uint64](bytes)
return ok(int(value mod uint64(max)))
proc handleMixNodeConnection(
mixProto: MixProtocol, conn: Connection
) {.async: (raises: [CancelledError]).} =
var receivedBytes: seq[byte]
when defined(enable_mix_benchmarks):
var metadata: seq[byte]
var fromPeerId: PeerId
try:
when defined(enable_mix_benchmarks):
metadata = await conn.readLp(16)
fromPeerId = conn.peerId
receivedBytes = await conn.readLp(packetSize)
except Exception as e:
error "Failed to read: ", err = e.msg
finally:
if conn != nil:
try:
await conn.close()
except CatchableError as e:
error "Failed to close incoming stream: ", err = e.msg
when defined(enable_mix_benchmarks):
let startTime = Moment.now()
if metadata.len == 0:
mix_messages_error.inc(labelValues = ["Intermediate/Exit", "NO_DATA"])
return # No data, end of stream
if receivedBytes.len == 0:
mix_messages_error.inc(labelValues = ["Intermediate/Exit", "NO_DATA"])
return # No data, end of stream
# Process the packet
let (multiAddr, _, mixPrivKey, _, _) = getMixNodeInfo(mixProto.mixNodeInfo)
let sphinxPacket = SphinxPacket.deserialize(receivedBytes).valueOr:
error "Sphinx packet deserialization error", err = error
mix_messages_error.inc(labelValues = ["Intermediate/Exit", "INVALID_SPHINX"])
return
let processedSP = processSphinxPacket(sphinxPacket, mixPrivKey, mixProto.tagManager).valueOr:
error "Failed to process Sphinx packet", err = error
mix_messages_error.inc(labelValues = ["Intermediate/Exit", "INVALID_SPHINX"])
return
when defined(enable_mix_benchmarks):
let
orig = uint64.fromBytesLE(metadata[0 ..< 8])
msgId = uint64.fromBytesLE(metadata[8 ..< 16])
case processedSP.status
of Exit:
mix_messages_recvd.inc(labelValues = [$processedSP.status])
# This is the exit node, forward to destination
let msgChunk = MessageChunk.deserialize(processedSP.messageChunk).valueOr:
error "Deserialization failed", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_SPHINX"])
return
let unpaddedMsg = unpadMessage(msgChunk).valueOr:
error "Unpadding message failed", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_SPHINX"])
return
let deserialized = MixMessage.deserialize(unpaddedMsg).valueOr:
error "Deserialization failed", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_SPHINX"])
return
let (surbs, message) = extractSURBs(deserialized.message).valueOr:
error "Extracting surbs from payload failed", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_MSG_SURBS"])
return
trace "Exit node - Received mix message",
receiver = multiAddr, message = deserialized.message, codec = deserialized.codec
if processedSP.destination == Hop():
error "no destination available"
mix_messages_error.inc(labelValues = ["Exit", "NO_DESTINATION"])
return
let destBytes = getHop(processedSP.destination)
let fullAddrStr = bytesToMultiAddr(destBytes).valueOr:
error "Failed to convert bytes to multiaddress", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_DEST"])
return
let parts = fullAddrStr.split("/p2p/")
if parts.len != 2:
error "Invalid multiaddress format", parts
mix_messages_error.inc(labelValues = ["Exit", "INVALID_DEST"])
return
# Create MultiAddress and PeerId
let destAddr = MultiAddress.init(parts[0]).valueOr:
error "Failed to parse location multiaddress: ", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_DEST"])
return
let destPeerId = PeerId.init(parts[1]).valueOr:
error "Failed to initialize PeerId", err = error
mix_messages_error.inc(labelValues = ["Exit", "INVALID_DEST"])
return
when defined(enable_mix_benchmarks):
benchmarkLog "Exit",
mixProto.switch.peerInfo.peerId,
startTime,
msgId,
orig,
Opt.some(fromPeerId),
Opt.some(destPeerId)
await mixProto.exitLayer.onMessage(
deserialized.codec, message, destAddr, destPeerId, surbs
)
mix_messages_forwarded.inc(labelValues = ["Exit"])
of Reply:
trace "# Reply", id = processedSP.id
try:
if not mixProto.connCreds.hasKey(processedSP.id):
mix_messages_error.inc(labelValues = ["Sender/Reply", "NO_CONN_FOUND"])
return
let connCred = mixProto.connCreds[processedSP.id]
mixProto.connCreds.del(processedSP.id)
var couldProcessReply = false
var reply: seq[byte]
for sk in connCred.surbSKSeq:
let processReplyRes = processReply(sk[1], sk[0], processedSP.delta_prime)
if processReplyRes.isOk:
couldProcessReply = true
reply = processReplyRes.value()
break
if couldProcessReply:
let msgChunk = MessageChunk.deserialize(reply).valueOr:
error "Deserialization failed", err = error
mix_messages_error.inc(labelValues = ["Reply", "INVALID_SPHINX"])
return
let unpaddedMsg = unpadMessage(msgChunk).valueOr:
error "Unpadding message failed", err = error
mix_messages_error.inc(labelValues = ["Reply", "INVALID_SPHINX"])
return
let deserialized = MixMessage.deserialize(unpaddedMsg).valueOr:
error "Deserialization failed", err = error
mix_messages_error.inc(labelValues = ["Reply", "INVALID_SPHINX"])
return
when defined(enable_mix_benchmarks):
benchmarkLog "Reply",
mixProto.switch.peerInfo.peerId,
startTime,
msgId,
orig,
Opt.some(fromPeerId),
Opt.none(PeerId)
await connCred.incoming.put(deserialized.message)
else:
error "could not process reply", id = processedSP.id
except KeyError:
doAssert false, "checked with hasKey"
of Intermediate:
trace "# Intermediate: ", multiAddr = multiAddr
# Add delay
mix_messages_recvd.inc(labelValues = ["Intermediate"])
await sleepAsync(milliseconds(processedSP.delayMs))
# Forward to next hop
let nextHopBytes = getHop(processedSP.nextHop)
let fullAddrStr = bytesToMultiAddr(nextHopBytes).valueOr:
error "Failed to convert bytes to multiaddress", err = error
mix_messages_error.inc(labelValues = ["Intermediate", "INVALID_NEXTHOP"])
return
let parts = fullAddrStr.split("/p2p/")
if parts.len != 2:
error "Invalid multiaddress format", parts = parts
mix_messages_error.inc(labelValues = ["Intermediate", "INVALID_NEXTHOP"])
return
let
locationAddrStr = parts[0]
peerIdStr = parts[1]
# Create MultiAddress and PeerId
let locationAddr = MultiAddress.init(locationAddrStr).valueOr:
error "Failed to parse location multiaddress: ", err = error
mix_messages_error.inc(labelValues = ["Intermediate", "INVALID_NEXTHOP"])
return
let peerId = PeerId.init(peerIdStr).valueOr:
error "Failed to initialize PeerId", err = error
mix_messages_error.inc(labelValues = ["Intermediate", "INVALID_NEXTHOP"])
return
when defined(enable_mix_benchmarks):
benchmarkLog "Intermediate",
mixProto.switch.peerInfo.peerId,
startTime,
msgId,
orig,
Opt.some(fromPeerId),
Opt.some(peerId)
var nextHopConn: Connection
try:
nextHopConn = await mixProto.switch.dial(peerId, @[locationAddr], MixProtocolID)
when defined(enable_mix_benchmarks):
await nextHopConn.writeLp(metadata)
await nextHopConn.writeLp(processedSP.serializedSphinxPacket)
mix_messages_forwarded.inc(labelValues = ["Intermediate"])
except CatchableError as e:
error "Failed to dial next hop: ", err = e.msg
finally:
if nextHopConn != nil:
try:
await nextHopConn.close()
except CatchableError as e:
error "Failed to close outgoing stream: ", err = e.msg
mix_messages_error.inc(labelValues = ["Intermediate", "DIAL_FAILED"])
of Duplicate:
mix_messages_error.inc(labelValues = ["Intermediate/Exit", "DUPLICATE"])
discard
of InvalidMAC:
mix_messages_error.inc(labelValues = ["Intermediate/Exit", "INVALID_MAC"])
discard
proc getMaxMessageSizeForCodec*(
codec: string, numberOfSurbs: uint8 = 0
): Result[int, string] =
## Computes the maximum payload size (in bytes) available for a message when encoded
## with the given `codec`, optionally including space for the chosen number of surbs.
## Returns an error if the codec + surb overhead exceeds the data capacity.
let serializedMsg = ?MixMessage.init(@[], codec).serialize()
var totalLen = serializedMsg.len + surbLenSize + (int(numberOfSurbs) * surbSize)
if numberOfSurbs > 0:
totalLen += surbIdLen
if totalLen > dataSize:
return err("cannot encode messages for this codec")
return ok(dataSize - totalLen)
proc buildSurbs(
mixProto: MixProtocol,
incoming: AsyncQueue[seq[byte]],
numSurbs: uint8,
exitPeerId: PeerId,
): Result[seq[SURB], string] =
var response: seq[SURB]
var surbSK: seq[(secret, key)] = @[]
var id: I
hmacDrbgGenerate(mixProto.rng[], id)
for _ in uint8(0) ..< numSurbs:
var
multiAddrs: seq[string] = @[]
publicKeys: seq[FieldElement] = @[]
hops: seq[Hop] = @[]
delay: seq[seq[byte]] = @[]
# Select L mix nodes at random
let numMixNodes = mixProto.pubNodeInfo.len
if mixProto.pubNodeInfo.len < L:
return err("No. of public mix nodes less than path length")
var
pubNodeInfoKeys = toSeq(mixProto.pubNodeInfo.keys)
randPeerId: PeerId
availableIndices = toSeq(0 ..< numMixNodes)
# Remove exit node from nodes to consider for surbs
let index = pubNodeInfoKeys.find(exitPeerId)
if index != -1:
availableIndices.del(index)
else:
return err("could not find exit node")
var i = 0
while i < L:
let (multiAddr, mixPubKey, delayMillisec) =
if i < L - 1:
let randomIndexPosition = cryptoRandomInt(availableIndices.len).valueOr:
return err("failed to generate random num: " & error)
let selectedIndex = availableIndices[randomIndexPosition]
randPeerId = pubNodeInfoKeys[selectedIndex]
availableIndices.del(randomIndexPosition)
debug "Selected mix node for surbs: ", indexInPath = i, peerId = randPeerId
let mixPubInfo = getMixPubInfo(mixProto.pubNodeInfo.getOrDefault(randPeerId))
# Compute delay
let delayMillisec = cryptoRandomInt(3).valueOr:
mix_messages_error.inc(labelValues = ["Entry/SURB", "NON_RECOVERABLE"])
return err("failed to generate random number: " & error)
(mixPubInfo[0], mixPubInfo[1], delayMillisec)
else:
let mixPubInfo = mixProto.mixNodeInfo.getMixNodeInfo()
(mixPubInfo[0], mixPubInfo[1], 0)
multiAddrs.add(multiAddr)
publicKeys.add(mixPubKey)
let multiAddrBytes = multiAddrToBytes(multiAddr).valueOr:
mix_messages_error.inc(labelValues = ["Entry/SURB", "INVALID_MIX_INFO"])
return err("failed to convert multiaddress to bytes: " & error)
hops.add(Hop.init(multiAddrBytes))
delay.add(uint16ToBytes(delayMillisec.uint16))
i = i + 1
let surb = createSURB(publicKeys, delay, hops, id).valueOr:
return err(error)
surbSK.add((surb.secret.get(), surb.key))
response.add(surb)
if surbSK.len != 0:
mixProto.connCreds[id] = ConnCreds(surbSKSeq: surbSK, incoming: incoming)
return ok(response)
proc prepareMsgWithSurbs(
mixProto: MixProtocol,
incoming: AsyncQueue[seq[byte]],
msg: seq[byte],
numSurbs: uint8 = 0,
exitPeerId: PeerId,
): Result[seq[byte], string] =
let surbs = mixProto.buildSurbs(incoming, numSurbs, exitPeerId).valueOr:
return err(error)
let serialized = ?serializeMessageWithSURBs(msg, surbs)
ok(serialized)
type SendPacketType* = enum
Entry
Reply
type SendPacketConfig = object
logType: SendPacketType
when defined(enable_mix_benchmarks):
startTime: Moment
orig: uint64
msgId: uint64
origAndMsgId: seq[byte]
proc sendPacket(
mixProto: MixProtocol,
multiAddrs: string,
sphinxPacket: seq[byte],
config: SendPacketConfig,
) {.async: (raises: []).} =
let label = $config.logType
# Send the wrapped message to the first mix node in the selected path
let parts = multiAddrs.split("/p2p/")
if parts.len != 2:
error "Invalid multiaddress format", parts = parts
mix_messages_error.inc(labelValues = [label, "NON_RECOVERABLE"])
return
let firstMixAddr = MultiAddress.init(parts[0]).valueOr:
error "Failed to initialize MultiAddress", err = error
mix_messages_error.inc(labelValues = [label, "NON_RECOVERABLE"])
return
let firstMixPeerId = PeerId.init(parts[1]).valueOr:
error "Failed to initialize PeerId", err = error
mix_messages_error.inc(labelValues = [label, "NON_RECOVERABLE"])
return
when defined(enable_mix_benchmarks):
if config.logType == Entry:
benchmarkLog "Sender",
mixProto.switch.peerInfo.peerId,
config.startTime,
config.msgId,
config.orig,
Opt.none(PeerId),
Opt.some(firstMixPeerId)
var nextHopConn: Connection
try:
nextHopConn =
await mixProto.switch.dial(firstMixPeerId, @[firstMixAddr], @[MixProtocolID])
when defined(enable_mix_benchmarks):
await nextHopConn.writeLp(config.origAndMsgId)
await nextHopConn.writeLp(sphinxPacket)
mix_messages_forwarded.inc(labelValues = ["Entry"])
except CatchableError as e:
error "Failed to send message to next hop: ", err = e.msg
mix_messages_error.inc(labelValues = [label, "SEND_FAILED"])
finally:
if nextHopConn != nil:
try:
await nextHopConn.close()
except CatchableError as e:
error "Failed to close outgoing stream: ", err = e.msg
proc buildMessage(
msg: seq[byte], codec: string, multiAddr: string
): Result[Message, (string, string)] =
let mixMsg = MixMessage.init(msg, codec)
let serialized = mixMsg.serialize().valueOr:
return err(("message serialization failed: " & error, "NON_RECOVERABLE"))
if len(serialized) > dataSize:
return err(("message size exceeds maximum payload size", "INVALID_SIZE"))
let peerId = getPeerIdFromMultiAddr(multiAddr).valueOr:
return err(("failed to get peer id from multiaddress: " & error, "INVALID_DEST"))
let paddedMsg = padMessage(serialized, peerId)
let serializedMsgChunk = paddedMsg.serialize().valueOr:
return err(("failed to serialize padded message: " & error, "NON_RECOVERABLE"))
ok(Message.init(serializedMsgChunk))
## Represents the final target of a mixnet message.
## contains the peer id and multiaddress of the destination node.
type MixDestination* = object
peerId*: PeerId
address*: MultiAddress
proc init*(T: typedesc[MixDestination], peerId: PeerId, address: MultiAddress): T =
## Initializes a destination object with the given peer id and multiaddress.
T(peerId: peerId, address: address)
proc `$`*(d: MixDestination): string =
$d.address & "/p2p/" & $d.peerId
proc anonymizeLocalProtocolSend*(
mixProto: MixProtocol,
incoming: AsyncQueue[seq[byte]],
msg: seq[byte],
codec: string,
destination: MixDestination,
numSurbs: uint8,
) {.async.} =
var config = SendPacketConfig(logType: Entry)
when defined(enable_mix_benchmarks):
config.startTime = Moment.now()
let (multiAddr, _, _, _, _) = getMixNodeInfo(mixProto.mixNodeInfo)
when defined(enable_mix_benchmarks):
# Assumes a fixed gossipsub message layout of 100
config.orig = uint64.fromBytesLE(msg[5 ..< 13])
config.msgId = uint64.fromBytesLE(msg[13 ..< 21])
config.origAndMsgId = msg[5 ..< 21]
mix_messages_recvd.inc(labelValues = ["Entry"])
var
multiAddrs: seq[string] = @[]
publicKeys: seq[FieldElement] = @[]
hop: seq[Hop] = @[]
delay: seq[seq[byte]] = @[]
exitPeerId: PeerId
# Select L mix nodes at random
let numMixNodes = mixProto.pubNodeInfo.len
var numAvailableNodes = numMixNodes
debug "Destination data", destination
if mixProto.pubNodeInfo.hasKey(destination.peerId):
numAvailableNodes = numMixNodes - 1
if numAvailableNodes < L:
error "No. of public mix nodes less than path length.",
numMixNodes = numAvailableNodes, pathLength = L
mix_messages_error.inc(labelValues = ["Entry", "LOW_MIX_POOL"])
return
var
pubNodeInfoKeys = toSeq(mixProto.pubNodeInfo.keys)
randPeerId: PeerId
availableIndices = toSeq(0 ..< numMixNodes)
var i = 0
while i < L:
let randomIndexPosition = cryptoRandomInt(availableIndices.len).valueOr:
error "Failed to genanrate random number", err = error
mix_messages_error.inc(labelValues = ["Entry", "NON_RECOVERABLE"])
return
let selectedIndex = availableIndices[randomIndexPosition]
randPeerId = pubNodeInfoKeys[selectedIndex]
availableIndices.del(randomIndexPosition)
# Skip the destination peer
if randPeerId == destination.peerId:
continue
# Last hop will be the exit node that will forward the request
if i == L - 1:
exitPeerId = randPeerId
debug "Selected mix node: ", indexInPath = i, peerId = randPeerId
# Extract multiaddress, mix public key, and hop
let (multiAddr, mixPubKey, _) =
getMixPubInfo(mixProto.pubNodeInfo.getOrDefault(randPeerId))
multiAddrs.add(multiAddr)
publicKeys.add(mixPubKey)
let multiAddrBytes = multiAddrToBytes(multiAddr).valueOr:
error "Failed to convert multiaddress to bytes", err = error
mix_messages_error.inc(labelValues = ["Entry", "INVALID_MIX_INFO"])
#TODO: should we skip and pick a different node here??
return
hop.add(Hop.init(multiAddrBytes))
# Compute delay
let delayMillisec =
if i != L - 1:
cryptoRandomInt(3).valueOr:
error "Failed to generate random number", err = error
mix_messages_error.inc(labelValues = ["Entry", "NON_RECOVERABLE"])
return
else:
0 # Last hop does not require a delay
delay.add(uint16ToBytes(delayMillisec.uint16))
i = i + 1
#Encode destination
let destAddrBytes = multiAddrToBytes($destination).valueOr:
error "Failed to convert multiaddress to bytes", err = error
mix_messages_error.inc(labelValues = ["Entry", "INVALID_DEST"])
return
let destHop = Hop.init(destAddrBytes)
let msgWithSurbs = mixProto.prepareMsgWithSurbs(incoming, msg, numSurbs, exitPeerId).valueOr:
error "Could not prepend SURBs", err = error
return
let message = buildMessage(msgWithSurbs, codec, multiAddr).valueOr:
error "Error building message", err = error[0]
mix_messages_error.inc(labelValues = ["Entry", error[1]])
return
# Wrap in Sphinx packet
let sphinxPacket = wrapInSphinxPacket(message, publicKeys, delay, hop, destHop).valueOr:
error "Failed to wrap in sphinx packet", err = error
mix_messages_error.inc(labelValues = ["Entry", "NON_RECOVERABLE"])
return
# Send the wrapped message to the first mix node in the selected path
await mixProto.sendPacket(multiAddrs[0], sphinxPacket, config)
proc reply(
mixProto: MixProtocol, surb: SURB, msg: seq[byte]
) {.async: (raises: [CancelledError]).} =
let multiAddr = bytesToMultiAddr(surb.hop.getHop()).valueOr:
error "could not obtain multiaddress from hop", err = error
return
# Message does not require a codec, as it is already associated to a specific I
let message = buildMessage(msg, "", multiAddr).valueOr:
error "could not build reply message", err = error
return
let sphinxPacket = useSURB(surb.header, surb.key, message).valueOr:
error "Use SURB error", err = error
return
await mixProto.sendPacket(multiAddr, sphinxPacket, SendPacketConfig(logType: Reply))
proc new*(
T: typedesc[MixProtocol],
mixNodeInfo: MixNodeInfo,
pubNodeInfo: Table[PeerId, MixPubInfo],
switch: Switch,
tagManager: TagManager = TagManager.new(),
rng: ref HmacDrbgContext = newRng(),
): T =
let mixProto = new(T)
mixProto.mixNodeInfo = mixNodeInfo
mixProto.pubNodeInfo = pubNodeInfo
mixProto.switch = switch
mixProto.tagManager = tagManager
mixProto.destReadBehavior = newTable[string, destReadBehaviorCb]()
let onReplyDialer = proc(
surb: SURB, message: seq[byte]
) {.async: (raises: [CancelledError]).} =
await mixProto.reply(surb, message)
mixProto.exitLayer = ExitLayer.init(switch, onReplyDialer, mixProto.destReadBehavior)
mixProto.codecs = @[MixProtocolID]
mixProto.rng = rng
mixProto.handler = proc(
conn: Connection, proto: string
) {.async: (raises: [CancelledError]).} =
await mixProto.handleMixNodeConnection(conn)
mixProto
proc new*(
T: typedesc[MixProtocol],
index, numNodes: int,
switch: Switch,
nodeFolderInfoPath: string = ".",
rng: ref HmacDrbgContext = newRng(),
): Result[T, string] =
## Constructs a new `MixProtocol` instance for the mix node at `index`,
## loading its private info from `nodeInfo` and the public info of all other nodes from `pubInfo`.
let mixNodeInfo = loadMixNodeInfo(index, nodeFolderInfoPath / fmt"nodeInfo").valueOr:
return err("Failed to load mix node info for index " & $index & " - err: " & error)
let pubNodeInfo = loadAllButIndexMixPubInfo(
index, numNodes, nodeFolderInfoPath / fmt"pubInfo"
).valueOr:
return err("Failed to load mix pub info for index " & $index & " - err: " & error)
let mixProto =
MixProtocol.new(mixNodeInfo, pubNodeInfo, switch, TagManager.new(), rng)
return ok(mixProto)
# TODO: look into removing this
proc setNodePool*(
mixProtocol: MixProtocol, mixNodeTable: Table[PeerId, MixPubInfo]
) {.gcsafe, raises: [].} =
mixProtocol.pubNodeInfo = mixNodeTable
# TODO: look into removing this
proc getNodePoolSize*(mixProtocol: MixProtocol): int {.gcsafe, raises: [].} =
mixProtocol.pubNodeInfo.len