This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Use ./build.sh (wrapper around CMake). Builds go under build/<target>/, where target is debug, release, relwithdebinfo, or minsizerel (defaults to debug when only flags are given).
Common invocations:
./build.sh debug— build debug./build.sh debug --test— build and run the full unit test binary./build.sh debug --test RudpSuite— run a single Boost.Test suite./build.sh debug --test */TestRudpHandshakeHappyPath— run a single test case./build.sh debug --asan --test— AddressSanitizer build + tests./build.sh debug --clean/./build.sh rm— soft clean / wipebuild/
The test binary is produced at build/<target>/tests/minxtests and is a standard Boost.Test executable. Run it directly with Boost.Test flags (--run_test=<suite>, --log_level=test_suite, --color_output=yes) for finer control than --test gives. Current suites: RudpSuite, RudpStreamSuite, TcpServerSuite, MinxProxySuite, MinxMockValidationSuite, MinxPoWCacheSuite, SpamFilterSuite, UtilsSuite.
Examples live under examples/ (hello, verify, startstop, proxy) and get built automatically when MINX_BUILD_EXAMPLES=ON (the default). Binaries land in build/<target>/examples/<name>/.
- Always run builds and tests through
build.sh— don't invokecmake/ctest/ the test binary ad-hoc. The wrapper encodes the debug/release/asan variants and the test-filter semantics; sidestepping it drifts config between runs. - Always redirect
build.shoutput to a file and inspect it afterwards withgrep/tail/head. Don't pipebuild.shdirectly into filters in the same command — the output is long, interleaves CMake + compile + test logs, and you usually need to look at it from more than one angle (failures, warnings, a specific test case). Example:./build.sh debug --test > /tmp/minx-build.log 2>&1thengrep -E "(FAILED|error:)" /tmp/minx-build.log. ./build.sh --test <filter>forwards the filter verbatim to Boost.Test's--run_test=, so any Boost.Test filter syntax works:RudpSuite(one suite),RudpSuite/*(suite wildcard),*/TestRudpHandshakeHappyPath(one case across any suite),RudpSuite/TestA:RudpSuite/TestB(multiple cases, colon-separated).
- Boost ≥ 1.83 (system-installed, not fetched):
system,log,log_setup,unit_test_framework. ASIO is used header-only (BOOST_ASIO_HEADER_ONLY). - RandomX (FetchContent from tevador/RandomX@master) — compiled with
ARCH=nativefor the local CPU by default; comment that line inCMakeLists.txtif you need portable binaries. - logkv (FetchContent from fcecin/logkv@main) — header-only (
INTERFACEtarget). Minx only useslogkv::encodeHex/decodeHex(types.h) andlogkv::Reader/Writer/serializer/insufficient_buffer(buffer.h). Minx does not use logkv's persistentStore; logkv is used mostly for deser.
The canonical wire-format and protocol specification lives in README.md (message codes 0xFA..0xFF, ticket exchange, PoW puzzle layout, default RandomX engine). Don't duplicate that here; read it when touching anything that handles bytes on the wire.
Message codes [0x00..0xF9] are application-defined passthrough; 0xFF is the EXTENSION lane used by MinxStdExtensions and RUDP.
Three layered concerns. Understanding where a change belongs requires seeing all three.
Owns the UDP socket and all MINX protocol state: ticket generation/spending (BucketCache<uint64_t>), RandomX-based PoW verification, double-spend table (sliding 1-hour slots), IP ban filter (IPFilter, /24 or /56 prefixes), and amplification-defense spam filter (SpamFilter, count-min sketch over IP prefixes).
Minx does NOT own threads. Callers provide two boost::asio::io_contexts via openSocket(addr, netIO, taskIO):
netIO— UDP send/recv. Serialized via an internal strand; running multiple threads on it is pointless.taskIO— invokesMinxListenercallbacks and runs PoW verification viaverifyPoWs(). Listener callbacks are NOT thread-safe — iftaskIOhas multiple threads, the listener must handle concurrency itself.
MinxRunner (include/minx/minxrunner.h) is the convenience wrapper that owns both io_contexts plus net/task/pow thread pools. Use it when you want a batteries-included instance; use raw Minx when you need to multiplex io_contexts across other subsystems (as MinxProxy does).
PoW verification is key-scoped: each Hash server key gets one PoWEngine (cache + optional dataset + VM pool). Full dataset = ~2GB RAM and faster verify; cache-only = ~256MB and slower. Toggle via Minx::setUseDataset(bool).
MinxStdExtensions(stdext.h) — routes MINX EXTENSION (0xFF) packets by 8-byte key: upper 2 bytes = caller-defined "meta", lower 6 bytes = family ID. Build-once, move-intoMinx::setExtensionHandler(std::move(ext).build()).Rudp(rudp/rudp.h) — reliable+unreliable transport riding on MinxStdExtensions (family0xFAB1CEC14742). Passive state machine: no threads, no timers. Application drives time viatick(now_us)and feeds inbound wire bytes viaonPacket(...). Emits via aSendFncallback that the glue code normally forwards toMinx::sendExtension. Per-channel token bucket; handshake nonce-exchange derives a session token; 32-bit SACK porosity over a contiguous ack.RudpStream(rudp/rudp_stream.h) — Asio AsyncStream adapter so Boost.Beast (HTTP/WebSocket) or any Asio-generic byte-stream can run over one RUDP channel. Not thread-safe; single io_context only.- RUDP caveat: the RUDP code and its docstrings are partly machine-generated and marked experimental. When touching it, prefer behavior confirmed by
tests/test_rudp.cppover docstring assertions.
MinxProxy (proxy/minxproxy.h) is a TCP-to-UDP bridge: clients connect over TCP using 2-byte length-prefixed frames (TcpServer / TcpSession); the proxy rewrites tickets and forwards to a single upstream UDP MINX server over N parallel ticket "channels". GET_INFO is answered from cache; INIT is swallowed; everything else is forwarded. The proxy owns its own io_context + thread.
MinxProxyClient (proxy/minxproxyclient.h) is the client-side counterpart — drop-in replacement for Minx on the client, talks to a MinxProxy over TCP, provides its own local PoW mining pipeline. MinxClientTransport (proxy/minxclienttransport.h) is the unified facade that picks UDP direct or TCP-via-proxy based on the endpoint type passed to its constructor.
- Types (types.h) —
Hash = array<uint8_t, 32>,Bytes = boost::container::static_vector<char, 1280>(noteMAX_DATA_SIZE = 1280, the IPv6 MTU),SockAddr = boost::asio::ip::udp::endpoint. - Logging (blog.h) — a
Boost.Logfaçade. UseLOGINFO << "..." << VAR(x);patterns. Per-module severity viaLOG_MODULE("mymod")in a cpp file andblog::set_level("mymod", blog::trace)at runtime. Classes opt into instance-name tagging viaMINX_LOG_INSTANCE_STANDARD_BOILERPLATE/LOG_INSTANCE_NAME(expr). Csprng(csprng.h) — SipHash counter-mode CSPRNG, seeded fromstd::random_deviceby default;Csprng(k0, k1)is the deterministic constructor for tests.
tests/minx_mock.hprovidesMinxMockListener(callback stats + std::function hooks) andTestNode/MinxMockFixturefor multi-node Minx tests that drivenetio/taskioviapoll()instead of real threads.- RUDP tests typically instantiate two
Rudpobjects and hand-wire theirSendFncallbacks into each other'sonPacket, manipulating simulated time in microseconds — no sockets, no real io_context. - Long-running tests (proxy stress, full RandomX init) are slow; prefer
--run_test=SuiteName/TestCaseduring iteration and reserve the full suite for pre-commit.