Files
node/CMakeLists.txt
zeekayandHanzo Dev 375c83bf8c A height is decided on Go's rule, and an imported tip is anchored
The proposer was height mod n with no fallback, so one seat down halted the
chain at its turn, and a node that read an export could never build on it.

- windower: Go's proposervm windower (lux-rs/node src/proposer.rs), held to
  windower_test.go's vectors; a proposal is ML-DSA-signed by its slot's
  proposer and checked against it.
- engine: lux-rs/node's step rule, clause for clause — one proposer a window
  from a clock counted off the tip's time, one signature a height through a
  durable journal, the lowest live block that can still reach alpha after a
  settle window, no building beside a live block or f+1 votes, sibling caps,
  one settle deadline a height, the retry, and catch-up on certificates.
- the anchor: a gated validator signs one statement at its imported tip (T, H)
  under its own tag, through the same journal; alpha of them are the
  certificate that lifts the gate, and T+1 is built on it.
- the mesh binds an address, resolves names, accepts on its own thread and is
  swept again, so a seat that restarts is met again.
- tests: windower, statement, engine and live (five seats anchor Zoo
  mainnet's tip and build on it; one down; one that imported another tip; one
  that restarts and catches up).

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-24 16:13:14 -07:00

323 lines
17 KiB
CMake
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (C) 2026, Lux Industries, Inc. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause-Eco
#
# node — a running consensus node: the consensus engine + a ZAP vote mesh over
# real TCP sockets. Layering (each layer below is REUSED unmodified):
#
# blst → bls_signature → consensus (the gate + Node, and its BLS domain)
# [all built by the consensus checkout's CMakeLists, added as a subdirectory]
# lux::zap wire codec (zap-cpp-core, header-only)
# ──────────────────────── NEW in node ────────────────────────
# FrameReader (non-blocking reassembler, header-only)
# MeshVoteTransport (VoteTransport over N TCP sockets, header-only)
# Node2Host (listen/accept/dial mesh + drive the Node) ← the one .cpp
#
# Reused, NOT vendored: the consensus checkout (the engine) and the luxcpp
# checkout (blst, cevm::crypto::bls, zap-cpp-core). Both are found automatically
# where they actually sit; -DCONSENSUS_DIR / -DLUXCPP_ROOT override.
cmake_minimum_required(VERSION 3.20)
project(node LANGUAGES C CXX ASM)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()
# Sanitizer for node's + consensus's OWN code (NOT the reused blst/bls assembly
# crypto). TSan here is load-bearing: the mesh setup is multi-threaded and the gate
# mutex is exercised across threads. Usage: -DNODE_SANITIZE=thread (or address,undefined)
set(NODE_SANITIZE "" CACHE STRING "Sanitizer for node+consensus code (e.g. thread | address,undefined)")
# Propagate to the consensus subdir so the reused gate is instrumented to match,
# while blst/bls_signature stay clean (consensus scopes it the same way).
if (NODE_SANITIZE AND NOT CONSENSUS_SANITIZE)
set(CONSENSUS_SANITIZE "${NODE_SANITIZE}" CACHE STRING "" FORCE)
endif()
# find_checkout(VAR MARKER candidate...) — first candidate holding MARKER wins.
# One search, used for both reused trees, so "where does X live" is stated once.
function(find_checkout out marker)
foreach(_root ${ARGN})
get_filename_component(_abs "${_root}" ABSOLUTE)
if(EXISTS "${_abs}/${marker}")
set(${out} "${_abs}" PARENT_SCOPE)
return()
endif()
endforeach()
set(${out} "${out}-NOTFOUND" PARENT_SCOPE)
endfunction()
# consensus — the C++ consensus lives in the luxcpp checkout, so look there first.
# The sibling candidate stays for a side-by-side checkout, but it is LAST and it is
# why the marker is a header rather than a directory name: beside node in the lux
# checkout sits luxfi/consensus, the GO implementation, under the very same name.
# Matching on include/lux/consensus/node.hpp is what tells the two apart.
find_checkout(_consensus "include/lux/consensus/node.hpp"
"${CMAKE_SOURCE_DIR}/../../luxcpp/consensus"
"${CMAKE_SOURCE_DIR}/../luxcpp/consensus"
"${CMAKE_SOURCE_DIR}/../consensus")
# cevm — the C++ EVM that executes the C-Chain's blocks. Found the same way the
# other reused trees are, and matched on a source file rather than the directory
# name for the same reason: `cevm` is also the name of a Go binding and of a
# Conan package. lib/evm/state/processor.hpp is what identifies the checkout.
find_checkout(_cevm "lib/evm/state/processor.hpp"
"${CMAKE_SOURCE_DIR}/../cevm"
"${CMAKE_SOURCE_DIR}/../../luxcpp/cevm"
"${CMAKE_SOURCE_DIR}/../luxcpp/cevm")
# luxcpp — blst + cevm::crypto::bls + zap-cpp-core all live under one root.
find_checkout(_luxcpp "blst/bindings/blst.h"
"${CMAKE_SOURCE_DIR}/../../luxcpp"
"${CMAKE_SOURCE_DIR}/../luxcpp"
"${CMAKE_SOURCE_DIR}/..")
set(CONSENSUS_DIR "${_consensus}" CACHE PATH "consensus checkout")
set(CEVM_DIR "${_cevm}" CACHE PATH "cevm checkout")
set(LUXCPP_ROOT "${_luxcpp}" CACHE PATH "luxcpp checkout root")
set(ZAP_DIR "${LUXCPP_ROOT}/zap-cpp-core/include" CACHE PATH "zap-cpp-core include root")
foreach(_p "${CONSENSUS_DIR}/CMakeLists.txt"
"${CEVM_DIR}/lib/evm/state/processor.hpp"
"${ZAP_DIR}/lux/zap/wire.hpp"
"${LUXCPP_ROOT}/blst/bindings/blst.h"
"${LUXCPP_ROOT}/crypto/bls/cpp/bls_signature.cpp")
if(NOT EXISTS "${_p}")
message(FATAL_ERROR "node: required reused asset not found: ${_p}\n"
"searched for consensus and luxcpp beside this checkout; "
"set -DCONSENSUS_DIR=/path/to/consensus -DLUXCPP_ROOT=/path/to/luxcpp")
endif()
endforeach()
message(STATUS "node: consensus = ${CONSENSUS_DIR}")
message(STATUS "node: luxcpp = ${LUXCPP_ROOT}")
message(STATUS "node: cevm = ${CEVM_DIR}")
# cevm's dependencies (intx, nlohmann_json, lux-crypto, bls, lux-zap-core) come
# from Conan, so node is configured with the toolchain Conan generated for it.
# Saying so here — rather than failing 200 lines later inside cevm's
# find_package(intx) — is the difference between a build you can fix and one you
# have to bisect.
if(NOT TARGET intx::intx AND NOT CMAKE_TOOLCHAIN_FILE)
message(FATAL_ERROR
"node: cevm's dependencies come from Conan and no toolchain was given.\n"
" conan install ${CEVM_DIR} -pr ${CEVM_DIR}/.github/conan/manylinux-relax.profile \\\n"
" -s build_type=Release -s compiler.cppstd=gnu20 \\\n"
" --output-folder=${CEVM_DIR}/build-node --build=missing\n"
" cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=${CEVM_DIR}/build-node/build/Release/generators/conan_toolchain.cmake")
endif()
find_package(Threads REQUIRED)
# ── AWS-LC — the ONLY TLS stack on this machine offering luxd's mandatory
# X25519MLKEM768 hybrid group (`network/peer/tls_config.go`'s single-
# element CurvePreferences list). System OpenSSL here is 3.0.13, which
# predates the group entirely; AWS-LC 0.45.0 (the exact version node.rs's
# own `aws-lc-rs` dependency resolves to) has it at the SAME wire codepoint
# Go's crypto/tls assigns (`SSL_GROUP_X25519_MLKEM768 = 0x11EC`). Built
# standalone from its own CMakeLists (its source ships inside the
# `aws-lc-sys` crate; this repo depends on neither Cargo nor that crate,
# only on the C library it vendors) — see LLM.md for the exact command.
# Not a Conan package: nothing in this stack packages it.
# Found the way the other two reused trees are found, rather than named on
# the command line: it is a checkout beside them and it was the one
# dependency that made configuring this repo a two-step job. -DAWSLC_SRC
# and -DAWSLC_BUILD_DIR still override.
set(AWSLC_SRC "" CACHE PATH "AWS-LC source tree (has include/openssl/ssl.h)")
set(AWSLC_BUILD_DIR "" CACHE PATH "AWS-LC build dir (has crypto/libcrypto.a, ssl/libssl.a)")
if(NOT AWSLC_SRC)
find_checkout(AWSLC_SRC "include/openssl/ssl.h"
"${CMAKE_SOURCE_DIR}/../aws-lc"
"${CMAKE_SOURCE_DIR}/../../luxcpp/aws-lc"
"${CMAKE_SOURCE_DIR}/../luxcpp/aws-lc")
endif()
if(NOT AWSLC_BUILD_DIR AND AWSLC_SRC)
# The build is a directory under the source, and it counts only if the
# library is actually in it: a configured-but-unbuilt tree is not an answer.
find_checkout(AWSLC_BUILD_DIR "ssl/libssl.a"
"${AWSLC_SRC}/build"
"${AWSLC_SRC}/build/Release")
endif()
if(NOT AWSLC_SRC OR NOT EXISTS "${AWSLC_SRC}/include/openssl/ssl.h" OR
NOT AWSLC_BUILD_DIR OR NOT EXISTS "${AWSLC_BUILD_DIR}/ssl/libssl.a")
message(FATAL_ERROR
"node: AWS-LC not found. Build it once — see LLM.md \"Building AWS-LC\" —\n"
"then pass -DAWSLC_SRC=<...>/aws-lc -DAWSLC_BUILD_DIR=<build dir>.")
endif()
message(STATUS "node: aws-lc = ${AWSLC_BUILD_DIR}")
add_library(awslc_crypto STATIC IMPORTED)
set_target_properties(awslc_crypto PROPERTIES IMPORTED_LOCATION "${AWSLC_BUILD_DIR}/crypto/libcrypto.a")
add_library(awslc_ssl STATIC IMPORTED)
set_target_properties(awslc_ssl PROPERTIES
IMPORTED_LOCATION "${AWSLC_BUILD_DIR}/ssl/libssl.a"
INTERFACE_LINK_LIBRARIES awslc_crypto
INTERFACE_INCLUDE_DIRECTORIES "${AWSLC_SRC}/include")
# ── Reuse consensus's build (brings the blst / bls_signature / consensus targets
# with correct include + link propagation). One build definition for the reused
# layers — no duplication here.
add_subdirectory("${CONSENSUS_DIR}" consensus-build)
# ── Reuse cevm's build the same way. EXCLUDE_FROM_ALL because node links four of
# its libraries and does not want its 40-odd test and benchmark binaries; they
# stay buildable by name. Static, because luxd is one process: cevm defaults
# to a shared library so it can be loaded as an EVMC plugin, which is not how
# it is used here.
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(CEVM_TESTING OFF CACHE BOOL "" FORCE)
add_subdirectory("${CEVM_DIR}" cevm-build EXCLUDE_FROM_ALL)
# ── node — the NEW host library (the only compiled unit; the rest is header-only)
# libluxcrypto — the ML-DSA-65 and ML-KEM-768 the post-quantum handshake signs
# and encapsulates with. It is a Go build output, `make dist` in luxfi/crypto,
# and it is found the way the other reused trees are rather than named.
find_checkout(_luxcrypto "libluxcrypto.a"
"${CMAKE_SOURCE_DIR}/../../lux/crypto/dist"
"${CMAKE_SOURCE_DIR}/../lux/crypto/dist"
"$ENV{LUX_LIB_DIR}")
if(_luxcrypto)
message(STATUS "node: luxcrypto = ${_luxcrypto}")
add_library(luxcrypto STATIC IMPORTED)
set_target_properties(luxcrypto PROPERTIES
IMPORTED_LOCATION "${_luxcrypto}/libluxcrypto.a"
INTERFACE_INCLUDE_DIRECTORIES "${_luxcrypto}")
else()
message(FATAL_ERROR
"node: no libluxcrypto.a. Build it with `make dist` in luxfi/crypto, or set "
"LUX_LIB_DIR to the directory holding it.")
endif()
add_library(node STATIC
src/mesh.cpp # the socket dance
src/windower.cpp # who proposes at a height and a slot: Go's proposervm windower
src/statement.cpp # what a validator signs — a vote or an anchor — and α of them
src/journal.cpp # one statement a height, on the device before it is signed
src/ledger.cpp # the certificates accepted on, the import gate, the held block
src/engine.cpp # the height rule: windows, one signature, the converged winner
src/network.cpp # which chain aliases this node's network owns
src/rpc.cpp # HTTP JSON-RPC, and the relay to a chain's own server
src/run.cpp # a node on one network's spec
src/validators.cpp # the set-root commitment a vote binds
src/committee.cpp # the network description all three daemons read
src/plugin.cpp # a chain in another process, over ZAP
src/signer.cpp # this validator's own name and voting key
src/staking.cpp # a luxd peer's identity: P-256 cert + NodeID + BLS + ML-DSA
src/peer_tls.cpp # TLS 1.3 + X25519MLKEM768, over AWS-LC
src/keccak.cpp # SHAKE256/cSHAKE256 and the SP 800-185 encodings under them
src/pq_handshake.cpp # ML-DSA-65 identity + ML-KEM-768 session, the strict-PQ link
src/peer.cpp) # the p2p Handshake/PushQuery/vote path to a live luxd
target_include_directories(node PUBLIC include "${ZAP_DIR}")
target_link_libraries(node PUBLIC consensus Threads::Threads awslc_ssl luxcrypto)
# A Go archive brings its own runtime, which wants the platform frameworks it
# calls into. Whole-archive so the runtime's initializer survives dead-strip:
# nothing in C++ calls it, and a linker that keeps only what is called drops it.
if(APPLE)
target_link_libraries(node PUBLIC
"-Wl,-force_load,${_luxcrypto}/libluxcrypto.a"
"-framework CoreFoundation" "-framework Security" resolv)
else()
target_link_libraries(node PUBLIC -Wl,--whole-archive luxcrypto -Wl,--no-whole-archive)
endif()
# evm-state carries the StateDB, process_block and the Merkle-Patricia-Trie; evm
# is the interpreter behind evmc_create_cevm. lux::crypto-secp256k1 is what
# recovers a transaction's sender, and nlohmann_json is the RPC's codec.
# find_package again at THIS level, not because cevm forgot to: an imported
# target a subdirectory creates is scoped to that subdirectory, so node has to
# ask for the two it links by name itself. Same packages, same Conan-generated
# configs, resolved a second time — not a second source of truth.
find_package(crypto CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
target_link_libraries(node PUBLIC evm-state evm lux::crypto-secp256k1 lux::crypto-sha256 nlohmann_json::nlohmann_json)
target_compile_options(node PRIVATE -Wall -Wextra -Wpedantic)
# ── luxd — this node on a local Lux chain
add_executable(luxd src/luxd.cpp)
target_link_libraries(luxd PRIVATE node)
target_compile_options(luxd PRIVATE -Wall -Wextra)
# ── lux-join — a validator on a REAL, external luxd network (not this node's
# own self-referential mesh): PQ-gated TLS, the p2p Handshake, and a signed
# Quasar vote on whatever luxd proposes. See LLM.md.
add_executable(lux-join src/lux_join_main.cpp)
target_link_libraries(lux-join PRIVATE node)
target_compile_options(lux-join PRIVATE -Wall -Wextra)
# ── tests — one registration, one list
# frame_reader the reassembler in isolation (fragmentation, batching, limits)
# wire_vector the bytes on the wire, written from the spec and read off a socket
# mesh_transport what one hostile or dead socket can do to this node
# mesh_formation mesh setup is bounded, and a missing validator is not a dead cluster
# windower Go's proposer schedule, held to windower_test.go's vectors
# statement the vote and the anchor, their domain, the journal both go
# through, the certificate rule, the ledger and held records
# live five validators import Zoo mainnet's tip, anchor it and
# build on it; one seat down; one that imported another tip;
# one that restarts and catches up
# hostile_decode what a stranger can send the two decoders that take his bytes
# set_root the validator-set commitment, against Go's encoding
# committee the network description: the parse, the refusals, the root,
# and the possession proof that is checked rather than trusted
# chain_route every spelling of a chain's path reaches that one chain
# archive_proxy what a relayed answer's status is, when this node is a proxy
# pq_handshake the strict-PQ peer handshake: Go's own answers to the three
# derivations, then a full exchange over a real socket pair
# pq_vector the validator link held to a handshake the GO node produced:
# its frames, its names, its session key (LP-10602)
# engine one validator against a scripted peer: a proposal from the
# wrong window is never parsed, a refused block is never
# signed, a gated node signs only its anchor, a chain that
# refuses to build does not stop it
# run what a node refuses before it starts: a spec whose genesis
# names another chain, a network no spec has
# plugin cevm's EVM plugin, started the way the Go node starts one
# and driven through a height over ZAP
enable_testing()
set(NODE_TESTS frame_reader wire_vector mesh_transport mesh_formation hostile_decode set_root committee chain_route archive_proxy pq_handshake pq_vector run windower statement live engine)
foreach(_t ${NODE_TESTS})
add_executable(${_t}_test test/${_t}_test.cpp)
target_link_libraries(${_t}_test PRIVATE node)
target_compile_options(${_t}_test PRIVATE -Wall -Wextra)
add_test(NAME ${_t} COMMAND ${_t}_test)
# cevm is a subdirectory and registers its own tests, which this build does
# not compile; the label is what says which tests are this repository's.
set_tests_properties(${_t} PROPERTIES LABELS node)
endforeach()
# The plugin test drives THIS build's EVM plugin — cevm's, the one the node runs
# its chain in — the way the node does, so it is built with the test.
add_executable(plugin_test test/plugin_test.cpp)
target_link_libraries(plugin_test PRIVATE node)
target_compile_options(plugin_test PRIVATE -Wall -Wextra)
add_dependencies(plugin_test cevm-bin)
add_test(NAME plugin COMMAND plugin_test $<TARGET_FILE:cevm-bin>)
set_tests_properties(plugin PROPERTIES LABELS node)
# The committee test reads REAL FILES — the same files the Rust node reads, so
# "both accept this" is a fact about one artifact rather than about two string
# literals that were typed twice.
target_compile_definitions(committee_test PRIVATE
COMMITTEE_FIXTURES="${CMAKE_CURRENT_SOURCE_DIR}/test/committee")
# Same discipline for the link: the vector is a file, and it came off a wire
# with a Go node at the other end of it.
target_compile_definitions(pq_vector_test PRIVATE
PQ_VECTORS="${CMAKE_CURRENT_SOURCE_DIR}/test/pq")
# Apply NODE_SANITIZE to node's own targets only (the consensus subdir scopes
# its own; blst/bls stay clean). One place for the whole node surface.
if (NODE_SANITIZE)
set(_n2_san -fsanitize=${NODE_SANITIZE} -fno-omit-frame-pointer)
set(_n2_targets node luxd)
foreach(_t ${NODE_TESTS})
list(APPEND _n2_targets ${_t}_test)
endforeach()
foreach(_t ${_n2_targets})
target_compile_options(${_t} PRIVATE ${_n2_san})
target_link_options(${_t} PRIVATE ${_n2_san})
endforeach()
endif()