mirror of
https://github.com/luxfi/node.git
synced 2026-10-30 15:24:05 +00:00
Before this there were three SHA-256s in the C++ tree, two builds of the same secp256k1, and a keccak compiled twice. blue/gpu-seam states the primitives once — hashes, the RFC 6962 fold, and public-key recovery — behind one interface per language, with a complete CPU backend and an optional kernel plugin behind the same door. A node built with nothing installed is a whole node, not a degraded one; that is what `make gpu-differential` runs first, with the library deliberately out of sight. Where the seam meets the node's core. The branch had each chain add gpu/cpp itself, which was right when each chain also compiled its own crypto. Core now holds what every chain shares, so core links the seam and a chain that links core has it. `lux::core::sha256` is `lux::gpu::sha256`; the private compiles of sha256.cpp and ripemd160.cpp under core, of `lux_pvm_crypto` and of `xvm_crypto` are gone. The Q-chain came along: it had its own SHA-256 with its own test vectors, and it identifies transactions with it. Its two callers ask the seam now — the streaming one folds its four pieces into the buffer the run of updates already meant. TWO DIVERGENCES, both found by tests the gate cannot see. A malleated signature — `s` negated, recovery parity flipped — names the same key, and Go spends under it: `RecoverPubkey` checks the length and `v < 4`, and the low-`s` refusal lives in `VerifySignature`, which no spend path calls. The first-party C++ curve recovers under it too. `k256` alone refuses a high `s`, and the P-chain's own code used to put the pair back in the form k256 reads — so routing it at the seam made the Rust backend the only one of three that would not spend an output Go spends, which is a chain split anyone can build by negating one scalar. The normalization moved into the seam, where the rule now lives once for every chain, and `a_malleated_signature_recovers_the_same_key` pins it beside the recovery-id vector. The X-chain gives two refusals here and the seam gives one. A byte that is not a recovery id at all is a different complaint from a recovery attempted and failed, and the reference makes that distinction; `Option` cannot carry it. The seam keeps the curve's rule and the chain keeps its own wording. What the seam already closed, and is why it is worth having: masking the recovery byte with `v & 1` read 2 as 0 and recovered the address of a DIFFERENT signature — one XOR of one byte produced a transaction C++ accepted and Go and Rust refused. 2 and 3 are named and refused now, in one place. Verified: 725 vectors, four implementations, agreed on every compared field, exit 0. C++ P-chain 23/23, X-chain 19/19, Q-chain 13/13; the seam's own suite 369 checks in C++, 16 in Rust, Go green; Rust P-chain 438 and X-chain 310 over the seam.
173 lines
7.6 KiB
CMake
173 lines
7.6 KiB
CMake
# Copyright (C) 2026, Lux Industries Inc. All rights reserved.
|
|
# SPDX-License-Identifier: BSD-3-Clause-Eco
|
|
#
|
|
# xvm — the Lux X-Chain (the UTXO DAG and asset transfers) in C++, behind the
|
|
# node's VM seam.
|
|
#
|
|
# Layering, each layer below REUSED unmodified:
|
|
#
|
|
# luxcpp/crypto sha256, ripemd160, keccak256, secp256k1 (recover+sign)
|
|
# lux-cpp/consensus the Id type the seam speaks (header-only here)
|
|
# lux-cpp/node include/lux/node/vm.hpp — the VM seam itself
|
|
# zap-proto/cpp the ZAP protocol SDK (published, pinned in zap.cmake)
|
|
# ─────────────────────────── NEW in xvm ────────────────────────────
|
|
# wire the (TypeKind, ShapeKind) fx envelopes
|
|
# fx the fx primitives and the three fx families
|
|
# txs the five transactions and the UTXO vocabulary
|
|
# state the occupied set, and a diff over it
|
|
# root the execution root a validator signs
|
|
# executor syntax → semantics → apply
|
|
# block the block
|
|
# vm the seam implementation
|
|
#
|
|
# Reused, NOT vendored. The checkouts are found where they actually sit;
|
|
# -DLUXCPP_CRYPTO_DIR / -DNODE_DIR / -DCONSENSUS_DIR override.
|
|
|
|
cmake_minimum_required(VERSION 3.20)
|
|
project(xvm LANGUAGES C CXX)
|
|
|
|
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()
|
|
|
|
set(XVM_SANITIZE "" CACHE STRING "Sanitizer for xvm's own code (e.g. address,undefined)")
|
|
|
|
# The wire this chain speaks comes from the published SDK, not from here.
|
|
include(${CMAKE_CURRENT_SOURCE_DIR}/../zap.cmake)
|
|
|
|
# The wire's accessors: schema/wire.zap in, include/lux/xvm/gen out.
|
|
zap_schema(wire-schema
|
|
${CMAKE_CURRENT_SOURCE_DIR}/schema/wire.zap
|
|
${CMAKE_CURRENT_SOURCE_DIR}/include/lux/xvm/gen)
|
|
|
|
# find_checkout(VAR MARKER candidate...) — first candidate holding MARKER wins.
|
|
# The marker is a FILE, not a directory name, because names are ambiguous here:
|
|
# `consensus` beside node in the lux checkout is the GO implementation, and only
|
|
# the header tells the two apart.
|
|
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()
|
|
|
|
find_checkout(_crypto "include/lux/crypto/secp256k1.h"
|
|
"${LUXCPP_CRYPTO_DIR}"
|
|
"$ENV{HOME}/work/luxcpp/crypto"
|
|
"${CMAKE_SOURCE_DIR}/../../../../luxcpp/crypto")
|
|
if(NOT _crypto)
|
|
message(FATAL_ERROR
|
|
"luxcpp/crypto not found (looked for include/lux/crypto/secp256k1.h). "
|
|
"Pass -DLUXCPP_CRYPTO_DIR=/path/to/luxcpp/crypto.")
|
|
endif()
|
|
message(STATUS "xvm: reusing crypto at ${_crypto}")
|
|
|
|
find_checkout(_node "include/lux/node/vm.hpp"
|
|
"${NODE_DIR}"
|
|
"$ENV{HOME}/work/lux-cpp/node"
|
|
"${CMAKE_SOURCE_DIR}/../../../../lux-cpp/node")
|
|
find_checkout(_consensus "include/lux/consensus/quorum_cert_engine.hpp"
|
|
"${CONSENSUS_DIR}"
|
|
"$ENV{HOME}/work/lux-cpp/consensus"
|
|
"${CMAKE_SOURCE_DIR}/../../../../lux-cpp/consensus")
|
|
|
|
# The seam is the point of the port, so its absence is a hard error rather than
|
|
# a quiet feature-off: a chain built against no seam is a library nothing runs.
|
|
if(NOT _node OR NOT _consensus)
|
|
message(FATAL_ERROR
|
|
"the VM seam was not found — node=${_node} consensus=${_consensus}. "
|
|
"Pass -DNODE_DIR=/path/to/lux-cpp/node -DCONSENSUS_DIR=/path/to/lux-cpp/consensus.")
|
|
endif()
|
|
message(STATUS "xvm: VM seam at ${_node}, Id from ${_consensus}")
|
|
|
|
# ── core: the names, the store and the primitive seam, shared with every other
|
|
# chain here ─────────────────────────────────────────────────────────────────
|
|
#
|
|
# Core links gpu/cpp, so the hashes, keccak and signature recovery all arrive
|
|
# through the one interface a chain asks a primitive through. They used to be
|
|
# compiled here and again in platformvm; a primitive with two builds has two
|
|
# places to drift.
|
|
set(LUXCPP_CRYPTO_DIR "${_crypto}" CACHE PATH "luxcpp/crypto checkout")
|
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../core ${CMAKE_CURRENT_BINARY_DIR}/core)
|
|
|
|
# ── xvm itself ───────────────────────────────────────────────────────────────
|
|
add_library(xvm STATIC
|
|
src/address.cpp
|
|
src/wire.cpp
|
|
src/fx.cpp
|
|
src/txs.cpp
|
|
src/state.cpp
|
|
src/root.cpp
|
|
src/executor.cpp
|
|
src/block.cpp
|
|
src/genesis.cpp
|
|
src/mempool.cpp
|
|
src/vm.cpp
|
|
)
|
|
target_include_directories(xvm PUBLIC
|
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
|
${_node}/include
|
|
${_consensus}/include
|
|
)
|
|
target_link_libraries(xvm PUBLIC zap::zap lux_core)
|
|
target_compile_options(xvm PRIVATE -Wall -Wextra)
|
|
|
|
# ── tests ────────────────────────────────────────────────────────────────────
|
|
# wire the bytes, against the Go X-Chain's own (golden.hpp)
|
|
# root the execution root, against the cross-language KAT
|
|
# projection chain state -> leaves, and the fold at every edge
|
|
# txs the tx vocabulary: verify, sort, round-trip
|
|
# fx the three fx families' spend and operation gates
|
|
# syntactic every syntactic rejection, ported case for case
|
|
# semantic the chain's own agreement, incl. import/export
|
|
# executor what applying a tx does to the UTXO set
|
|
# address bech32, against Go's own strings
|
|
# state the diff, and what acceptance means
|
|
# block block bytes, ids, and parsing
|
|
# mempool the admission gate, the pool, and the bloom filter
|
|
# vm build → verify → accept → REJECT over the node's seam
|
|
enable_testing()
|
|
set(XVM_TESTS wire root projection syntactic txs fx executor address state block genesis
|
|
mempool durability semantic vm)
|
|
foreach(_t ${XVM_TESTS})
|
|
add_executable(${_t}_test test/${_t}_test.cpp)
|
|
target_link_libraries(${_t}_test PRIVATE xvm)
|
|
target_include_directories(${_t}_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/test)
|
|
target_compile_options(${_t}_test PRIVATE -Wall -Wextra)
|
|
add_test(NAME ${_t} COMMAND ${_t}_test)
|
|
endforeach()
|
|
|
|
# The C++ X-chain's evaluator for the cross-language chain differential. It is
|
|
# not a test: it prints this chain's answer for every X vector in
|
|
# conformance/corpus/vectors.tsv and says nothing about whether that answer is
|
|
# right. Deciding that is the runner's job, and it needs all three answers to do
|
|
# it. Built unconditionally — a differential that quietly lost one of its three
|
|
# voices would report agreement among whoever was left.
|
|
add_executable(xvm_conformance test/conformance.cpp)
|
|
target_link_libraries(xvm_conformance PRIVATE xvm)
|
|
target_compile_options(xvm_conformance PRIVATE -Wall -Wextra)
|
|
|
|
# Everything that links the sanitized library is sanitized, the evaluator
|
|
# included — it is declared above this block for exactly that reason. Left out,
|
|
# it failed to LINK under -DXVM_SANITIZE while ctest still reported fourteen
|
|
# passes, so the differential was the one voice a sanitized run silently lost.
|
|
if(XVM_SANITIZE)
|
|
set(_san -fsanitize=${XVM_SANITIZE} -fno-omit-frame-pointer)
|
|
set(_targets xvm xvm_conformance)
|
|
foreach(_t ${XVM_TESTS})
|
|
list(APPEND _targets ${_t}_test)
|
|
endforeach()
|
|
foreach(_t ${_targets})
|
|
target_compile_options(${_t} PRIVATE ${_san})
|
|
target_link_options(${_t} PRIVATE ${_san})
|
|
endforeach()
|
|
endif()
|