Run a Mix network of storage nodes
Stand up a local Mix network and download a file through it, with the content lookup anonymised.
This document is accurate for Testnet v0.2.1.
This procedure stands up a small local Mix network using logosctl: six Logos Storage Module nodes on one machine—four Mix relays wired around a bootstrap node, plus two storage nodes that route their DHT lookups through the relays. At the end, one storage node uploads a file and the other downloads it with the lookup tunnelled over Mix.
- A supported OS:
- Linux
- Mac OS (should work, but not tested)
jqon yourPATH.- To verify, run:
jq --version
- To verify, run:
logosctlinstalled.- Install it by running
curl -fsSL https://raw.githubusercontent.com/logos-co/logos-docs/main/resources/scripts/install-logosctl.sh | sudo sh
- Install it by running
What to expect
- You can run several
logosctldaemons side by side, each in its own session (--config-dir). - You can set up a private Mix network and configure storage nodes to anonymise their lookups through it.
- You can exchange a file between two storage nodes and verify the content lookup was tunnelled over Mix.
Download and install the storage module
All six nodes below share one already-unpacked copy of storage_module, installed once into a throwaway session. Each node's daemon is then pointed at that directory with --modules-dir, so there's no need to repeat the install per node.
-
Start a throwaway session and install the storage module package into it. Package installs are handled by a module bundled inside the daemon, so the daemon has to be running first:
logosctl daemon start --detach --config-dir ./install-sessionlogosctl --config-dir ./install-session catalog refreshlogosctl --config-dir ./install-session package install storage_module --version 2.1.2 --yes -
Confirm the module landed, then stop this session—its only job was the install:
logosctl --config-dir ./install-session package lslogosctl --config-dir ./install-session daemon stopThe package is now unpacked under
./install-session/modules/storage_module/.
Launch the bootstrap Mix node (node 1)
The first node is the bootstrap node: the other nodes use it to join the Mix network.
-
Write node 1's configuration. It uses
"mix-enabled": trueand"no-bootstrap-node": truebecause it is the bootstrap node. Paths are absolute ($(pwd)) because each module runs in its own process:mkdir -p "$(pwd)/storage-data/node-1"cat > config-1.json <<EOF{"log-level": "DEBUG","data-dir": "$(pwd)/storage-data/node-1","log-file": "$(pwd)/storage-data/node-1/storage.log","nat": "extip:127.0.0.1","disc-port": 9091,"listen-port": 8081,"mix-enabled": true,"no-bootstrap-node": true}EOF -
Start a
logosctldaemon for node 1, detached, with its own session and pointed at the shared module directory. Its logs go to./logosctl-1/logs/daemon.log, so there's no need to redirect output by hand:logosctl daemon start --detach --config-dir ./logosctl-1 --modules-dir ./install-session/modules -
Load the module, initialise it, and start the node:
logosctl --config-dir ./logosctl-1 module load storage_modulelogosctl --config-dir ./logosctl-1 call storage_module init @config-1.jsonlogosctl --config-dir ./logosctl-1 call storage_module start# Wait a few seconds for the node to start -
Read node 1's SPR out of
debugand save it: the other nodes use this value as theirbootstrap-node(see Connectivity):logosctl --config-dir ./logosctl-1 call storage_module debug \| jq -er '.result.value.spr' > bootstrap-spr.txt
Launch the remaining Mix relays (nodes 2–4)
Nodes 2, 3 and 4 are identical to node 1, except that they join through node 1's SPR as their bootstrap-node.
-
Write the configs for nodes 2–4, with per-node ports and data dirs:
BOOTSTRAP=$(cat bootstrap-spr.txt)for id in 2 3 4; domkdir -p "$(pwd)/storage-data/node-$id"cat > "config-$id.json" <<EOF{"log-level": "DEBUG","data-dir": "$(pwd)/storage-data/node-$id","log-file": "$(pwd)/storage-data/node-$id/storage.log","nat": "extip:127.0.0.1","disc-port": $((9090 + id)),"listen-port": $((8080 + id)),"mix-enabled": true,"bootstrap-node": ["$BOOTSTRAP"]}EOFdone -
Start one daemon per node, each detached with its own session, pointed at the shared module directory:
for id in 2 3 4; dologosctl daemon start --detach --config-dir ./logosctl-$id --modules-dir ./install-session/modulesdone -
Load the module, initialise from each config, and start each node:
for id in 2 3 4; dologosctl --config-dir ./logosctl-$id module load storage_modulelogosctl --config-dir ./logosctl-$id call storage_module init @config-$id.jsonlogosctl --config-dir ./logosctl-$id call storage_module startdone# Wait a few seconds for the nodes to start -
Verify the network is up: every node must report a non-empty identity (
idandspr) throughdebug:for id in 1 2 3 4; dostarted_up=$(logosctl --config-dir ./logosctl-$id call storage_module debug \| jq -e '(.result.value.id // "") != "" and (.result.value.spr // "") != ""')if [ "$started_up" = "true" ]; thenecho "Node $id is up"elseecho "Node $id is down"fidone# You should see "Node X is up" for all nodes from 1 to 4
Build the Mix relay pool
The storage nodes need two files to use the Mix relays:
mix-pool.json: the relays' peer IDs, multiaddrs, mix and libp2p public keys.mix-proxies.json: the relays' TCP SPRs.
Since this is a local network, every relay is reachable at 127.0.0.1 on its fixed listen-port (808<id>), so the pool multiAddr is built from that rather than from the announced addresses.
-
Save the debug output of each relay to
debug-<id>.jsonto make the data extraction easier:for id in 1 2 3 4; dologosctl --config-dir ./logosctl-$id call storage_module debug > debug-$id.jsondone -
Assemble
mix-pool.json.debugalready returnspeerId,mixPubKeyandlibp2pPubKeyin exactly the form the pool wants:for id in 1 2 3 4; doADDR="/ip4/127.0.0.1/tcp/$((8080 + id))"jq --arg ma "$ADDR" '{peerId: .result.value.id,multiAddr: $ma,mixPubKey: .result.value.mixPubKey,libp2pPubKey: .result.value.libp2pPubKey}' debug-$id.jsondone | jq -s '{version: 1, relays: .}' > mix-pool.json -
Collect the relays' proxy SPRs (
providerRecord) into a JSON array:jq -s -c '[.[].result.value.providerRecord]' debug-*.json > mix-proxies.json
Start the storage nodes (5 and 6)
The four nodes so far are the Mix relays. Now add the storage nodes that actually use them: their config is the same, plus the dht-mix-proxy list and the mix-pool path built in the previous section.
-
Write the storage node configs:
BOOTSTRAP=$(cat bootstrap-spr.txt)PROXIES=$(cat mix-proxies.json)for id in 5 6; domkdir -p "$(pwd)/storage-data/node-$id"cat > "config-$id.json" <<EOF{"log-level": "DEBUG","data-dir": "$(pwd)/storage-data/node-$id","log-file": "$(pwd)/storage-data/node-$id/storage.log","disc-port": $((9090 + id)),"listen-port": $((8080 + id)),"nat": "extip:127.0.0.1","mix-enabled": true,"bootstrap-node": ["$BOOTSTRAP"],"dht-mix-proxy": $PROXIES,"mix-pool": "$(pwd)/mix-pool.json"}EOFdone -
Start one daemon per storage node, each detached with its own session, pointed at the shared module directory:
for id in 5 6; dologosctl daemon start --detach --config-dir ./logosctl-$id --modules-dir ./install-session/modulesdone -
Load the module, initialise from each config, and start each node:
for id in 5 6; dologosctl --config-dir ./logosctl-$id module load storage_modulelogosctl --config-dir ./logosctl-$id call storage_module init @config-$id.jsonlogosctl --config-dir ./logosctl-$id call storage_module startdone# Wait a few seconds for the nodes to start -
Verify the storage nodes are up:
for id in 5 6; dologosctl --config-dir ./logosctl-$id call storage_module debug \| jq -e '(.result.value.id // "") != "" and (.result.value.spr // "") != ""'done
Upload from one node, download through Mix
Node 5 seeds a file, and node 6 downloads it with local=false to force a network lookup—the lookup that Mix hides.
-
Create a small file and upload it through node 5:
echo "Hello through Mix from the storage doc-test." > hello.txtlogosctl --config-dir ./logosctl-5 call storage_module uploadUrl "$(pwd)/hello.txt" 65536 -
The upload runs in the background; give it a moment, then read the CID of the stored manifest from node 5:
logosctl --config-dir ./logosctl-5 call storage_module manifests \| jq -er '.result.value[0].cid' > cid.txt -
Download the CID through node 6:
logosctl --config-dir ./logosctl-6 call storage_module downloadToUrl "$(cat cid.txt)" "$(pwd)/downloaded.txt" false 65536# Wait a few seconds for the download to complete -
Confirm the lookup was tunnelled through Mix: node 6's log records the relay selection (SURB):
grep "Selected mix node for surbs" storage-data/node-6/storage.log logosctl-6/logs/daemon.log -
Verify the round-trip: the downloaded file matches what node 5 uploaded:
cat downloaded.txt# Hello through Mix from the storage doc-test.
Shut the network down
-
For each node: stop the libp2p node, destroy the storage context, and stop the daemon. The
|| truelets the loop continue past a node that is already gone:for id in 1 2 3 4 5 6; dologosctl --config-dir ./logosctl-$id call storage_module stop || truelogosctl --config-dir ./logosctl-$id call storage_module destroy || truelogosctl --config-dir ./logosctl-$id daemon stop || truedone -
Confirm the daemons have stopped:
ps aux | grep logosctl | grep -v 'grep' | wc -l# Should print "0"