diff --git a/.gitmodules b/.gitmodules index a8190a1..e898776 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "external/c-libp2p"] - path = external/c-libp2p - url = https://github.com/Pier-Two/c-libp2p.git [submodule "external/c-ssz"] path = external/c-ssz url = https://github.com/Pier-Two/c-ssz.git @@ -23,3 +20,6 @@ [submodule "external/c-leanvm-xmss"] path = external/c-leanvm-xmss url = https://github.com/Pier-Two/c-leanvm-xmss.git +[submodule "external/c-lean-libp2p"] + path = external/c-lean-libp2p + url = https://github.com/Pier-Two/c-lean-libp2p.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 6410239..df7e5ac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,9 @@ set(CMAKE_POLICY_VERSION_MINIMUM 3.5) if(APPLE) set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "Target architecture for macOS builds" FORCE) + add_compile_definitions(_DARWIN_C_SOURCE) + add_compile_definitions(PATH_MAX=4096) + add_compile_definitions(NAME_MAX=255) endif() set(CMAKE_C_STANDARD 17) @@ -68,6 +71,7 @@ set(LANTERN_LIBRARY_SOURCES src/http/common.c src/http/metrics.c src/http/server.c + src/test_driver/driver.c src/storage/storage.c src/support/log.c src/support/string_list.c @@ -76,8 +80,8 @@ set(LANTERN_LIBRARY_SOURCES src/support/secure_mem.c src/crypto/xmss.c src/internal/yaml_parser.c - external/c-libp2p/src/multiformats/multibase/encoding/base64_url.c - external/c-libp2p/external/wjcryptlib/lib/WjCryptLib_Sha256.c + tests/support/fixture_loader.c + tests/support/state_store_adapter.c ) function(lantern_define_library target_name wrapper_target) @@ -86,13 +90,9 @@ function(lantern_define_library target_name wrapper_target) PUBLIC ${PROJECT_SOURCE_DIR}/include PRIVATE + ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/external/jsmn - ${PROJECT_SOURCE_DIR}/external/c-libp2p/src/protocol/gossipsub/proto - ${PROJECT_SOURCE_DIR}/external/c-libp2p/src/protocol/gossipsub/proto/gen - ${PROJECT_SOURCE_DIR}/external/c-libp2p/external/secp256k1/include - ${PROJECT_SOURCE_DIR}/external/c-libp2p/external/libtomcrypt/src/headers - ${PROJECT_SOURCE_DIR}/external/c-libp2p/external/wjcryptlib/lib ) target_compile_definitions(${target_name} PRIVATE @@ -100,7 +100,7 @@ function(lantern_define_library target_name wrapper_target) _POSIX_C_SOURCE=200809L ) lantern_configure_dependencies(${target_name} ${wrapper_target}) - target_link_libraries(${target_name} PRIVATE secp256k1 libtomcrypt Threads::Threads) + target_link_libraries(${target_name} PRIVATE Threads::Threads) endfunction() lantern_define_library(lantern lantern_c_leanvm_xmss) @@ -228,9 +228,6 @@ if(LANTERN_BUILD_TESTS) lantern_client_gossip_test PRIVATE ${PROJECT_SOURCE_DIR}/tests/unit - ${PROJECT_SOURCE_DIR}/external/c-libp2p - ${PROJECT_SOURCE_DIR}/external/c-libp2p/src/protocol/gossipsub/proto - ${PROJECT_SOURCE_DIR}/external/c-libp2p/src/protocol/gossipsub/proto/gen ) target_compile_definitions( lantern_client_gossip_test @@ -305,12 +302,10 @@ if(LANTERN_BUILD_TESTS) add_test(NAME lantern_consensus_runtime COMMAND lantern_consensus_runtime_test) add_executable(lantern_state_test tests/unit/test_state.c) - target_sources(lantern_state_test PRIVATE tests/support/state_store_adapter.c) target_link_libraries(lantern_state_test PRIVATE ${LANTERN_TEST_LINK_LIB}) add_test(NAME lantern_state COMMAND lantern_state_test) add_executable(lantern_off_head_replay_test tests/unit/test_off_head_replay.c) - target_sources(lantern_off_head_replay_test PRIVATE tests/support/state_store_adapter.c) target_link_libraries(lantern_off_head_replay_test PRIVATE ${LANTERN_TEST_LINK_LIB}) add_test(NAME lantern_off_head_replay COMMAND lantern_off_head_replay_test) set_tests_properties(lantern_off_head_replay PROPERTIES TIMEOUT 120) @@ -326,8 +321,6 @@ if(LANTERN_BUILD_TESTS) add_executable(lantern_networking_messages_test tests/unit/test_networking_messages.c - tests/support/state_store_adapter.c - tests/support/fixture_loader.c ) target_link_libraries(lantern_networking_messages_test PRIVATE lantern) target_include_directories( @@ -363,7 +356,6 @@ if(LANTERN_BUILD_TESTS) add_test(NAME lantern_fork_choice COMMAND lantern_fork_choice_test) add_executable(lantern_storage_test tests/unit/test_storage.c) - target_sources(lantern_storage_test PRIVATE tests/support/state_store_adapter.c) target_link_libraries(lantern_storage_test PRIVATE ${LANTERN_TEST_LINK_LIB}) add_test(NAME lantern_storage COMMAND lantern_storage_test) @@ -386,8 +378,6 @@ if(LANTERN_BUILD_TESTS) add_executable(lantern_consensus_vectors_test tests/integration/test_consensus_vectors.c tests/integration/consensus_fixture_runner.c - tests/support/state_store_adapter.c - tests/support/fixture_loader.c ) target_link_libraries(lantern_consensus_vectors_test PRIVATE ${LANTERN_TEST_LINK_LIB}) target_include_directories( @@ -416,8 +406,6 @@ if(LANTERN_BUILD_TESTS) "— registering lantern_verify_signatures_vectors") add_executable(lantern_verify_signatures_vectors_test tests/integration/test_verify_signatures_vectors.c - tests/support/state_store_adapter.c - tests/support/fixture_loader.c ) target_link_libraries(lantern_verify_signatures_vectors_test PRIVATE ${LANTERN_TEST_LINK_LIB}) target_include_directories( @@ -446,8 +434,6 @@ if(LANTERN_BUILD_TESTS) add_executable(lantern_genesis_vectors_test tests/integration/test_genesis_vectors.c tests/integration/consensus_fixture_runner.c - tests/support/state_store_adapter.c - tests/support/fixture_loader.c ) target_link_libraries(lantern_genesis_vectors_test PRIVATE ${LANTERN_TEST_LINK_LIB}) target_include_directories( @@ -475,8 +461,6 @@ if(LANTERN_BUILD_TESTS) "— registering lantern_ssz_vectors") add_executable(lantern_ssz_vectors_test tests/integration/test_ssz_vectors.c - tests/support/state_store_adapter.c - tests/support/fixture_loader.c ) target_link_libraries(lantern_ssz_vectors_test PRIVATE ${LANTERN_TEST_LINK_LIB}) target_include_directories( @@ -504,8 +488,6 @@ if(LANTERN_BUILD_TESTS) "— registering lantern_networking_codec_vectors") add_executable(lantern_networking_codec_vectors_test tests/integration/test_networking_codec_vectors.c - tests/support/state_store_adapter.c - tests/support/fixture_loader.c ) target_link_libraries(lantern_networking_codec_vectors_test PRIVATE ${LANTERN_TEST_LINK_LIB}) target_include_directories( @@ -513,9 +495,6 @@ if(LANTERN_BUILD_TESTS) PRIVATE ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/external/jsmn - ${PROJECT_SOURCE_DIR}/external/c-libp2p/src/protocol/gossipsub/proto - ${PROJECT_SOURCE_DIR}/external/c-libp2p/src/protocol/gossipsub/proto/gen - ${PROJECT_SOURCE_DIR}/external/c-libp2p/external/noise-c/include ) target_compile_definitions( lantern_networking_codec_vectors_test @@ -530,25 +509,6 @@ if(LANTERN_BUILD_TESTS) "— skipping lantern_networking_codec_vectors") endif() - # Mirror libp2p dependency test names into CTestCustom so top-level `ctest` - # ignores them (they are dependency-only suites we do not build/run). - set(_lantern_libp2p_binary_dir ${CMAKE_BINARY_DIR}/c-libp2p) - if(EXISTS "${_lantern_libp2p_binary_dir}") - get_property(_lantern_libp2p_tests - DIRECTORY "${_lantern_libp2p_binary_dir}" - PROPERTY TESTS) - if(_lantern_libp2p_tests) - set(_lantern_ctest_custom "${CMAKE_BINARY_DIR}/CTestCustom.cmake") - file(WRITE "${_lantern_ctest_custom}" -"# Auto-generated by lantern/CMakeLists.txt – skip c-libp2p dependency tests.\n" -"set(CTEST_CUSTOM_TESTS_IGNORE\n") - foreach(_lantern_test_name IN LISTS _lantern_libp2p_tests) - file(APPEND "${_lantern_ctest_custom}" " ${_lantern_test_name}\n") - endforeach() - file(APPEND "${_lantern_ctest_custom}" ")\n") - endif() - endif() - set(_lantern_ctest_targets lantern_client_vote lantern_client_pending @@ -577,4 +537,15 @@ if(LANTERN_BUILD_TESTS) set_tests_properties(lantern_client_vote PROPERTIES TIMEOUT 300) set_tests_properties(lantern_state PROPERTIES TIMEOUT 300) + get_property(_lantern_build_targets DIRECTORY PROPERTY BUILDSYSTEM_TARGETS) + foreach(_lantern_target IN LISTS _lantern_build_targets) + if(_lantern_target MATCHES "^lantern_.*_test$") + target_compile_options( + ${_lantern_target} + PRIVATE + $<$:-UNDEBUG> + ) + endif() + endforeach() + endif() diff --git a/Dockerfile b/Dockerfile index 058e6c2..3edb744 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,8 +33,6 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=apt-cache-${TARGE python3-pip \ curl \ ccache \ - libtommath-dev \ - libssl-dev \ zlib1g-dev # Install latest Rust toolchain via rustup @@ -69,23 +67,14 @@ RUN --mount=type=cache,target=/root/.cargo/registry,sharing=locked,id=cargo-regi && cargo build --release --locked \ && find target/release -name '*.a' -exec ranlib {} \; -RUN --mount=type=cache,target=/root/.ccache,sharing=locked,id=ccache-${TARGETPLATFORM} \ - cmake -S external/c-libp2p/external/libtommath -B deps/libtommath -DBUILD_SHARED_LIBS=ON -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ - && cmake --build deps/libtommath --parallel "$(nproc)" \ - && cmake --install deps/libtommath - -RUN --mount=type=cache,target=/root/.ccache,sharing=locked,id=ccache-${TARGETPLATFORM} \ - cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DLANTERN_GIT_COMMIT="${GIT_COMMIT}" -DLANTERN_GIT_BRANCH="${GIT_BRANCH}" - ARG LANTERN_FORCE_REBUILD=0 -RUN echo "LANTERN_FORCE_REBUILD=${LANTERN_FORCE_REBUILD}" RUN --mount=type=cache,target=/root/.ccache,sharing=locked,id=ccache-${TARGETPLATFORM} \ - cmake --build build --target lantern_cli --parallel "$(nproc)" - -RUN --mount=type=cache,target=/root/.ccache,sharing=locked,id=ccache-${TARGETPLATFORM} \ - cmake --build build --target lantern_client_test --parallel "$(nproc)" || true - -RUN mkdir -p /opt/lantern/bin \ + --mount=type=cache,target=/usr/src/lantern/build,sharing=locked,id=lantern-build-${TARGETPLATFORM} \ + echo "LANTERN_FORCE_REBUILD=${LANTERN_FORCE_REBUILD}" \ + && cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DLANTERN_GIT_COMMIT="${GIT_COMMIT}" -DLANTERN_GIT_BRANCH="${GIT_BRANCH}" \ + && cmake --build build --target lantern_cli --parallel "$(nproc)" \ + && (cmake --build build --target lantern_client_test --parallel "$(nproc)" || true) \ + && mkdir -p /opt/lantern/bin \ && cp build/lantern_cli /opt/lantern/bin/lantern \ && mkdir -p /opt/lantern/lib \ && find build -maxdepth 2 -type f -name "*.so*" -exec cp {} /opt/lantern/lib/ \; \ diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index a7ebf43..7cac17f 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -14,25 +14,99 @@ endfunction() function(_lantern_define_static target_name source_dir) if(NOT TARGET ${target_name}) - add_library(${target_name} STATIC - ${source_dir}/src/ssz_constants.c - ${source_dir}/src/ssz_deserialize.c - ${source_dir}/src/ssz_merkle.c - ${source_dir}/src/ssz_serialize.c - ${source_dir}/src/ssz_utils.c - ${source_dir}/lib/mincrypt/sha256.c - ) - target_include_directories(${target_name} - PUBLIC - ${source_dir}/include - ${source_dir}/lib - ) - # Ensure POSIX-sized types (e.g., ssize_t) are available without editing the submodule. - target_compile_options(${target_name} - PRIVATE - -include sys/types.h - ) + if(NOT EXISTS ${source_dir}/CMakeLists.txt) + message(FATAL_ERROR "Missing c-ssz sources at ${source_dir}. Run scripts/bootstrap.sh to fetch git submodules.") + endif() + + set(SSZ_USE_SYSTEM_CRYPTO ON CACHE BOOL "Build c-ssz against the vendored AWS-LC OpenSSL target" FORCE) + set(SSZ_BUILD_TESTS OFF CACHE BOOL "Do not build c-ssz tests from Lantern" FORCE) + set(BUILD_TESTING OFF CACHE BOOL "" FORCE) + + if(NOT TARGET ssz) + add_subdirectory(${source_dir} ${CMAKE_BINARY_DIR}/c-ssz EXCLUDE_FROM_ALL) + set_property(DIRECTORY ${CMAKE_BINARY_DIR}/c-ssz PROPERTY EXCLUDE_FROM_TESTING TRUE) + endif() + + add_library(${target_name} INTERFACE) + target_link_libraries(${target_name} INTERFACE ssz) + endif() +endfunction() + +function(_lantern_configure_awslc_openssl_package source_dir) + if(NOT TARGET crypto) + message(FATAL_ERROR "AWS-LC crypto target must exist before configuring the OpenSSL package shim.") + endif() + + set(_awslc_include_dir "${source_dir}/external/aws-lc/include") + set(_openssl_config_dir "${CMAKE_BINARY_DIR}/aws-lc-openssl") + file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/c-lean-libp2p/external/aws-lc/symbol_prefix_include") + file(MAKE_DIRECTORY "${_openssl_config_dir}") + file(WRITE "${_openssl_config_dir}/OpenSSLConfig.cmake" +"set(OpenSSL_FOUND TRUE) +set(OPENSSL_FOUND TRUE) +set(OpenSSL_VERSION \"3.0.0\") +set(OPENSSL_VERSION \"3.0.0\") +set(OPENSSL_INCLUDE_DIR \"${_awslc_include_dir}\") +set(OPENSSL_INCLUDE_DIRS \"${_awslc_include_dir}\") +set(OpenSSL_Crypto_FOUND TRUE) +set(OpenSSL_SSL_FOUND TRUE) +if(NOT TARGET OpenSSL::Crypto) + add_library(OpenSSL::Crypto INTERFACE IMPORTED) + set_target_properties(OpenSSL::Crypto PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES \"${_awslc_include_dir}\") + target_link_libraries(OpenSSL::Crypto INTERFACE crypto) +endif() +if(NOT TARGET OpenSSL::SSL) + add_library(OpenSSL::SSL INTERFACE IMPORTED) + set_target_properties(OpenSSL::SSL PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES \"${_awslc_include_dir}\") + target_link_libraries(OpenSSL::SSL INTERFACE ssl OpenSSL::Crypto) +endif() +set(OPENSSL_CRYPTO_LIBRARY OpenSSL::Crypto) +set(OPENSSL_CRYPTO_LIBRARIES OpenSSL::Crypto) +set(OPENSSL_SSL_LIBRARY OpenSSL::SSL) +set(OPENSSL_SSL_LIBRARIES OpenSSL::SSL) +set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto) +") + set(OpenSSL_DIR "${_openssl_config_dir}" CACHE PATH "Resolve OpenSSL-compatible consumers to vendored AWS-LC" FORCE) + set(OPENSSL_ROOT_DIR "${source_dir}/external/aws-lc" CACHE PATH "Vendored AWS-LC root" FORCE) + set(OPENSSL_INCLUDE_DIR "${_awslc_include_dir}" CACHE PATH "Vendored AWS-LC OpenSSL-compatible headers" FORCE) + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE CACHE BOOL "Prefer vendored dependency package configs" FORCE) +endfunction() + +function(_lantern_define_c_lean_libp2p target_name source_dir) + if(TARGET ${target_name}) + return() + endif() + + if(NOT EXISTS ${source_dir}/CMakeLists.txt) + message(FATAL_ERROR "Missing c-lean-libp2p sources at ${source_dir}. Run scripts/bootstrap.sh to fetch git submodules.") + endif() + if(NOT EXISTS ${source_dir}/external/aws-lc/CMakeLists.txt) + message(FATAL_ERROR "Missing AWS-LC sources at ${source_dir}/external/aws-lc. Run git submodule update --init --recursive external/c-lean-libp2p.") + endif() + if(NOT EXISTS ${source_dir}/external/ngtcp2/CMakeLists.txt) + message(FATAL_ERROR "Missing ngtcp2 sources at ${source_dir}/external/ngtcp2. Run git submodule update --init --recursive external/c-lean-libp2p.") + endif() + if(NOT EXISTS ${source_dir}/external/secp256k1/CMakeLists.txt) + message(FATAL_ERROR "Missing secp256k1 sources at ${source_dir}/external/secp256k1. Run git submodule update --init --recursive external/c-lean-libp2p.") endif() + + set(BUILD_TESTING OFF CACHE BOOL "Do not build dependency tests from Lantern" FORCE) + set(LIBP2P_BUILD_INTEROP_BINARY OFF CACHE BOOL "Do not build c-lean-libp2p interop binaries from Lantern" FORCE) + set(LIBP2P_BUILD_GOSSIPSUB_INTEROP_BINARY OFF CACHE BOOL "Do not build c-lean-libp2p gossipsub interop binaries from Lantern" FORCE) + set(BUILD_LIBSSL ON CACHE BOOL "Build AWS-LC libssl for c-lean-libp2p" FORCE) + set(BUILD_TOOL OFF CACHE BOOL "Do not build AWS-LC tools from Lantern" FORCE) + set(DISABLE_GO ON CACHE BOOL "Do not require Go when building AWS-LC" FORCE) + set(DISABLE_PERL ON CACHE BOOL "Use generated AWS-LC assembly sources" FORCE) + + if(NOT TARGET c_lean_libp2p) + add_subdirectory(${source_dir} ${CMAKE_BINARY_DIR}/c-lean-libp2p EXCLUDE_FROM_ALL) + set_property(DIRECTORY ${CMAKE_BINARY_DIR}/c-lean-libp2p PROPERTY EXCLUDE_FROM_TESTING TRUE) + endif() + + add_library(${target_name} INTERFACE) + target_link_libraries(${target_name} INTERFACE c_lean_libp2p) endfunction() find_program(CARGO_EXECUTABLE cargo) @@ -51,9 +125,9 @@ function(_lantern_define_snappy target_name source_dir) ) target_compile_definitions(${target_name} PUBLIC - NDEBUG=1 _DEFAULT_SOURCE PRIVATE + NDEBUG=1 typeof=__typeof__ ) endif() @@ -131,7 +205,8 @@ function(lantern_configure_dependencies target) set(external_root ${PROJECT_SOURCE_DIR}/external) - _lantern_define_interface(lantern_libp2p ${external_root}/c-libp2p) + _lantern_define_c_lean_libp2p(lantern_c_lean_libp2p ${external_root}/c-lean-libp2p) + _lantern_configure_awslc_openssl_package(${external_root}/c-lean-libp2p) _lantern_define_static(lantern_c_ssz ${external_root}/c-ssz) _lantern_define_snappy(lantern_snappy_c ${external_root}/snappy-c) _lantern_define_c_leanvm_xmss_variant( @@ -148,96 +223,12 @@ function(lantern_configure_dependencies target) TEST_CONFIG ) - set(libp2p_source_dir ${external_root}/c-libp2p) - if(EXISTS ${libp2p_source_dir}/CMakeLists.txt) - if(NOT TARGET libp2p_unified) - # c-libp2p builds numerous component libraries (sha3, libtomcrypt, etc.) - # that expose identically named symbols. When these components are - # forced to build as static archives (BUILD_SHARED_LIBS=OFF) the final - # link step for lantern_cli pulls both archives into a single binary - # and the duplicate SHA3/Keccak symbols collide, causing cmake to hang - # inside the linker until it times out. Let the subproject build - # shared objects so each archive keeps its own namespace. - if(DEFINED BUILD_SHARED_LIBS) - set(_lantern_prev_build_shared_libs "${BUILD_SHARED_LIBS}") - set(_lantern_had_build_shared_libs TRUE) - else() - set(_lantern_prev_build_shared_libs "") - set(_lantern_had_build_shared_libs FALSE) - endif() - set(BUILD_SHARED_LIBS ON CACHE BOOL "Build shared libraries" FORCE) - set(ENABLE_COVERAGE OFF CACHE BOOL "Disable coverage when building c-libp2p as a dependency" FORCE) - if(DEFINED CMAKE_POSITION_INDEPENDENT_CODE) - set(_lantern_had_pic TRUE) - set(_lantern_prev_pic "${CMAKE_POSITION_INDEPENDENT_CODE}") - else() - set(_lantern_had_pic FALSE) - set(_lantern_prev_pic "") - endif() - set(CMAKE_POSITION_INDEPENDENT_CODE ON) - set(_saved_fetchcontent_basedir "${FETCHCONTENT_BASE_DIR}") - set(FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/c-libp2p/_deps) - add_subdirectory(${libp2p_source_dir} ${CMAKE_BINARY_DIR}/c-libp2p EXCLUDE_FROM_ALL) - set_property(DIRECTORY ${CMAKE_BINARY_DIR}/c-libp2p PROPERTY EXCLUDE_FROM_TESTING TRUE) - set(FETCHCONTENT_BASE_DIR "${_saved_fetchcontent_basedir}") - if(_lantern_had_build_shared_libs) - set(BUILD_SHARED_LIBS "${_lantern_prev_build_shared_libs}" CACHE BOOL "Build shared libraries" FORCE) - else() - unset(BUILD_SHARED_LIBS CACHE) - endif() - if(_lantern_had_pic) - set(CMAKE_POSITION_INDEPENDENT_CODE "${_lantern_prev_pic}") - else() - unset(CMAKE_POSITION_INDEPENDENT_CODE) - endif() - if(TARGET libtomcrypt) - target_include_directories(libtomcrypt PRIVATE ${libp2p_source_dir}/external/libtommath) - target_include_directories(libtomcrypt PRIVATE ${libp2p_source_dir}/external/libtomcrypt/src/headers) - endif() - if(TARGET secp256k1) - # The shared libp2p peer-id target links against secp256k1; force PIC to avoid linker failures on Linux. - set_target_properties(secp256k1 PROPERTIES POSITION_INDEPENDENT_CODE ON) - endif() - if(TARGET libp2p_unified) - target_include_directories(libp2p_unified PUBLIC ${libp2p_source_dir}) - endif() - set(libp2p_binary_dir ${CMAKE_BINARY_DIR}/c-libp2p) - if(TARGET protocol_quic) - target_include_directories(protocol_quic PRIVATE ${libp2p_binary_dir}/_deps/picotls-src/include) - target_include_directories(protocol_quic PRIVATE ${libp2p_source_dir}) - target_include_directories(protocol_quic PRIVATE ${libp2p_source_dir}/include/libp2p) - target_include_directories( - protocol_quic PRIVATE ${libp2p_source_dir}/external/libtomcrypt/src/headers) - if(EXISTS ${CMAKE_BINARY_DIR}/_deps/picotls-src/include) - target_include_directories( - protocol_quic PRIVATE ${CMAKE_BINARY_DIR}/_deps/picotls-src/include) - endif() - endif() - if(TARGET protocol_noise) - target_include_directories(protocol_noise PRIVATE ${libp2p_binary_dir}/_deps/picotls-src/include) - target_include_directories(protocol_noise PRIVATE ${libp2p_source_dir}) - target_include_directories(protocol_noise PRIVATE ${libp2p_source_dir}/include/libp2p) - target_include_directories( - protocol_noise PRIVATE ${libp2p_source_dir}/external/libtomcrypt/src/headers) - if(EXISTS ${CMAKE_BINARY_DIR}/_deps/picotls-src/include) - target_include_directories( - protocol_noise PRIVATE ${CMAKE_BINARY_DIR}/_deps/picotls-src/include) - endif() - endif() - endif() - else() - message(FATAL_ERROR "Missing c-libp2p sources at ${libp2p_source_dir}. Run scripts/bootstrap.sh to fetch git submodules.") - endif() - target_link_libraries(${target} PUBLIC - lantern_libp2p + lantern_c_lean_libp2p lantern_c_ssz lantern_snappy_c ${wrapper_target} - libp2p_unified - protocol_gossipsub - protocol_ping ) if(CMAKE_DL_LIBS) diff --git a/cmake/Findlibtommath.cmake b/cmake/Findlibtommath.cmake deleted file mode 100644 index 64e1a60..0000000 --- a/cmake/Findlibtommath.cmake +++ /dev/null @@ -1,38 +0,0 @@ -# Findlibtommath.cmake -# Custom find module for libtommath - -# Check if libtommath is already a target -if(TARGET libtommath) - set(libtommath_FOUND TRUE) - set(libtommath_LIBRARIES libtommath) - get_target_property(libtommath_INCLUDE_DIRS libtommath INTERFACE_INCLUDE_DIRECTORIES) - if(NOT libtommath_INCLUDE_DIRS) - set(libtommath_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/external/libtommath) - endif() - return() -endif() - -# Find the headers -find_path(libtommath_INCLUDE_DIR tommath.h - PATHS ${CMAKE_SOURCE_DIR}/external/libtommath - NO_DEFAULT_PATH -) - -# Find the library -find_library(libtommath_LIBRARY - NAMES tommath libtommath - PATHS - ${CMAKE_BINARY_DIR}/external/libtommath - ${CMAKE_BINARY_DIR}/lib - NO_DEFAULT_PATH -) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(libtommath DEFAULT_MSG libtommath_LIBRARY libtommath_INCLUDE_DIR) - -if(libtommath_FOUND) - set(libtommath_LIBRARIES ${libtommath_LIBRARY}) - set(libtommath_INCLUDE_DIRS ${libtommath_INCLUDE_DIR}) -endif() - -mark_as_advanced(libtommath_INCLUDE_DIR libtommath_LIBRARY) diff --git a/cmake/picotls/dtrace-utils.cmake b/cmake/picotls/dtrace-utils.cmake deleted file mode 100644 index 29ffda9..0000000 --- a/cmake/picotls/dtrace-utils.cmake +++ /dev/null @@ -1,56 +0,0 @@ -# Patched copy of picotls' dtrace-utils to keep generated probe headers -# inside the dependency build tree. The original file writes a temporary -# `.tmp.dprobes.h` next to the invoking source directory; at the top level -# of c-libp2p this ends up littering the repository root. We override it -# during FetchContent's PATCH step so the temporary file is written (and -# removed) from the dependency's binary directory instead. - -FUNCTION (CHECK_DTRACE d_file) - MESSAGE(STATUS "Detecting USDT support") - SET(HAVE_DTRACE "OFF" PARENT_SCOPE) - SET(DTRACE_USES_OBJFILE "OFF" PARENT_SCOPE) - IF ((CMAKE_SYSTEM_NAME STREQUAL "Darwin") OR (CMAKE_SYSTEM_NAME STREQUAL "Linux")) - # Use an absolute path so the temporary probe header is created inside - # the dependency's binary tree rather than the caller's source tree. - SET(_dtrace_tmp "${CMAKE_CURRENT_BINARY_DIR}/.tmp.dprobes.h") - EXECUTE_PROCESS( - COMMAND dtrace -o "${_dtrace_tmp}" -s "${d_file}" -h - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - RESULT_VARIABLE DTRACE_RESULT) - FILE(REMOVE "${_dtrace_tmp}") - IF (DTRACE_RESULT EQUAL 0) - MESSAGE(STATUS "Detecting USDT support - found") - SET(HAVE_DTRACE "ON" PARENT_SCOPE) - IF (CMAKE_SYSTEM_NAME STREQUAL "Linux") - SET(DTRACE_USES_OBJFILE "ON" PARENT_SCOPE) - ENDIF () - ELSE () - MESSAGE(STATUS "Detecting USDT support - not found") - ENDIF () - ELSE () - MESSAGE(STATUS "Detecting USDT support - disabled on this platform") - ENDIF () -ENDFUNCTION () - -FUNCTION (DEFINE_DTRACE_DEPENDENCIES d_file prefix) - ADD_CUSTOM_COMMAND( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h - COMMAND dtrace -o ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h -s ${d_file} -h - DEPENDS ${d_file}) - SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h PROPERTIES GENERATED TRUE) - IF (DTRACE_USES_OBJFILE) - ADD_CUSTOM_COMMAND( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.o - # /usr/bin/dtrace uses deterministic temporary files, do not let make parallelize - COMMAND flock /tmp/dtrace.lock dtrace -o ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.o -s ${d_file} -G - DEPENDS ${d_file}) - SET_SOURCE_FILES_PROPERTIES(${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.o PROPERTIES GENERATED TRUE) - ADD_CUSTOM_TARGET(generate-${prefix}-probes - DEPENDS - ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h - ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.o) - ELSE () - ADD_CUSTOM_TARGET(generate-${prefix}-probes - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${prefix}-probes.h) - ENDIF () -ENDFUNCTION () diff --git a/external/c-lean-libp2p b/external/c-lean-libp2p new file mode 160000 index 0000000..68905f9 --- /dev/null +++ b/external/c-lean-libp2p @@ -0,0 +1 @@ +Subproject commit 68905f922be58f0a7c6f4ff87ab7376b452178ab diff --git a/external/c-libp2p b/external/c-libp2p deleted file mode 160000 index 4e5cbaa..0000000 --- a/external/c-libp2p +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4e5cbaa4ac7ce17b761deb8f5eccdaeb2d3737bb diff --git a/external/c-ssz b/external/c-ssz index 030b149..3677510 160000 --- a/external/c-ssz +++ b/external/c-ssz @@ -1 +1 @@ -Subproject commit 030b14938da757f69a03f10548d9a129bb75d706 +Subproject commit 36775104d6838812c61b095d27ea996e3d79124f diff --git a/include/lantern/consensus/containers.h b/include/lantern/consensus/containers.h index 937c61d..bbfc0df 100644 --- a/include/lantern/consensus/containers.h +++ b/include/lantern/consensus/containers.h @@ -5,6 +5,8 @@ #include #include +#include "ssz_types.h" + #define LANTERN_ROOT_SIZE 32 #ifndef LANTERN_SIGNATURE_SIZE #define LANTERN_SIGNATURE_SIZE 2536 @@ -32,9 +34,7 @@ typedef struct { size_t capacity; } LanternByteList; -typedef struct { - uint8_t bytes[LANTERN_ROOT_SIZE]; -} LanternRoot; +typedef ssz_chunk_t LanternRoot; typedef struct { uint8_t bytes[LANTERN_SIGNATURE_SIZE]; @@ -137,8 +137,6 @@ typedef struct { typedef struct { LanternAggregatedAttestations attestations; - /* Decode-time hint: true when body attestations came from legacy plain-vote layout. */ - bool legacy_plain_attestation_layout; } LanternBlockBody; typedef struct { diff --git a/include/lantern/consensus/hash.h b/include/lantern/consensus/hash.h index 690bdbd..62e36de 100644 --- a/include/lantern/consensus/hash.h +++ b/include/lantern/consensus/hash.h @@ -3,43 +3,47 @@ #include +#include "ssz_types.h" + #include "lantern/consensus/containers.h" #include "lantern/consensus/state.h" -int lantern_hash_tree_root_config(const LanternConfig *config, LanternRoot *out_root); -int lantern_hash_tree_root_checkpoint(const LanternCheckpoint *checkpoint, LanternRoot *out_root); -int lantern_hash_tree_root_attestation_data(const LanternAttestationData *data, LanternRoot *out_root); -int lantern_hash_tree_root_vote(const LanternVote *vote, LanternRoot *out_root); -int lantern_hash_tree_root_signed_vote(const LanternSignedVote *vote, LanternRoot *out_root); -int lantern_hash_tree_root_signature(const LanternSignature *signature, LanternRoot *out_root); -int lantern_hash_tree_root_validator(const LanternValidator *validator, LanternRoot *out_root); -int lantern_hash_tree_root_aggregated_attestation(const LanternAggregatedAttestation *attestation, LanternRoot *out_root); -int lantern_hash_tree_root_aggregated_signature_proof( +ssz_error_t lantern_hash_tree_root_config(const LanternConfig *config, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_checkpoint(const LanternCheckpoint *checkpoint, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_attestation_data(const LanternAttestationData *data, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_vote(const LanternVote *vote, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_signed_vote(const LanternSignedVote *vote, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_signature(const LanternSignature *signature, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_validator(const LanternValidator *validator, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_aggregated_attestation(const LanternAggregatedAttestation *attestation, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_aggregated_signature_proof( const LanternAggregatedSignatureProof *proof, LanternRoot *out_root); -int lantern_hash_tree_root_signed_aggregated_attestation( +ssz_error_t lantern_hash_tree_root_signed_aggregated_attestation( const LanternSignedAggregatedAttestation *attestation, LanternRoot *out_root); -int lantern_hash_tree_root_block_signatures( +ssz_error_t lantern_hash_tree_root_block_signatures( const LanternBlockSignatures *signatures, LanternRoot *out_root); -int lantern_hash_tree_root_block_body(const LanternBlockBody *body, LanternRoot *out_root); -int lantern_hash_tree_root_block_header(const LanternBlockHeader *header, LanternRoot *out_root); -int lantern_hash_tree_root_block(const LanternBlock *block, LanternRoot *out_root); -int lantern_hash_tree_root_signed_block(const LanternSignedBlock *block, LanternRoot *out_root); -int lantern_hash_tree_root_state(const LanternState *state, LanternRoot *out_root); -int lantern_hash_tree_root_validators(const uint8_t *pubkeys, size_t count, LanternRoot *out_root); -int lantern_hash_tree_root_validators_dual( +ssz_error_t lantern_hash_tree_root_block_body(const LanternBlockBody *body, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_block_header(const LanternBlockHeader *header, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_block(const LanternBlock *block, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_signed_block(const LanternSignedBlock *block, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_state(const LanternState *state, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_state_cached(LanternState *state, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_validators(const uint8_t *pubkeys, size_t count, LanternRoot *out_root); +ssz_error_t lantern_hash_tree_root_validators_dual( const uint8_t *attestation_pubkeys, const uint8_t *proposal_pubkeys, size_t count, LanternRoot *out_root); +void lantern_state_hash_cache_reset(LanternState *state); -int lantern_merkleize_root_list( +ssz_error_t lantern_merkleize_root_list( const struct lantern_root_list *list, size_t limit, LanternRoot *out_root); -int lantern_merkleize_bitlist( +ssz_error_t lantern_merkleize_bitlist( const struct lantern_bitlist *bitlist, size_t limit, LanternRoot *out_root); diff --git a/include/lantern/consensus/ssz.h b/include/lantern/consensus/ssz.h index 2635c5d..4c15f67 100644 --- a/include/lantern/consensus/ssz.h +++ b/include/lantern/consensus/ssz.h @@ -7,118 +7,97 @@ #include "lantern/consensus/containers.h" #include "lantern/consensus/state.h" +#include "ssz_types.h" #define LANTERN_CONFIG_SSZ_SIZE (sizeof(uint64_t)) #define LANTERN_CHECKPOINT_SSZ_SIZE (LANTERN_ROOT_SIZE + sizeof(uint64_t)) #define LANTERN_ATTESTATION_DATA_SSZ_SIZE (sizeof(uint64_t) + 3u * LANTERN_CHECKPOINT_SSZ_SIZE) #define LANTERN_VOTE_SSZ_SIZE (sizeof(uint64_t) + LANTERN_ATTESTATION_DATA_SSZ_SIZE) #define LANTERN_SIGNED_VOTE_SSZ_SIZE (LANTERN_VOTE_SSZ_SIZE + LANTERN_SIGNATURE_SIZE) -#define LANTERN_SIGNED_VOTE_SSZ_SIZE_LEGACY LANTERN_SIGNED_VOTE_SSZ_SIZE #define LANTERN_VALIDATOR_SSZ_SIZE ((2u * LANTERN_VALIDATOR_PUBKEY_SIZE) + sizeof(uint64_t)) -#define LANTERN_VALIDATOR_SSZ_SIZE_LEGACY (LANTERN_VALIDATOR_PUBKEY_SIZE + sizeof(uint64_t)) #define LANTERN_BLOCK_HEADER_SSZ_SIZE (sizeof(uint64_t) * 2u + 3u * LANTERN_ROOT_SIZE) -int lantern_ssz_encode_config(const LanternConfig *config, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_config(LanternConfig *config, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_config(const LanternConfig *config, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_config(LanternConfig *config, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_checkpoint(const LanternCheckpoint *checkpoint, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_checkpoint(LanternCheckpoint *checkpoint, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_checkpoint(const LanternCheckpoint *checkpoint, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_checkpoint(LanternCheckpoint *checkpoint, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_attestation_data( +ssz_error_t lantern_ssz_encode_attestation_data( const LanternAttestationData *data, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_attestation_data( +ssz_error_t lantern_ssz_decode_attestation_data( LanternAttestationData *data, const uint8_t *raw, size_t raw_len); -int lantern_ssz_encode_vote(const LanternVote *vote, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_vote(LanternVote *vote, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_vote(const LanternVote *vote, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_vote(LanternVote *vote, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_signed_vote(const LanternSignedVote *vote, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_signed_vote(LanternSignedVote *vote, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_signed_vote_legacy( - const LanternSignedVote *vote, - uint8_t *out, - size_t out_len, - size_t *written); +ssz_error_t lantern_ssz_encode_signed_vote(const LanternSignedVote *vote, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_signed_vote(LanternSignedVote *vote, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_signed_aggregated_attestation( +ssz_error_t lantern_ssz_encode_signed_aggregated_attestation( const LanternSignedAggregatedAttestation *attestation, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_signed_aggregated_attestation( +ssz_error_t lantern_ssz_decode_signed_aggregated_attestation( LanternSignedAggregatedAttestation *attestation, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_aggregated_attestation( +ssz_error_t lantern_ssz_encode_aggregated_attestation( const LanternAggregatedAttestation *attestation, uint8_t *out, size_t remaining, size_t *written); -int lantern_ssz_decode_aggregated_attestation( +ssz_error_t lantern_ssz_decode_aggregated_attestation( LanternAggregatedAttestation *attestation, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_aggregated_signature_proof( +ssz_error_t lantern_ssz_encode_aggregated_signature_proof( const LanternAggregatedSignatureProof *proof, uint8_t *out, size_t remaining, size_t *written); -int lantern_ssz_decode_aggregated_signature_proof( +ssz_error_t lantern_ssz_decode_aggregated_signature_proof( LanternAggregatedSignatureProof *proof, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_block_signatures( +ssz_error_t lantern_ssz_encode_block_signatures( const LanternBlockSignatures *signatures, uint8_t *out, size_t remaining, size_t *written); -int lantern_ssz_decode_block_signatures( +ssz_error_t lantern_ssz_decode_block_signatures( LanternBlockSignatures *signatures, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_validator( +ssz_error_t lantern_ssz_encode_validator( const LanternValidator *validator, uint8_t *out, size_t remaining, size_t *written); -int lantern_ssz_decode_validator( +ssz_error_t lantern_ssz_decode_validator( LanternValidator *validator, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_block_header(const LanternBlockHeader *header, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_block_header(LanternBlockHeader *header, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_block_header(const LanternBlockHeader *header, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_block_header(LanternBlockHeader *header, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_block_body(const LanternBlockBody *body, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_block_body(LanternBlockBody *body, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_block_body(const LanternBlockBody *body, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_block_body(LanternBlockBody *body, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_block(const LanternBlock *block, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_block(LanternBlock *block, const uint8_t *data, size_t data_len); -int lantern_ssz_decode_block_strict(LanternBlock *block, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_block(const LanternBlock *block, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_block(LanternBlock *block, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_signed_block(const LanternSignedBlock *block, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_encode_signed_block_canonical( - const LanternSignedBlock *block, - uint8_t *out, - size_t out_len, - size_t *written); -int lantern_ssz_decode_signed_block(LanternSignedBlock *block, const uint8_t *data, size_t data_len); -int lantern_ssz_decode_signed_block_strict( - LanternSignedBlock *block, - const uint8_t *data, - size_t data_len); -int lantern_ssz_encode_signed_block_legacy( - const LanternSignedBlock *block, - uint8_t *out, - size_t out_len, - size_t *written); +ssz_error_t lantern_ssz_encode_signed_block(const LanternSignedBlock *block, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_signed_block(LanternSignedBlock *block, const uint8_t *data, size_t data_len); -int lantern_ssz_encode_state(const LanternState *state, uint8_t *out, size_t out_len, size_t *written); -int lantern_ssz_decode_state(LanternState *state, const uint8_t *data, size_t data_len); +ssz_error_t lantern_ssz_encode_state(const LanternState *state, uint8_t *out, size_t out_len, size_t *written); +ssz_error_t lantern_ssz_decode_state(LanternState *state, const uint8_t *data, size_t data_len); #endif /* LANTERN_CONSENSUS_SSZ_H */ diff --git a/include/lantern/consensus/state.h b/include/lantern/consensus/state.h index ef4e857..9a13a16 100644 --- a/include/lantern/consensus/state.h +++ b/include/lantern/consensus/state.h @@ -10,6 +10,7 @@ typedef struct lantern_store LanternStore; struct lantern_attestation_signature_map; struct lantern_aggregated_payload_pool; +struct lantern_state_hash_cache; typedef struct { const LanternAttestations *attestations; @@ -43,6 +44,7 @@ typedef struct { LanternValidator *validators; size_t validator_count; size_t validator_capacity; + struct lantern_state_hash_cache *hash_cache; } LanternState; void lantern_root_list_init(struct lantern_root_list *list); diff --git a/include/lantern/core/client.h b/include/lantern/core/client.h index 4eb7a3d..216a1c6 100644 --- a/include/lantern/core/client.h +++ b/include/lantern/core/client.h @@ -85,10 +85,9 @@ struct lantern_client_options { bool is_aggregator; }; -struct libp2p_subscription; -struct libp2p_protocol_server; struct lantern_peer_status_entry; struct lantern_active_blocks_request; +struct lantern_async_block_import_job; struct lantern_backfill_entry { LanternRoot root; LanternRoot parent_root; @@ -191,6 +190,13 @@ struct lantern_local_validator { uint64_t last_attested_slot; }; +struct lantern_network_view { + uint64_t latest_observed_head_slot; + uint64_t network_finalized_slot; + bool has_latest_observed_head_slot; + bool has_network_finalized_slot; +}; + struct lantern_client { char *data_dir; char *node_id; @@ -203,8 +209,6 @@ struct lantern_client { struct lantern_genesis_artifacts genesis; struct lantern_enr_record local_enr; struct lantern_libp2p_host network; - struct libp2p_protocol_server *ping_server; - bool ping_running; struct lantern_gossipsub_service gossip; bool gossip_running; struct lantern_reqresp_service reqresp; @@ -253,9 +257,9 @@ struct lantern_client { size_t connected_peers; pthread_mutex_t connection_lock; bool connection_lock_initialized; - struct libp2p_subscription *connection_subscription; struct lantern_string_list dialer_peers; struct lantern_string_list connected_peer_ids; + struct lantern_string_list connected_peer_refs; struct lantern_string_list inbound_peer_ids; struct lantern_string_list status_failure_peer_ids; struct lantern_pending_block_list pending_blocks; @@ -263,6 +267,16 @@ struct lantern_client { struct lantern_backfill_session backfill; pthread_mutex_t pending_lock; bool pending_lock_initialized; + struct lantern_async_block_import_job *block_import_head; + struct lantern_async_block_import_job *block_import_tail; + size_t block_import_queue_len; + pthread_mutex_t block_import_lock; + pthread_cond_t block_import_cond; + pthread_t block_import_thread; + bool block_import_lock_initialized; + bool block_import_cond_initialized; + bool block_import_thread_started; + bool block_import_stop; LanternRoot sync_last_requested_root; uint64_t sync_last_requested_root_ms; uint64_t sync_started_ms; @@ -272,15 +286,18 @@ struct lantern_client { uint64_t sync_target_slot; LanternSyncState sync_state; bool sync_in_progress; + struct lantern_network_view network_view; + uint64_t last_status_log_slot; + bool has_last_status_log_slot; + uint64_t last_duty_skip_slot; + bool has_last_duty_skip_slot; + const char *last_duty_skip_reason; size_t status_requests_inflight_total; size_t status_requests_peak; bool status_guard_disabled; pthread_t dialer_thread; bool dialer_thread_started; int dialer_stop_flag; - pthread_t ping_thread; - bool ping_thread_started; - int ping_stop_flag; struct lantern_peer_status_entry *peer_status_entries; size_t peer_status_count; size_t peer_status_capacity; diff --git a/include/lantern/networking/gossipsub_service.h b/include/lantern/networking/gossipsub_service.h index 23d6298..4995d50 100644 --- a/include/lantern/networking/gossipsub_service.h +++ b/include/lantern/networking/gossipsub_service.h @@ -5,11 +5,9 @@ #include #include "lantern/consensus/containers.h" -#include "peer_id/peer_id.h" +#include "lantern/networking/libp2p.h" -struct libp2p_host; typedef struct libp2p_gossipsub libp2p_gossipsub_t; -typedef struct libp2p_gossipsub_validator_handle libp2p_gossipsub_validator_handle_t; struct lantern_gossipsub_validation_pool; #ifdef __cplusplus @@ -17,7 +15,7 @@ extern "C" { #endif struct lantern_gossipsub_config { - struct libp2p_host *host; + struct lantern_libp2p_host *network; const char *devnet; const char *data_dir; const char *topic_network_name; @@ -28,25 +26,51 @@ struct lantern_gossipsub_config { typedef int (*lantern_gossipsub_block_handler)( const LanternSignedBlock *block, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_block_ssz, size_t raw_block_ssz_len, void *user_data); typedef int (*lantern_gossipsub_vote_handler)( const LanternSignedVote *vote, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_vote_payload, size_t raw_vote_payload_len, void *user_data); typedef int (*lantern_gossipsub_aggregated_attestation_handler)( const LanternSignedAggregatedAttestation *attestation, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_attestation_payload, size_t raw_attestation_payload_len, void *user_data); +#define LANTERN_GOSSIPSUB_MAX_TRACKED_PEERS 128u +#define LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER 8u +#define LANTERN_GOSSIPSUB_RETRY_INITIAL_US 250000ull +#define LANTERN_GOSSIPSUB_RETRY_MAX_US 5000000ull + +struct lantern_gossipsub_peer_connection_state { + struct lantern_peer_id peer; + libp2p_host_conn_t *conns[LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER]; + uint8_t conn_inbound[LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER]; + uint8_t conn_closing[LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER]; + size_t conn_count; + libp2p_host_conn_t *primary_conn; + uint8_t primary_inbound; + libp2p_host_conn_t *writer_conn; + libp2p_host_stream_t *writer_stream; + libp2p_host_conn_t *opening_conn; + libp2p_host_time_us_t next_retry_us; + uint64_t retry_backoff_us; + uint8_t used; +}; + struct lantern_gossipsub_service { + struct lantern_libp2p_host *network; libp2p_gossipsub_t *gossipsub; + void *gossipsub_storage; + size_t gossipsub_storage_len; + libp2p_host_protocol_t gossipsub_protocols[LIBP2P_GOSSIPSUB_PROTOCOL_COUNT]; + size_t gossipsub_protocol_count; char block_topic[128]; char vote_topic[128]; char vote_subnet_topic[128]; @@ -66,14 +90,10 @@ struct lantern_gossipsub_service { void *vote_handler_user_data; lantern_gossipsub_aggregated_attestation_handler aggregated_attestation_handler; void *aggregated_attestation_handler_user_data; - libp2p_gossipsub_validator_handle_t *block_validator_handle; - libp2p_gossipsub_validator_handle_t *vote_validator_handle; - libp2p_gossipsub_validator_handle_t *vote_subnet_validator_handle; - libp2p_gossipsub_validator_handle_t *aggregated_attestation_validator_handle; char (*extra_vote_subnet_topics)[128]; - libp2p_gossipsub_validator_handle_t **extra_vote_subnet_validator_handles; size_t extra_vote_subnet_topic_count; struct lantern_gossipsub_validation_pool *validation_pool; + struct lantern_gossipsub_peer_connection_state peer_connections[LANTERN_GOSSIPSUB_MAX_TRACKED_PEERS]; }; void lantern_gossipsub_service_init(struct lantern_gossipsub_service *service); diff --git a/include/lantern/networking/libp2p.h b/include/lantern/networking/libp2p.h index 5a814b7..c408cf8 100644 --- a/include/lantern/networking/libp2p.h +++ b/include/lantern/networking/libp2p.h @@ -1,18 +1,46 @@ #ifndef LANTERN_NETWORKING_LIBP2P_H #define LANTERN_NETWORKING_LIBP2P_H +#include #include #include +#include "libp2p/libp2p_host.h" +#include "libp2p/libp2p_host_secp256k1_identity.h" +#include "peer_id/peer_id.h" +#include "protocol/gossipsub/gossipsub.h" +#include "protocol/identify/identify.h" +#include "protocol/ping/ping.h" +#include "transport/quic/quic_identity.h" +#include "transport/quic/quic_service.h" + #ifdef __cplusplus extern "C" { #endif struct lantern_enr_record; -struct libp2p_host; -typedef struct peer_id peer_id_t; +struct lantern_libp2p_host; + +#define LANTERN_LIBP2P_MAX_PROTOCOLS 24u +#define LANTERN_LIBP2P_MAX_EVENT_HANDLERS 8u +#define LANTERN_LIBP2P_MAX_DRIVE_HANDLERS 8u +#define LANTERN_LIBP2P_MULTIADDR_MAX_BYTES 256u +#define LANTERN_LIBP2P_PEER_TEXT_MAX_BYTES 128u + +struct lantern_peer_id { + uint8_t bytes[LIBP2P_PEER_ID_MAX_BYTES]; + size_t len; +}; + +typedef void (*lantern_libp2p_host_event_handler)( + struct lantern_libp2p_host *network, + const libp2p_host_event_t *event, + void *user_data); -#define LANTERN_LIBP2P_DEFAULT_PEER_TTL_MS (300000) +typedef void (*lantern_libp2p_drive_handler)( + struct lantern_libp2p_host *network, + libp2p_host_time_us_t now_us, + void *user_data); struct lantern_libp2p_config { const char *listen_multiaddr; @@ -22,28 +50,68 @@ struct lantern_libp2p_config { }; struct lantern_libp2p_host { - struct libp2p_host *host; + libp2p_host_t *host; + void *host_storage; + size_t host_storage_len; + uint8_t listen_multiaddr[LANTERN_LIBP2P_MULTIADDR_MAX_BYTES]; + size_t listen_multiaddr_len; + uint8_t local_peer_id[LIBP2P_PEER_ID_MAX_BYTES]; + size_t local_peer_id_len; + libp2p_host_secp256k1_identity_t host_identity_storage; + libp2p_host_identity_t host_identity; + uint8_t certificate_der[LIBP2P_QUIC_CERTIFICATE_DER_MAX_BYTES]; + uint8_t certificate_key_der[LIBP2P_QUIC_CERTIFICATE_KEY_DER_MAX_BYTES]; + libp2p_quic_local_identity_t quic_identity; + libp2p_quic_service_config_t quic_config; + libp2p_quic_allocator_t allocator; + libp2p_ping_t ping; + libp2p_identify_t identify; + libp2p_host_protocol_t default_protocols[3]; + size_t default_protocol_count; + lantern_libp2p_host_event_handler event_handlers[LANTERN_LIBP2P_MAX_EVENT_HANDLERS]; + void *event_handler_user_data[LANTERN_LIBP2P_MAX_EVENT_HANDLERS]; + size_t event_handler_count; + lantern_libp2p_drive_handler drive_handlers[LANTERN_LIBP2P_MAX_DRIVE_HANDLERS]; + void *drive_handler_user_data[LANTERN_LIBP2P_MAX_DRIVE_HANDLERS]; + size_t drive_handler_count; + pthread_t drive_thread; + int drive_thread_started; + int stop_flag; int started; }; void lantern_libp2p_host_init(struct lantern_libp2p_host *state); void lantern_libp2p_host_reset(struct lantern_libp2p_host *state); +int lantern_libp2p_host_prepare(struct lantern_libp2p_host *state, const struct lantern_libp2p_config *config); +int lantern_libp2p_host_launch(struct lantern_libp2p_host *state); int lantern_libp2p_host_start(struct lantern_libp2p_host *state, const struct lantern_libp2p_config *config); void lantern_libp2p_host_stop(struct lantern_libp2p_host *state); -int lantern_libp2p_host_add_enr_peer( +int lantern_libp2p_host_register_protocol( struct lantern_libp2p_host *state, - const struct lantern_enr_record *record, - int ttl_ms); + const libp2p_host_protocol_t *protocol); +int lantern_libp2p_host_register_event_handler( + struct lantern_libp2p_host *state, + lantern_libp2p_host_event_handler handler, + void *user_data); +int lantern_libp2p_host_register_drive_handler( + struct lantern_libp2p_host *state, + lantern_libp2p_drive_handler handler, + void *user_data); +int lantern_libp2p_validate_enr_peer(const struct lantern_enr_record *record); +int lantern_libp2p_host_dial_multiaddr( + struct lantern_libp2p_host *state, + const char *multiaddr_text); int lantern_libp2p_enr_to_multiaddr( const struct lantern_enr_record *record, char *buffer, size_t buffer_len, - peer_id_t **peer_id); -int lantern_libp2p_encode_secp256k1_private_key_proto( - const uint8_t *secret, - size_t secret_len, - uint8_t **out, - size_t *out_len); + struct lantern_peer_id *peer_id); +int lantern_peer_id_from_text(const char *text, struct lantern_peer_id *out_peer); +int lantern_peer_id_to_text(const struct lantern_peer_id *peer, char *buffer, size_t buffer_len); +int lantern_peer_id_equal(const struct lantern_peer_id *left, const struct lantern_peer_id *right); +libp2p_host_time_us_t lantern_libp2p_now_us(void); +libp2p_quic_err_t lantern_libp2p_quic_random(uint8_t *out, size_t out_len, void *user_data); +libp2p_gossipsub_err_t lantern_libp2p_gossipsub_random(uint8_t *out, size_t out_len, void *user_data); #ifdef __cplusplus } diff --git a/include/lantern/networking/reqresp_service.h b/include/lantern/networking/reqresp_service.h index ed83d15..5a314b0 100644 --- a/include/lantern/networking/reqresp_service.h +++ b/include/lantern/networking/reqresp_service.h @@ -5,10 +5,10 @@ #include #include #include +#include +#include "lantern/networking/libp2p.h" #include "lantern/networking/messages.h" -#include "libp2p/stream.h" -#include "peer_id/peer_id.h" #define LANTERN_REQRESP_STATUS_PROTOCOL_SNAPPY "/leanconsensus/req/status/1/ssz_snappy" #define LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_SNAPPY "/leanconsensus/req/blocks_by_root/1/ssz_snappy" @@ -25,6 +25,7 @@ #define LANTERN_REQRESP_RESPONSE_INVALID_REQUEST 1u #define LANTERN_REQRESP_RESPONSE_SERVER_ERROR 2u #define LANTERN_REQRESP_RESPONSE_RESOURCE_UNAVAILABLE 3u +#define LANTERN_REQRESP_MAX_TRACKED_CONNECTIONS 64u /** * Reqresp service error codes. @@ -45,6 +46,7 @@ typedef enum LANTERN_REQRESP_ERR_VARINT_HEADER_TOO_LONG = -1005, LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE = -1006, LANTERN_REQRESP_ERR_ALLOC = -1007, + LANTERN_REQRESP_ERR_INVALID_PAYLOAD = -1008, } lantern_reqresp_error; enum lantern_reqresp_protocol_kind { @@ -57,11 +59,30 @@ enum lantern_reqresp_protocol_kind { #define LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL #define LANTERN_STATUS_PREVIEW_BYTES LANTERN_REQRESP_STATUS_PREVIEW_BYTES -struct libp2p_host; -struct libp2p_protocol_server; -struct libp2p_subscription; struct lantern_log_metadata; +struct lantern_reqresp_stream; +struct lantern_reqresp_exchange; + +struct lantern_reqresp_stream_ops { + ssize_t (*read)(void *io_ctx, void *buf, size_t len); + ssize_t (*write)(void *io_ctx, const void *buf, size_t len); + int (*close)(void *io_ctx); + int (*reset)(void *io_ctx); + int (*set_deadline)(void *io_ctx, uint64_t ms); + int (*shutdown_write)(void *io_ctx); + void (*free_ctx)(void *io_ctx); +}; + +struct lantern_reqresp_stream { + libp2p_host_t *host; + libp2p_host_stream_t *stream; + void *io_ctx; + struct lantern_reqresp_stream_ops ops; + struct lantern_peer_id remote_peer; + bool has_remote_peer; +}; + struct lantern_reqresp_service_callbacks { void *context; int (*build_status)(void *context, LanternStatusMessage *out_status); @@ -78,19 +99,46 @@ struct lantern_reqresp_service_callbacks { const LanternRoot *roots, size_t root_count, LanternSignedBlockList *out_blocks); + int (*handle_block_response)( + void *context, + const LanternSignedBlock *block, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + const char *peer_id); + void (*blocks_request_complete)( + void *context, + const char *peer_id, + const LanternRoot *roots, + size_t root_count, + uint64_t request_id, + int success); }; struct lantern_reqresp_service_config { - struct libp2p_host *host; + struct lantern_libp2p_host *network; const struct lantern_reqresp_service_callbacks *callbacks; }; +struct lantern_reqresp_protocol_context { + struct lantern_reqresp_service *service; + enum lantern_reqresp_protocol_kind kind; +}; + +struct lantern_reqresp_conn_entry { + struct lantern_peer_id peer; + libp2p_host_conn_t *conn; +}; + struct lantern_reqresp_service { - struct libp2p_host *host; + struct lantern_libp2p_host *network; struct lantern_reqresp_service_callbacks callbacks; - struct libp2p_protocol_server *status_server; - struct libp2p_protocol_server *blocks_server; - struct libp2p_subscription *event_subscription; + libp2p_host_protocol_t status_protocol; + libp2p_host_protocol_t blocks_protocol; + struct lantern_reqresp_protocol_context status_context; + struct lantern_reqresp_protocol_context blocks_context; + struct lantern_reqresp_conn_entry conns[LANTERN_REQRESP_MAX_TRACKED_CONNECTIONS]; + size_t conn_count; + struct lantern_reqresp_exchange *exchanges; int lock_initialized; pthread_mutex_t lock; }; @@ -103,15 +151,27 @@ void lantern_reqresp_service_init(struct lantern_reqresp_service *service); void lantern_reqresp_service_reset(struct lantern_reqresp_service *service); int lantern_reqresp_service_request_status( struct lantern_reqresp_service *service, - const peer_id_t *peer_id, + const struct lantern_peer_id *peer_id, const char *peer_id_text); +int lantern_reqresp_service_request_blocks( + struct lantern_reqresp_service *service, + const struct lantern_peer_id *peer_id, + const char *peer_id_text, + const LanternRoot *roots, + size_t root_count, + uint64_t request_id); int lantern_reqresp_service_start( struct lantern_reqresp_service *service, const struct lantern_reqresp_service_config *config); +struct lantern_reqresp_stream *lantern_reqresp_stream_from_ops( + void *io_ctx, + const struct lantern_reqresp_stream_ops *ops, + const struct lantern_peer_id *remote_peer); +void lantern_reqresp_stream_free(struct lantern_reqresp_stream *stream); int lantern_reqresp_read_response_chunk( struct lantern_reqresp_service *service, - libp2p_stream_t *stream, + struct lantern_reqresp_stream *stream, enum lantern_reqresp_protocol_kind protocol, uint8_t **out_data, size_t *out_len, diff --git a/include/lantern/support/strings.h b/include/lantern/support/strings.h index 34f1f2d..1557b75 100644 --- a/include/lantern/support/strings.h +++ b/include/lantern/support/strings.h @@ -6,6 +6,7 @@ char *lantern_string_duplicate(const char *source); char *lantern_string_duplicate_len(const char *source, size_t length); +size_t lantern_string_copy(char *dst, size_t dst_len, const char *src); char *lantern_trim_whitespace(char *value); int lantern_hex_decode(const char *hex, uint8_t *out, size_t out_len); int lantern_bytes_to_hex(const uint8_t *bytes, size_t len, char *out, size_t out_len, int include_prefix); diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh index adf4674..318996e 100755 --- a/scripts/bootstrap.sh +++ b/scripts/bootstrap.sh @@ -48,7 +48,7 @@ if [[ -d "${ROOT_DIR}/.git" ]]; then echo "bootstrap: skipping submodule sync (requested)" >&2 else git submodule update --init --recursive \ - external/c-libp2p \ + external/c-lean-libp2p \ external/c-ssz \ external/c-leanvm-xmss \ tools/leanSpec diff --git a/src/consensus/containers.c b/src/consensus/containers.c index 2717f62..b7e2b1e 100644 --- a/src/consensus/containers.c +++ b/src/consensus/containers.c @@ -1077,7 +1077,6 @@ void lantern_block_body_init(LanternBlockBody *body) { return; } lantern_aggregated_attestations_init(&body->attestations); - body->legacy_plain_attestation_layout = false; } void lantern_block_body_reset(LanternBlockBody *body) { @@ -1085,7 +1084,6 @@ void lantern_block_body_reset(LanternBlockBody *body) { return; } lantern_aggregated_attestations_reset(&body->attestations); - body->legacy_plain_attestation_layout = false; } void lantern_block_init(LanternBlock *block) { diff --git a/src/consensus/fork_choice.c b/src/consensus/fork_choice.c index 827c559..2dc2e85 100644 --- a/src/consensus/fork_choice.c +++ b/src/consensus/fork_choice.c @@ -621,7 +621,7 @@ int lantern_fork_choice_set_anchor_with_state( if (block_root_hint) { root = *block_root_hint; } else { - if (lantern_hash_tree_root_block(anchor_block, &root) != 0) { + if (lantern_hash_tree_root_block(anchor_block, &root) != SSZ_SUCCESS) { return -1; } } @@ -686,16 +686,12 @@ int lantern_fork_choice_set_anchor_with_state( block_root_hint ? "true" : "false", anchor_state ? "true" : "false", existed ? "true" : "false"); - if (latest_justified) { - store->latest_justified = *latest_justified; - } else { - memset(&store->latest_justified, 0, sizeof(store->latest_justified)); - } - if (latest_finalized) { - store->latest_finalized = *latest_finalized; - } else { - memset(&store->latest_finalized, 0, sizeof(store->latest_finalized)); - } + LanternCheckpoint anchor_checkpoint = { + .root = root, + .slot = anchor_block->slot, + }; + store->latest_justified = latest_justified ? *latest_justified : anchor_checkpoint; + store->latest_finalized = latest_finalized ? *latest_finalized : anchor_checkpoint; store->head = root; store->has_head = true; store->safe_target = root; @@ -748,65 +744,9 @@ static bool checkpoint_known_in_store( if (!store->blocks || checkpoint_index >= store->block_len) { return false; } - if (store->has_anchor && root_compare(&checkpoint->root, &store->anchor_root) == 0) { - return true; - } return store->blocks[checkpoint_index].slot == checkpoint->slot; } -static bool checkpoint_root_present_in_store( - const LanternForkChoice *store, - const LanternCheckpoint *checkpoint) { - if (!store || !checkpoint || root_is_zero(&checkpoint->root)) { - return false; - } - size_t checkpoint_index = 0; - if (!map_lookup(store, &checkpoint->root, &checkpoint_index)) { - return false; - } - return store->blocks && checkpoint_index < store->block_len; -} - -static void normalize_checkpoint_for_anchor_alias( - const LanternForkChoice *store, - const LanternCheckpoint *checkpoint, - LanternCheckpoint *out_checkpoint, - const char *label) { - if (!out_checkpoint) { - return; - } - memset(out_checkpoint, 0, sizeof(*out_checkpoint)); - if (!checkpoint) { - return; - } - *out_checkpoint = *checkpoint; - if (!store || !store->has_anchor || root_is_zero(&checkpoint->root)) { - return; - } - if (checkpoint_root_present_in_store(store, checkpoint)) { - return; - } - if (checkpoint->slot > store->anchor_slot) { - return; - } - - char original_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - char anchor_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - format_root_hex(&checkpoint->root, original_hex, sizeof(original_hex)); - format_root_hex(&store->anchor_root, anchor_hex, sizeof(anchor_hex)); - lantern_log_info( - "forkchoice", - &(const struct lantern_log_metadata){0}, - "aliasing %s checkpoint slot=%" PRIu64 " original_root=%s" - " anchor_slot=%" PRIu64 " anchor_root=%s", - label ? label : "post-state", - checkpoint->slot, - original_hex[0] ? original_hex : "0x0", - store->anchor_slot, - anchor_hex[0] ? anchor_hex : "0x0"); - out_checkpoint->root = store->anchor_root; -} - static bool should_replace_checkpoint( const LanternCheckpoint *current, const LanternCheckpoint *candidate) { @@ -851,68 +791,47 @@ static int update_latest_checkpoints( } LanternCheckpoint latest_justified = store->latest_justified; LanternCheckpoint latest_finalized = store->latest_finalized; - LanternCheckpoint normalized_post_justified; - LanternCheckpoint normalized_post_finalized; - const LanternCheckpoint *effective_post_justified = post_justified; - const LanternCheckpoint *effective_post_finalized = post_finalized; - - if (post_justified) { - normalize_checkpoint_for_anchor_alias( - store, - post_justified, - &normalized_post_justified, - "justified"); - effective_post_justified = &normalized_post_justified; - } - if (post_finalized) { - normalize_checkpoint_for_anchor_alias( - store, - post_finalized, - &normalized_post_finalized, - "finalized"); - effective_post_finalized = &normalized_post_finalized; - } - if (effective_post_justified && !root_is_zero(&effective_post_justified->root)) { - if (!checkpoint_known_in_store(store, effective_post_justified)) { - return -1; - } - if (should_replace_checkpoint(&latest_justified, effective_post_justified)) { + if (post_justified && !root_is_zero(&post_justified->root)) { + if (should_replace_checkpoint(&latest_justified, post_justified)) { + if (!checkpoint_known_in_store(store, post_justified)) { + return -1; + } log_checkpoint_decision( "justified", "advance", &latest_justified, - effective_post_justified); - latest_justified = *effective_post_justified; + post_justified); + latest_justified = *post_justified; } else if ( - effective_post_justified->slot == latest_justified.slot - && root_compare(&effective_post_justified->root, &latest_justified.root) != 0) { + post_justified->slot == latest_justified.slot + && root_compare(&post_justified->root, &latest_justified.root) != 0) { log_checkpoint_decision( "justified", "tie_keep_current", &latest_justified, - effective_post_justified); + post_justified); } } - if (effective_post_finalized && !root_is_zero(&effective_post_finalized->root)) { - if (!checkpoint_known_in_store(store, effective_post_finalized)) { - return -1; - } - if (should_replace_checkpoint(&latest_finalized, effective_post_finalized)) { + if (post_finalized && !root_is_zero(&post_finalized->root)) { + if (should_replace_checkpoint(&latest_finalized, post_finalized)) { + if (!checkpoint_known_in_store(store, post_finalized)) { + return -1; + } log_checkpoint_decision( "finalized", "advance", &latest_finalized, - effective_post_finalized); - latest_finalized = *effective_post_finalized; + post_finalized); + latest_finalized = *post_finalized; } else if ( - effective_post_finalized->slot == latest_finalized.slot - && root_compare(&effective_post_finalized->root, &latest_finalized.root) != 0) { + post_finalized->slot == latest_finalized.slot + && root_compare(&post_finalized->root, &latest_finalized.root) != 0) { log_checkpoint_decision( "finalized", "tie_keep_current", &latest_finalized, - effective_post_finalized); + post_finalized); } } @@ -970,12 +889,12 @@ int lantern_fork_choice_add_block_with_state( if (block_root_hint) { block_root = *block_root_hint; } else { - if (lantern_hash_tree_root_block(block, &block_root) != 0) { + if (lantern_hash_tree_root_block(block, &block_root) != SSZ_SUCCESS) { return -1; } } LanternRoot hashed_block_root = {0}; - bool have_hashed_block_root = lantern_hash_tree_root_block(block, &hashed_block_root) == 0; + bool have_hashed_block_root = lantern_hash_tree_root_block(block, &hashed_block_root) == SSZ_SUCCESS; char hinted_block_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; char hashed_block_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; if (block_root_hint && have_hashed_block_root diff --git a/src/consensus/hash.c b/src/consensus/hash.c index 251cddf..f97cdee 100644 --- a/src/consensus/hash.c +++ b/src/consensus/hash.c @@ -1,17 +1,21 @@ #include "lantern/consensus/hash.h" -#include "lantern/support/log.h" #include #include #include #include -#include "ssz_constants.h" -#include "ssz_merkle.h" -#include "ssz_utils.h" -#include "mincrypt/sha256.h" +#include "ssz.h" #include "pq-bindings-c-rust.h" +#define LANTERN_RETURN_IF_SSZ_ERROR(expr) \ + do { \ + ssz_error_t lantern_err__ = (expr); \ + if (lantern_err__ != SSZ_SUCCESS) { \ + return lantern_err__; \ + } \ + } while (0) + /* XMSS signature layout constants (LeanSpec prod config). */ static const size_t LANTERN_XMSS_FP_BYTES = 4u; static const size_t LANTERN_XMSS_HASH_LEN_FE = 8u; @@ -21,7 +25,7 @@ static const size_t LANTERN_XMSS_HASH_DIGEST_BYTES = static const size_t LANTERN_XMSS_RHO_BYTES = (LANTERN_XMSS_RAND_LEN_FE * LANTERN_XMSS_FP_BYTES); static const size_t LANTERN_XMSS_SIGNATURE_FIXED_SECTION = - (SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_XMSS_RHO_BYTES + SSZ_BYTE_SIZE_OF_UINT32); + (SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_XMSS_RHO_BYTES + SSZ_BYTES_PER_LENGTH_OFFSET); static size_t xmss_node_list_limit(void) { uint64_t lifetime = pq_get_lifetime(); if (lifetime == 0u || (lifetime & (lifetime - 1u)) != 0u) { @@ -41,16 +45,16 @@ static size_t xmss_node_list_limit(void) { return (size_t)1u << exponent; } -static void chunk_from_uint64(uint64_t value, uint8_t out[SSZ_BYTES_PER_CHUNK]) { - memset(out, 0, SSZ_BYTES_PER_CHUNK); - out[0] = (uint8_t)(value & 0xFFu); - out[1] = (uint8_t)((value >> 8) & 0xFFu); - out[2] = (uint8_t)((value >> 16) & 0xFFu); - out[3] = (uint8_t)((value >> 24) & 0xFFu); - out[4] = (uint8_t)((value >> 32) & 0xFFu); - out[5] = (uint8_t)((value >> 40) & 0xFFu); - out[6] = (uint8_t)((value >> 48) & 0xFFu); - out[7] = (uint8_t)((value >> 56) & 0xFFu); +static ssz_error_t chunk_from_uint64(uint64_t value, ssz_chunk_t *out) { + return ssz_hash_tree_root_uint64(value, out); +} + +static void chunk_from_root(const LanternRoot *root, ssz_chunk_t *out) { + memcpy(out->bytes, root->bytes, SSZ_BYTES_PER_CHUNK); +} + +static void root_from_chunk(const ssz_chunk_t *chunk, LanternRoot *out_root) { + memcpy(out_root->bytes, chunk->bytes, SSZ_BYTES_PER_CHUNK); } static uint32_t read_u32_le(const uint8_t *data) { @@ -63,408 +67,277 @@ static uint32_t read_u32_le(const uint8_t *data) { | ((uint32_t)data[3] << 24); } -int lantern_hash_tree_root_validators_dual( +ssz_error_t lantern_hash_tree_root_validators_dual( const uint8_t *attestation_pubkeys, const uint8_t *proposal_pubkeys, size_t count, LanternRoot *out_root); -static int hash_block_signatures(const LanternBlockSignatures *signatures, LanternRoot *out_root); +static ssz_error_t hash_block_signatures(const LanternBlockSignatures *signatures, LanternRoot *out_root); -static int merkleize_chunks( - const uint8_t *chunks, +static ssz_error_t merkleize_chunks( + const ssz_chunk_t *chunks, size_t chunk_count, size_t limit, LanternRoot *out_root) { if (!out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - ssz_error_t err = ssz_merkleize(chunks, chunk_count, limit, temp_root); + uint64_t effective_limit = limit == 0u ? SSZ_NO_LIMIT : (uint64_t)limit; + ssz_chunk_t temp_root; + ssz_error_t err = ssz_merkleize(chunks, chunk_count, effective_limit, NULL, NULL, &temp_root); if (err != SSZ_SUCCESS) { - return -1; + return err; } - memcpy(out_root->bytes, temp_root, SSZ_BYTES_PER_CHUNK); - return 0; + root_from_chunk(&temp_root, out_root); + return SSZ_SUCCESS; } -static bool bitlist_bit_is_set(const struct lantern_bitlist *list, size_t index) { - if (!list || !list->bytes || index >= list->bit_length) { - return false; - } - size_t byte_index = index / 8u; - if (byte_index >= list->capacity) { - return false; - } - uint8_t mask = (uint8_t)(1u << (index % 8u)); - return (list->bytes[byte_index] & mask) != 0u; -} - -static int hash_byte_vector(const uint8_t *bytes, size_t length, LanternRoot *out_root) { +static ssz_error_t hash_byte_vector(const uint8_t *bytes, size_t length, LanternRoot *out_root) { if (!out_root) { - return -1; - } - size_t chunk_count = (length + SSZ_BYTES_PER_CHUNK - 1u) / SSZ_BYTES_PER_CHUNK; - if (chunk_count == 0) { - chunk_count = 1; - } - uint8_t *chunks = calloc(chunk_count, SSZ_BYTES_PER_CHUNK); - if (!chunks) { - return -1; - } - if (bytes && length > 0) { - memcpy(chunks, bytes, length); + return SSZ_ERR_INVALID_ARGUMENT; + } + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_vector_fixed( + bytes, + length, + sizeof(uint8_t), + NULL, + NULL, + &root); + if (err != SSZ_SUCCESS) { + return err; } - int result = merkleize_chunks(chunks, chunk_count, 0, out_root); - free(chunks); - return result; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -static int hash_byte_list(const uint8_t *bytes, size_t length, size_t max_length, LanternRoot *out_root) { +static ssz_error_t hash_byte_list(const uint8_t *bytes, size_t length, size_t max_length, LanternRoot *out_root) { if (!out_root) { - return -1; - } - if (length > max_length) { - return -1; - } - size_t chunk_limit = (max_length + SSZ_BYTES_PER_CHUNK - 1u) / SSZ_BYTES_PER_CHUNK; - size_t chunk_count = 0; - if (length > 0) { - chunk_count = (length + SSZ_BYTES_PER_CHUNK - 1u) / SSZ_BYTES_PER_CHUNK; - } - uint8_t *chunks = NULL; - if (chunk_count > 0) { - if (chunk_count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; - } - chunks = calloc(chunk_count, SSZ_BYTES_PER_CHUNK); - if (!chunks) { - return -1; - } - if (bytes) { - memcpy(chunks, bytes, length); - } - } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - ssz_error_t err = ssz_merkleize(chunks, chunk_count, chunk_limit, temp_root); - if (chunks) { - free(chunks); - } + return SSZ_ERR_INVALID_ARGUMENT; + } + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_list_fixed( + bytes, + length, + max_length, + sizeof(uint8_t), + NULL, + NULL, + &root); if (err != SSZ_SUCCESS) { - return -1; + return err; } - err = ssz_mix_in_length(temp_root, (uint64_t)length, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -static int hash_digest_list_root( +static ssz_error_t hash_digest_list_root( const uint8_t *chunks, size_t count, LanternRoot *out_root) { if (!out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } if (count > 0 && !chunks) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } size_t limit = xmss_node_list_limit(); if (limit == 0u) { - return -1; + return SSZ_ERR_OVERFLOW; } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - ssz_error_t err = ssz_merkleize(chunks, count, limit, temp_root); + ssz_chunk_t *roots = NULL; + if (count > 0) { + roots = calloc(count, sizeof(*roots)); + if (!roots) { + return SSZ_ERR_HASH_FAILURE; + } + for (size_t i = 0; i < count; ++i) { + memcpy(roots[i].bytes, chunks + (i * SSZ_BYTES_PER_CHUNK), SSZ_BYTES_PER_CHUNK); + } + } + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_list_roots( + roots, + count, + limit, + NULL, + NULL, + &root); + free(roots); if (err != SSZ_SUCCESS) { - return -1; + return err; } - err = ssz_mix_in_length(temp_root, (uint64_t)count, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -static int hash_xmss_signature(const LanternSignature *signature, LanternRoot *out_root) { +static ssz_error_t hash_xmss_signature(const LanternSignature *signature, LanternRoot *out_root) { if (!signature || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } const uint8_t *data = signature->bytes; const size_t data_len = LANTERN_SIGNATURE_SIZE; if (data_len < LANTERN_XMSS_SIGNATURE_FIXED_SECTION) { - return -1; + return SSZ_ERR_ENCODING_INVALID; } uint32_t path_offset = read_u32_le(data); - uint32_t hashes_offset = read_u32_le(data + SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_XMSS_RHO_BYTES); + uint32_t hashes_offset = read_u32_le(data + SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_XMSS_RHO_BYTES); if (path_offset != LANTERN_XMSS_SIGNATURE_FIXED_SECTION) { - return -1; + return SSZ_ERR_ENCODING_INVALID; } if (hashes_offset < path_offset || hashes_offset > data_len) { - return -1; + return SSZ_ERR_OFFSET_INVALID; } size_t path_len = hashes_offset - path_offset; - if (path_len < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; + if (path_len < SSZ_BYTES_PER_LENGTH_OFFSET) { + return SSZ_ERR_ENCODING_INVALID; } uint32_t siblings_offset = read_u32_le(data + path_offset); - if (siblings_offset != SSZ_BYTE_SIZE_OF_UINT32) { - return -1; + if (siblings_offset != SSZ_BYTES_PER_LENGTH_OFFSET) { + return SSZ_ERR_OFFSET_INVALID; } size_t siblings_start = path_offset + siblings_offset; if (siblings_start > hashes_offset) { - return -1; + return SSZ_ERR_OFFSET_INVALID; } size_t siblings_len = hashes_offset - siblings_start; if (siblings_len % LANTERN_XMSS_HASH_DIGEST_BYTES != 0) { - return -1; + return SSZ_ERR_ENCODING_INVALID; } size_t siblings_count = siblings_len / LANTERN_XMSS_HASH_DIGEST_BYTES; size_t node_limit = xmss_node_list_limit(); if (node_limit == 0u) { - return -1; + return SSZ_ERR_OVERFLOW; } if (siblings_count > node_limit) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } size_t hashes_len = data_len - hashes_offset; if (hashes_len % LANTERN_XMSS_HASH_DIGEST_BYTES != 0) { - return -1; + return SSZ_ERR_ENCODING_INVALID; } size_t hashes_count = hashes_len / LANTERN_XMSS_HASH_DIGEST_BYTES; if (hashes_count > node_limit) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } LanternRoot siblings_root; - if (hash_digest_list_root(data + siblings_start, siblings_count, &siblings_root) != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(hash_digest_list_root(data + siblings_start, siblings_count, &siblings_root)); LanternRoot hashes_root; - if (hash_digest_list_root(data + hashes_offset, hashes_count, &hashes_root) != 0) { - return -1; - } - - uint8_t rho_chunk[SSZ_BYTES_PER_CHUNK]; - memset(rho_chunk, 0, sizeof(rho_chunk)); - memcpy(rho_chunk, data + SSZ_BYTE_SIZE_OF_UINT32, LANTERN_XMSS_RHO_BYTES); - LanternRoot rho_root; - memcpy(rho_root.bytes, rho_chunk, SSZ_BYTES_PER_CHUNK); - - uint8_t chunks[3][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], siblings_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], rho_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[2], hashes_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 3, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(hash_digest_list_root(data + hashes_offset, hashes_count, &hashes_root)); + + ssz_chunk_t chunks[3]; + chunk_from_root(&siblings_root, &chunks[0]); + memset(chunks[1].bytes, 0, sizeof(chunks[1].bytes)); + memcpy(chunks[1].bytes, data + SSZ_BYTES_PER_LENGTH_OFFSET, LANTERN_XMSS_RHO_BYTES); + chunk_from_root(&hashes_root, &chunks[2]); + return merkleize_chunks(chunks, 3, 0, out_root); } -static int hash_validator( +static ssz_error_t hash_validator( const uint8_t *attestation_pubkey, const uint8_t *proposal_pubkey, uint64_t index, LanternRoot *out_root) { if (!out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot attestation_pubkey_root; - if (hash_byte_vector( - attestation_pubkey, - LANTERN_VALIDATOR_PUBKEY_SIZE, - &attestation_pubkey_root) - != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(hash_byte_vector( + attestation_pubkey, + LANTERN_VALIDATOR_PUBKEY_SIZE, + &attestation_pubkey_root)); LanternRoot proposal_pubkey_root; - if (hash_byte_vector( - proposal_pubkey, - LANTERN_VALIDATOR_PUBKEY_SIZE, - &proposal_pubkey_root) - != 0) { - return -1; - } - uint8_t chunks[3][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], attestation_pubkey_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], proposal_pubkey_root.bytes, SSZ_BYTES_PER_CHUNK); - chunk_from_uint64(index, chunks[2]); - return merkleize_chunks(&chunks[0][0], 3, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(hash_byte_vector( + proposal_pubkey, + LANTERN_VALIDATOR_PUBKEY_SIZE, + &proposal_pubkey_root)); + ssz_chunk_t chunks[3]; + chunk_from_root(&attestation_pubkey_root, &chunks[0]); + chunk_from_root(&proposal_pubkey_root, &chunks[1]); + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(index, &chunks[2])); + return merkleize_chunks(chunks, 3, 0, out_root); } -static int zero_merkle_root(size_t chunk_limit, LanternRoot *out_root) { - if (!out_root) { - return -1; - } - uint64_t effective = chunk_limit; - if (effective == 0) { - memset(out_root->bytes, 0, LANTERN_ROOT_SIZE); - return 0; - } - uint64_t padded = next_pow_of_two(effective); - if (padded == 0) { - return -1; - } - LanternRoot current; - memset(current.bytes, 0, LANTERN_ROOT_SIZE); - uint8_t buffer[SSZ_BYTES_PER_CHUNK * 2u]; - uint64_t depth = 0; - while (((uint64_t)1 << depth) < padded) { - memcpy(buffer, current.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(buffer + SSZ_BYTES_PER_CHUNK, current.bytes, SSZ_BYTES_PER_CHUNK); - SHA256_hash(buffer, sizeof(buffer), current.bytes); - ++depth; - } - *out_root = current; - return 0; -} - -static void hash_root_pair(const LanternRoot *left, const LanternRoot *right, LanternRoot *out_root) { - uint8_t buffer[SSZ_BYTES_PER_CHUNK * 2u]; - memcpy(buffer, left->bytes, SSZ_BYTES_PER_CHUNK); - memcpy(buffer + SSZ_BYTES_PER_CHUNK, right->bytes, SSZ_BYTES_PER_CHUNK); - SHA256_hash(buffer, sizeof(buffer), out_root->bytes); -} - -static int hash_empty_list_root(size_t element_limit, LanternRoot *out_root) { - if (!out_root) { - return -1; - } - LanternRoot zero_root; - size_t limit = element_limit ? element_limit : 1u; - if (zero_merkle_root(limit, &zero_root) != 0) { - return -1; - } - return ssz_mix_in_length(zero_root.bytes, 0, out_root->bytes) == SSZ_SUCCESS ? 0 : -1; -} - -static int hash_empty_bitlist_root(size_t bit_limit, LanternRoot *out_root) { - if (!out_root) { - return -1; - } - size_t bits_per_chunk = SSZ_BYTES_PER_CHUNK * 8u; - size_t chunk_limit = bit_limit ? ((bit_limit + bits_per_chunk - 1u) / bits_per_chunk) : 1u; - LanternRoot zero_root; - if (zero_merkle_root(chunk_limit, &zero_root) != 0) { - return -1; - } - return ssz_mix_in_length(zero_root.bytes, 0, out_root->bytes) == SSZ_SUCCESS ? 0 : -1; -} - -/* Justification roots and votes are already canonicalized before entering state. */ -static int merkleize_sorted_justifications( - const struct lantern_root_list *roots, - const struct lantern_bitlist *validators, - size_t validator_count, - LanternRoot *out_roots_root, - LanternRoot *out_validators_root) { - (void)validator_count; - if (!roots || !validators || !out_roots_root || !out_validators_root) { - return -1; - } - - size_t bits_per_chunk = SSZ_BYTES_PER_CHUNK * 8u; - - if (roots->length == 0) { - if (hash_empty_list_root(LANTERN_HISTORICAL_ROOTS_LIMIT, out_roots_root) != 0) { - return -1; - } - } else if (lantern_merkleize_root_list(roots, LANTERN_HISTORICAL_ROOTS_LIMIT, out_roots_root) != 0) { - return -1; - } - - size_t chunk_limit = - (LANTERN_JUSTIFICATION_VALIDATORS_LIMIT + bits_per_chunk - 1u) / bits_per_chunk; - if (validators->bit_length == 0) { - if (hash_empty_bitlist_root(LANTERN_JUSTIFICATION_VALIDATORS_LIMIT, out_validators_root) != 0) { - return -1; - } - return 0; - } - return lantern_merkleize_bitlist(validators, chunk_limit, out_validators_root); -} - -int lantern_hash_tree_root_config(const LanternConfig *config, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_config(const LanternConfig *config, LanternRoot *out_root) { if (!config || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } /* Config only contains genesis_time for SSZ hashing (matches Zeam's BeamStateConfig). * num_validators is stored separately and not part of the SSZ-encoded config. */ - uint8_t chunks[1][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(config->genesis_time, chunks[0]); - return merkleize_chunks(&chunks[0][0], 1, 0, out_root); + ssz_chunk_t chunks[1]; + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(config->genesis_time, &chunks[0])); + return merkleize_chunks(chunks, 1, 0, out_root); } -int lantern_hash_tree_root_checkpoint(const LanternCheckpoint *checkpoint, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_checkpoint(const LanternCheckpoint *checkpoint, LanternRoot *out_root) { if (!checkpoint || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], checkpoint->root.bytes, SSZ_BYTES_PER_CHUNK); - chunk_from_uint64(checkpoint->slot, chunks[1]); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + ssz_chunk_t chunks[2]; + chunk_from_root(&checkpoint->root, &chunks[0]); + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(checkpoint->slot, &chunks[1])); + return merkleize_chunks(chunks, 2, 0, out_root); } -int lantern_hash_tree_root_attestation_data(const LanternAttestationData *data, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_attestation_data(const LanternAttestationData *data, LanternRoot *out_root) { if (!data || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot head_root; LanternRoot target_root; LanternRoot source_root; - if (lantern_hash_tree_root_checkpoint(&data->head, &head_root) != 0) { - return -1; - } - if (lantern_hash_tree_root_checkpoint(&data->target, &target_root) != 0) { - return -1; - } - if (lantern_hash_tree_root_checkpoint(&data->source, &source_root) != 0) { - return -1; - } - uint8_t chunks[4][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(data->slot, chunks[0]); - memcpy(chunks[1], head_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[2], target_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[3], source_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 4, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_checkpoint(&data->head, &head_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_checkpoint(&data->target, &target_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_checkpoint(&data->source, &source_root)); + ssz_chunk_t chunks[4]; + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(data->slot, &chunks[0])); + chunk_from_root(&head_root, &chunks[1]); + chunk_from_root(&target_root, &chunks[2]); + chunk_from_root(&source_root, &chunks[3]); + return merkleize_chunks(chunks, 4, 0, out_root); } -int lantern_hash_tree_root_vote(const LanternVote *vote, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_vote(const LanternVote *vote, LanternRoot *out_root) { if (!vote || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote->data, &data_root) != 0) { - return -1; - } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(vote->validator_id, chunks[0]); - memcpy(chunks[1], data_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_attestation_data(&vote->data, &data_root)); + ssz_chunk_t chunks[2]; + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(vote->validator_id, &chunks[0])); + chunk_from_root(&data_root, &chunks[1]); + return merkleize_chunks(chunks, 2, 0, out_root); } -int lantern_hash_tree_root_signed_vote(const LanternSignedVote *vote, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_signed_vote(const LanternSignedVote *vote, LanternRoot *out_root) { if (!vote || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot data_root; LanternRoot signature_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &data_root) != 0) { - return -1; - } - if (hash_xmss_signature(&vote->signature, &signature_root) != 0) { - return -1; - } - uint8_t chunks[3][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(vote->data.validator_id, chunks[0]); - memcpy(chunks[1], data_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[2], signature_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 3, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_attestation_data(&vote->data.data, &data_root)); + LANTERN_RETURN_IF_SSZ_ERROR(hash_xmss_signature(&vote->signature, &signature_root)); + ssz_chunk_t chunks[3]; + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(vote->data.validator_id, &chunks[0])); + chunk_from_root(&data_root, &chunks[1]); + chunk_from_root(&signature_root, &chunks[2]); + return merkleize_chunks(chunks, 3, 0, out_root); } -int lantern_hash_tree_root_signature(const LanternSignature *signature, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_signature(const LanternSignature *signature, LanternRoot *out_root) { return hash_xmss_signature(signature, out_root); } -int lantern_hash_tree_root_validator(const LanternValidator *validator, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_validator(const LanternValidator *validator, LanternRoot *out_root) { if (!validator || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } return hash_validator( validator->attestation_pubkey, @@ -473,498 +346,641 @@ int lantern_hash_tree_root_validator(const LanternValidator *validator, LanternR out_root); } -int lantern_hash_tree_root_aggregated_attestation(const LanternAggregatedAttestation *attestation, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_aggregated_attestation(const LanternAggregatedAttestation *attestation, LanternRoot *out_root) { if (!attestation || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } if (attestation->aggregation_bits.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } LanternRoot bits_root; size_t bits_per_chunk = SSZ_BYTES_PER_CHUNK * 8u; size_t bitlist_limit = (LANTERN_VALIDATOR_REGISTRY_LIMIT + bits_per_chunk - 1u) / bits_per_chunk; - if (attestation->aggregation_bits.bit_length == 0) { - if (hash_empty_bitlist_root(LANTERN_VALIDATOR_REGISTRY_LIMIT, &bits_root) != 0) { - return -1; - } - } else if (lantern_merkleize_bitlist(&attestation->aggregation_bits, bitlist_limit, &bits_root) != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(lantern_merkleize_bitlist(&attestation->aggregation_bits, bitlist_limit, &bits_root)); LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&attestation->data, &data_root) != 0) { - return -1; - } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], bits_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], data_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_attestation_data(&attestation->data, &data_root)); + ssz_chunk_t chunks[2]; + chunk_from_root(&bits_root, &chunks[0]); + chunk_from_root(&data_root, &chunks[1]); + return merkleize_chunks(chunks, 2, 0, out_root); } -int lantern_hash_tree_root_aggregated_signature_proof( +ssz_error_t lantern_hash_tree_root_aggregated_signature_proof( const LanternAggregatedSignatureProof *proof, LanternRoot *out_root) { if (!proof || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } if (proof->participants.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } if (proof->proof_data.length > LANTERN_AGG_PROOF_MAX_BYTES) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } LanternRoot participants_root; size_t bits_per_chunk = SSZ_BYTES_PER_CHUNK * 8u; size_t bitlist_limit = (LANTERN_VALIDATOR_REGISTRY_LIMIT + bits_per_chunk - 1u) / bits_per_chunk; - if (proof->participants.bit_length == 0) { - if (hash_empty_bitlist_root(LANTERN_VALIDATOR_REGISTRY_LIMIT, &participants_root) != 0) { - return -1; - } - } else if (lantern_merkleize_bitlist(&proof->participants, bitlist_limit, &participants_root) != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(lantern_merkleize_bitlist(&proof->participants, bitlist_limit, &participants_root)); LanternRoot proof_root; - if (hash_byte_list( - proof->proof_data.data, - proof->proof_data.length, - LANTERN_AGG_PROOF_MAX_BYTES, - &proof_root) - != 0) { - return -1; - } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], participants_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], proof_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(hash_byte_list( + proof->proof_data.data, + proof->proof_data.length, + LANTERN_AGG_PROOF_MAX_BYTES, + &proof_root)); + ssz_chunk_t chunks[2]; + chunk_from_root(&participants_root, &chunks[0]); + chunk_from_root(&proof_root, &chunks[1]); + return merkleize_chunks(chunks, 2, 0, out_root); } -int lantern_hash_tree_root_signed_aggregated_attestation( +ssz_error_t lantern_hash_tree_root_signed_aggregated_attestation( const LanternSignedAggregatedAttestation *attestation, LanternRoot *out_root) { if (!attestation || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot data_root; LanternRoot proof_root; - if (lantern_hash_tree_root_attestation_data(&attestation->data, &data_root) != 0) { - return -1; - } - if (lantern_hash_tree_root_aggregated_signature_proof(&attestation->proof, &proof_root) != 0) { - return -1; - } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], data_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], proof_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_attestation_data(&attestation->data, &data_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_aggregated_signature_proof(&attestation->proof, &proof_root)); + ssz_chunk_t chunks[2]; + chunk_from_root(&data_root, &chunks[0]); + chunk_from_root(&proof_root, &chunks[1]); + return merkleize_chunks(chunks, 2, 0, out_root); } -int lantern_hash_tree_root_block_signatures( +ssz_error_t lantern_hash_tree_root_block_signatures( const LanternBlockSignatures *signatures, LanternRoot *out_root) { return hash_block_signatures(signatures, out_root); } -int lantern_merkleize_root_list( +ssz_error_t lantern_merkleize_root_list( const struct lantern_root_list *list, size_t limit, LanternRoot *out_root) { if (!list || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } size_t count = list->length; - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - uint8_t *chunk_bytes = NULL; + ssz_chunk_t root; + ssz_chunk_t *roots = NULL; if (count > 0) { if (!list->items) { - return -1; - } - if (count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } - size_t total_bytes = count * SSZ_BYTES_PER_CHUNK; - chunk_bytes = malloc(total_bytes); - if (!chunk_bytes) { - return -1; + roots = calloc(count, sizeof(*roots)); + if (!roots) { + return SSZ_ERR_HASH_FAILURE; } for (size_t i = 0; i < count; ++i) { - memcpy(chunk_bytes + (i * SSZ_BYTES_PER_CHUNK), list->items[i].bytes, SSZ_BYTES_PER_CHUNK); + chunk_from_root(&list->items[i], &roots[i]); } } - ssz_error_t err = ssz_merkleize(chunk_bytes, count, limit, temp_root); - if (chunk_bytes) { - free(chunk_bytes); - } + ssz_error_t err = ssz_hash_tree_root_list_roots(roots, count, limit, NULL, NULL, &root); + free(roots); if (err != SSZ_SUCCESS) { - return -1; + return err; } - err = ssz_mix_in_length(temp_root, (uint64_t)count, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -int lantern_merkleize_bitlist( +ssz_error_t lantern_merkleize_bitlist( const struct lantern_bitlist *bitlist, size_t limit, LanternRoot *out_root) { if (!bitlist || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } size_t bit_count = bitlist->bit_length; if (bit_count > 0 && !bitlist->bytes) { - return -1; - } - - size_t bitfield_len = bit_count ? ((bit_count + 7u) / 8u) : 0u; - size_t chunk_count = bitfield_len ? ((bitfield_len + SSZ_BYTES_PER_CHUNK - 1u) / SSZ_BYTES_PER_CHUNK) : 0u; - size_t effective_limit = limit ? limit : (chunk_count > 0u ? chunk_count : 1u); - if (chunk_count > effective_limit) { - return -1; - } - - uint64_t padded = next_pow_of_two((uint64_t)effective_limit); - if (padded == 0u) { - return -1; - } - - unsigned int depth = 0u; - while (((uint64_t)1u << depth) < padded) { - ++depth; + return SSZ_ERR_INVALID_ARGUMENT; } - - LanternRoot zero_roots[64]; - memset(zero_roots[0].bytes, 0, LANTERN_ROOT_SIZE); - for (unsigned int i = 1u; i <= depth; ++i) { - hash_root_pair(&zero_roots[i - 1u], &zero_roots[i - 1u], &zero_roots[i]); - } - - LanternRoot stack[64]; - bool has_stack[64]; - memset(has_stack, 0, sizeof(has_stack)); - - for (size_t chunk_index = 0u; chunk_index < chunk_count; ++chunk_index) { - LanternRoot node; - memset(node.bytes, 0, LANTERN_ROOT_SIZE); - size_t chunk_offset = chunk_index * SSZ_BYTES_PER_CHUNK; - size_t remaining_bytes = bitfield_len - chunk_offset; - size_t copy_len = remaining_bytes < SSZ_BYTES_PER_CHUNK ? remaining_bytes : SSZ_BYTES_PER_CHUNK; - memcpy(node.bytes, bitlist->bytes + chunk_offset, copy_len); - if ((bit_count % 8u) != 0u && chunk_index == chunk_count - 1u) { - size_t last_byte = (bitfield_len - 1u) - chunk_offset; - uint8_t mask = (uint8_t)((1u << (bit_count % 8u)) - 1u); - node.bytes[last_byte] &= mask; - } - - size_t merge_index = chunk_index; - unsigned int level = 0u; - while ((merge_index & 1u) != 0u) { - hash_root_pair(&stack[level], &node, &node); - has_stack[level] = false; - merge_index >>= 1u; - ++level; - } - stack[level] = node; - has_stack[level] = true; + if (limit > (SIZE_MAX / (SSZ_BYTES_PER_CHUNK * 8u))) { + return SSZ_ERR_OVERFLOW; } - - if (chunk_count == 0u) { - return ssz_mix_in_length(zero_roots[depth].bytes, (uint64_t)bit_count, out_root->bytes) == SSZ_SUCCESS - ? 0 - : -1; - } - - for (unsigned int level = 0u; level < depth; ++level) { - if (!has_stack[level]) { - continue; - } - - LanternRoot node = stack[level]; - has_stack[level] = false; - hash_root_pair(&node, &zero_roots[level], &node); - - unsigned int carry_level = level + 1u; - while (carry_level < 64u && has_stack[carry_level]) { - hash_root_pair(&stack[carry_level], &node, &node); - has_stack[carry_level] = false; - ++carry_level; - } - if (carry_level >= 64u) { - return -1; - } - stack[carry_level] = node; - has_stack[carry_level] = true; - } - - LanternRoot root = depth < 64u && has_stack[depth] ? stack[depth] : zero_roots[depth]; - ssz_error_t err = ssz_mix_in_length(root.bytes, (uint64_t)bit_count, out_root->bytes); + size_t bitfield_len = bit_count ? ((bit_count + 7u) / 8u) : 0u; + uint64_t bit_limit = (uint64_t)limit * (uint64_t)SSZ_BYTES_PER_CHUNK * 8u; + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_bitlist( + bitlist->bytes, + bitfield_len, + bit_count, + bit_limit, + NULL, + NULL, + &root); if (err != SSZ_SUCCESS) { - return -1; + return err; } - return 0; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -static int hash_aggregated_attestations(const LanternAggregatedAttestations *attestations, LanternRoot *out_root) { +static ssz_error_t hash_aggregated_attestations(const LanternAggregatedAttestations *attestations, LanternRoot *out_root) { if (!attestations || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } size_t count = attestations->length; - uint8_t *chunks = NULL; + ssz_chunk_t *chunks = NULL; if (count > 0) { if (!attestations->data) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } if (count > LANTERN_MAX_ATTESTATIONS) { - return -1; - } - if (count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } - size_t total_bytes = count * SSZ_BYTES_PER_CHUNK; - chunks = malloc(total_bytes); + chunks = calloc(count, sizeof(*chunks)); if (!chunks) { - return -1; + return SSZ_ERR_HASH_FAILURE; } for (size_t i = 0; i < count; ++i) { LanternRoot att_root; - if (lantern_hash_tree_root_aggregated_attestation(&attestations->data[i], &att_root) != 0) { + ssz_error_t err = lantern_hash_tree_root_aggregated_attestation(&attestations->data[i], &att_root); + if (err != SSZ_SUCCESS) { free(chunks); - return -1; + return err; } - memcpy(chunks + (i * SSZ_BYTES_PER_CHUNK), att_root.bytes, SSZ_BYTES_PER_CHUNK); - } - } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - memset(temp_root, 0, sizeof(temp_root)); - ssz_error_t err = ssz_merkleize(chunks, attestations->length, LANTERN_MAX_ATTESTATIONS, temp_root); - if (chunks) { - free(chunks); - } - if (err != SSZ_SUCCESS) { - return -1; - } - err = ssz_mix_in_length(temp_root, (uint64_t)attestations->length, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; -} - -static int single_participant_from_aggregate( - const LanternAggregatedAttestation *attestation, - uint64_t *out_validator_id) { - if (!attestation || !out_validator_id) { - return -1; - } - size_t bit_length = attestation->aggregation_bits.bit_length; - if (bit_length == 0 || bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; - } - bool found = false; - uint64_t validator_id = 0; - for (size_t i = 0; i < bit_length; ++i) { - if (!bitlist_bit_is_set(&attestation->aggregation_bits, i)) { - continue; + chunk_from_root(&att_root, &chunks[i]); } - if (found) { - return -1; - } - found = true; - validator_id = (uint64_t)i; - } - if (!found) { - return -1; - } - *out_validator_id = validator_id; - return 0; -} - -static int hash_plain_attestations_from_aggregated( - const LanternAggregatedAttestations *attestations, - LanternRoot *out_root) { - if (!attestations || !out_root) { - return -1; - } - size_t count = attestations->length; - uint8_t *chunks = NULL; - if (count > 0) { - if (!attestations->data) { - return -1; - } - if (count > LANTERN_MAX_ATTESTATIONS) { - return -1; - } - if (count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; - } - chunks = malloc(count * SSZ_BYTES_PER_CHUNK); - if (!chunks) { - return -1; - } - for (size_t i = 0; i < count; ++i) { - uint64_t validator_id = 0; - if (single_participant_from_aggregate(&attestations->data[i], &validator_id) != 0) { - free(chunks); - return -1; - } - LanternVote vote; - memset(&vote, 0, sizeof(vote)); - vote.validator_id = validator_id; - vote.data = attestations->data[i].data; - - LanternRoot vote_root; - if (lantern_hash_tree_root_vote(&vote, &vote_root) != 0) { - free(chunks); - return -1; - } - memcpy(chunks + (i * SSZ_BYTES_PER_CHUNK), vote_root.bytes, SSZ_BYTES_PER_CHUNK); - } - } - - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - memset(temp_root, 0, sizeof(temp_root)); - ssz_error_t err = ssz_merkleize(chunks, count, LANTERN_MAX_ATTESTATIONS, temp_root); - if (chunks) { - free(chunks); } + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_list_roots( + chunks, + attestations->length, + LANTERN_MAX_ATTESTATIONS, + NULL, + NULL, + &root); + free(chunks); if (err != SSZ_SUCCESS) { - return -1; + return err; } - err = ssz_mix_in_length(temp_root, (uint64_t)count, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -static int hash_attestation_signatures(const LanternAttestationSignatures *signatures, LanternRoot *out_root) { +static ssz_error_t hash_attestation_signatures(const LanternAttestationSignatures *signatures, LanternRoot *out_root) { if (!signatures || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } size_t count = signatures->length; - uint8_t *chunks = NULL; + ssz_chunk_t *chunks = NULL; if (count > 0) { if (!signatures->data) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } if (count > LANTERN_MAX_BLOCK_SIGNATURES) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } - if (count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; - } - size_t total_bytes = count * SSZ_BYTES_PER_CHUNK; - chunks = malloc(total_bytes); + chunks = calloc(count, sizeof(*chunks)); if (!chunks) { - return -1; + return SSZ_ERR_HASH_FAILURE; } for (size_t i = 0; i < count; ++i) { LanternRoot sig_root; - if (lantern_hash_tree_root_aggregated_signature_proof(&signatures->data[i], &sig_root) != 0) { + ssz_error_t err = lantern_hash_tree_root_aggregated_signature_proof(&signatures->data[i], &sig_root); + if (err != SSZ_SUCCESS) { free(chunks); - return -1; + return err; } - memcpy(chunks + (i * SSZ_BYTES_PER_CHUNK), sig_root.bytes, SSZ_BYTES_PER_CHUNK); + chunk_from_root(&sig_root, &chunks[i]); } } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - memset(temp_root, 0, sizeof(temp_root)); - ssz_error_t err = ssz_merkleize(chunks, count, LANTERN_MAX_BLOCK_SIGNATURES, temp_root); - if (chunks) { - free(chunks); - } + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_list_roots( + chunks, + count, + LANTERN_MAX_BLOCK_SIGNATURES, + NULL, + NULL, + &root); + free(chunks); if (err != SSZ_SUCCESS) { - return -1; + return err; } - err = ssz_mix_in_length(temp_root, (uint64_t)count, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -static int hash_block_signatures(const LanternBlockSignatures *signatures, LanternRoot *out_root) { +static ssz_error_t hash_block_signatures(const LanternBlockSignatures *signatures, LanternRoot *out_root) { if (!signatures || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot attestation_root; - if (hash_attestation_signatures(&signatures->attestation_signatures, &attestation_root) != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(hash_attestation_signatures(&signatures->attestation_signatures, &attestation_root)); LanternRoot proposer_root; - if (hash_xmss_signature(&signatures->proposer_signature, &proposer_root) != 0) { - return -1; - } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], attestation_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], proposer_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(hash_xmss_signature(&signatures->proposer_signature, &proposer_root)); + ssz_chunk_t chunks[2]; + chunk_from_root(&attestation_root, &chunks[0]); + chunk_from_root(&proposer_root, &chunks[1]); + return merkleize_chunks(chunks, 2, 0, out_root); } -int lantern_hash_tree_root_block_body(const LanternBlockBody *body, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_block_body(const LanternBlockBody *body, LanternRoot *out_root) { if (!body || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot att_root; - bool used_legacy_plain_hash = false; - if (body->legacy_plain_attestation_layout - && hash_plain_attestations_from_aggregated(&body->attestations, &att_root) == 0) { - used_legacy_plain_hash = true; - } else if (hash_aggregated_attestations(&body->attestations, &att_root) != 0) { - return -1; - } - static bool logged_legacy_plain_hash = false; - if (used_legacy_plain_hash && !logged_legacy_plain_hash) { - lantern_log_warn( - "hash", - NULL, - "block body root using legacy plain attestation compatibility hashing count=%zu", - body->attestations.length); - logged_legacy_plain_hash = true; - } - uint8_t chunks[1][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], att_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 1, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(hash_aggregated_attestations(&body->attestations, &att_root)); + ssz_chunk_t chunks[1]; + chunk_from_root(&att_root, &chunks[0]); + return merkleize_chunks(chunks, 1, 0, out_root); } -int lantern_hash_tree_root_block_header(const LanternBlockHeader *header, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_block_header(const LanternBlockHeader *header, LanternRoot *out_root) { if (!header || !out_root) { - return -1; - } - uint8_t chunks[5][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(header->slot, chunks[0]); - chunk_from_uint64(header->proposer_index, chunks[1]); - memcpy(chunks[2], header->parent_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[3], header->state_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[4], header->body_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 5, 0, out_root); + return SSZ_ERR_INVALID_ARGUMENT; + } + ssz_chunk_t chunks[5]; + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(header->slot, &chunks[0])); + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(header->proposer_index, &chunks[1])); + chunk_from_root(&header->parent_root, &chunks[2]); + chunk_from_root(&header->state_root, &chunks[3]); + chunk_from_root(&header->body_root, &chunks[4]); + return merkleize_chunks(chunks, 5, 0, out_root); } -int lantern_hash_tree_root_block(const LanternBlock *block, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_block(const LanternBlock *block, LanternRoot *out_root) { if (!block || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot body_root; - if (lantern_hash_tree_root_block_body(&block->body, &body_root) != 0) { - return -1; - } - uint8_t chunks[5][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(block->slot, chunks[0]); - chunk_from_uint64(block->proposer_index, chunks[1]); - memcpy(chunks[2], block->parent_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[3], block->state_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[4], body_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 5, 0, out_root); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_block_body(&block->body, &body_root)); + ssz_chunk_t chunks[5]; + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(block->slot, &chunks[0])); + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(block->proposer_index, &chunks[1])); + chunk_from_root(&block->parent_root, &chunks[2]); + chunk_from_root(&block->state_root, &chunks[3]); + chunk_from_root(&body_root, &chunks[4]); + return merkleize_chunks(chunks, 5, 0, out_root); } -int lantern_hash_tree_root_signed_block(const LanternSignedBlock *block, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_signed_block(const LanternSignedBlock *block, LanternRoot *out_root) { if (!block || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot message_root; - if (lantern_hash_tree_root_block(&block->block, &message_root) != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_block(&block->block, &message_root)); LanternRoot signatures_root; - if (hash_block_signatures(&block->signatures, &signatures_root) != 0) { - return -1; + LANTERN_RETURN_IF_SSZ_ERROR(hash_block_signatures(&block->signatures, &signatures_root)); + ssz_chunk_t chunks[2]; + chunk_from_root(&message_root, &chunks[0]); + chunk_from_root(&signatures_root, &chunks[1]); + return merkleize_chunks(chunks, 2, 0, out_root); +} + +struct lantern_merkle_cache_box { + ssz_merkle_cache_t cache; + ssz_merkle_cache_storage_t storage; + ssz_merkle_cache_sync_workspace_t workspace; + ssz_chunk_t *nodes; + uint64_t *leaf_dirty_bits; + size_t *leaf_dirty_word_idx; + uint64_t *parent_dirty_bits[2]; + size_t *parent_dirty_word_idx[2]; + ssz_chunk_t *gather_pairs; + ssz_chunk_t *gather_hashes; + size_t *gather_parent_indices; + uint64_t *token_values; + uint64_t *token_valid_bits; + ssz_chunk_t *root_batch_roots; + bool bound; +}; + +struct lantern_state_hash_cache { + struct lantern_merkle_cache_box state_root; + struct lantern_merkle_cache_box validators; +}; + +static void merkle_cache_box_reset(struct lantern_merkle_cache_box *box) { + if (!box) { + return; + } + free(box->nodes); + free(box->leaf_dirty_bits); + free(box->leaf_dirty_word_idx); + free(box->parent_dirty_bits[0]); + free(box->parent_dirty_bits[1]); + free(box->parent_dirty_word_idx[0]); + free(box->parent_dirty_word_idx[1]); + free(box->gather_pairs); + free(box->gather_hashes); + free(box->gather_parent_indices); + free(box->token_values); + free(box->token_valid_bits); + free(box->root_batch_roots); + memset(box, 0, sizeof(*box)); +} + +static void *cache_alloc(size_t count, size_t item_size) { + return count == 0u ? NULL : calloc(count, item_size); +} + +static bool cache_allocation_missing(const void *ptr, size_t count) { + return count != 0u && !ptr; +} + +static ssz_error_t merkle_cache_box_bind( + struct lantern_merkle_cache_box *box, + uint64_t initial_leaf_count, + uint64_t leaf_limit, + uint64_t reserved_leaf_capacity, + uint64_t logical_length, + bool mix_in_length, + bool with_tokens) { + if (!box) { + return SSZ_ERR_INVALID_ARGUMENT; + } + if (box->bound) { + return SSZ_SUCCESS; + } + + ssz_merkle_cache_config_t config; + memset(&config, 0, sizeof(config)); + config.struct_size = sizeof(config); + config.initial_leaf_count = initial_leaf_count; + config.leaf_limit = leaf_limit; + config.reserved_leaf_capacity = reserved_leaf_capacity; + config.logical_length = logical_length; + config.mix_in_length = mix_in_length; + config.hash_fn = NULL; + + ssz_merkle_cache_requirements_t req; + ssz_error_t err = ssz_merkle_cache_requirements(&config, &req); + if (err != SSZ_SUCCESS) { + return err; + } + + box->nodes = cache_alloc(req.nodes_count, sizeof(*box->nodes)); + box->leaf_dirty_bits = cache_alloc(req.leaf_dirty_words, sizeof(*box->leaf_dirty_bits)); + box->leaf_dirty_word_idx = cache_alloc(req.leaf_dirty_words, sizeof(*box->leaf_dirty_word_idx)); + box->parent_dirty_bits[0] = cache_alloc(req.parent_dirty_words, sizeof(*box->parent_dirty_bits[0])); + box->parent_dirty_bits[1] = cache_alloc(req.parent_dirty_words, sizeof(*box->parent_dirty_bits[1])); + box->parent_dirty_word_idx[0] = cache_alloc(req.parent_dirty_words, sizeof(*box->parent_dirty_word_idx[0])); + box->parent_dirty_word_idx[1] = cache_alloc(req.parent_dirty_words, sizeof(*box->parent_dirty_word_idx[1])); + box->gather_pairs = cache_alloc(req.gather_pairs_count, sizeof(*box->gather_pairs)); + box->gather_hashes = cache_alloc(req.gather_hashes_count, sizeof(*box->gather_hashes)); + box->gather_parent_indices = cache_alloc(req.gather_parent_indices_count, sizeof(*box->gather_parent_indices)); + box->root_batch_roots = cache_alloc(req.root_batch_roots_count, sizeof(*box->root_batch_roots)); + if (with_tokens) { + box->token_values = cache_alloc(req.token_values_count, sizeof(*box->token_values)); + box->token_valid_bits = cache_alloc(req.token_valid_words, sizeof(*box->token_valid_bits)); + } + + if (cache_allocation_missing(box->nodes, req.nodes_count) + || cache_allocation_missing(box->leaf_dirty_bits, req.leaf_dirty_words) + || cache_allocation_missing(box->leaf_dirty_word_idx, req.leaf_dirty_words) + || cache_allocation_missing(box->parent_dirty_bits[0], req.parent_dirty_words) + || cache_allocation_missing(box->parent_dirty_bits[1], req.parent_dirty_words) + || cache_allocation_missing(box->parent_dirty_word_idx[0], req.parent_dirty_words) + || cache_allocation_missing(box->parent_dirty_word_idx[1], req.parent_dirty_words) + || cache_allocation_missing(box->gather_pairs, req.gather_pairs_count) + || cache_allocation_missing(box->gather_hashes, req.gather_hashes_count) + || cache_allocation_missing(box->gather_parent_indices, req.gather_parent_indices_count) + || cache_allocation_missing(box->root_batch_roots, req.root_batch_roots_count) + || (with_tokens && cache_allocation_missing(box->token_values, req.token_values_count)) + || (with_tokens && cache_allocation_missing(box->token_valid_bits, req.token_valid_words))) { + merkle_cache_box_reset(box); + return SSZ_ERR_HASH_FAILURE; + } + + box->storage.struct_size = sizeof(box->storage); + box->storage.nodes = box->nodes; + box->storage.nodes_count = req.nodes_count; + box->storage.leaf_dirty_bits = box->leaf_dirty_bits; + box->storage.leaf_dirty_words = req.leaf_dirty_words; + box->storage.leaf_dirty_word_idx = box->leaf_dirty_word_idx; + box->storage.leaf_dirty_word_idx_count = req.leaf_dirty_words; + box->storage.parent_dirty_bits[0] = box->parent_dirty_bits[0]; + box->storage.parent_dirty_bits[1] = box->parent_dirty_bits[1]; + box->storage.parent_dirty_words = req.parent_dirty_words; + box->storage.parent_dirty_word_idx[0] = box->parent_dirty_word_idx[0]; + box->storage.parent_dirty_word_idx[1] = box->parent_dirty_word_idx[1]; + box->storage.parent_dirty_word_idx_count = req.parent_dirty_words; + box->storage.gather_pairs = box->gather_pairs; + box->storage.gather_pairs_count = req.gather_pairs_count; + box->storage.gather_hashes = box->gather_hashes; + box->storage.gather_hashes_count = req.gather_hashes_count; + box->storage.gather_parent_indices = box->gather_parent_indices; + box->storage.gather_parent_indices_count = req.gather_parent_indices_count; + box->storage.token_values = box->token_values; + box->storage.token_values_count = with_tokens ? req.token_values_count : 0u; + box->storage.token_valid_bits = box->token_valid_bits; + box->storage.token_valid_words = with_tokens ? req.token_valid_words : 0u; + + box->workspace.struct_size = sizeof(box->workspace); + box->workspace.root_batch_roots = box->root_batch_roots; + box->workspace.root_batch_roots_count = req.root_batch_roots_count; + + err = ssz_merkle_cache_bind(&config, &box->storage, &box->cache); + if (err != SSZ_SUCCESS) { + merkle_cache_box_reset(box); + return err; + } + box->bound = true; + return SSZ_SUCCESS; +} + +static struct lantern_state_hash_cache *state_hash_cache_ensure(LanternState *state) { + if (!state) { + return NULL; + } + if (!state->hash_cache) { + state->hash_cache = calloc(1u, sizeof(*state->hash_cache)); + } + return state->hash_cache; +} + +void lantern_state_hash_cache_reset(LanternState *state) { + if (!state || !state->hash_cache) { + return; + } + merkle_cache_box_reset(&state->hash_cache->state_root); + merkle_cache_box_reset(&state->hash_cache->validators); + free(state->hash_cache); + state->hash_cache = NULL; +} + +static ssz_error_t hash_state_validators_stateless(const LanternState *state, LanternRoot *out_root) { + if (!state || !out_root) { + return SSZ_ERR_INVALID_ARGUMENT; + } + if (state->validator_count == 0) { + return lantern_hash_tree_root_validators(NULL, 0, out_root); + } + if (!state->validators) { + return SSZ_ERR_INVALID_ARGUMENT; + } + ssz_chunk_t *validator_chunks = calloc(state->validator_count, sizeof(*validator_chunks)); + if (!validator_chunks) { + return SSZ_ERR_HASH_FAILURE; + } + for (size_t i = 0; i < state->validator_count; ++i) { + LanternRoot validator_root; + ssz_error_t err = hash_validator( + state->validators[i].attestation_pubkey, + state->validators[i].proposal_pubkey, + state->validators[i].index, + &validator_root); + if (err != SSZ_SUCCESS) { + free(validator_chunks); + return err; + } + chunk_from_root(&validator_root, &validator_chunks[i]); + } + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_list_roots( + validator_chunks, + state->validator_count, + LANTERN_VALIDATOR_REGISTRY_LIMIT, + NULL, + NULL, + &root); + free(validator_chunks); + if (err != SSZ_SUCCESS) { + return err; + } + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; +} + +struct validator_cache_ctx { + const LanternState *state; +}; + +static void fnv1a_update(uint64_t *hash, const uint8_t *bytes, size_t len) { + const uint64_t fnv_prime = UINT64_C(1099511628211); + if (!hash || (!bytes && len > 0u)) { + return; + } + for (size_t i = 0; i < len; ++i) { + *hash ^= bytes[i]; + *hash *= fnv_prime; + } +} + +static uint64_t validator_cache_token_one(const LanternValidator *validator) { + uint64_t hash = UINT64_C(1469598103934665603); + fnv1a_update(&hash, validator->attestation_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE); + fnv1a_update(&hash, validator->proposal_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE); + uint8_t index_le[sizeof(uint64_t)]; + for (size_t i = 0; i < sizeof(index_le); ++i) { + index_le[i] = (uint8_t)((validator->index >> (i * 8u)) & 0xFFu); + } + fnv1a_update(&hash, index_le, sizeof(index_le)); + return hash == 0u ? 1u : hash; +} + +static ssz_error_t validator_cache_token(const void *ctx, uint64_t member_id, uint64_t *out_token) { + const struct validator_cache_ctx *cache_ctx = ctx; + if (!cache_ctx || !cache_ctx->state || !out_token || member_id >= cache_ctx->state->validator_count) { + return SSZ_ERR_INVALID_ARGUMENT; } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], message_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], signatures_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 2, 0, out_root); + *out_token = validator_cache_token_one(&cache_ctx->state->validators[member_id]); + return SSZ_SUCCESS; } -int lantern_hash_tree_root_state(const LanternState *state, LanternRoot *out_root) { +static ssz_error_t validator_cache_root_one( + const LanternState *state, + uint64_t member_id, + ssz_chunk_t *out_root) { + if (!state || !state->validators || !out_root || member_id >= state->validator_count) { + return SSZ_ERR_INVALID_ARGUMENT; + } + LanternRoot root; + const LanternValidator *validator = &state->validators[member_id]; + LANTERN_RETURN_IF_SSZ_ERROR(hash_validator( + validator->attestation_pubkey, + validator->proposal_pubkey, + validator->index, + &root)); + chunk_from_root(&root, out_root); + return SSZ_SUCCESS; +} + +static ssz_error_t validator_cache_root(const void *ctx, uint64_t member_id, ssz_chunk_t *out_root) { + const struct validator_cache_ctx *cache_ctx = ctx; + return validator_cache_root_one(cache_ctx ? cache_ctx->state : NULL, member_id, out_root); +} + +static ssz_error_t validator_cache_root_batch( + const void *ctx, + uint64_t start_index, + uint64_t count, + ssz_chunk_t *out_roots) { + const struct validator_cache_ctx *cache_ctx = ctx; + if (!cache_ctx || !cache_ctx->state || !out_roots) { + return SSZ_ERR_INVALID_ARGUMENT; + } + for (uint64_t i = 0; i < count; ++i) { + ssz_error_t err = validator_cache_root_one(cache_ctx->state, start_index + i, &out_roots[i]); + if (err != SSZ_SUCCESS) { + return err; + } + } + return SSZ_SUCCESS; +} + +static ssz_error_t hash_state_validators_cached(LanternState *state, LanternRoot *out_root) { if (!state || !out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; + } + struct lantern_state_hash_cache *cache = state_hash_cache_ensure(state); + if (!cache) { + return hash_state_validators_stateless(state, out_root); + } + if (merkle_cache_box_bind( + &cache->validators, + 0u, + LANTERN_VALIDATOR_REGISTRY_LIMIT, + LANTERN_VALIDATOR_REGISTRY_LIMIT, + 0u, + true, + true) + != SSZ_SUCCESS) { + return hash_state_validators_stateless(state, out_root); + } + + struct validator_cache_ctx ctx = {.state = state}; + ssz_member_codec_t codec = { + .ctx = &ctx, + .write = NULL, + .read = NULL, + .root = validator_cache_root, + }; + ssz_merkle_cache_sync_composite_opts_t opts; + memset(&opts, 0, sizeof(opts)); + opts.struct_size = sizeof(opts); + opts.ctx = &ctx; + opts.token = validator_cache_token; + opts.root_batch = validator_cache_root_batch; + opts.workspace = &cache->validators.workspace; + + ssz_error_t err = ssz_merkle_cache_sync_composite( + &cache->validators.cache, + state->validator_count, + LANTERN_VALIDATOR_REGISTRY_LIMIT, + &codec, + &opts); + if (err != SSZ_SUCCESS) { + return hash_state_validators_stateless(state, out_root); + } + ssz_chunk_t root; + if (ssz_merkle_cache_root(&cache->validators.cache, &root) != SSZ_SUCCESS) { + return hash_state_validators_stateless(state, out_root); + } + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; +} + +static ssz_error_t state_field_chunks( + const LanternState *state, + const LanternRoot *validators_root, + ssz_chunk_t chunks[10]) { + if (!state || !validators_root || !chunks) { + return SSZ_ERR_INVALID_ARGUMENT; } LanternRoot config_root; @@ -975,157 +991,122 @@ int lantern_hash_tree_root_state(const LanternState *state, LanternRoot *out_roo LanternRoot justified_slots_root; LanternRoot justification_roots_root; LanternRoot justification_validators_root; - LanternRoot validators_root; - memset(&validators_root, 0, sizeof(validators_root)); - if (lantern_hash_tree_root_config(&state->config, &config_root) != 0) { - return -1; - } - if (lantern_hash_tree_root_block_header(&state->latest_block_header, &header_root) != 0) { - return -1; - } - if (lantern_hash_tree_root_checkpoint(&state->latest_justified, &justified_root) != 0) { - return -1; - } - if (lantern_hash_tree_root_checkpoint(&state->latest_finalized, &finalized_root) != 0) { - return -1; - } - if (state->historical_block_hashes.length == 0) { - if (hash_empty_list_root(LANTERN_HISTORICAL_ROOTS_LIMIT, &historical_root) != 0) { - return -1; - } - } else if (lantern_merkleize_root_list(&state->historical_block_hashes, LANTERN_HISTORICAL_ROOTS_LIMIT, &historical_root) != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_config(&state->config, &config_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_block_header(&state->latest_block_header, &header_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_checkpoint(&state->latest_justified, &justified_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_hash_tree_root_checkpoint(&state->latest_finalized, &finalized_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_merkleize_root_list(&state->historical_block_hashes, LANTERN_HISTORICAL_ROOTS_LIMIT, &historical_root)); size_t bits_per_chunk = SSZ_BYTES_PER_CHUNK * 8u; size_t justified_chunk_limit = (LANTERN_HISTORICAL_ROOTS_LIMIT + bits_per_chunk - 1u) / bits_per_chunk; - if (state->justified_slots.bit_length == 0) { - if (hash_empty_bitlist_root(LANTERN_HISTORICAL_ROOTS_LIMIT, &justified_slots_root) != 0) { - return -1; - } - } else if (lantern_merkleize_bitlist(&state->justified_slots, justified_chunk_limit, &justified_slots_root) != 0) { - return -1; - } - if (state->justification_roots.length == 0) { - if (hash_empty_list_root(LANTERN_HISTORICAL_ROOTS_LIMIT, &justification_roots_root) != 0) { - return -1; - } - } else if (lantern_merkleize_root_list(&state->justification_roots, LANTERN_HISTORICAL_ROOTS_LIMIT, &justification_roots_root) - != 0) { - return -1; - } + LANTERN_RETURN_IF_SSZ_ERROR(lantern_merkleize_bitlist(&state->justified_slots, justified_chunk_limit, &justified_slots_root)); + LANTERN_RETURN_IF_SSZ_ERROR(lantern_merkleize_root_list( + &state->justification_roots, + LANTERN_HISTORICAL_ROOTS_LIMIT, + &justification_roots_root)); size_t justification_validators_chunk_limit = (LANTERN_JUSTIFICATION_VALIDATORS_LIMIT + bits_per_chunk - 1u) / bits_per_chunk; - if (state->justification_validators.bit_length == 0) { - if (hash_empty_bitlist_root(LANTERN_JUSTIFICATION_VALIDATORS_LIMIT, &justification_validators_root) != 0) { - return -1; - } - } else if (lantern_merkleize_bitlist( - &state->justification_validators, - justification_validators_chunk_limit, - &justification_validators_root) - != 0) { - return -1; + LANTERN_RETURN_IF_SSZ_ERROR(lantern_merkleize_bitlist( + &state->justification_validators, + justification_validators_chunk_limit, + &justification_validators_root)); + + chunk_from_root(&config_root, &chunks[0]); + LANTERN_RETURN_IF_SSZ_ERROR(chunk_from_uint64(state->slot, &chunks[1])); + chunk_from_root(&header_root, &chunks[2]); + chunk_from_root(&justified_root, &chunks[3]); + chunk_from_root(&finalized_root, &chunks[4]); + chunk_from_root(&historical_root, &chunks[5]); + chunk_from_root(&justified_slots_root, &chunks[6]); + chunk_from_root(validators_root, &chunks[7]); + chunk_from_root(&justification_roots_root, &chunks[8]); + chunk_from_root(&justification_validators_root, &chunks[9]); + return SSZ_SUCCESS; +} + +ssz_error_t lantern_hash_tree_root_state(const LanternState *state, LanternRoot *out_root) { + if (!state || !out_root) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (state->validator_count == 0) { - if (lantern_hash_tree_root_validators(NULL, 0, &validators_root) != 0) { - return -1; - } - } else { - if (!state->validators || state->validator_count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; - } - uint8_t *validator_chunks = malloc(state->validator_count * SSZ_BYTES_PER_CHUNK); - if (!validator_chunks) { - return -1; - } - for (size_t i = 0; i < state->validator_count; ++i) { - LanternRoot validator_root; - if (hash_validator( - state->validators[i].attestation_pubkey, - state->validators[i].proposal_pubkey, - state->validators[i].index, - &validator_root) - != 0) { - free(validator_chunks); - return -1; - } - memcpy( - validator_chunks + (i * SSZ_BYTES_PER_CHUNK), - validator_root.bytes, - SSZ_BYTES_PER_CHUNK); - } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - ssz_error_t validator_err = - ssz_merkleize(validator_chunks, state->validator_count, LANTERN_VALIDATOR_REGISTRY_LIMIT, temp_root); - free(validator_chunks); - if (validator_err != SSZ_SUCCESS) { - return -1; - } - validator_err = ssz_mix_in_length(temp_root, (uint64_t)state->validator_count, validators_root.bytes); - if (validator_err != SSZ_SUCCESS) { - return -1; - } + LanternRoot validators_root; + LANTERN_RETURN_IF_SSZ_ERROR(hash_state_validators_stateless(state, &validators_root)); + ssz_chunk_t chunks[10]; + LANTERN_RETURN_IF_SSZ_ERROR(state_field_chunks(state, &validators_root, chunks)); + return merkleize_chunks(chunks, 10, 0, out_root); +} + +ssz_error_t lantern_hash_tree_root_state_cached(LanternState *state, LanternRoot *out_root) { + if (!state || !out_root) { + return SSZ_ERR_INVALID_ARGUMENT; } + LanternRoot validators_root; + LANTERN_RETURN_IF_SSZ_ERROR(hash_state_validators_cached(state, &validators_root)); + ssz_chunk_t chunks[10]; + LANTERN_RETURN_IF_SSZ_ERROR(state_field_chunks(state, &validators_root, chunks)); - uint8_t chunks[10][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], config_root.bytes, SSZ_BYTES_PER_CHUNK); - chunk_from_uint64(state->slot, chunks[1]); - memcpy(chunks[2], header_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[3], justified_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[4], finalized_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[5], historical_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[6], justified_slots_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[7], validators_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[8], justification_roots_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[9], justification_validators_root.bytes, SSZ_BYTES_PER_CHUNK); - return merkleize_chunks(&chunks[0][0], 10, 0, out_root); + struct lantern_state_hash_cache *cache = state_hash_cache_ensure(state); + if (!cache + || merkle_cache_box_bind(&cache->state_root, 0u, SSZ_NO_LIMIT, 10u, 0u, false, false) != SSZ_SUCCESS) { + return merkleize_chunks(chunks, 10, 0, out_root); + } + if (ssz_merkle_cache_update_root_range(&cache->state_root.cache, 0u, chunks, 10u) != SSZ_SUCCESS) { + return merkleize_chunks(chunks, 10, 0, out_root); + } + ssz_chunk_t root; + if (ssz_merkle_cache_root(&cache->state_root.cache, &root) != SSZ_SUCCESS) { + return merkleize_chunks(chunks, 10, 0, out_root); + } + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } -int lantern_hash_tree_root_validators(const uint8_t *pubkeys, size_t count, LanternRoot *out_root) { +ssz_error_t lantern_hash_tree_root_validators(const uint8_t *pubkeys, size_t count, LanternRoot *out_root) { return lantern_hash_tree_root_validators_dual(pubkeys, pubkeys, count, out_root); } -int lantern_hash_tree_root_validators_dual( +ssz_error_t lantern_hash_tree_root_validators_dual( const uint8_t *attestation_pubkeys, const uint8_t *proposal_pubkeys, size_t count, LanternRoot *out_root) { if (!out_root) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } - uint8_t *chunks = NULL; + ssz_chunk_t *chunks = NULL; if (count > 0) { if (!attestation_pubkeys || !proposal_pubkeys) { - return -1; - } - if (count > SIZE_MAX / SSZ_BYTES_PER_CHUNK) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } - chunks = malloc(count * SSZ_BYTES_PER_CHUNK); + chunks = calloc(count, sizeof(*chunks)); if (!chunks) { - return -1; + return SSZ_ERR_HASH_FAILURE; } for (size_t i = 0; i < count; ++i) { LanternRoot validator_root; - if (hash_validator( + ssz_error_t err = hash_validator( attestation_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), proposal_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), (uint64_t)i, - &validator_root) - != 0) { + &validator_root); + if (err != SSZ_SUCCESS) { free(chunks); - return -1; + return err; } - memcpy(chunks + (i * SSZ_BYTES_PER_CHUNK), validator_root.bytes, SSZ_BYTES_PER_CHUNK); + chunk_from_root(&validator_root, &chunks[i]); } } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - ssz_error_t err = ssz_merkleize(chunks, count, LANTERN_VALIDATOR_REGISTRY_LIMIT, temp_root); + ssz_chunk_t root; + ssz_error_t err = ssz_hash_tree_root_list_roots( + chunks, + count, + LANTERN_VALIDATOR_REGISTRY_LIMIT, + NULL, + NULL, + &root); free(chunks); if (err != SSZ_SUCCESS) { - return -1; + return err; } - err = ssz_mix_in_length(temp_root, (uint64_t)count, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + root_from_chunk(&root, out_root); + return SSZ_SUCCESS; } diff --git a/src/consensus/signature.c b/src/consensus/signature.c index 9133251..df8a942 100644 --- a/src/consensus/signature.c +++ b/src/consensus/signature.c @@ -34,8 +34,6 @@ static bool bytes_are_zero(const uint8_t *bytes, size_t length) { struct lantern_recursive_child_input { struct PQSignatureSchemePublicKey **pubkey_handles; - uint8_t *canonical_bytes; - size_t canonical_length; }; static pthread_once_t g_xmss_verifier_setup_once = PTHREAD_ONCE_INIT; @@ -66,7 +64,6 @@ static void lantern_recursive_child_input_reset(struct lantern_recursive_child_i } } free(child->pubkey_handles); - free(child->canonical_bytes); memset(child, 0, sizeof(*child)); } @@ -158,64 +155,7 @@ static bool prepare_recursive_child( return true; } - /* Legacy single-participant proofs in Lantern may still be stored as raw signature bytes. - * Canonicalize them to AggregatedXMSS bytes before passing them to recursive aggregation. - */ - if (participant_count != 1u || proof->proof_data.length != LANTERN_SIGNATURE_SIZE) { - return false; - } - - struct PQSignature *signature_handle = NULL; - enum PQSigningError sig_err = pq_signature_deserialize( - proof->proof_data.data, - proof->proof_data.length, - &signature_handle); - if (sig_err != Success || !signature_handle) { - if (signature_handle) { - pq_signature_free(signature_handle); - } - return false; - } - - uint8_t *buffer = malloc(LANTERN_AGG_PROOF_MAX_BYTES); - if (!buffer) { - pq_signature_free(signature_handle); - return false; - } - - const struct PQSignature *signature_refs[1] = {signature_handle}; - uintptr_t written_len = 0u; - pq_xmss_aggregation_setup_prover(); - enum PQSigningError agg_err = pq_aggregate_signatures( - (const struct PQSignatureSchemePublicKey *const *)out_child->pubkey_handles, - signature_refs, - 1u, - message->bytes, - LANTERN_ROOT_SIZE, - epoch, - buffer, - LANTERN_AGG_PROOF_MAX_BYTES, - &written_len); - pq_signature_free(signature_handle); - if (agg_err != Success || written_len == 0u || written_len > LANTERN_AGG_PROOF_MAX_BYTES) { - free(buffer); - return false; - } - - out_child->canonical_bytes = malloc((size_t)written_len); - if (!out_child->canonical_bytes) { - free(buffer); - return false; - } - memcpy(out_child->canonical_bytes, buffer, (size_t)written_len); - out_child->canonical_length = (size_t)written_len; - free(buffer); - - out_input->pubkeys = (const struct PQSignatureSchemePublicKey *const *)out_child->pubkey_handles; - out_input->pubkey_count = participant_count; - out_input->agg_bytes = out_child->canonical_bytes; - out_input->agg_len = out_child->canonical_length; - return true; + return false; } static void log_agg_proof_preview(const LanternByteList *proof) { diff --git a/src/consensus/ssz.c b/src/consensus/ssz.c index 6f0213d..23ee5aa 100644 --- a/src/consensus/ssz.c +++ b/src/consensus/ssz.c @@ -1,659 +1,522 @@ #include "lantern/consensus/ssz.h" -#include #include #include #include -#include "ssz_constants.h" -#include "ssz_deserialize.h" -#include "ssz_serialize.h" +#include "ssz.h" #include "lantern/consensus/state.h" -#include "lantern/support/log.h" -static int write_u32(uint8_t *out, size_t remaining, uint32_t value) { - if (!out || remaining < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; +static const size_t CONFIG_FIELDS[] = { + sizeof(uint64_t), +}; +static const ssz_container_schema_t CONFIG_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(CONFIG_FIELDS); + +static const size_t CHECKPOINT_FIELDS[] = { + LANTERN_ROOT_SIZE, + sizeof(uint64_t), +}; +static const ssz_container_schema_t CHECKPOINT_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(CHECKPOINT_FIELDS); + +static const size_t ATTESTATION_DATA_FIELDS[] = { + sizeof(uint64_t), + LANTERN_CHECKPOINT_SSZ_SIZE, + LANTERN_CHECKPOINT_SSZ_SIZE, + LANTERN_CHECKPOINT_SSZ_SIZE, +}; +static const ssz_container_schema_t ATTESTATION_DATA_SCHEMA = + SSZ_CONTAINER_SCHEMA_FROM_ARRAY(ATTESTATION_DATA_FIELDS); + +static const size_t VOTE_FIELDS[] = { + sizeof(uint64_t), + LANTERN_ATTESTATION_DATA_SSZ_SIZE, +}; +static const ssz_container_schema_t VOTE_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(VOTE_FIELDS); + +static const size_t SIGNED_VOTE_FIELDS[] = { + LANTERN_VOTE_SSZ_SIZE, + LANTERN_SIGNATURE_SIZE, +}; +static const ssz_container_schema_t SIGNED_VOTE_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(SIGNED_VOTE_FIELDS); + +static const size_t VALIDATOR_FIELDS[] = { + LANTERN_VALIDATOR_PUBKEY_SIZE, + LANTERN_VALIDATOR_PUBKEY_SIZE, + sizeof(uint64_t), +}; +static const ssz_container_schema_t VALIDATOR_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(VALIDATOR_FIELDS); + +static const size_t AGGREGATED_ATTESTATION_FIELDS[] = { + 0u, + LANTERN_ATTESTATION_DATA_SSZ_SIZE, +}; +static const ssz_container_schema_t AGGREGATED_ATTESTATION_SCHEMA = + SSZ_CONTAINER_SCHEMA_FROM_ARRAY(AGGREGATED_ATTESTATION_FIELDS); + +static const size_t AGGREGATED_SIGNATURE_PROOF_FIELDS[] = { + 0u, + 0u, +}; +static const ssz_container_schema_t AGGREGATED_SIGNATURE_PROOF_SCHEMA = + SSZ_CONTAINER_SCHEMA_FROM_ARRAY(AGGREGATED_SIGNATURE_PROOF_FIELDS); + +static const size_t SIGNED_AGGREGATED_ATTESTATION_FIELDS[] = { + LANTERN_ATTESTATION_DATA_SSZ_SIZE, + 0u, +}; +static const ssz_container_schema_t SIGNED_AGGREGATED_ATTESTATION_SCHEMA = + SSZ_CONTAINER_SCHEMA_FROM_ARRAY(SIGNED_AGGREGATED_ATTESTATION_FIELDS); + +static const size_t BLOCK_SIGNATURES_FIELDS[] = { + 0u, + LANTERN_SIGNATURE_SIZE, +}; +static const ssz_container_schema_t BLOCK_SIGNATURES_SCHEMA = + SSZ_CONTAINER_SCHEMA_FROM_ARRAY(BLOCK_SIGNATURES_FIELDS); + +static const size_t BLOCK_BODY_FIELDS[] = { + 0u, +}; +static const ssz_container_schema_t BLOCK_BODY_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(BLOCK_BODY_FIELDS); + +static const size_t BLOCK_HEADER_FIELDS[] = { + sizeof(uint64_t), + sizeof(uint64_t), + LANTERN_ROOT_SIZE, + LANTERN_ROOT_SIZE, + LANTERN_ROOT_SIZE, +}; +static const ssz_container_schema_t BLOCK_HEADER_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(BLOCK_HEADER_FIELDS); + +static const size_t BLOCK_FIELDS[] = { + sizeof(uint64_t), + sizeof(uint64_t), + LANTERN_ROOT_SIZE, + LANTERN_ROOT_SIZE, + 0u, +}; +static const ssz_container_schema_t BLOCK_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(BLOCK_FIELDS); + +static const size_t SIGNED_BLOCK_FIELDS[] = { + 0u, + 0u, +}; +static const ssz_container_schema_t SIGNED_BLOCK_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(SIGNED_BLOCK_FIELDS); + +static const size_t STATE_FIELDS[] = { + LANTERN_CONFIG_SSZ_SIZE, + sizeof(uint64_t), + LANTERN_BLOCK_HEADER_SSZ_SIZE, + LANTERN_CHECKPOINT_SSZ_SIZE, + LANTERN_CHECKPOINT_SSZ_SIZE, + 0u, + 0u, + 0u, + 0u, + 0u, +}; +static const ssz_container_schema_t STATE_SCHEMA = SSZ_CONTAINER_SCHEMA_FROM_ARRAY(STATE_FIELDS); + +static ssz_error_t lantern_rc_to_ssz(int rc) { + return rc == 0 ? SSZ_SUCCESS : SSZ_ERR_INVALID_ARGUMENT; +} + +static ssz_error_t prepare_output(size_t required, uint8_t *out, size_t out_len, size_t *written) { + if (!out && !written) { + return SSZ_ERR_INVALID_ARGUMENT; + } + if (out && out_len < required) { + return SSZ_ERR_BUFFER_TOO_SMALL; } - size_t written = SSZ_BYTE_SIZE_OF_UINT32; - ssz_error_t err = ssz_serialize_uint32(&value, out, &written); - if (err != SSZ_SUCCESS || written != SSZ_BYTE_SIZE_OF_UINT32) { - return -1; + if (written) { + *written = required; } - return 0; + return SSZ_SUCCESS; } -static int read_u32(const uint8_t *data, size_t remaining, uint32_t *value) { - if (!data || !value || remaining < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; +static ssz_error_t write_bytes( + const uint8_t *src, + size_t src_len, + uint8_t *out, + size_t out_len, + size_t *written) { + ssz_error_t err = SSZ_SUCCESS; + if (src_len > 0u && !src) { + err = SSZ_ERR_INVALID_ARGUMENT; + } else { + err = prepare_output(src_len, out, out_len, written); + if (err == SSZ_SUCCESS && out && src_len > 0u) { + memcpy(out, src, src_len); + } } - ssz_error_t err = ssz_deserialize_uint32(data, SSZ_BYTE_SIZE_OF_UINT32, value); - return err == SSZ_SUCCESS ? 0 : -1; + return err; } -static int write_u64(uint8_t *out, size_t remaining, uint64_t value) { - if (!out || remaining < SSZ_BYTE_SIZE_OF_UINT64) { - return -1; +static ssz_error_t read_bytes(uint8_t *dst, size_t dst_len, const uint8_t *data, size_t data_len) { + if (!dst || (!data && data_len > 0u)) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t written = SSZ_BYTE_SIZE_OF_UINT64; - ssz_error_t err = ssz_serialize_uint64(&value, out, &written); - if (err != SSZ_SUCCESS || written != SSZ_BYTE_SIZE_OF_UINT64) { - return -1; + if (data_len != dst_len) { + return SSZ_ERR_ENCODING_INVALID; } - return 0; + if (dst_len > 0u) { + memcpy(dst, data, dst_len); + } + return SSZ_SUCCESS; } -static int read_u64(const uint8_t *data, size_t remaining, uint64_t *value) { - if (!data || !value || remaining < SSZ_BYTE_SIZE_OF_UINT64) { - return -1; +static ssz_error_t write_u64(uint64_t value, uint8_t *out, size_t out_len, size_t *written) { + ssz_error_t err = prepare_output(sizeof(uint64_t), out, out_len, written); + if (err == SSZ_SUCCESS && out) { + err = ssz_serialize_uint64(value, out); } - ssz_error_t err = ssz_deserialize_uint64(data, SSZ_BYTE_SIZE_OF_UINT64, value); - return err == SSZ_SUCCESS ? 0 : -1; + return err; } -static int write_root(uint8_t *out, size_t remaining, const LanternRoot *root) { - if (!out || !root || remaining < LANTERN_ROOT_SIZE) { - return -1; - } - memcpy(out, root->bytes, LANTERN_ROOT_SIZE); - return 0; +static ssz_error_t read_u64(const uint8_t *data, size_t data_len, uint64_t *value) { + return ssz_deserialize_uint64(data, data_len, value); } -static int read_root(const uint8_t *data, size_t remaining, LanternRoot *root) { - if (!data || !root || remaining < LANTERN_ROOT_SIZE) { - return -1; - } - memcpy(root->bytes, data, LANTERN_ROOT_SIZE); - return 0; +static ssz_error_t write_root(const LanternRoot *root, uint8_t *out, size_t out_len, size_t *written) { + return write_bytes(root ? root->bytes : NULL, LANTERN_ROOT_SIZE, out, out_len, written); } -static void set_written(size_t *written, size_t value) { - if (written) { - *written = value; - } +static ssz_error_t read_root(const uint8_t *data, size_t data_len, LanternRoot *root) { + return read_bytes(root ? root->bytes : NULL, LANTERN_ROOT_SIZE, data, data_len); } -static int encode_aggregated_attestations( - const LanternAggregatedAttestations *attestations, - uint8_t *out, - size_t remaining, - size_t *written); -static int decode_aggregated_attestations( - LanternAggregatedAttestations *attestations, - const uint8_t *data, - size_t data_len); -static int decode_legacy_plain_attestations_as_aggregated( - LanternAggregatedAttestations *attestations, - const uint8_t *data, - size_t data_len); -static int encode_legacy_plain_attestations_from_aggregated( - const LanternAggregatedAttestations *attestations, - uint8_t *out, - size_t remaining, - size_t *written); -static int encode_attestation_signatures( - const LanternAttestationSignatures *signatures, - uint8_t *out, - size_t remaining, - size_t *written); -static int decode_attestation_signatures( - LanternAttestationSignatures *signatures, - const uint8_t *data, - size_t data_len); -static int encode_attestation_data( - const LanternAttestationData *data, +static ssz_error_t write_signature( + const LanternSignature *signature, uint8_t *out, size_t out_len, - size_t *written); -static int decode_attestation_data( - LanternAttestationData *data, - const uint8_t *raw, - size_t raw_len); + size_t *written) { + return write_bytes(signature ? signature->bytes : NULL, LANTERN_SIGNATURE_SIZE, out, out_len, written); +} -static size_t bitlist_encoded_size(size_t bit_length) { - if (bit_length == 0) { - return 1; - } - size_t byte_len = (bit_length + 7u) / 8u; - if ((bit_length % 8u) == 0) { - return byte_len + 1u; - } - return byte_len; +static ssz_error_t read_signature(const uint8_t *data, size_t data_len, LanternSignature *signature) { + return read_bytes(signature ? signature->bytes : NULL, LANTERN_SIGNATURE_SIZE, data, data_len); } -static int encode_block_signatures( - const LanternBlockSignatures *signatures, +static ssz_error_t encode_bitlist( + const struct lantern_bitlist *list, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!signatures || !out) { - return -1; - } - const size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_SIGNATURE_SIZE; - if (fixed_section > UINT32_MAX || remaining < fixed_section) { - return -1; - } - - size_t att_written = 0; - if (encode_attestation_signatures( - &signatures->attestation_signatures, - out + fixed_section, - remaining - fixed_section, - &att_written) - != 0) { - return -1; - } - if (write_u32(out, remaining, (uint32_t)fixed_section) != 0) { - return -1; - } - memcpy(out + SSZ_BYTE_SIZE_OF_UINT32, signatures->proposer_signature.bytes, LANTERN_SIGNATURE_SIZE); - - size_t total = fixed_section + att_written; - if (total > remaining) { - return -1; + if (!list) { + return SSZ_ERR_INVALID_ARGUMENT; } - set_written(written, total); - return 0; + size_t byte_len = (list->bit_length + 7u) / 8u; + return ssz_serialize_bitlist( + list->bytes, + byte_len, + list->bit_length, + SSZ_NO_LIMIT, + out, + out_len, + written); } -static int decode_block_signatures_standard( - LanternBlockSignatures *signatures, +static ssz_error_t decode_bitlist_with_limit( + struct lantern_bitlist *list, const uint8_t *data, - size_t data_len) { - if (!signatures || !data) { - return -1; - } - const size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_SIGNATURE_SIZE; - if (data_len < fixed_section) { - return -1; - } - uint32_t att_offset = 0; - if (read_u32(data, data_len, &att_offset) != 0) { - return -1; - } - if (att_offset != fixed_section || att_offset > data_len) { - return -1; - } - memcpy( - signatures->proposer_signature.bytes, - data + SSZ_BYTE_SIZE_OF_UINT32, - LANTERN_SIGNATURE_SIZE); - size_t att_size = data_len - att_offset; - if (decode_attestation_signatures(&signatures->attestation_signatures, data + att_offset, att_size) != 0) { - return -1; + size_t data_len, + uint64_t max_bits) { + if (!list) { + return SSZ_ERR_INVALID_ARGUMENT; + } + size_t scratch_len = data_len == 0u ? 1u : data_len; + uint8_t *decoded = calloc(scratch_len, sizeof(*decoded)); + if (!decoded) { + return SSZ_ERR_INVALID_ARGUMENT; + } + uint64_t bit_length = 0u; + ssz_error_t err = ssz_deserialize_bitlist( + data, + data_len, + max_bits, + decoded, + scratch_len, + &bit_length); + if (err == SSZ_SUCCESS && bit_length > SIZE_MAX) { + err = SSZ_ERR_OVERFLOW; + } + if (err == SSZ_SUCCESS) { + err = lantern_rc_to_ssz(lantern_bitlist_resize(list, (size_t)bit_length)); + } + if (err == SSZ_SUCCESS) { + size_t byte_len = ((size_t)bit_length + 7u) / 8u; + if (byte_len > 0u) { + memcpy(list->bytes, decoded, byte_len); + } } - return 0; + free(decoded); + return err; } -static int decode_block_signatures_attestation_only( - LanternBlockSignatures *signatures, - const uint8_t *data, - size_t data_len) { - if (!signatures) { - return -1; - } - - if (decode_attestation_signatures(&signatures->attestation_signatures, data, data_len) != 0) { - return -1; - } - - memset(signatures->proposer_signature.bytes, 0, LANTERN_SIGNATURE_SIZE); - - static bool logged_att_only_decode = false; - if (!logged_att_only_decode) { - lantern_log_warn( - "ssz", - NULL, - "block signatures decoded using attestation-only layout len=%zu count=%zu", - data_len, - signatures->attestation_signatures.length); - logged_att_only_decode = true; - } - - return 0; +static ssz_error_t decode_bitlist(struct lantern_bitlist *list, const uint8_t *data, size_t data_len) { + return decode_bitlist_with_limit(list, data, data_len, SSZ_NO_LIMIT); } -static int decode_block_signatures_opaque( - LanternBlockSignatures *signatures, - size_t data_len) { - if (!signatures) { - return -1; - } - - if (lantern_attestation_signatures_resize(&signatures->attestation_signatures, 0) != 0) { - return -1; - } - memset(signatures->proposer_signature.bytes, 0, LANTERN_SIGNATURE_SIZE); - - static bool logged_opaque_decode = false; - if (!logged_opaque_decode) { - lantern_log_warn( - "ssz", - NULL, - "block signatures decoded using opaque fallback len=%zu", - data_len); - logged_opaque_decode = true; +static ssz_error_t encode_byte_list( + const LanternByteList *list, + uint8_t *out, + size_t out_len, + size_t *written) { + if (!list) { + return SSZ_ERR_INVALID_ARGUMENT; } - - return 0; + return ssz_serialize_list_fixed( + list->data, + list->length, + LANTERN_AGG_PROOF_MAX_BYTES, + sizeof(uint8_t), + out, + out_len, + written); } -static int decode_block_signatures( - LanternBlockSignatures *signatures, - const uint8_t *data, - size_t data_len) { - if (decode_block_signatures_standard(signatures, data, data_len) == 0) { - return 0; +static ssz_error_t decode_byte_list(LanternByteList *list, const uint8_t *data, size_t data_len) { + if (!list) { + return SSZ_ERR_INVALID_ARGUMENT; } - - if (decode_block_signatures_attestation_only(signatures, data, data_len) == 0) { - return 0; + if (data_len > LANTERN_AGG_PROOF_MAX_BYTES) { + return SSZ_ERR_LIMIT_EXCEEDED; } - - if (decode_block_signatures_opaque(signatures, data_len) == 0) { - return 0; + ssz_error_t err = lantern_rc_to_ssz(lantern_byte_list_resize(list, data_len)); + if (err != SSZ_SUCCESS) { + return err; } - - return -1; + uint64_t decoded_count = 0u; + err = ssz_deserialize_list_fixed( + data, + data_len, + LANTERN_AGG_PROOF_MAX_BYTES, + sizeof(uint8_t), + list->data, + list->capacity, + &decoded_count); + if (err == SSZ_SUCCESS && decoded_count != data_len) { + err = SSZ_ERR_ENCODING_INVALID; + } + return err; } -static int encode_root_list(const struct lantern_root_list *list, uint8_t *out, size_t remaining, size_t *written) { - if (!list || !out) { - return -1; - } - size_t root_bytes = list->length * LANTERN_ROOT_SIZE; - if (root_bytes > remaining) { - return -1; - } - if (root_bytes > 0 && !list->items) { - return -1; - } - if (root_bytes > 0) { - memcpy(out, list->items, root_bytes); +static ssz_error_t encode_root_list( + const struct lantern_root_list *list, + uint8_t *out, + size_t out_len, + size_t *written) { + if (!list) { + return SSZ_ERR_INVALID_ARGUMENT; } - set_written(written, root_bytes); - return 0; + return ssz_serialize_list_fixed( + (const uint8_t *)list->items, + list->length, + SSZ_NO_LIMIT, + LANTERN_ROOT_SIZE, + out, + out_len, + written); } -static int decode_root_list(struct lantern_root_list *list, const uint8_t *data, size_t data_len) { +static ssz_error_t decode_root_list(struct lantern_root_list *list, const uint8_t *data, size_t data_len) { if (!list) { - return -1; + return SSZ_ERR_INVALID_ARGUMENT; } - if (data_len == 0) { - return lantern_root_list_resize(list, 0); - } - if (!data || data_len % LANTERN_ROOT_SIZE != 0) { - return -1; + if (data_len % LANTERN_ROOT_SIZE != 0u) { + return SSZ_ERR_ENCODING_INVALID; } size_t count = data_len / LANTERN_ROOT_SIZE; - if (lantern_root_list_resize(list, count) != 0) { - return -1; - } - memcpy(list->items, data, data_len); - return 0; -} - -static int encode_validators_list( - const LanternValidator *validators, - size_t count, + ssz_error_t err = lantern_rc_to_ssz(lantern_root_list_resize(list, count)); + if (err != SSZ_SUCCESS) { + return err; + } + uint64_t decoded_count = 0u; + err = ssz_deserialize_list_fixed( + data, + data_len, + SSZ_NO_LIMIT, + LANTERN_ROOT_SIZE, + (uint8_t *)list->items, + count * sizeof(*list->items), + &decoded_count); + if (err == SSZ_SUCCESS && decoded_count != count) { + err = SSZ_ERR_ENCODING_INVALID; + } + return err; +} + +struct config_codec_ctx { + const LanternConfig *write; + LanternConfig *read; +}; + +static ssz_error_t config_write( + const void *ctx, + uint64_t member_id, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!out) { - return -1; - } - if (count == 0) { - set_written(written, 0); - return 0; - } - if (!validators) { - return -1; - } - if (count > SIZE_MAX / LANTERN_VALIDATOR_SSZ_SIZE) { - return -1; - } - size_t total = count * LANTERN_VALIDATOR_SSZ_SIZE; - if (total > remaining) { - return -1; - } - for (size_t i = 0; i < count; ++i) { - size_t base = i * LANTERN_VALIDATOR_SSZ_SIZE; - memcpy(out + base, validators[i].attestation_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE); - memcpy( - out + base + LANTERN_VALIDATOR_PUBKEY_SIZE, - validators[i].proposal_pubkey, - LANTERN_VALIDATOR_PUBKEY_SIZE); - if (write_u64(out + base + (2u * LANTERN_VALIDATOR_PUBKEY_SIZE), - remaining - base - (2u * LANTERN_VALIDATOR_PUBKEY_SIZE), - validators[i].index) != 0) { - return -1; - } + const struct config_codec_ctx *config_ctx = ctx; + if (!config_ctx || !config_ctx->write || member_id != 0u) { + return SSZ_ERR_INVALID_ARGUMENT; } - set_written(written, total); - return 0; + return write_u64(config_ctx->write->genesis_time, out, out_len, written); } -static int decode_validators_list( - LanternState *state, - const uint8_t *data, - size_t data_len) { - if (!state) { - return -1; - } - size_t count = 0; - bool legacy_layout = false; - if (state->config.num_validators != 0) { - count = (size_t)state->config.num_validators; - size_t expected_size = count * LANTERN_VALIDATOR_SSZ_SIZE; - size_t legacy_expected_size = count * LANTERN_VALIDATOR_SSZ_SIZE_LEGACY; - if (data_len == legacy_expected_size) { - legacy_layout = true; - } else if (data_len != expected_size) { - return -1; - } - } else { - if (data_len == 0) { - state->config.num_validators = 0; - return lantern_state_set_validator_pubkeys(state, NULL, 0); - } - if (data_len % LANTERN_VALIDATOR_SSZ_SIZE == 0) { - count = data_len / LANTERN_VALIDATOR_SSZ_SIZE; - } else if (data_len % LANTERN_VALIDATOR_SSZ_SIZE_LEGACY == 0) { - legacy_layout = true; - count = data_len / LANTERN_VALIDATOR_SSZ_SIZE_LEGACY; - } else { - return -1; - } - } - if (count == 0) { - state->config.num_validators = 0; - return lantern_state_set_validator_pubkeys(state, NULL, 0); - } - if (!data) { - return -1; - } - uint8_t *attestation_pubkeys = malloc(count * LANTERN_VALIDATOR_PUBKEY_SIZE); - uint8_t *proposal_pubkeys = malloc(count * LANTERN_VALIDATOR_PUBKEY_SIZE); - if (!attestation_pubkeys || !proposal_pubkeys) { - free(attestation_pubkeys); - free(proposal_pubkeys); - return -1; - } - for (size_t i = 0; i < count; ++i) { - const size_t base = legacy_layout - ? (i * LANTERN_VALIDATOR_SSZ_SIZE_LEGACY) - : (i * LANTERN_VALIDATOR_SSZ_SIZE); - memcpy( - attestation_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), - data + base, - LANTERN_VALIDATOR_PUBKEY_SIZE); - if (legacy_layout) { - memcpy( - proposal_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), - data + base, - LANTERN_VALIDATOR_PUBKEY_SIZE); - } else { - memcpy( - proposal_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), - data + base + LANTERN_VALIDATOR_PUBKEY_SIZE, - LANTERN_VALIDATOR_PUBKEY_SIZE); - } - } - int rc = lantern_state_set_validator_pubkeys_dual( - state, - attestation_pubkeys, - proposal_pubkeys, - count); - free(attestation_pubkeys); - free(proposal_pubkeys); - if (rc != 0) { - return -1; - } - /* Restore indices from SSZ data (state setters default to i). */ - for (size_t i = 0; i < count; ++i) { - uint64_t idx = 0; - const size_t base = legacy_layout - ? (i * LANTERN_VALIDATOR_SSZ_SIZE_LEGACY) - : (i * LANTERN_VALIDATOR_SSZ_SIZE); - const size_t index_offset = legacy_layout - ? (base + LANTERN_VALIDATOR_PUBKEY_SIZE) - : (base + (2u * LANTERN_VALIDATOR_PUBKEY_SIZE)); - if (read_u64(data + index_offset, - SSZ_BYTE_SIZE_OF_UINT64, &idx) != 0) { - return -1; - } - state->validators[i].index = idx; +static ssz_error_t config_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct config_codec_ctx *config_ctx = ctx; + if (!config_ctx || !config_ctx->read || member_id != 0u) { + return SSZ_ERR_INVALID_ARGUMENT; } - state->config.num_validators = count; - return 0; + config_ctx->read->num_validators = 0u; + return read_u64(data, data_len, &config_ctx->read->genesis_time); } -int lantern_ssz_encode_validator( - const LanternValidator *validator, +ssz_error_t lantern_ssz_encode_config( + const LanternConfig *config, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!validator || !out) { - return -1; - } - return encode_validators_list(validator, 1u, out, remaining, written); -} - -int lantern_ssz_decode_validator( - LanternValidator *validator, - const uint8_t *data, - size_t data_len) { - if (!validator || !data || data_len != LANTERN_VALIDATOR_SSZ_SIZE) { - return -1; - } - - LanternState scratch; - lantern_state_init(&scratch); - scratch.config.num_validators = 1u; - - int rc = decode_validators_list(&scratch, data, data_len); - if (rc == 0) { - if (scratch.validator_count != 1u || !scratch.validators) { - rc = -1; - } else { - *validator = scratch.validators[0]; - scratch.validators = NULL; - scratch.validator_count = 0u; - scratch.config.num_validators = 0u; - } - } - - lantern_state_reset(&scratch); - return rc; + struct config_codec_ctx ctx = {.write = config}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = config_write}; + return ssz_serialize_container(&CONFIG_SCHEMA, &codec, out, out_len, written); } -static int encode_bitlist(const struct lantern_bitlist *list, uint8_t *out, size_t remaining, size_t *written) { - if (!list || !out) { - return -1; - } - if (list->bit_length == 0) { - if (remaining < 1) { - return -1; - } - out[0] = 0x01; - set_written(written, 1); - return 0; - } - size_t byte_len = (list->bit_length + 7u) / 8u; - bool needs_extra = (list->bit_length % 8u) == 0; - size_t total = bitlist_encoded_size(list->bit_length); - if (total > remaining) { - return -1; - } - if (!list->bytes) { - return -1; - } - if (byte_len > 0) { - memcpy(out, list->bytes, byte_len); - } - uint8_t length_bit_index = (uint8_t)(list->bit_length % 8); - if (needs_extra) { - out[total - 1] = (uint8_t)(1u << length_bit_index); - } else { - out[byte_len - 1] |= (uint8_t)(1u << length_bit_index); - } - set_written(written, total); - return 0; +ssz_error_t lantern_ssz_decode_config(LanternConfig *config, const uint8_t *data, size_t data_len) { + struct config_codec_ctx ctx = {.read = config}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = config_read}; + return ssz_deserialize_container(data, data_len, &CONFIG_SCHEMA, &codec); } -static int decode_bitlist(struct lantern_bitlist *list, const uint8_t *data, size_t data_len) { - if (!list) { - return -1; - } - if (data_len == 0) { - return lantern_bitlist_resize(list, 0); - } - if (!data) { - return -1; - } - uint8_t last = data[data_len - 1]; - if (last == 0) { - return -1; - } - int msb = -1; - for (int i = 7; i >= 0; --i) { - if ((last >> i) & 1) { - msb = i; - break; - } - } - if (msb < 0) { - return -1; - } - size_t bit_length = (data_len - 1) * 8 + (size_t)msb; - if (lantern_bitlist_resize(list, bit_length) != 0) { - return -1; - } - size_t byte_len = (bit_length + 7) / 8; - if (byte_len > 0) { - memcpy(list->bytes, data, byte_len); - uint8_t remainder = (uint8_t)(bit_length % 8); - if (remainder != 0) { - uint8_t mask = (uint8_t)((1u << remainder) - 1u); - list->bytes[byte_len - 1] &= mask; - } - } - return 0; -} +struct checkpoint_codec_ctx { + const LanternCheckpoint *write; + LanternCheckpoint *read; +}; -static int decode_bitlist_with_limit( - struct lantern_bitlist *list, - const uint8_t *data, - size_t data_len, - size_t max_bits) { - if (decode_bitlist(list, data, data_len) != 0) { - return -1; +static ssz_error_t checkpoint_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct checkpoint_codec_ctx *checkpoint_ctx = ctx; + if (!checkpoint_ctx || !checkpoint_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (list->bit_length > max_bits) { - return -1; + switch (member_id) { + case 0: + return write_root(&checkpoint_ctx->write->root, out, out_len, written); + case 1: + return write_u64(checkpoint_ctx->write->slot, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - return 0; } -static int encode_byte_list(const LanternByteList *list, uint8_t *out, size_t remaining, size_t *written) { - if (!list || !out) { - return -1; - } - if (list->length > LANTERN_AGG_PROOF_MAX_BYTES) { - return -1; - } - if (list->length > remaining) { - return -1; +static ssz_error_t checkpoint_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct checkpoint_codec_ctx *checkpoint_ctx = ctx; + if (!checkpoint_ctx || !checkpoint_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (list->length > 0 && !list->data) { - return -1; + switch (member_id) { + case 0: + return read_root(data, data_len, &checkpoint_ctx->read->root); + case 1: + return read_u64(data, data_len, &checkpoint_ctx->read->slot); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - if (list->length > 0) { - memcpy(out, list->data, list->length); - } - set_written(written, list->length); - return 0; } -static int decode_byte_list(LanternByteList *list, const uint8_t *data, size_t data_len) { - if (!list) { - return -1; - } - if (data_len == 0) { - return lantern_byte_list_resize(list, 0); - } - if (!data) { - return -1; - } - if (data_len > LANTERN_AGG_PROOF_MAX_BYTES) { - return -1; - } - if (lantern_byte_list_resize(list, data_len) != 0) { - return -1; - } - memcpy(list->data, data, data_len); - return 0; +ssz_error_t lantern_ssz_encode_checkpoint( + const LanternCheckpoint *checkpoint, + uint8_t *out, + size_t out_len, + size_t *written) { + struct checkpoint_codec_ctx ctx = {.write = checkpoint}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = checkpoint_write}; + return ssz_serialize_container(&CHECKPOINT_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_encode_config(const LanternConfig *config, uint8_t *out, size_t out_len, size_t *written) { - if (!config || !out || out_len < LANTERN_CONFIG_SSZ_SIZE) { - return -1; - } - /* SSZ config only contains genesis_time to match Zeam/Ream's BeamStateConfig. - * num_validators is derived from the validators list, not stored in config. */ - if (write_u64(out, out_len, config->genesis_time) != 0) { - return -1; - } - set_written(written, SSZ_BYTE_SIZE_OF_UINT64); - return 0; +ssz_error_t lantern_ssz_decode_checkpoint( + LanternCheckpoint *checkpoint, + const uint8_t *data, + size_t data_len) { + struct checkpoint_codec_ctx ctx = {.read = checkpoint}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = checkpoint_read}; + return ssz_deserialize_container(data, data_len, &CHECKPOINT_SCHEMA, &codec); } -int lantern_ssz_decode_config(LanternConfig *config, const uint8_t *data, size_t data_len) { - if (!config || !data || data_len < LANTERN_CONFIG_SSZ_SIZE) { - return -1; - } - /* SSZ config only contains genesis_time to match Zeam/Ream's BeamStateConfig. - * num_validators is derived from the validators list, not stored in config. */ - if (read_u64(data, data_len, &config->genesis_time) != 0) { - return -1; - } - return 0; -} +struct attestation_data_codec_ctx { + const LanternAttestationData *write; + LanternAttestationData *read; +}; -int lantern_ssz_encode_checkpoint(const LanternCheckpoint *checkpoint, uint8_t *out, size_t out_len, size_t *written) { - if (!checkpoint || !out || out_len < LANTERN_CHECKPOINT_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (write_root(out + offset, out_len - offset, &checkpoint->root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (write_u64(out + offset, out_len - offset, checkpoint->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - set_written(written, offset); - return 0; +static ssz_error_t attestation_data_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct attestation_data_codec_ctx *data_ctx = ctx; + if (!data_ctx || !data_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + return write_u64(data_ctx->write->slot, out, out_len, written); + case 1: + return lantern_ssz_encode_checkpoint(&data_ctx->write->head, out, out_len, written); + case 2: + return lantern_ssz_encode_checkpoint(&data_ctx->write->target, out, out_len, written); + case 3: + return lantern_ssz_encode_checkpoint(&data_ctx->write->source, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +static ssz_error_t attestation_data_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct attestation_data_codec_ctx *data_ctx = ctx; + if (!data_ctx || !data_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + return read_u64(data, data_len, &data_ctx->read->slot); + case 1: + return lantern_ssz_decode_checkpoint(&data_ctx->read->head, data, data_len); + case 2: + return lantern_ssz_decode_checkpoint(&data_ctx->read->target, data, data_len); + case 3: + return lantern_ssz_decode_checkpoint(&data_ctx->read->source, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +static ssz_error_t encode_attestation_data( + const LanternAttestationData *data, + uint8_t *out, + size_t out_len, + size_t *written) { + struct attestation_data_codec_ctx ctx = {.write = data}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = attestation_data_write}; + return ssz_serialize_container(&ATTESTATION_DATA_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_decode_checkpoint(LanternCheckpoint *checkpoint, const uint8_t *data, size_t data_len) { - if (!checkpoint || !data || data_len != LANTERN_CHECKPOINT_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (read_root(data + offset, data_len - offset, &checkpoint->root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (read_u64(data + offset, data_len - offset, &checkpoint->slot) != 0) { - return -1; - } - return 0; +static ssz_error_t decode_attestation_data( + LanternAttestationData *data, + const uint8_t *raw, + size_t raw_len) { + struct attestation_data_codec_ctx ctx = {.read = data}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = attestation_data_read}; + return ssz_deserialize_container(raw, raw_len, &ATTESTATION_DATA_SCHEMA, &codec); } -int lantern_ssz_encode_attestation_data( +ssz_error_t lantern_ssz_encode_attestation_data( const LanternAttestationData *data, uint8_t *out, size_t out_len, @@ -661,1599 +524,1127 @@ int lantern_ssz_encode_attestation_data( return encode_attestation_data(data, out, out_len, written); } -int lantern_ssz_decode_attestation_data( +ssz_error_t lantern_ssz_decode_attestation_data( LanternAttestationData *data, const uint8_t *raw, size_t raw_len) { return decode_attestation_data(data, raw, raw_len); } -static int encode_attestation_data( - const LanternAttestationData *data, +struct vote_codec_ctx { + const LanternVote *write; + LanternVote *read; +}; + +static ssz_error_t vote_write( + const void *ctx, + uint64_t member_id, uint8_t *out, size_t out_len, size_t *written) { - if (!data || !out || out_len < LANTERN_ATTESTATION_DATA_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (write_u64(out + offset, out_len - offset, data->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - size_t tmp_written = 0; - if (lantern_ssz_encode_checkpoint(&data->head, out + offset, out_len - offset, &tmp_written) != 0) { - return -1; - } - offset += tmp_written; - if (lantern_ssz_encode_checkpoint(&data->target, out + offset, out_len - offset, &tmp_written) != 0) { - return -1; + const struct vote_codec_ctx *vote_ctx = ctx; + if (!vote_ctx || !vote_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - offset += tmp_written; - if (lantern_ssz_encode_checkpoint(&data->source, out + offset, out_len - offset, &tmp_written) != 0) { - return -1; + switch (member_id) { + case 0: + return write_u64(vote_ctx->write->validator_id, out, out_len, written); + case 1: + return encode_attestation_data(&vote_ctx->write->data, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - offset += tmp_written; - set_written(written, offset); - return 0; } -static int decode_attestation_data(LanternAttestationData *data, const uint8_t *raw, size_t raw_len) { - if (!data || !raw || raw_len < LANTERN_ATTESTATION_DATA_SSZ_SIZE) { - return -1; +static ssz_error_t vote_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct vote_codec_ctx *vote_ctx = ctx; + if (!vote_ctx || !vote_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t offset = 0; - if (read_u64(raw + offset, raw_len - offset, &data->slot) != 0) { - return -1; + switch (member_id) { + case 0: + return read_u64(data, data_len, &vote_ctx->read->validator_id); + case 1: + return decode_attestation_data(&vote_ctx->read->data, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (lantern_ssz_decode_checkpoint(&data->head, raw + offset, LANTERN_CHECKPOINT_SSZ_SIZE) != 0) { - return -1; - } - offset += LANTERN_CHECKPOINT_SSZ_SIZE; - if (lantern_ssz_decode_checkpoint(&data->target, raw + offset, LANTERN_CHECKPOINT_SSZ_SIZE) != 0) { - return -1; - } - offset += LANTERN_CHECKPOINT_SSZ_SIZE; - if (lantern_ssz_decode_checkpoint(&data->source, raw + offset, LANTERN_CHECKPOINT_SSZ_SIZE) != 0) { - return -1; - } - return 0; } -static int encode_vote_internal(const LanternVote *vote, uint8_t *out, size_t out_len, size_t *written) { - if (!vote || !out || out_len < LANTERN_VOTE_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (write_u64(out + offset, out_len - offset, vote->validator_id) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - size_t data_written = 0; - if (encode_attestation_data(&vote->data, out + offset, out_len - offset, &data_written) != 0) { - return -1; - } - offset += data_written; - - set_written(written, offset); - return 0; +static ssz_error_t encode_vote_internal( + const LanternVote *vote, + uint8_t *out, + size_t out_len, + size_t *written) { + struct vote_codec_ctx ctx = {.write = vote}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = vote_write}; + return ssz_serialize_container(&VOTE_SCHEMA, &codec, out, out_len, written); } -static int decode_vote_internal(LanternVote *vote, const uint8_t *data, size_t data_len) { - if (!vote || !data || data_len != LANTERN_VOTE_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (read_u64(data + offset, data_len - offset, &vote->validator_id) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (decode_attestation_data(&vote->data, data + offset, data_len - offset) != 0) { - return -1; - } - return 0; +static ssz_error_t decode_vote_internal(LanternVote *vote, const uint8_t *data, size_t data_len) { + struct vote_codec_ctx ctx = {.read = vote}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = vote_read}; + return ssz_deserialize_container(data, data_len, &VOTE_SCHEMA, &codec); } -int lantern_ssz_encode_vote(const LanternVote *vote, uint8_t *out, size_t out_len, size_t *written) { +ssz_error_t lantern_ssz_encode_vote( + const LanternVote *vote, + uint8_t *out, + size_t out_len, + size_t *written) { return encode_vote_internal(vote, out, out_len, written); } -int lantern_ssz_decode_vote(LanternVote *vote, const uint8_t *data, size_t data_len) { +ssz_error_t lantern_ssz_decode_vote(LanternVote *vote, const uint8_t *data, size_t data_len) { return decode_vote_internal(vote, data, data_len); } -int lantern_ssz_encode_signed_vote(const LanternSignedVote *vote, uint8_t *out, size_t out_len, size_t *written) { - if (!vote || !out || out_len < LANTERN_SIGNED_VOTE_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (encode_vote_internal(&vote->data, out + offset, out_len - offset, NULL) != 0) { - return -1; - } - offset += LANTERN_VOTE_SSZ_SIZE; - memcpy(out + offset, vote->signature.bytes, LANTERN_SIGNATURE_SIZE); - offset += LANTERN_SIGNATURE_SIZE; - set_written(written, offset); - return 0; -} +struct signed_vote_codec_ctx { + const LanternSignedVote *write; + LanternSignedVote *read; +}; -int lantern_ssz_encode_signed_vote_legacy( - const LanternSignedVote *vote, +static ssz_error_t signed_vote_write( + const void *ctx, + uint64_t member_id, uint8_t *out, size_t out_len, size_t *written) { - return lantern_ssz_encode_signed_vote(vote, out, out_len, written); -} - -int lantern_ssz_decode_signed_vote(LanternSignedVote *vote, const uint8_t *data, size_t data_len) { - if (!vote || !data || data_len != LANTERN_SIGNED_VOTE_SSZ_SIZE) { - return -1; + const struct signed_vote_codec_ctx *vote_ctx = ctx; + if (!vote_ctx || !vote_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t offset = 0; - if (decode_vote_internal(&vote->data, data + offset, LANTERN_VOTE_SSZ_SIZE) != 0) { - return -1; + switch (member_id) { + case 0: + return encode_vote_internal(&vote_ctx->write->data, out, out_len, written); + case 1: + return write_signature(&vote_ctx->write->signature, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - offset += LANTERN_VOTE_SSZ_SIZE; - memcpy(vote->signature.bytes, data + offset, LANTERN_SIGNATURE_SIZE); - return 0; } -static int aggregated_attestation_encoded_size( - const LanternAggregatedAttestation *attestation, - size_t *out_size) { - if (!attestation || !out_size) { - return -1; - } - if (attestation->aggregation_bits.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; +static ssz_error_t signed_vote_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct signed_vote_codec_ctx *vote_ctx = ctx; + if (!vote_ctx || !vote_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t bits_size = bitlist_encoded_size(attestation->aggregation_bits.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE; - if (fixed_section > SIZE_MAX - bits_size) { - return -1; + switch (member_id) { + case 0: + return decode_vote_internal(&vote_ctx->read->data, data, data_len); + case 1: + return read_signature(data, data_len, &vote_ctx->read->signature); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - *out_size = fixed_section + bits_size; - return 0; } -static int encode_aggregated_attestation( - const LanternAggregatedAttestation *attestation, +ssz_error_t lantern_ssz_encode_signed_vote( + const LanternSignedVote *vote, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!attestation || !out) { - return -1; - } - if (attestation->aggregation_bits.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; - } - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE; - if (fixed_section > UINT32_MAX) { - return -1; - } - if (remaining < fixed_section) { - return -1; - } - uint32_t bits_offset = (uint32_t)fixed_section; - if (write_u32(out, remaining, bits_offset) != 0) { - return -1; - } - size_t data_written = 0; - if (encode_attestation_data( - &attestation->data, - out + SSZ_BYTE_SIZE_OF_UINT32, - remaining - SSZ_BYTE_SIZE_OF_UINT32, - &data_written) - != 0) { - return -1; - } - if (data_written != LANTERN_ATTESTATION_DATA_SSZ_SIZE) { - return -1; - } - size_t bits_written = 0; - if (encode_bitlist( - &attestation->aggregation_bits, - out + bits_offset, - remaining - bits_offset, - &bits_written) - != 0) { - return -1; - } - size_t total = bits_offset + bits_written; - if (total > remaining) { - return -1; - } - set_written(written, total); - return 0; + struct signed_vote_codec_ctx ctx = {.write = vote}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = signed_vote_write}; + return ssz_serialize_container(&SIGNED_VOTE_SCHEMA, &codec, out, out_len, written); } -static int decode_aggregated_attestation( - LanternAggregatedAttestation *attestation, +ssz_error_t lantern_ssz_decode_signed_vote( + LanternSignedVote *vote, const uint8_t *data, size_t data_len) { - if (!attestation || !data) { - return -1; - } - const size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE; - if (data_len < fixed_section) { - return -1; - } - uint32_t bits_offset = 0; - if (read_u32(data, data_len, &bits_offset) != 0) { - return -1; - } - if (bits_offset < fixed_section || bits_offset > data_len) { - return -1; - } - if (decode_attestation_data( - &attestation->data, - data + SSZ_BYTE_SIZE_OF_UINT32, - LANTERN_ATTESTATION_DATA_SSZ_SIZE) - != 0) { - return -1; - } - size_t bits_size = data_len - bits_offset; - if (decode_bitlist_with_limit( - &attestation->aggregation_bits, - data + bits_offset, - bits_size, - LANTERN_VALIDATOR_REGISTRY_LIMIT) - != 0) { - return -1; - } - return 0; + struct signed_vote_codec_ctx ctx = {.read = vote}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = signed_vote_read}; + return ssz_deserialize_container(data, data_len, &SIGNED_VOTE_SCHEMA, &codec); } -static int encode_aggregated_attestations( - const LanternAggregatedAttestations *attestations, +struct validator_codec_ctx { + const LanternValidator *write; + LanternValidator *read; +}; + +static ssz_error_t validator_write( + const void *ctx, + uint64_t member_id, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!attestations || !out) { - return -1; - } - if (attestations->length > LANTERN_MAX_ATTESTATIONS) { - return -1; + const struct validator_codec_ctx *validator_ctx = ctx; + if (!validator_ctx || !validator_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (attestations->length == 0) { - set_written(written, 0); - return 0; + switch (member_id) { + case 0: + return write_bytes(validator_ctx->write->attestation_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE, out, out_len, written); + case 1: + return write_bytes(validator_ctx->write->proposal_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE, out, out_len, written); + case 2: + return write_u64(validator_ctx->write->index, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - if (!attestations->data) { - return -1; - } - size_t offset_table = attestations->length * SSZ_BYTE_SIZE_OF_UINT32; - if (offset_table > remaining) { - return -1; +} + +static ssz_error_t validator_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct validator_codec_ctx *validator_ctx = ctx; + if (!validator_ctx || !validator_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t cursor = offset_table; - for (size_t i = 0; i < attestations->length; ++i) { - size_t element_size = 0; - if (aggregated_attestation_encoded_size(&attestations->data[i], &element_size) != 0) { - return -1; - } - if (cursor > UINT32_MAX) { - return -1; - } - if (write_u32(out + (i * SSZ_BYTE_SIZE_OF_UINT32), remaining - (i * SSZ_BYTE_SIZE_OF_UINT32), (uint32_t)cursor) != 0) { - return -1; - } - if (cursor > remaining || element_size > remaining - cursor) { - return -1; - } - if (encode_aggregated_attestation( - &attestations->data[i], - out + cursor, - remaining - cursor, - NULL) - != 0) { - return -1; - } - cursor += element_size; + switch (member_id) { + case 0: + return read_bytes(validator_ctx->read->attestation_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE, data, data_len); + case 1: + return read_bytes(validator_ctx->read->proposal_pubkey, LANTERN_VALIDATOR_PUBKEY_SIZE, data, data_len); + case 2: + return read_u64(data, data_len, &validator_ctx->read->index); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - set_written(written, cursor); - return 0; } -static int decode_aggregated_attestations( - LanternAggregatedAttestations *attestations, +ssz_error_t lantern_ssz_encode_validator( + const LanternValidator *validator, + uint8_t *out, + size_t out_len, + size_t *written) { + struct validator_codec_ctx ctx = {.write = validator}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = validator_write}; + return ssz_serialize_container(&VALIDATOR_SCHEMA, &codec, out, out_len, written); +} + +ssz_error_t lantern_ssz_decode_validator( + LanternValidator *validator, const uint8_t *data, size_t data_len) { - if (!attestations) { - return -1; - } - if (data_len == 0) { - return lantern_aggregated_attestations_resize(attestations, 0); + struct validator_codec_ctx ctx = {.read = validator}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = validator_read}; + return ssz_deserialize_container(data, data_len, &VALIDATOR_SCHEMA, &codec); +} + +static ssz_error_t encode_validators_list( + const LanternValidator *validators, + size_t count, + uint8_t *out, + size_t out_len, + size_t *written) { + if (count > SIZE_MAX / LANTERN_VALIDATOR_SSZ_SIZE) { + return SSZ_ERR_OVERFLOW; } - if (!data || data_len < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; + size_t total = count * LANTERN_VALIDATOR_SSZ_SIZE; + ssz_error_t err = prepare_output(total, out, out_len, written); + if (err != SSZ_SUCCESS || !out || count == 0u) { + return err; } - uint32_t first_offset = 0; - if (read_u32(data, data_len, &first_offset) != 0) { - return -1; + if (!validators) { + return SSZ_ERR_INVALID_ARGUMENT; + } + size_t cursor = 0u; + for (size_t i = 0u; i < count; ++i) { + size_t item_written = 0u; + err = lantern_ssz_encode_validator(&validators[i], out + cursor, out_len - cursor, &item_written); + if (err != SSZ_SUCCESS) { + return err; + } + if (item_written != LANTERN_VALIDATOR_SSZ_SIZE) { + return SSZ_ERR_TYPE_MISMATCH; + } + cursor += item_written; } - if (first_offset < SSZ_BYTE_SIZE_OF_UINT32 || first_offset > data_len || (first_offset % SSZ_BYTE_SIZE_OF_UINT32) != 0) { - return -1; + return SSZ_SUCCESS; +} + +static ssz_error_t decode_validators_list( + LanternState *state, + const uint8_t *data, + size_t data_len) { + if (!state) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t count = first_offset / SSZ_BYTE_SIZE_OF_UINT32; - if (count > LANTERN_MAX_ATTESTATIONS) { - return -1; + if (data_len % LANTERN_VALIDATOR_SSZ_SIZE != 0u) { + return SSZ_ERR_ENCODING_INVALID; } - if (count == 0) { - return lantern_aggregated_attestations_resize(attestations, 0); + size_t count = data_len / LANTERN_VALIDATOR_SSZ_SIZE; + if (count > LANTERN_VALIDATOR_REGISTRY_LIMIT) { + return SSZ_ERR_LIMIT_EXCEEDED; } - if (lantern_aggregated_attestations_resize(attestations, count) != 0) { - return -1; + if (count == 0u) { + state->config.num_validators = 0u; + return lantern_rc_to_ssz(lantern_state_set_validator_pubkeys(state, NULL, 0u)); } - size_t offset_table = count * SSZ_BYTE_SIZE_OF_UINT32; - if (offset_table != first_offset || offset_table > data_len) { - return -1; + if (!data) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t *offsets = calloc(count, sizeof(*offsets)); - if (!offsets) { - return -1; + if (count > SIZE_MAX / LANTERN_VALIDATOR_PUBKEY_SIZE) { + return SSZ_ERR_OVERFLOW; } - for (size_t i = 0; i < count; ++i) { - uint32_t value = 0; - if (read_u32(data + (i * SSZ_BYTE_SIZE_OF_UINT32), data_len - (i * SSZ_BYTE_SIZE_OF_UINT32), &value) != 0) { - free(offsets); - return -1; + uint8_t *attestation_pubkeys = malloc(count * LANTERN_VALIDATOR_PUBKEY_SIZE); + uint8_t *proposal_pubkeys = malloc(count * LANTERN_VALIDATOR_PUBKEY_SIZE); + if (!attestation_pubkeys || !proposal_pubkeys) { + free(attestation_pubkeys); + free(proposal_pubkeys); + return SSZ_ERR_INVALID_ARGUMENT; + } + + ssz_error_t err = SSZ_SUCCESS; + LanternValidator decoded; + for (size_t i = 0u; i < count && err == SSZ_SUCCESS; ++i) { + memset(&decoded, 0, sizeof(decoded)); + err = lantern_ssz_decode_validator( + &decoded, + data + (i * LANTERN_VALIDATOR_SSZ_SIZE), + LANTERN_VALIDATOR_SSZ_SIZE); + if (err == SSZ_SUCCESS) { + memcpy(attestation_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), + decoded.attestation_pubkey, + LANTERN_VALIDATOR_PUBKEY_SIZE); + memcpy(proposal_pubkeys + (i * LANTERN_VALIDATOR_PUBKEY_SIZE), + decoded.proposal_pubkey, + LANTERN_VALIDATOR_PUBKEY_SIZE); } - offsets[i] = value; } - for (size_t i = 0; i < count; ++i) { - if (offsets[i] < offset_table || offsets[i] > data_len) { - free(offsets); - return -1; - } - if (i > 0 && offsets[i] < offsets[i - 1]) { - free(offsets); - return -1; + + if (err == SSZ_SUCCESS) { + err = lantern_rc_to_ssz(lantern_state_set_validator_pubkeys_dual( + state, + attestation_pubkeys, + proposal_pubkeys, + count)); + } + if (err == SSZ_SUCCESS) { + for (size_t i = 0u; i < count; ++i) { + memset(&decoded, 0, sizeof(decoded)); + err = lantern_ssz_decode_validator( + &decoded, + data + (i * LANTERN_VALIDATOR_SSZ_SIZE), + LANTERN_VALIDATOR_SSZ_SIZE); + if (err != SSZ_SUCCESS) { + break; + } + state->validators[i].index = decoded.index; } } - for (size_t i = 0; i < count; ++i) { - size_t chunk_end = (i + 1 < count) ? offsets[i + 1] : data_len; - if (chunk_end < offsets[i] || chunk_end > data_len) { - free(offsets); - return -1; - } - size_t chunk_size = chunk_end - offsets[i]; - if (decode_aggregated_attestation(&attestations->data[i], data + offsets[i], chunk_size) != 0) { - free(offsets); - return -1; - } + if (err == SSZ_SUCCESS) { + state->config.num_validators = count; } - free(offsets); - return 0; + free(attestation_pubkeys); + free(proposal_pubkeys); + return err; } -static int decode_legacy_plain_attestations_as_aggregated( - LanternAggregatedAttestations *attestations, - const uint8_t *data, - size_t data_len) { - if (!attestations) { - return -1; - } - if (data_len == 0) { - return lantern_aggregated_attestations_resize(attestations, 0); - } - if (!data || (data_len % LANTERN_VOTE_SSZ_SIZE) != 0) { - return -1; - } - size_t count = data_len / LANTERN_VOTE_SSZ_SIZE; - if (count > LANTERN_MAX_ATTESTATIONS) { - return -1; - } +struct aggregated_attestation_codec_ctx { + const LanternAggregatedAttestation *write; + LanternAggregatedAttestation *read; +}; - LanternAttestations legacy_attestations; - lantern_attestations_init(&legacy_attestations); - if (lantern_attestations_resize(&legacy_attestations, count) != 0) { - lantern_attestations_reset(&legacy_attestations); - return -1; +static ssz_error_t aggregated_attestation_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct aggregated_attestation_codec_ctx *att_ctx = ctx; + if (!att_ctx || !att_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + if (att_ctx->write->aggregation_bits.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { + return SSZ_ERR_LIMIT_EXCEEDED; + } + return encode_bitlist(&att_ctx->write->aggregation_bits, out, out_len, written); + case 1: + return encode_attestation_data(&att_ctx->write->data, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } +} - int rc = -1; - for (size_t i = 0; i < count; ++i) { - if (lantern_ssz_decode_vote( - &legacy_attestations.data[i], - data + (i * LANTERN_VOTE_SSZ_SIZE), - LANTERN_VOTE_SSZ_SIZE) - != 0) { - goto cleanup; - } +static ssz_error_t aggregated_attestation_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct aggregated_attestation_codec_ctx *att_ctx = ctx; + if (!att_ctx || !att_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (lantern_wrap_attestations_as_aggregated(&legacy_attestations, attestations) != 0) { - goto cleanup; + switch (member_id) { + case 0: + return decode_bitlist_with_limit( + &att_ctx->read->aggregation_bits, + data, + data_len, + LANTERN_VALIDATOR_REGISTRY_LIMIT); + case 1: + return decode_attestation_data(&att_ctx->read->data, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - rc = 0; - -cleanup: - lantern_attestations_reset(&legacy_attestations); - return rc; } -static int single_participant_validator_id( +static ssz_error_t encode_aggregated_attestation( const LanternAggregatedAttestation *attestation, - uint64_t *out_validator_id) { - if (!attestation || !out_validator_id) { - return -1; - } + uint8_t *out, + size_t out_len, + size_t *written) { + struct aggregated_attestation_codec_ctx ctx = {.write = attestation}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = aggregated_attestation_write}; + return ssz_serialize_container(&AGGREGATED_ATTESTATION_SCHEMA, &codec, out, out_len, written); +} - size_t bit_length = attestation->aggregation_bits.bit_length; - if (bit_length == 0 || bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; - } +static ssz_error_t decode_aggregated_attestation( + LanternAggregatedAttestation *attestation, + const uint8_t *data, + size_t data_len) { + struct aggregated_attestation_codec_ctx ctx = {.read = attestation}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = aggregated_attestation_read}; + return ssz_deserialize_container(data, data_len, &AGGREGATED_ATTESTATION_SCHEMA, &codec); +} - bool found = false; - uint64_t validator_id = 0; - for (size_t i = 0; i < bit_length; ++i) { - if (!lantern_bitlist_get(&attestation->aggregation_bits, i)) { - continue; - } - if (found) { - return -1; - } - found = true; - validator_id = (uint64_t)i; - } +struct aggregated_attestations_codec_ctx { + const LanternAggregatedAttestations *write; + LanternAggregatedAttestations *read; +}; - if (!found) { - return -1; +static ssz_error_t aggregated_attestations_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct aggregated_attestations_codec_ctx *list_ctx = ctx; + if (!list_ctx || !list_ctx->write || member_id >= list_ctx->write->length) { + return SSZ_ERR_INVALID_ARGUMENT; } + return encode_aggregated_attestation(&list_ctx->write->data[member_id], out, out_len, written); +} - *out_validator_id = validator_id; - return 0; +static ssz_error_t aggregated_attestations_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct aggregated_attestations_codec_ctx *list_ctx = ctx; + if (!list_ctx || !list_ctx->read || member_id != list_ctx->read->length) { + return SSZ_ERR_INVALID_ARGUMENT; + } + LanternAggregatedAttestation attestation; + lantern_aggregated_attestation_init(&attestation); + ssz_error_t err = decode_aggregated_attestation(&attestation, data, data_len); + if (err == SSZ_SUCCESS) { + err = lantern_rc_to_ssz(lantern_aggregated_attestations_append(list_ctx->read, &attestation)); + } + lantern_aggregated_attestation_reset(&attestation); + return err; } -static int encode_legacy_plain_attestations_from_aggregated( +static ssz_error_t encode_aggregated_attestations( const LanternAggregatedAttestations *attestations, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!attestations || !out) { - return -1; - } - - size_t count = attestations->length; - if (count > LANTERN_MAX_ATTESTATIONS) { - return -1; - } - if (count > 0 && !attestations->data) { - return -1; + if (!attestations) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (count > SIZE_MAX / LANTERN_VOTE_SSZ_SIZE) { - return -1; + if (attestations->length > LANTERN_MAX_ATTESTATIONS) { + return SSZ_ERR_LIMIT_EXCEEDED; } - - size_t required = count * LANTERN_VOTE_SSZ_SIZE; - if (required > remaining) { - return -1; + if (attestations->length > 0u && !attestations->data) { + return SSZ_ERR_INVALID_ARGUMENT; } + struct aggregated_attestations_codec_ctx ctx = {.write = attestations}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = aggregated_attestations_write}; + return ssz_serialize_list_variable( + attestations->length, + LANTERN_MAX_ATTESTATIONS, + &codec, + out, + out_len, + written); +} - size_t cursor = 0; - for (size_t i = 0; i < count; ++i) { - uint64_t validator_id = 0; - if (single_participant_validator_id(&attestations->data[i], &validator_id) != 0) { - return -1; - } - - LanternVote vote; - memset(&vote, 0, sizeof(vote)); - vote.validator_id = validator_id; - vote.data = attestations->data[i].data; - - size_t vote_written = 0; - if (lantern_ssz_encode_vote( - &vote, - out + cursor, - remaining - cursor, - &vote_written) - != 0) { - return -1; - } - if (vote_written != LANTERN_VOTE_SSZ_SIZE) { - return -1; +static ssz_error_t decode_aggregated_attestations( + LanternAggregatedAttestations *attestations, + const uint8_t *data, + size_t data_len) { + if (!attestations) { + return SSZ_ERR_INVALID_ARGUMENT; + } + ssz_error_t err = lantern_rc_to_ssz(lantern_aggregated_attestations_resize(attestations, 0u)); + if (err != SSZ_SUCCESS) { + return err; + } + struct aggregated_attestations_codec_ctx ctx = {.read = attestations}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = aggregated_attestations_read}; + uint64_t count = 0u; + err = ssz_deserialize_list_variable( + data, + data_len, + LANTERN_MAX_ATTESTATIONS, + SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_ATTESTATION_DATA_SSZ_SIZE + 1u, + &codec, + &count); + if (err == SSZ_SUCCESS && count != attestations->length) { + err = SSZ_ERR_ENCODING_INVALID; + } + return err; +} + +struct signature_proof_codec_ctx { + const LanternAggregatedSignatureProof *write; + LanternAggregatedSignatureProof *read; +}; + +static ssz_error_t signature_proof_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct signature_proof_codec_ctx *proof_ctx = ctx; + if (!proof_ctx || !proof_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + if (proof_ctx->write->participants.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { + return SSZ_ERR_LIMIT_EXCEEDED; } - cursor += vote_written; + return encode_bitlist(&proof_ctx->write->participants, out, out_len, written); + case 1: + return encode_byte_list(&proof_ctx->write->proof_data, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - - set_written(written, cursor); - return 0; } -static int aggregated_signature_proof_encoded_size( - const LanternAggregatedSignatureProof *proof, - size_t *out_size) { - if (!proof || !out_size) { - return -1; - } - if (proof->participants.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; - } - if (proof->proof_data.length > LANTERN_AGG_PROOF_MAX_BYTES) { - return -1; +static ssz_error_t signature_proof_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct signature_proof_codec_ctx *proof_ctx = ctx; + if (!proof_ctx || !proof_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t participants_size = bitlist_encoded_size(proof->participants.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (fixed_section > SIZE_MAX - participants_size) { - return -1; - } - if (fixed_section + participants_size > SIZE_MAX - proof->proof_data.length) { - return -1; + switch (member_id) { + case 0: + return decode_bitlist_with_limit( + &proof_ctx->read->participants, + data, + data_len, + LANTERN_VALIDATOR_REGISTRY_LIMIT); + case 1: + return decode_byte_list(&proof_ctx->read->proof_data, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - *out_size = fixed_section + participants_size + proof->proof_data.length; - return 0; } -static int encode_aggregated_signature_proof( +static ssz_error_t encode_aggregated_signature_proof( const LanternAggregatedSignatureProof *proof, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!proof || !out) { - return -1; - } - if (proof->participants.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return -1; - } - if (proof->proof_data.length > LANTERN_AGG_PROOF_MAX_BYTES) { - return -1; - } - size_t participants_size = bitlist_encoded_size(proof->participants.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (fixed_section > UINT32_MAX) { - return -1; - } - if (fixed_section > remaining) { - return -1; - } - size_t proof_offset = fixed_section + participants_size; - if (proof_offset > UINT32_MAX) { - return -1; - } - if (proof_offset > remaining) { - return -1; - } - uint32_t participants_offset_u32 = (uint32_t)fixed_section; - uint32_t proof_offset_u32 = (uint32_t)proof_offset; - if (write_u32(out, remaining, participants_offset_u32) != 0) { - return -1; - } - if (write_u32(out + SSZ_BYTE_SIZE_OF_UINT32, remaining - SSZ_BYTE_SIZE_OF_UINT32, proof_offset_u32) != 0) { - return -1; - } - size_t participants_written = 0; - if (encode_bitlist( - &proof->participants, - out + participants_offset_u32, - remaining - participants_offset_u32, - &participants_written) - != 0) { - return -1; - } - if (participants_written != participants_size) { - return -1; - } - size_t proof_written = 0; - if (encode_byte_list( - &proof->proof_data, - out + proof_offset_u32, - remaining - proof_offset_u32, - &proof_written) - != 0) { - return -1; - } - if (proof_written != proof->proof_data.length) { - return -1; - } - size_t total = proof_offset + proof_written; - if (total > remaining) { - return -1; - } - set_written(written, total); - return 0; + struct signature_proof_codec_ctx ctx = {.write = proof}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = signature_proof_write}; + return ssz_serialize_container(&AGGREGATED_SIGNATURE_PROOF_SCHEMA, &codec, out, out_len, written); } -static int decode_aggregated_signature_proof( +static ssz_error_t decode_aggregated_signature_proof( LanternAggregatedSignatureProof *proof, const uint8_t *data, size_t data_len) { - if (!proof || !data) { - return -1; - } - const size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (data_len < fixed_section) { - return -1; - } - uint32_t participants_offset = 0; - uint32_t proof_offset = 0; - if (read_u32(data, data_len, &participants_offset) != 0) { - return -1; - } - if (read_u32(data + SSZ_BYTE_SIZE_OF_UINT32, data_len - SSZ_BYTE_SIZE_OF_UINT32, &proof_offset) != 0) { - return -1; - } - if (participants_offset < fixed_section || proof_offset < participants_offset) { - return -1; - } - if (proof_offset > data_len) { - return -1; - } - size_t participants_size = proof_offset - participants_offset; - size_t proof_size = data_len - proof_offset; - if (decode_bitlist_with_limit( - &proof->participants, - data + participants_offset, - participants_size, - LANTERN_VALIDATOR_REGISTRY_LIMIT) - != 0) { - return -1; - } - if (decode_byte_list(&proof->proof_data, data + proof_offset, proof_size) != 0) { - return -1; - } - return 0; + struct signature_proof_codec_ctx ctx = {.read = proof}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = signature_proof_read}; + return ssz_deserialize_container(data, data_len, &AGGREGATED_SIGNATURE_PROOF_SCHEMA, &codec); } -int lantern_ssz_encode_signed_aggregated_attestation( - const LanternSignedAggregatedAttestation *attestation, +struct signed_aggregated_attestation_codec_ctx { + const LanternSignedAggregatedAttestation *write; + LanternSignedAggregatedAttestation *read; +}; + +static ssz_error_t signed_aggregated_attestation_write( + const void *ctx, + uint64_t member_id, uint8_t *out, size_t out_len, size_t *written) { - if (!attestation || !out) { - return -1; - } - size_t proof_size = 0; - if (aggregated_signature_proof_encoded_size(&attestation->proof, &proof_size) != 0) { - return -1; - } - const size_t fixed_section = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT32; - if (fixed_section > UINT32_MAX) { - return -1; - } - if (proof_size > SIZE_MAX - fixed_section) { - return -1; + const struct signed_aggregated_attestation_codec_ctx *att_ctx = ctx; + if (!att_ctx || !att_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t required = fixed_section + proof_size; - if (out_len < required) { - return -1; - } - - if (encode_attestation_data( - &attestation->data, - out, - out_len, - NULL) - != 0) { - return -1; + switch (member_id) { + case 0: + return encode_attestation_data(&att_ctx->write->data, out, out_len, written); + case 1: + return encode_aggregated_signature_proof(&att_ctx->write->proof, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } +} - if (write_u32( - out + LANTERN_ATTESTATION_DATA_SSZ_SIZE, - out_len - LANTERN_ATTESTATION_DATA_SSZ_SIZE, - (uint32_t)fixed_section) - != 0) { - return -1; +static ssz_error_t signed_aggregated_attestation_read( + void *ctx, + uint64_t member_id, + const uint8_t *data, + size_t data_len) { + struct signed_aggregated_attestation_codec_ctx *att_ctx = ctx; + if (!att_ctx || !att_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (encode_aggregated_signature_proof( - &attestation->proof, - out + fixed_section, - out_len - fixed_section, - NULL) - != 0) { - return -1; + switch (member_id) { + case 0: + return decode_attestation_data(&att_ctx->read->data, data, data_len); + case 1: + return decode_aggregated_signature_proof(&att_ctx->read->proof, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - set_written(written, required); - return 0; } -int lantern_ssz_decode_signed_aggregated_attestation( +ssz_error_t lantern_ssz_encode_signed_aggregated_attestation( + const LanternSignedAggregatedAttestation *attestation, + uint8_t *out, + size_t out_len, + size_t *written) { + struct signed_aggregated_attestation_codec_ctx ctx = {.write = attestation}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = signed_aggregated_attestation_write}; + return ssz_serialize_container(&SIGNED_AGGREGATED_ATTESTATION_SCHEMA, &codec, out, out_len, written); +} + +ssz_error_t lantern_ssz_decode_signed_aggregated_attestation( LanternSignedAggregatedAttestation *attestation, const uint8_t *data, size_t data_len) { - if (!attestation || !data) { - return -1; - } - const size_t fixed_section = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT32; - if (data_len < fixed_section) { - return -1; - } - if (decode_attestation_data( - &attestation->data, - data, - LANTERN_ATTESTATION_DATA_SSZ_SIZE) - != 0) { - return -1; - } - uint32_t proof_offset = 0; - if (read_u32( - data + LANTERN_ATTESTATION_DATA_SSZ_SIZE, - data_len - LANTERN_ATTESTATION_DATA_SSZ_SIZE, - &proof_offset) - != 0) { - return -1; - } - if ((size_t)proof_offset != fixed_section || proof_offset > data_len) { - return -1; - } - if (decode_aggregated_signature_proof( - &attestation->proof, - data + proof_offset, - data_len - proof_offset) - != 0) { - return -1; - } - return 0; + struct signed_aggregated_attestation_codec_ctx ctx = {.read = attestation}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = signed_aggregated_attestation_read}; + return ssz_deserialize_container(data, data_len, &SIGNED_AGGREGATED_ATTESTATION_SCHEMA, &codec); } -int lantern_ssz_encode_aggregated_attestation( +ssz_error_t lantern_ssz_encode_aggregated_attestation( const LanternAggregatedAttestation *attestation, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - return encode_aggregated_attestation(attestation, out, remaining, written); + return encode_aggregated_attestation(attestation, out, out_len, written); } -int lantern_ssz_decode_aggregated_attestation( +ssz_error_t lantern_ssz_decode_aggregated_attestation( LanternAggregatedAttestation *attestation, const uint8_t *data, size_t data_len) { return decode_aggregated_attestation(attestation, data, data_len); } -int lantern_ssz_encode_aggregated_signature_proof( +ssz_error_t lantern_ssz_encode_aggregated_signature_proof( const LanternAggregatedSignatureProof *proof, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - return encode_aggregated_signature_proof(proof, out, remaining, written); + return encode_aggregated_signature_proof(proof, out, out_len, written); } -int lantern_ssz_decode_aggregated_signature_proof( +ssz_error_t lantern_ssz_decode_aggregated_signature_proof( LanternAggregatedSignatureProof *proof, const uint8_t *data, size_t data_len) { return decode_aggregated_signature_proof(proof, data, data_len); } -int lantern_ssz_encode_block_signatures( - const LanternBlockSignatures *signatures, +struct attestation_signatures_codec_ctx { + const LanternAttestationSignatures *write; + LanternAttestationSignatures *read; +}; + +static ssz_error_t attestation_signatures_write( + const void *ctx, + uint64_t member_id, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - return encode_block_signatures(signatures, out, remaining, written); + const struct attestation_signatures_codec_ctx *list_ctx = ctx; + if (!list_ctx || !list_ctx->write || member_id >= list_ctx->write->length) { + return SSZ_ERR_INVALID_ARGUMENT; + } + return encode_aggregated_signature_proof(&list_ctx->write->data[member_id], out, out_len, written); } -int lantern_ssz_decode_block_signatures( - LanternBlockSignatures *signatures, - const uint8_t *data, - size_t data_len) { - return decode_block_signatures(signatures, data, data_len); +static ssz_error_t attestation_signatures_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct attestation_signatures_codec_ctx *list_ctx = ctx; + if (!list_ctx || !list_ctx->read || member_id != list_ctx->read->length) { + return SSZ_ERR_INVALID_ARGUMENT; + } + LanternAggregatedSignatureProof proof; + lantern_aggregated_signature_proof_init(&proof); + ssz_error_t err = decode_aggregated_signature_proof(&proof, data, data_len); + if (err == SSZ_SUCCESS) { + err = lantern_rc_to_ssz(lantern_attestation_signatures_append(list_ctx->read, &proof)); + } + lantern_aggregated_signature_proof_reset(&proof); + return err; } -static int encode_attestation_signatures( +static ssz_error_t encode_attestation_signatures( const LanternAttestationSignatures *signatures, uint8_t *out, - size_t remaining, + size_t out_len, size_t *written) { - if (!signatures || !out) { - return -1; + if (!signatures) { + return SSZ_ERR_INVALID_ARGUMENT; } if (signatures->length > LANTERN_MAX_BLOCK_SIGNATURES) { - return -1; - } - if (signatures->length == 0) { - set_written(written, 0); - return 0; - } - if (!signatures->data) { - return -1; + return SSZ_ERR_LIMIT_EXCEEDED; } - size_t offset_table = signatures->length * SSZ_BYTE_SIZE_OF_UINT32; - if (offset_table > remaining) { - return -1; + if (signatures->length > 0u && !signatures->data) { + return SSZ_ERR_INVALID_ARGUMENT; } - size_t cursor = offset_table; - for (size_t i = 0; i < signatures->length; ++i) { - size_t element_size = 0; - if (aggregated_signature_proof_encoded_size(&signatures->data[i], &element_size) != 0) { - return -1; - } - if (cursor > UINT32_MAX) { - return -1; - } - if (write_u32(out + (i * SSZ_BYTE_SIZE_OF_UINT32), remaining - (i * SSZ_BYTE_SIZE_OF_UINT32), (uint32_t)cursor) != 0) { - return -1; - } - if (cursor > remaining || element_size > remaining - cursor) { - return -1; - } - if (encode_aggregated_signature_proof(&signatures->data[i], out + cursor, remaining - cursor, NULL) != 0) { - return -1; - } - cursor += element_size; - } - set_written(written, cursor); - return 0; + struct attestation_signatures_codec_ctx ctx = {.write = signatures}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = attestation_signatures_write}; + return ssz_serialize_list_variable( + signatures->length, + LANTERN_MAX_BLOCK_SIGNATURES, + &codec, + out, + out_len, + written); } -static int decode_attestation_signatures( +static ssz_error_t decode_attestation_signatures( LanternAttestationSignatures *signatures, const uint8_t *data, size_t data_len) { if (!signatures) { - return -1; - } - if (data_len == 0) { - return lantern_attestation_signatures_resize(signatures, 0); - } - if (!data || data_len < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; - } - uint32_t first_offset = 0; - if (read_u32(data, data_len, &first_offset) != 0) { - return -1; - } - if (first_offset < SSZ_BYTE_SIZE_OF_UINT32 || first_offset > data_len || (first_offset % SSZ_BYTE_SIZE_OF_UINT32) != 0) { - return -1; - } - size_t count = first_offset / SSZ_BYTE_SIZE_OF_UINT32; - if (count > LANTERN_MAX_BLOCK_SIGNATURES) { - return -1; - } - if (count == 0) { - return lantern_attestation_signatures_resize(signatures, 0); - } - if (lantern_attestation_signatures_resize(signatures, count) != 0) { - return -1; - } - size_t offset_table = count * SSZ_BYTE_SIZE_OF_UINT32; - if (offset_table != first_offset || offset_table > data_len) { - return -1; - } - size_t *offsets = calloc(count, sizeof(*offsets)); - if (!offsets) { - return -1; - } - for (size_t i = 0; i < count; ++i) { - uint32_t value = 0; - if (read_u32(data + (i * SSZ_BYTE_SIZE_OF_UINT32), data_len - (i * SSZ_BYTE_SIZE_OF_UINT32), &value) != 0) { - free(offsets); - return -1; - } - offsets[i] = value; - } - for (size_t i = 0; i < count; ++i) { - if (offsets[i] < offset_table || offsets[i] > data_len) { - free(offsets); - return -1; - } - if (i > 0 && offsets[i] < offsets[i - 1]) { - free(offsets); - return -1; - } + return SSZ_ERR_INVALID_ARGUMENT; + } + ssz_error_t err = lantern_rc_to_ssz(lantern_attestation_signatures_resize(signatures, 0u)); + if (err != SSZ_SUCCESS) { + return err; + } + struct attestation_signatures_codec_ctx ctx = {.read = signatures}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = attestation_signatures_read}; + uint64_t count = 0u; + err = ssz_deserialize_list_variable( + data, + data_len, + LANTERN_MAX_BLOCK_SIGNATURES, + (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + 1u, + &codec, + &count); + if (err == SSZ_SUCCESS && count != signatures->length) { + err = SSZ_ERR_ENCODING_INVALID; + } + return err; +} + +struct block_signatures_codec_ctx { + const LanternBlockSignatures *write; + LanternBlockSignatures *read; +}; + +static ssz_error_t block_signatures_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct block_signatures_codec_ctx *sig_ctx = ctx; + if (!sig_ctx || !sig_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - for (size_t i = 0; i < count; ++i) { - size_t chunk_end = (i + 1 < count) ? offsets[i + 1] : data_len; - if (chunk_end < offsets[i] || chunk_end > data_len) { - free(offsets); - return -1; - } - size_t chunk_size = chunk_end - offsets[i]; - if (decode_aggregated_signature_proof(&signatures->data[i], data + offsets[i], chunk_size) != 0) { - free(offsets); - return -1; - } + switch (member_id) { + case 0: + return encode_attestation_signatures(&sig_ctx->write->attestation_signatures, out, out_len, written); + case 1: + return write_signature(&sig_ctx->write->proposer_signature, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - free(offsets); - return 0; } -int lantern_ssz_encode_block_header(const LanternBlockHeader *header, uint8_t *out, size_t out_len, size_t *written) { - if (!header || !out || out_len < LANTERN_BLOCK_HEADER_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (write_u64(out + offset, out_len - offset, header->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (write_u64(out + offset, out_len - offset, header->proposer_index) != 0) { - return -1; +static ssz_error_t block_signatures_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct block_signatures_codec_ctx *sig_ctx = ctx; + if (!sig_ctx || !sig_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (write_root(out + offset, out_len - offset, &header->parent_root) != 0) { - return -1; + switch (member_id) { + case 0: + return decode_attestation_signatures(&sig_ctx->read->attestation_signatures, data, data_len); + case 1: + return read_signature(data, data_len, &sig_ctx->read->proposer_signature); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - offset += LANTERN_ROOT_SIZE; - if (write_root(out + offset, out_len - offset, &header->state_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (write_root(out + offset, out_len - offset, &header->body_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - set_written(written, offset); - return 0; } -int lantern_ssz_decode_block_header(LanternBlockHeader *header, const uint8_t *data, size_t data_len) { - if (!header || !data || data_len != LANTERN_BLOCK_HEADER_SSZ_SIZE) { - return -1; - } - size_t offset = 0; - if (read_u64(data + offset, data_len - offset, &header->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (read_u64(data + offset, data_len - offset, &header->proposer_index) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (read_root(data + offset, data_len - offset, &header->parent_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (read_root(data + offset, data_len - offset, &header->state_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (read_root(data + offset, data_len - offset, &header->body_root) != 0) { - return -1; - } - return 0; +static ssz_error_t encode_block_signatures( + const LanternBlockSignatures *signatures, + uint8_t *out, + size_t out_len, + size_t *written) { + struct block_signatures_codec_ctx ctx = {.write = signatures}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = block_signatures_write}; + return ssz_serialize_container(&BLOCK_SIGNATURES_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_encode_block_body(const LanternBlockBody *body, uint8_t *out, size_t out_len, size_t *written) { - if (!body || !out) { - return -1; - } - - uint32_t att_offset = SSZ_BYTE_SIZE_OF_UINT32; - if (out_len < att_offset) { - return -1; - } - if (write_u32(out, out_len, att_offset) != 0) { - return -1; - } - size_t att_written = 0; - if (body->legacy_plain_attestation_layout - && encode_legacy_plain_attestations_from_aggregated( - &body->attestations, - out + att_offset, - out_len - att_offset, - &att_written) - == 0) { - /* Preserve legacy plain attestation body layout when requested. */ - } else { - if (encode_aggregated_attestations( - &body->attestations, - out + att_offset, - out_len - att_offset, - &att_written) - != 0) { - return -1; - } - } - size_t total = att_offset + att_written; - if (total > out_len) { - return -1; - } - set_written(written, total); - return 0; +static ssz_error_t decode_block_signatures( + LanternBlockSignatures *signatures, + const uint8_t *data, + size_t data_len) { + struct block_signatures_codec_ctx ctx = {.read = signatures}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = block_signatures_read}; + return ssz_deserialize_container(data, data_len, &BLOCK_SIGNATURES_SCHEMA, &codec); } -int lantern_ssz_decode_block_body(LanternBlockBody *body, const uint8_t *data, size_t data_len) { - if (!body || !data || data_len < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; - } - body->legacy_plain_attestation_layout = false; - - uint32_t att_offset = 0; - if (read_u32(data, data_len, &att_offset) != 0) { - return -1; - } - if (att_offset > data_len || att_offset < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; - } - - size_t att_size = data_len - att_offset; - if (decode_aggregated_attestations(&body->attestations, data + att_offset, att_size) != 0) { - if (decode_legacy_plain_attestations_as_aggregated(&body->attestations, data + att_offset, att_size) != 0) { - return -1; - } - body->legacy_plain_attestation_layout = true; - static bool logged_legacy_decode = false; - if (!logged_legacy_decode) { - lantern_log_warn( - "ssz", - NULL, - "block body decoded using legacy plain attestation layout att_size=%zu count=%zu", - att_size, - body->attestations.length); - logged_legacy_decode = true; - } - } - return 0; +ssz_error_t lantern_ssz_encode_block_signatures( + const LanternBlockSignatures *signatures, + uint8_t *out, + size_t out_len, + size_t *written) { + return encode_block_signatures(signatures, out, out_len, written); } -static int decode_block_body_strict(LanternBlockBody *body, const uint8_t *data, size_t data_len) { - if (!body || !data || data_len < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; - } - body->legacy_plain_attestation_layout = false; - - uint32_t att_offset = 0; - if (read_u32(data, data_len, &att_offset) != 0) { - return -1; - } - if (att_offset > data_len || att_offset < SSZ_BYTE_SIZE_OF_UINT32) { - return -1; - } - - size_t att_size = data_len - att_offset; - if (decode_aggregated_attestations(&body->attestations, data + att_offset, att_size) != 0) { - return -1; - } - return 0; +ssz_error_t lantern_ssz_decode_block_signatures( + LanternBlockSignatures *signatures, + const uint8_t *data, + size_t data_len) { + return decode_block_signatures(signatures, data, data_len); } -int lantern_ssz_encode_block(const LanternBlock *block, uint8_t *out, size_t out_len, size_t *written) { - if (!block || !out) { - return -1; - } +struct block_header_codec_ctx { + const LanternBlockHeader *write; + LanternBlockHeader *read; +}; - const size_t fixed_fields = (SSZ_BYTE_SIZE_OF_UINT64 * 2) + (LANTERN_ROOT_SIZE * 2); - const size_t fixed_section = fixed_fields + SSZ_BYTE_SIZE_OF_UINT32; /* single variable field offset */ - if (fixed_section > UINT32_MAX) { - return -1; - } - if (out_len < fixed_section) { - return -1; - } - - size_t offset = 0; - if (write_u64(out + offset, out_len - offset, block->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (write_u64(out + offset, out_len - offset, block->proposer_index) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (write_root(out + offset, out_len - offset, &block->parent_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (write_root(out + offset, out_len - offset, &block->state_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - - uint32_t body_offset = (uint32_t)fixed_section; - if (write_u32(out + offset, out_len - offset, body_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - - size_t body_written = 0; - if (lantern_ssz_encode_block_body(&block->body, out + body_offset, out_len - body_offset, &body_written) != 0) { - return -1; - } - - size_t total = body_offset + body_written; - if (total > UINT32_MAX) { - return -1; - } - set_written(written, total); - return 0; +static ssz_error_t block_header_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct block_header_codec_ctx *header_ctx = ctx; + if (!header_ctx || !header_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + return write_u64(header_ctx->write->slot, out, out_len, written); + case 1: + return write_u64(header_ctx->write->proposer_index, out, out_len, written); + case 2: + return write_root(&header_ctx->write->parent_root, out, out_len, written); + case 3: + return write_root(&header_ctx->write->state_root, out, out_len, written); + case 4: + return write_root(&header_ctx->write->body_root, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +static ssz_error_t block_header_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct block_header_codec_ctx *header_ctx = ctx; + if (!header_ctx || !header_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + return read_u64(data, data_len, &header_ctx->read->slot); + case 1: + return read_u64(data, data_len, &header_ctx->read->proposer_index); + case 2: + return read_root(data, data_len, &header_ctx->read->parent_root); + case 3: + return read_root(data, data_len, &header_ctx->read->state_root); + case 4: + return read_root(data, data_len, &header_ctx->read->body_root); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +ssz_error_t lantern_ssz_encode_block_header( + const LanternBlockHeader *header, + uint8_t *out, + size_t out_len, + size_t *written) { + struct block_header_codec_ctx ctx = {.write = header}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = block_header_write}; + return ssz_serialize_container(&BLOCK_HEADER_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_decode_block(LanternBlock *block, const uint8_t *data, size_t data_len) { - if (!block || !data) { - return -1; - } - - const size_t fixed_fields = (SSZ_BYTE_SIZE_OF_UINT64 * 2) + (LANTERN_ROOT_SIZE * 2); - const size_t min_size = fixed_fields + SSZ_BYTE_SIZE_OF_UINT32; - if (data_len < min_size) { - return -1; - } - - size_t offset = 0; - if (read_u64(data + offset, data_len - offset, &block->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (read_u64(data + offset, data_len - offset, &block->proposer_index) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (read_root(data + offset, data_len - offset, &block->parent_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (read_root(data + offset, data_len - offset, &block->state_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - - uint32_t body_offset = 0; - if (read_u32(data + offset, data_len - offset, &body_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - - if (body_offset > data_len || body_offset < min_size) { - return -1; - } - - size_t body_len = data_len - body_offset; - if (lantern_ssz_decode_block_body(&block->body, data + body_offset, body_len) != 0) { - return -1; - } - return 0; +ssz_error_t lantern_ssz_decode_block_header( + LanternBlockHeader *header, + const uint8_t *data, + size_t data_len) { + struct block_header_codec_ctx ctx = {.read = header}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = block_header_read}; + return ssz_deserialize_container(data, data_len, &BLOCK_HEADER_SCHEMA, &codec); } -int lantern_ssz_decode_block_strict(LanternBlock *block, const uint8_t *data, size_t data_len) { - if (!block || !data) { - return -1; - } - - const size_t fixed_fields = (SSZ_BYTE_SIZE_OF_UINT64 * 2) + (LANTERN_ROOT_SIZE * 2); - const size_t min_size = fixed_fields + SSZ_BYTE_SIZE_OF_UINT32; - if (data_len < min_size) { - return -1; - } - - size_t offset = 0; - if (read_u64(data + offset, data_len - offset, &block->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (read_u64(data + offset, data_len - offset, &block->proposer_index) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - if (read_root(data + offset, data_len - offset, &block->parent_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; - if (read_root(data + offset, data_len - offset, &block->state_root) != 0) { - return -1; - } - offset += LANTERN_ROOT_SIZE; +struct block_body_codec_ctx { + const LanternBlockBody *write; + LanternBlockBody *read; +}; - uint32_t body_offset = 0; - if (read_u32(data + offset, data_len - offset, &body_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - - if (body_offset > data_len || body_offset < min_size) { - return -1; - } - - size_t body_len = data_len - body_offset; - if (decode_block_body_strict(&block->body, data + body_offset, body_len) != 0) { - return -1; - } - return 0; -} - -int lantern_ssz_encode_signed_block( - const LanternSignedBlock *block, +static ssz_error_t block_body_write( + const void *ctx, + uint64_t member_id, uint8_t *out, size_t out_len, size_t *written) { - if (!block || !out) { - return -1; - } - - const size_t offset_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (offset_section > UINT32_MAX) { - return -1; - } - if (out_len < offset_section) { - return -1; - } - - size_t payload_offset = offset_section; - size_t message_written = 0; - if (lantern_ssz_encode_block( - &block->block, - out + payload_offset, - out_len - payload_offset, - &message_written) - != 0) { - return -1; + const struct block_body_codec_ctx *body_ctx = ctx; + if (!body_ctx || !body_ctx->write || member_id != 0u) { + return SSZ_ERR_INVALID_ARGUMENT; } + return encode_aggregated_attestations(&body_ctx->write->attestations, out, out_len, written); +} - size_t message_region_end = payload_offset + message_written; - if (message_region_end < payload_offset || message_region_end > out_len) { - return -1; - } - - size_t signatures_written = 0; - if (encode_block_signatures( - &block->signatures, - out + message_region_end, - out_len - message_region_end, - &signatures_written) - != 0) { - return -1; - } - - size_t total = message_region_end + signatures_written; - if (total < message_region_end || total > UINT32_MAX) { - return -1; - } - - if (payload_offset > UINT32_MAX || message_region_end > UINT32_MAX) { - return -1; - } - uint32_t message_offset = (uint32_t)payload_offset; - uint32_t signatures_offset = (uint32_t)message_region_end; - - if (write_u32(out, out_len, message_offset) != 0) { - return -1; +static ssz_error_t block_body_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct block_body_codec_ctx *body_ctx = ctx; + if (!body_ctx || !body_ctx->read || member_id != 0u) { + return SSZ_ERR_INVALID_ARGUMENT; } - if (write_u32(out + SSZ_BYTE_SIZE_OF_UINT32, out_len - SSZ_BYTE_SIZE_OF_UINT32, signatures_offset) != 0) { - return -1; - } - set_written(written, total); - return 0; + return decode_aggregated_attestations(&body_ctx->read->attestations, data, data_len); } -int lantern_ssz_encode_signed_block_canonical( - const LanternSignedBlock *block, +ssz_error_t lantern_ssz_encode_block_body( + const LanternBlockBody *body, uint8_t *out, size_t out_len, size_t *written) { - if (!block || !out) { - return -1; - } - - LanternSignedBlock canonical = *block; - canonical.block.body.legacy_plain_attestation_layout = false; - return lantern_ssz_encode_signed_block(&canonical, out, out_len, written); + struct block_body_codec_ctx ctx = {.write = body}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = block_body_write}; + return ssz_serialize_container(&BLOCK_BODY_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_decode_signed_block(LanternSignedBlock *block, const uint8_t *data, size_t data_len) { - if (!block || !data) { - return -1; - } - - const size_t offset_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (data_len < offset_section) { - return -1; - } - - uint32_t message_offset = 0; - if (read_u32(data, data_len, &message_offset) != 0) { - return -1; - } - uint32_t signatures_offset = 0; - if (read_u32(data + SSZ_BYTE_SIZE_OF_UINT32, data_len - SSZ_BYTE_SIZE_OF_UINT32, &signatures_offset) != 0) { - return -1; - } - - if (message_offset < offset_section || signatures_offset < offset_section) { - return -1; - } - if (message_offset > data_len || signatures_offset > data_len) { - return -1; - } - if (signatures_offset < message_offset) { - return -1; - } - - size_t message_len = signatures_offset - message_offset; - if (message_len == 0 || message_len > data_len - message_offset) { - return -1; - } +ssz_error_t lantern_ssz_decode_block_body( + LanternBlockBody *body, + const uint8_t *data, + size_t data_len) { + struct block_body_codec_ctx ctx = {.read = body}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = block_body_read}; + return ssz_deserialize_container(data, data_len, &BLOCK_BODY_SCHEMA, &codec); +} - if (lantern_ssz_decode_block( - &block->block, - data + message_offset, - message_len) - != 0) { - return -1; - } +struct block_codec_ctx { + const LanternBlock *write; + LanternBlock *read; +}; - size_t signatures_len = data_len - signatures_offset; - if (decode_block_signatures(&block->signatures, data + signatures_offset, signatures_len) != 0) { - return -1; - } - return 0; +static ssz_error_t block_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct block_codec_ctx *block_ctx = ctx; + if (!block_ctx || !block_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + return write_u64(block_ctx->write->slot, out, out_len, written); + case 1: + return write_u64(block_ctx->write->proposer_index, out, out_len, written); + case 2: + return write_root(&block_ctx->write->parent_root, out, out_len, written); + case 3: + return write_root(&block_ctx->write->state_root, out, out_len, written); + case 4: + return lantern_ssz_encode_block_body(&block_ctx->write->body, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +static ssz_error_t block_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct block_codec_ctx *block_ctx = ctx; + if (!block_ctx || !block_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; + } + switch (member_id) { + case 0: + return read_u64(data, data_len, &block_ctx->read->slot); + case 1: + return read_u64(data, data_len, &block_ctx->read->proposer_index); + case 2: + return read_root(data, data_len, &block_ctx->read->parent_root); + case 3: + return read_root(data, data_len, &block_ctx->read->state_root); + case 4: + return lantern_ssz_decode_block_body(&block_ctx->read->body, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +ssz_error_t lantern_ssz_encode_block( + const LanternBlock *block, + uint8_t *out, + size_t out_len, + size_t *written) { + struct block_codec_ctx ctx = {.write = block}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = block_write}; + return ssz_serialize_container(&BLOCK_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_decode_signed_block_strict(LanternSignedBlock *block, const uint8_t *data, size_t data_len) { - if (!block || !data) { - return -1; - } - - const size_t offset_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (data_len < offset_section) { - return -1; - } +ssz_error_t lantern_ssz_decode_block( + LanternBlock *block, + const uint8_t *data, + size_t data_len) { + struct block_codec_ctx ctx = {.read = block}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = block_read}; + return ssz_deserialize_container(data, data_len, &BLOCK_SCHEMA, &codec); +} - uint32_t message_offset = 0; - if (read_u32(data, data_len, &message_offset) != 0) { - return -1; - } - uint32_t signatures_offset = 0; - if (read_u32(data + SSZ_BYTE_SIZE_OF_UINT32, data_len - SSZ_BYTE_SIZE_OF_UINT32, &signatures_offset) != 0) { - return -1; - } +struct signed_block_codec_ctx { + const LanternSignedBlock *write; + LanternSignedBlock *read; +}; - if (message_offset < offset_section || signatures_offset < offset_section) { - return -1; - } - if (message_offset > data_len || signatures_offset > data_len) { - return -1; - } - if (signatures_offset < message_offset) { - return -1; +static ssz_error_t signed_block_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct signed_block_codec_ctx *block_ctx = ctx; + if (!block_ctx || !block_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; } - - size_t message_len = signatures_offset - message_offset; - if (message_len == 0 || message_len > data_len - message_offset) { - return -1; + switch (member_id) { + case 0: + return lantern_ssz_encode_block(&block_ctx->write->block, out, out_len, written); + case 1: + return encode_block_signatures(&block_ctx->write->signatures, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; } +} - if (lantern_ssz_decode_block_strict( - &block->block, - data + message_offset, - message_len) - != 0) { - return -1; +static ssz_error_t signed_block_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct signed_block_codec_ctx *block_ctx = ctx; + if (!block_ctx || !block_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; } - - size_t signatures_len = data_len - signatures_offset; - if (decode_block_signatures_standard(&block->signatures, data + signatures_offset, signatures_len) != 0) { - return -1; + switch (member_id) { + case 0: + return lantern_ssz_decode_block(&block_ctx->read->block, data, data_len); + case 1: + return decode_block_signatures(&block_ctx->read->signatures, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; } - return 0; } -int lantern_ssz_encode_signed_block_legacy( +ssz_error_t lantern_ssz_encode_signed_block( const LanternSignedBlock *block, uint8_t *out, size_t out_len, size_t *written) { - return lantern_ssz_encode_signed_block(block, out, out_len, written); + struct signed_block_codec_ctx ctx = {.write = block}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = signed_block_write}; + return ssz_serialize_container(&SIGNED_BLOCK_SCHEMA, &codec, out, out_len, written); } -int lantern_ssz_encode_state(const LanternState *state, uint8_t *out, size_t out_len, size_t *written) { - if (!state || !out) { - return -1; - } - - if (state->config.num_validators != (uint64_t)state->validator_count) { - return -1; - } - const size_t var_field_count = 5; - size_t offset = 0; - size_t tmp = 0; - - if (lantern_ssz_encode_config(&state->config, out + offset, out_len - offset, &tmp) != 0) { - return -1; - } - offset += tmp; - - if (write_u64(out + offset, out_len - offset, state->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - - if (lantern_ssz_encode_block_header(&state->latest_block_header, out + offset, out_len - offset, &tmp) != 0) { - return -1; - } - offset += tmp; - - if (lantern_ssz_encode_checkpoint(&state->latest_justified, out + offset, out_len - offset, &tmp) != 0) { - return -1; - } - offset += tmp; - - if (lantern_ssz_encode_checkpoint(&state->latest_finalized, out + offset, out_len - offset, &tmp) != 0) { - return -1; - } - offset += tmp; - - if (out_len < offset + (var_field_count * SSZ_BYTE_SIZE_OF_UINT32)) { - return -1; - } - - size_t variable_offset = offset + (var_field_count * SSZ_BYTE_SIZE_OF_UINT32); - if (variable_offset > UINT32_MAX) { - return -1; - } - - // Historical block hashes - if (write_u32(out + offset, out_len - offset, (uint32_t)variable_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - if (encode_root_list(&state->historical_block_hashes, out + variable_offset, out_len - variable_offset, &tmp) != 0) { - return -1; - } - variable_offset += tmp; - if (variable_offset > UINT32_MAX) { - return -1; - } - - // Justified slots bitlist - if (write_u32(out + offset, out_len - offset, (uint32_t)variable_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - if (encode_bitlist(&state->justified_slots, out + variable_offset, out_len - variable_offset, &tmp) != 0) { - return -1; - } - variable_offset += tmp; - if (variable_offset > UINT32_MAX) { - return -1; - } - - // Validators list - if (write_u32(out + offset, out_len - offset, (uint32_t)variable_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - if (state->validator_count > 0 && state->validator_count > SIZE_MAX / LANTERN_VALIDATOR_SSZ_SIZE) { - return -1; - } - size_t validator_bytes = state->validator_count * LANTERN_VALIDATOR_SSZ_SIZE; - if (validator_bytes > out_len - variable_offset) { - return -1; - } - if (encode_validators_list(state->validators, state->validator_count, out + variable_offset, out_len - variable_offset, &tmp) - != 0) { - return -1; - } - variable_offset += tmp; - if (variable_offset > UINT32_MAX) { - return -1; - } - - // Justification roots - if (write_u32(out + offset, out_len - offset, (uint32_t)variable_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - if (encode_root_list(&state->justification_roots, out + variable_offset, out_len - variable_offset, &tmp) != 0) { - return -1; - } - variable_offset += tmp; - if (variable_offset > UINT32_MAX) { - return -1; - } - - // Justification validators bitlist - if (write_u32(out + offset, out_len - offset, (uint32_t)variable_offset) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT32; - if (encode_bitlist(&state->justification_validators, out + variable_offset, out_len - variable_offset, &tmp) != 0) { - return -1; - } - variable_offset += tmp; - - set_written(written, variable_offset); - return 0; +ssz_error_t lantern_ssz_decode_signed_block( + LanternSignedBlock *block, + const uint8_t *data, + size_t data_len) { + struct signed_block_codec_ctx ctx = {.read = block}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = signed_block_read}; + return ssz_deserialize_container(data, data_len, &SIGNED_BLOCK_SCHEMA, &codec); } -int lantern_ssz_decode_state(LanternState *state, const uint8_t *data, size_t data_len) { - if (!state || !data) { - return -1; - } - - const size_t var_field_count = 5; - size_t offset = 0; - const size_t offsets_size = var_field_count * SSZ_BYTE_SIZE_OF_UINT32; - const size_t min_full_size = LANTERN_CONFIG_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT64 + LANTERN_BLOCK_HEADER_SSZ_SIZE - + (2 * LANTERN_CHECKPOINT_SSZ_SIZE) + offsets_size; - if (data_len < min_full_size) { - return -1; - } - if (lantern_ssz_decode_config(&state->config, data + offset, LANTERN_CONFIG_SSZ_SIZE) != 0) { - return -1; - } - offset += LANTERN_CONFIG_SSZ_SIZE; - if (read_u64(data + offset, data_len - offset, &state->slot) != 0) { - return -1; - } - offset += SSZ_BYTE_SIZE_OF_UINT64; - - if (data_len - offset < LANTERN_BLOCK_HEADER_SSZ_SIZE) { - return -1; - } - if (lantern_ssz_decode_block_header(&state->latest_block_header, data + offset, LANTERN_BLOCK_HEADER_SSZ_SIZE) != 0) { - return -1; - } - offset += LANTERN_BLOCK_HEADER_SSZ_SIZE; - if (data_len - offset < LANTERN_CHECKPOINT_SSZ_SIZE) { - return -1; - } - if (lantern_ssz_decode_checkpoint(&state->latest_justified, data + offset, LANTERN_CHECKPOINT_SSZ_SIZE) != 0) { - return -1; - } - offset += LANTERN_CHECKPOINT_SSZ_SIZE; - if (data_len - offset < LANTERN_CHECKPOINT_SSZ_SIZE) { - return -1; - } - if (lantern_ssz_decode_checkpoint(&state->latest_finalized, data + offset, LANTERN_CHECKPOINT_SSZ_SIZE) != 0) { - return -1; - } - offset += LANTERN_CHECKPOINT_SSZ_SIZE; - if (data_len - offset < offsets_size) { - return -1; - } - - size_t offsets_start = offset; - size_t offsets[var_field_count]; - size_t read_pos = offsets_start; - for (size_t i = 0; i < var_field_count; ++i) { - uint32_t value = 0; - if (read_u32(data + read_pos, data_len - read_pos, &value) != 0) { - return -1; - } - offsets[i] = value; - read_pos += SSZ_BYTE_SIZE_OF_UINT32; - } - size_t table_end = offsets_start + offsets_size; - for (size_t i = 0; i < var_field_count; ++i) { - if (offsets[i] < table_end || offsets[i] > data_len) { - return -1; - } - if (i > 0 && offsets[i] < offsets[i - 1]) { - return -1; - } - } - offset = table_end; - size_t payload_start = offsets[0]; - if (payload_start < offset || payload_start > data_len) { - return -1; - } +struct state_codec_ctx { + const LanternState *write; + LanternState *read; +}; - for (size_t i = 0; i < var_field_count; ++i) { - if (offsets[i] < offset || offsets[i] > data_len) { - return -1; - } - if (i > 0 && offsets[i] < offsets[i - 1]) { - return -1; - } +static ssz_error_t state_write( + const void *ctx, + uint64_t member_id, + uint8_t *out, + size_t out_len, + size_t *written) { + const struct state_codec_ctx *state_ctx = ctx; + if (!state_ctx || !state_ctx->write) { + return SSZ_ERR_INVALID_ARGUMENT; + } + const LanternState *state = state_ctx->write; + switch (member_id) { + case 0: + return lantern_ssz_encode_config(&state->config, out, out_len, written); + case 1: + return write_u64(state->slot, out, out_len, written); + case 2: + return lantern_ssz_encode_block_header(&state->latest_block_header, out, out_len, written); + case 3: + return lantern_ssz_encode_checkpoint(&state->latest_justified, out, out_len, written); + case 4: + return lantern_ssz_encode_checkpoint(&state->latest_finalized, out, out_len, written); + case 5: + return encode_root_list(&state->historical_block_hashes, out, out_len, written); + case 6: + return encode_bitlist(&state->justified_slots, out, out_len, written); + case 7: + return encode_validators_list(state->validators, state->validator_count, out, out_len, written); + case 8: + return encode_root_list(&state->justification_roots, out, out_len, written); + case 9: + return encode_bitlist(&state->justification_validators, out, out_len, written); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +static ssz_error_t state_read(void *ctx, uint64_t member_id, const uint8_t *data, size_t data_len) { + struct state_codec_ctx *state_ctx = ctx; + if (!state_ctx || !state_ctx->read) { + return SSZ_ERR_INVALID_ARGUMENT; + } + LanternState *state = state_ctx->read; + switch (member_id) { + case 0: + return lantern_ssz_decode_config(&state->config, data, data_len); + case 1: + return read_u64(data, data_len, &state->slot); + case 2: + return lantern_ssz_decode_block_header(&state->latest_block_header, data, data_len); + case 3: + return lantern_ssz_decode_checkpoint(&state->latest_justified, data, data_len); + case 4: + return lantern_ssz_decode_checkpoint(&state->latest_finalized, data, data_len); + case 5: + return decode_root_list(&state->historical_block_hashes, data, data_len); + case 6: + return decode_bitlist(&state->justified_slots, data, data_len); + case 7: + return decode_validators_list(state, data, data_len); + case 8: + return decode_root_list(&state->justification_roots, data, data_len); + case 9: + return decode_bitlist(&state->justification_validators, data, data_len); + default: + return SSZ_ERR_INVALID_ARGUMENT; + } +} + +ssz_error_t lantern_ssz_encode_state( + const LanternState *state, + uint8_t *out, + size_t out_len, + size_t *written) { + if (!state) { + return SSZ_ERR_INVALID_ARGUMENT; } - - size_t chunk_sizes[var_field_count]; - for (size_t i = 0; i < var_field_count; ++i) { - size_t chunk_end = (i + 1 < var_field_count) ? offsets[i + 1] : data_len; - if (chunk_end < offsets[i] || chunk_end > data_len) { - return -1; - } - chunk_sizes[i] = chunk_end - offsets[i]; + if (state->config.num_validators != (uint64_t)state->validator_count) { + return SSZ_ERR_ENCODING_INVALID; } + struct state_codec_ctx ctx = {.write = state}; + ssz_member_codec_t codec = {.ctx = &ctx, .write = state_write}; + return ssz_serialize_container(&STATE_SCHEMA, &codec, out, out_len, written); +} - if (decode_root_list(&state->historical_block_hashes, data + offsets[0], chunk_sizes[0]) != 0) { - return -1; - } - if (decode_bitlist(&state->justified_slots, data + offsets[1], chunk_sizes[1]) != 0) { - return -1; - } - if (decode_validators_list(state, data + offsets[2], chunk_sizes[2]) != 0) { - return -1; - } - if (decode_root_list(&state->justification_roots, data + offsets[3], chunk_sizes[3]) != 0) { - return -1; - } - if (decode_bitlist(&state->justification_validators, data + offsets[4], chunk_sizes[4]) != 0) { - return -1; +ssz_error_t lantern_ssz_decode_state( + LanternState *state, + const uint8_t *data, + size_t data_len) { + struct state_codec_ctx ctx = {.read = state}; + ssz_member_codec_t codec = {.ctx = &ctx, .read = state_read}; + ssz_error_t err = ssz_deserialize_container(data, data_len, &STATE_SCHEMA, &codec); + if (err == SSZ_SUCCESS) { + state->config.num_validators = (uint64_t)state->validator_count; } - - state->config.num_validators = (uint64_t)state->validator_count; - return 0; + return err; } diff --git a/src/consensus/state.c b/src/consensus/state.c index 8a27547..a83e077 100644 --- a/src/consensus/state.c +++ b/src/consensus/state.c @@ -409,7 +409,7 @@ static int lantern_state_validate_block_attestation_data_uniqueness(const Lanter for (size_t i = 0; i < attestations->length; ++i) { LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) != SSZ_SUCCESS) { lantern_log_warn( "state", &meta, @@ -590,7 +590,7 @@ static int lantern_state_verify_block_signatures( } if (rc == 0 - && lantern_hash_tree_root_attestation_data(&attestation->data, &jobs[i].message) != 0) { + && lantern_hash_tree_root_attestation_data(&attestation->data, &jobs[i].message) != SSZ_SUCCESS) { lantern_log_warn( "state", &meta, @@ -632,7 +632,7 @@ static int lantern_state_verify_block_signatures( } LanternRoot proposer_root; - if (lantern_hash_tree_root_block(block, &proposer_root) != 0) { + if (lantern_hash_tree_root_block(block, &proposer_root) != SSZ_SUCCESS) { lantern_log_warn("state", &meta, "failed to hash block for proposer signature"); return -1; } @@ -748,6 +748,9 @@ static int collect_attestations_for_checkpoint( if (!lantern_checkpoint_equal(&data.source, checkpoint)) { continue; } + if (data.target.slot <= data.source.slot) { + continue; + } if (lantern_root_list_contains(processed_data_roots, &entry->data_root)) { continue; } @@ -1305,7 +1308,7 @@ lantern_state_aggregate_result lantern_state_aggregate( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&raw_attestations->data[i].data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&raw_attestations->data[i].data, &data_root) != SSZ_SUCCESS) { rc = LANTERN_STATE_AGGREGATE_VALIDATOR; break; } @@ -2324,6 +2327,7 @@ void lantern_state_reset(LanternState *state) { if (!state) { return; } + lantern_state_hash_cache_reset(state); lantern_root_list_reset(&state->historical_block_hashes); lantern_bitlist_reset(&state->justified_slots); lantern_root_list_reset(&state->justification_roots); @@ -2361,7 +2365,7 @@ int lantern_state_generate_genesis(LanternState *state, uint64_t genesis_time, u LanternBlockBody empty_body; lantern_block_body_init(&empty_body); LanternRoot body_root; - if (lantern_hash_tree_root_block_body(&empty_body, &body_root) != 0) { + if (lantern_hash_tree_root_block_body(&empty_body, &body_root) != SSZ_SUCCESS) { lantern_block_body_reset(&empty_body); lantern_state_reset(state); return -1; @@ -2383,7 +2387,7 @@ int lantern_state_process_slot(LanternState *state) { } if (lantern_root_is_zero(&state->latest_block_header.state_root)) { LanternRoot computed; - if (lantern_hash_tree_root_state(state, &computed) != 0) { + if (lantern_hash_tree_root_state_cached(state, &computed) != SSZ_SUCCESS) { return -1; } state->latest_block_header.state_root = computed; @@ -2495,7 +2499,7 @@ int lantern_state_process_block_header(LanternState *state, const LanternBlock * } LanternRoot latest_header_root; - if (lantern_hash_tree_root_block_header(&state->latest_block_header, &latest_header_root) != 0) { + if (lantern_hash_tree_root_block_header(&state->latest_block_header, &latest_header_root) != SSZ_SUCCESS) { return -1; } if (memcmp(block->parent_root.bytes, latest_header_root.bytes, LANTERN_ROOT_SIZE) != 0) { @@ -2557,7 +2561,7 @@ int lantern_state_process_block_header(LanternState *state, const LanternBlock * } LanternRoot body_root; - if (lantern_hash_tree_root_block_body(&block->body, &body_root) != 0) { + if (lantern_hash_tree_root_block_body(&block->body, &body_root) != SSZ_SUCCESS) { return -1; } state->latest_block_header.slot = block->slot; @@ -3078,7 +3082,7 @@ int lantern_state_transition(LanternState *state, LanternStore *store, const Lan STATE_FAIL("process block failed"); } LanternRoot computed_state_root; - bool hashed_state = lantern_hash_tree_root_state(state, &computed_state_root) == 0; + bool hashed_state = lantern_hash_tree_root_state_cached(state, &computed_state_root) == SSZ_SUCCESS; if (hashed_state) { if (memcmp(block->state_root.bytes, computed_state_root.bytes, LANTERN_ROOT_SIZE) != 0) { char expected_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; @@ -3206,7 +3210,7 @@ int lantern_state_select_block_parent( } LanternRoot header_root; - if (lantern_hash_tree_root_block_header(&state->latest_block_header, &header_root) != 0) { + if (lantern_hash_tree_root_block_header(&state->latest_block_header, &header_root) != SSZ_SUCCESS) { return -1; } @@ -3337,12 +3341,20 @@ int lantern_state_collect_attestations_for_block( checkpoint = post_checkpoint; iteration += 1u; if (iteration > iteration_guard) { + uint64_t store_justified_slot = 0u; + if (store->fork_choice) { + const LanternCheckpoint *store_justified = + lantern_fork_choice_latest_justified(store->fork_choice); + store_justified_slot = store_justified ? store_justified->slot : 0u; + } lantern_log_warn( - "state", + "propose", &meta, - "attestation collection failed to converge after %zu iterations (payloads=%zu)", - iteration, - store->known_aggregated_payloads.length); + "slot %" PRIu64 ", skipped, reason: fixed_point_not_converged" + ", block_justified_slot %" PRIu64 ", store_justified_slot %" PRIu64, + block_slot, + checkpoint.slot, + store_justified_slot); rc = -1; break; } @@ -3399,7 +3411,7 @@ int lantern_state_compute_post_state( rc = -1; goto cleanup; } - if (out_state_root && lantern_hash_tree_root_state(&scratch, out_state_root) != 0) { + if (out_state_root && lantern_hash_tree_root_state_cached(&scratch, out_state_root) != SSZ_SUCCESS) { rc = -1; goto cleanup; } @@ -3467,9 +3479,12 @@ int lantern_state_compute_vote_checkpoints( if (lantern_fork_choice_block_info(fork_choice, &head_root, &head_slot, NULL, NULL) != 0) { return -1; } + const LanternCheckpoint *store_latest_justified = + lantern_fork_choice_latest_justified(fork_choice); const LanternCheckpoint *store_latest_finalized = lantern_fork_choice_latest_finalized(fork_choice); - LanternCheckpoint source_checkpoint = base_state->latest_justified; + LanternCheckpoint source_checkpoint = + store_latest_justified ? *store_latest_justified : base_state->latest_justified; LanternCheckpoint finalized_checkpoint = base_state->latest_finalized; /* At genesis latest_justified.root is a placeholder zero root. */ if (head_slot == 0u && base_state->latest_block_header.slot == 0u) { @@ -3542,11 +3557,11 @@ int lantern_state_compute_vote_checkpoints( parent_slot, target_hex[0] ? target_hex : "0x0", parent_hex[0] ? parent_hex : "0x0"); + } + target_root = parent_root; + target_slot = parent_slot; } - target_root = parent_root; - target_slot = parent_slot; } -} bool justifiable_slot_found = true; while (!lantern_slot_is_justifiable(target_slot, finalized_checkpoint.slot)) { diff --git a/src/consensus/store.c b/src/consensus/store.c index 0d03681..1138289 100644 --- a/src/consensus/store.c +++ b/src/consensus/store.c @@ -114,6 +114,50 @@ static bool aggregated_payload_entry_equals( return true; } +static size_t proof_participant_limit(const LanternAggregatedSignatureProof *proof) { + if (!proof) { + return 0u; + } + size_t limit = proof->participants.bit_length; + if (limit > LANTERN_VALIDATOR_REGISTRY_LIMIT) { + limit = LANTERN_VALIDATOR_REGISTRY_LIMIT; + } + return limit; +} + +static bool proof_has_participant(const LanternAggregatedSignatureProof *proof) { + if (!proof || !proof->participants.bytes) { + return false; + } + size_t limit = proof_participant_limit(proof); + for (size_t validator = 0; validator < limit; ++validator) { + if (lantern_bitlist_get(&proof->participants, validator)) { + return true; + } + } + return false; +} + +static bool proof_participants_subset_of( + const LanternAggregatedSignatureProof *candidate, + const LanternAggregatedSignatureProof *cover) { + if (!candidate || !candidate->participants.bytes || !cover || !cover->participants.bytes) { + return false; + } + size_t limit = proof_participant_limit(candidate); + bool has_participant = false; + for (size_t validator = 0; validator < limit; ++validator) { + if (!lantern_bitlist_get(&candidate->participants, validator)) { + continue; + } + has_participant = true; + if (!lantern_bitlist_get(&cover->participants, validator)) { + return false; + } + } + return has_participant; +} + static void attestation_signature_map_init(struct lantern_attestation_signature_map *map) { if (!map) { return; @@ -247,6 +291,65 @@ static void aggregated_payload_pool_remove_index( cache->length -= 1u; } +static bool aggregated_payload_pool_covers_proof_participants( + const struct lantern_aggregated_payload_pool *cache, + const LanternRoot *data_root, + const LanternAggregatedSignatureProof *proof) { + if (!cache || !data_root || !proof || !proof->participants.bytes) { + return false; + } + size_t limit = proof_participant_limit(proof); + bool has_participant = false; + for (size_t validator = 0; validator < limit; ++validator) { + if (!lantern_bitlist_get(&proof->participants, validator)) { + continue; + } + has_participant = true; + bool covered = false; + for (size_t i = 0; i < cache->length; ++i) { + if (!root_equals(&cache->entries[i].data_root, data_root)) { + continue; + } + if (lantern_bitlist_get(&cache->entries[i].proof.participants, validator)) { + covered = true; + break; + } + } + if (!covered) { + return false; + } + } + return has_participant; +} + +static void aggregated_payload_pool_prune_subsets_of_entry( + struct lantern_aggregated_payload_pool *cache, + size_t cover_index) { + if (!cache || !cache->entries || cover_index >= cache->length) { + return; + } + + LanternRoot data_root = cache->entries[cover_index].data_root; + size_t index = 0u; + while (index < cache->length) { + if (index == cover_index) { + index += 1u; + continue; + } + if (root_equals(&cache->entries[index].data_root, &data_root) + && proof_participants_subset_of( + &cache->entries[index].proof, + &cache->entries[cover_index].proof)) { + aggregated_payload_pool_remove_index(cache, index); + if (index < cover_index) { + cover_index -= 1u; + } + continue; + } + index += 1u; + } +} + static int aggregated_payload_pool_add( struct lantern_aggregated_payload_pool *cache, const LanternRoot *data_root, @@ -255,7 +358,7 @@ static int aggregated_payload_pool_add( if (!cache || !data_root || !proof) { return -1; } - if (proof->participants.bit_length == 0 || proof->proof_data.length == 0) { + if (!proof_has_participant(proof) || proof->proof_data.length == 0) { return -1; } for (size_t i = 0; i < cache->length; ++i) { @@ -265,6 +368,9 @@ static int aggregated_payload_pool_add( cache->entries[i].target_slot = target_slot; return 0; } + if (aggregated_payload_pool_covers_proof_participants(cache, data_root, proof)) { + return 0; + } if (cache->length >= LANTERN_AGG_PROOF_CACHE_LIMIT) { aggregated_payload_pool_remove_index(cache, 0u); } @@ -293,6 +399,7 @@ static int aggregated_payload_pool_add( } entry->target_slot = target_slot; cache->length += 1u; + aggregated_payload_pool_prune_subsets_of_entry(cache, cache->length - 1u); return 0; } diff --git a/src/core/client.c b/src/core/client.c index 94d91a2..e3bd59c 100644 --- a/src/core/client.c +++ b/src/core/client.c @@ -38,17 +38,6 @@ #endif #include "internal/yaml_parser.h" -#include "libp2p/errors.h" -#include "libp2p/events.h" -#include "libp2p/host.h" -#include "libp2p/protocol_dial.h" -#include "libp2p/stream.h" -#include "multiformats/unsigned_varint/unsigned_varint.h" -#include "peer_id/peer_id.h" -#include "protocol/gossipsub/gossipsub.h" -#include "protocol/identify/protocol_identify.h" -#include "protocol/ping/protocol_ping.h" - #include "client_internal.h" #include "lantern/consensus/containers.h" #include "lantern/consensus/duties.h" @@ -864,6 +853,7 @@ static void client_reset_base(struct lantern_client *client) lantern_string_list_init(&client->bootnodes); lantern_string_list_init(&client->dialer_peers); lantern_string_list_init(&client->connected_peer_ids); + lantern_string_list_init(&client->connected_peer_refs); lantern_string_list_init(&client->inbound_peer_ids); lantern_string_list_init(&client->status_failure_peer_ids); double now_seconds = lantern_time_now_seconds(); @@ -871,8 +861,6 @@ static void client_reset_base(struct lantern_client *client) lantern_genesis_artifacts_init(&client->genesis); lantern_enr_record_init(&client->local_enr); lantern_libp2p_host_init(&client->network); - client->ping_server = NULL; - client->ping_running = false; lantern_gossipsub_service_init(&client->gossip); lantern_reqresp_service_init(&client->reqresp); client->reqresp_running = false; @@ -895,9 +883,8 @@ static void client_reset_base(struct lantern_client *client) client->timing_stop_flag = 1; client->dialer_thread_started = false; client->dialer_stop_flag = 1; - client->ping_thread_started = false; - client->ping_stop_flag = 1; pending_block_list_init(&client->pending_blocks); + client->block_import_stop = true; pending_vote_list_init(&client->pending_gossip_votes); client->pending_lock_initialized = false; client->sync_state = LANTERN_SYNC_STATE_IDLE; @@ -1322,7 +1309,7 @@ static void client_log_generated_anchor_block(struct lantern_client *client) goto cleanup; } - if (lantern_hash_tree_root_state(&generated_state, &generated_state_root) != 0) + if (lantern_hash_tree_root_state(&generated_state, &generated_state_root) != SSZ_SUCCESS) { goto cleanup; } @@ -1335,7 +1322,7 @@ static void client_log_generated_anchor_block(struct lantern_client *client) lantern_block_body_init(&generated_block.body); body_initialized = true; - if (lantern_hash_tree_root_block(&generated_block, &generated_block_root) == 0) + if (lantern_hash_tree_root_block(&generated_block, &generated_block_root) == SSZ_SUCCESS) { char generated_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; format_root_hex(&generated_block_root, generated_hex, sizeof(generated_hex)); @@ -1391,7 +1378,7 @@ static void client_log_genesis_anchors( canonical_hex[0] = '\0'; body_hex[0] = '\0'; spec_header_hex[0] = '\0'; - if (lantern_hash_tree_root_block_header(&client->state.latest_block_header, &header_root) == 0) + if (lantern_hash_tree_root_block_header(&client->state.latest_block_header, &header_root) == SSZ_SUCCESS) { format_root_hex(&header_root, header_hex, sizeof(header_hex)); } @@ -1408,7 +1395,7 @@ static void client_log_genesis_anchors( ? *state_root : client->state.latest_block_header.state_root; lantern_block_body_init(&genesis_block.body); - if (lantern_hash_tree_root_block(&genesis_block, &genesis_block_root) == 0) + if (lantern_hash_tree_root_block(&genesis_block, &genesis_block_root) == SSZ_SUCCESS) { format_root_hex(&genesis_block_root, block_hex, sizeof(block_hex)); } @@ -1419,14 +1406,14 @@ static void client_log_genesis_anchors( sizeof(parent_hex)); LanternBlockHeader canonical_header = client->state.latest_block_header; canonical_header.state_root = state_root ? *state_root : canonical_header.state_root; - if (lantern_hash_tree_root_block_header(&canonical_header, &canonical_header_root) == 0) + if (lantern_hash_tree_root_block_header(&canonical_header, &canonical_header_root) == SSZ_SUCCESS) { format_root_hex(&canonical_header_root, canonical_hex, sizeof(canonical_hex)); } LanternBlockBody empty_body_snapshot; lantern_block_body_init(&empty_body_snapshot); LanternRoot default_body_root; - if (lantern_hash_tree_root_block_body(&empty_body_snapshot, &default_body_root) != 0) + if (lantern_hash_tree_root_block_body(&empty_body_snapshot, &default_body_root) != SSZ_SUCCESS) { memset(&default_body_root, 0, sizeof(default_body_root)); } @@ -1434,7 +1421,7 @@ static void client_log_genesis_anchors( LanternBlockHeader spec_header = client->state.latest_block_header; spec_header.state_root = state_root ? *state_root : spec_header.state_root; spec_header.body_root = default_body_root; - if (lantern_hash_tree_root_block_header(&spec_header, &spec_header_root) == 0) + if (lantern_hash_tree_root_block_header(&spec_header, &spec_header_root) == SSZ_SUCCESS) { format_root_hex(&spec_header_root, spec_header_hex, sizeof(spec_header_hex)); } @@ -1456,7 +1443,7 @@ static void client_log_genesis_anchors( &(const struct lantern_log_metadata){.validator = client->node_id}, "failed to size genesis signatures list"); } - else if (lantern_hash_tree_root_signed_block(&genesis_signed, &genesis_signed_block_root) == 0) + else if (lantern_hash_tree_root_signed_block(&genesis_signed, &genesis_signed_block_root) == SSZ_SUCCESS) { format_root_hex(&genesis_signed_block_root, signed_block_hex, sizeof(signed_block_hex)); } @@ -1508,7 +1495,7 @@ static lantern_client_error client_finalize_genesis_state(struct lantern_client return LANTERN_CLIENT_ERR_GENESIS; } LanternRoot state_root; - if (lantern_hash_tree_root_state(&client->state, &state_root) != 0) + if (lantern_hash_tree_root_state(&client->state, &state_root) != SSZ_SUCCESS) { return LANTERN_CLIENT_ERR_GENESIS; } @@ -2511,7 +2498,7 @@ static lantern_client_error client_load_state_from_checkpoint( bool decoded_owned = true; lantern_client_error result = LANTERN_CLIENT_OK; - if (lantern_ssz_decode_state(&decoded, state_bytes, state_len) != 0) + if (lantern_ssz_decode_state(&decoded, state_bytes, state_len) != SSZ_SUCCESS) { lantern_log_error( "checkpoint_sync", @@ -2580,7 +2567,7 @@ static lantern_client_error client_load_state_from_checkpoint( } LanternRoot state_root; - if (lantern_hash_tree_root_state(&decoded, &state_root) != 0) + if (lantern_hash_tree_root_state(&decoded, &state_root) != SSZ_SUCCESS) { lantern_log_error( "checkpoint_sync", @@ -2595,7 +2582,7 @@ static lantern_client_error client_load_state_from_checkpoint( LanternBlockHeader anchor_header = decoded.latest_block_header; anchor_header.state_root = state_root; LanternRoot anchor_root; - if (lantern_hash_tree_root_block_header(&anchor_header, &anchor_root) != 0) + if (lantern_hash_tree_root_block_header(&anchor_header, &anchor_root) != SSZ_SUCCESS) { lantern_log_error( "checkpoint_sync", @@ -2663,7 +2650,7 @@ static lantern_client_error client_load_state_from_checkpoint( LanternRoot adjusted_anchor_root = {0}; bool have_adjusted_state_root = false; bool have_adjusted_anchor_root = false; - if (lantern_hash_tree_root_state(&anchor_checkpoint_alias, &adjusted_state_root) == 0) + if (lantern_hash_tree_root_state(&anchor_checkpoint_alias, &adjusted_state_root) == SSZ_SUCCESS) { have_adjusted_state_root = true; LanternBlockHeader adjusted_anchor_header = anchor_checkpoint_alias.latest_block_header; @@ -2671,7 +2658,7 @@ static lantern_client_error client_load_state_from_checkpoint( if (lantern_hash_tree_root_block_header( &adjusted_anchor_header, &adjusted_anchor_root) - == 0) + == SSZ_SUCCESS) { have_adjusted_anchor_root = true; } @@ -3113,8 +3100,7 @@ static lantern_client_error client_start_runtime(struct lantern_client *client) /** * @brief Start libp2p host and connection-level services. * - * Loads the node key, starts the libp2p host, subscribes to connection events, - * and launches the ping service. + * Loads the node key and prepares the libp2p host. * * @param client Client to start networking for * @param options User options containing key paths @@ -3145,7 +3131,7 @@ static lantern_client_error client_start_network( .allow_outbound_identify = 1, }; - if (lantern_libp2p_host_start(&client->network, &net_cfg) != 0) + if (lantern_libp2p_host_prepare(&client->network, &net_cfg) != 0) { lantern_log_error( "client", @@ -3168,37 +3154,6 @@ static lantern_client_error client_start_network( } connection_counter_reset(client); - if (libp2p_event_subscribe( - client->network.host, - connection_events_cb, - client, - &client->connection_subscription) - != 0) - { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "failed to subscribe to libp2p connection events"); - return LANTERN_CLIENT_ERR_NETWORK; - } - - libp2p_protocol_server_t *ping_server = NULL; - if (libp2p_ping_service_start(client->network.host, &ping_server) != 0) - { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "failed to start libp2p ping service"); - return LANTERN_CLIENT_ERR_NETWORK; - } - - client->ping_server = ping_server; - client->ping_running = true; - lantern_log_info( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "libp2p ping service started"); - return LANTERN_CLIENT_OK; } @@ -3281,7 +3236,7 @@ static lantern_client_error client_start_protocols( attestation_committee_count); } struct lantern_gossipsub_config gossip_cfg = { - .host = client->network.host, + .network = &client->network, .devnet = client->devnet, .data_dir = client->data_dir, .topic_network_name = topic_network_name, @@ -3352,10 +3307,12 @@ static lantern_client_error client_start_protocols( req_callbacks.handle_status = reqresp_handle_status; req_callbacks.status_failure = reqresp_status_failure; req_callbacks.collect_blocks = reqresp_collect_blocks; + req_callbacks.handle_block_response = reqresp_handle_block_response; + req_callbacks.blocks_request_complete = reqresp_blocks_request_complete; struct lantern_reqresp_service_config req_config; memset(&req_config, 0, sizeof(req_config)); - req_config.host = client->network.host; + req_config.network = &client->network; req_config.callbacks = &req_callbacks; if (lantern_reqresp_service_start(&client->reqresp, &req_config) != 0) { @@ -3367,6 +3324,19 @@ static lantern_client_error client_start_protocols( } client->reqresp_running = true; + /* + * This handler may immediately send Status on CONN_ESTABLISHED. Register + * it after reqresp so reqresp has already cached the connection. + */ + if (lantern_libp2p_host_register_event_handler(&client->network, connection_events_cb, client) != 0) + { + lantern_log_error( + "network", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "failed to subscribe to libp2p connection events"); + return LANTERN_CLIENT_ERR_NETWORK; + } + if (append_genesis_bootnodes(client) != 0) { lantern_log_error( @@ -3398,6 +3368,15 @@ static lantern_client_error client_start_protocols( "local ENR prepared sequence=%" PRIu64, client->assigned_validators->enr.sequence); + if (lantern_libp2p_host_launch(&client->network) != 0) + { + lantern_log_error( + "client", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "failed to launch libp2p host"); + return LANTERN_CLIENT_ERR_NETWORK; + } + memset(node_key, 0, NODE_PRIVATE_KEY_SIZE); return LANTERN_CLIENT_OK; } @@ -3423,14 +3402,6 @@ static void client_start_background_services(struct lantern_client *client) "failed to start peer dialer thread"); } - if (start_ping_service(client) != 0) - { - lantern_log_warn( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "failed to start ping service thread"); - } - if (start_timing_service(client) != 0) { lantern_log_warn( @@ -3463,7 +3434,6 @@ static void shutdown_validator_and_keys(struct lantern_client *client) { stop_timing_service(client); stop_validator_service(client); - stop_ping_service(client); stop_peer_dialer(client); lantern_client_free_xmss_pubkeys(client); free(client->xmss_key_dir); @@ -3501,8 +3471,8 @@ static void shutdown_http_and_metrics(struct lantern_client *client) /** * @brief Tear down networking services and related synchronization primitives. * - * Unsubscribes from libp2p events, stops ping service, destroys connection - * lock, and clears peer tracking lists. + * Stops networking services, destroys connection lock, and clears peer tracking + * lists. * * @param client Client whose networking stack is being shut down * @@ -3510,32 +3480,6 @@ static void shutdown_http_and_metrics(struct lantern_client *client) */ static void shutdown_network_services(struct lantern_client *client) { - if (client->network.host && client->connection_subscription) - { - libp2p_event_unsubscribe(client->network.host, client->connection_subscription); - } - client->connection_subscription = NULL; - - if (client->network.host && client->ping_running && client->ping_server) - { - if (libp2p_ping_service_stop(client->network.host, client->ping_server) != 0) - { - lantern_log_warn( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "failed to stop libp2p ping service cleanly"); - } - else - { - lantern_log_info( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "shutdown: libp2p ping service stopped"); - } - } - client->ping_server = NULL; - client->ping_running = false; - if (client->connection_lock_initialized) { connection_counter_reset(client); @@ -3547,6 +3491,7 @@ static void shutdown_network_services(struct lantern_client *client) client->connected_peers = 0; } lantern_string_list_reset(&client->connected_peer_ids); + lantern_string_list_reset(&client->connected_peer_refs); lantern_string_list_reset(&client->inbound_peer_ids); } @@ -3934,6 +3879,12 @@ lantern_client_error lantern_init( goto error; } + err = lantern_client_block_importer_start(client); + if (err != LANTERN_CLIENT_OK) + { + goto error; + } + err = client_prepare_storage_and_genesis(client, options); if (err != LANTERN_CLIENT_OK) { @@ -4016,6 +3967,7 @@ void lantern_shutdown(struct lantern_client *client) shutdown_validator_and_keys(client); shutdown_http_and_metrics(client); + lantern_client_block_importer_stop(client); shutdown_network_services(client); shutdown_peer_tracking(client); shutdown_validator_lock(client); diff --git a/src/core/client_debug.c b/src/core/client_debug.c index 401b4b0..654f376 100644 --- a/src/core/client_debug.c +++ b/src/core/client_debug.c @@ -20,6 +20,7 @@ #include #include "lantern/support/log.h" +#include "lantern/support/strings.h" /* ============================================================================ * Debug Vote/Block Recording @@ -252,8 +253,7 @@ int lantern_client_debug_pending_entry( requested = entry->parent_requested; if (entry->peer_text[0]) { - strncpy(peer_copy, entry->peer_text, sizeof(peer_copy) - 1u); - peer_copy[sizeof(peer_copy) - 1u] = '\0'; + (void)lantern_string_copy(peer_copy, sizeof(peer_copy), entry->peer_text); } lantern_client_unlock_pending(mutable_client, locked); @@ -274,8 +274,7 @@ int lantern_client_debug_pending_entry( out_peer_text[0] = '\0'; if (peer_text_len > 1 && peer_copy[0]) { - strncpy(out_peer_text, peer_copy, peer_text_len - 1u); - out_peer_text[peer_text_len - 1u] = '\0'; + (void)lantern_string_copy(out_peer_text, peer_text_len, peer_copy); } } return LANTERN_CLIENT_OK; diff --git a/src/core/client_http.c b/src/core/client_http.c index 92e1b19..f7e562d 100644 --- a/src/core/client_http.c +++ b/src/core/client_http.c @@ -28,6 +28,7 @@ #include "lantern/metrics/lean_metrics.h" #include "lantern/storage/storage.h" #include "lantern/support/log.h" +#include "lantern/support/strings.h" /** @@ -188,7 +189,7 @@ int http_snapshot_fork_choice( if (lantern_hash_tree_root_block_header( &client->state.latest_block_header, &state_head_root) - != 0) + != SSZ_SUCCESS) { lantern_fork_choice_tree_snapshot_reset(&snapshot); lantern_client_unlock_state(client, state_locked); @@ -317,8 +318,7 @@ int http_validator_info_cb( out_info->global_index); if (written < 0 || (size_t)written >= sizeof(out_info->label)) { - strncpy(out_info->label, base, sizeof(out_info->label)); - out_info->label[sizeof(out_info->label) - 1] = '\0'; + (void)lantern_string_copy(out_info->label, sizeof(out_info->label), base); } return LANTERN_HTTP_CB_OK; } diff --git a/src/core/client_init.c b/src/core/client_init.c index 92f3480..2402daa 100644 --- a/src/core/client_init.c +++ b/src/core/client_init.c @@ -192,8 +192,9 @@ int append_unique_bootnode(struct lantern_string_list *list, const char *value) * * @spec subspecs/networking/discovery.py - peer discovery * - * Iterates through ENR records from genesis and adds them as bootnodes - * to the client's bootnode list and peer store. + * Iterates through ENR records from genesis, stores them in the client's + * bootnode list, and verifies that they can be converted to c-lean-libp2p + * dial addresses. Actual dialing starts after the libp2p host is launched. * * @param client Client instance * @return 0 on success, -1 on failure @@ -218,26 +219,23 @@ int append_genesis_bootnodes(struct lantern_client *client) { return -1; } - if (client->network.host) + if (lantern_libp2p_validate_enr_peer(record) != 0) { - if (lantern_libp2p_host_add_enr_peer(&client->network, record, LANTERN_LIBP2P_DEFAULT_PEER_TTL_MS) != 0) - { - lantern_log_warn( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = record->encoded}, - "failed to add ENR peer from genesis"); - continue; - } - lantern_log_info( + lantern_log_warn( "network", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = record->encoded}, - "bootnode registered sequence=%" PRIu64, - record->sequence); + "invalid ENR bootnode from genesis"); + continue; } + lantern_log_info( + "network", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = record->encoded}, + "bootnode registered sequence=%" PRIu64, + record->sequence); } return 0; } @@ -340,7 +338,7 @@ int populate_local_validators(struct lantern_client *client) client->validator_assignment.indices[i]); if (n < 0 || (size_t)n >= sizeof(indices_buf) - written) { - strncpy(indices_buf + (sizeof(indices_buf) > 4 ? sizeof(indices_buf) - 4 : 0), "...", 3); + memcpy(indices_buf + (sizeof(indices_buf) > 4 ? sizeof(indices_buf) - 4 : 0), "...", 3); indices_buf[sizeof(indices_buf) - 1] = '\0'; break; } diff --git a/src/core/client_internal.h b/src/core/client_internal.h index 514a936..08b6e49 100644 --- a/src/core/client_internal.h +++ b/src/core/client_internal.h @@ -113,6 +113,7 @@ bool lantern_root_is_zero(const LanternRoot *root); * @note Thread safety: This function is thread-safe */ bool lantern_validator_pubkey_is_zero(const uint8_t *pubkey); +const char *lantern_sync_state_name(LanternSyncState state); /** diff --git a/src/core/client_keys.c b/src/core/client_keys.c index 09d5096..5fcccee 100644 --- a/src/core/client_keys.c +++ b/src/core/client_keys.c @@ -51,7 +51,6 @@ static const size_t XMSS_WINDOWS_COLON_INDEX = 1u; static const size_t XMSS_WINDOWS_SEPARATOR_INDEX = 2u; static const char XMSS_DEFAULT_KEYS_DIR[] = "xmss-keys"; -static const char XMSS_ANNOTATED_VALIDATORS_FILENAME[] = "annotated_validators.yaml"; /* ============================================================================ diff --git a/src/core/client_network.c b/src/core/client_network.c index ff3151a..eddfe8f 100644 --- a/src/core/client_network.c +++ b/src/core/client_network.c @@ -23,26 +23,12 @@ #include #include -#include -#include -#include -#include - #include "lantern/networking/libp2p.h" #include "lantern/metrics/lean_metrics.h" #include "lantern/support/log.h" #include "lantern/support/string_list.h" -/* ============================================================================ - * Constants - * ============================================================================ */ - -static const unsigned LANTERN_PING_INTERVAL_SECONDS = 15u; -static const uint64_t LANTERN_PING_TIMEOUT_MS = 5000ULL; -static const uint32_t LANTERN_PING_FAILURES_BEFORE_DISCONNECT = 1u; - - /* ============================================================================ * Utilities * ============================================================================ */ @@ -56,28 +42,7 @@ static const uint32_t LANTERN_PING_FAILURES_BEFORE_DISCONNECT = 1u; * * @note Thread safety: This function is thread-safe */ -static int write_legacy_peer_id_text(const peer_id_t *peer, char *out, size_t out_len) -{ - if (!peer || !out || out_len == 0) - { - return -1; - } - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - peer, - PEER_ID_TEXT_LEGACY_BASE58, - out, - out_len, - &written); - if (rc != PEER_ID_OK) - { - out[0] = '\0'; - return -1; - } - return (int)written; -} - -static void format_peer_id_text(const peer_id_t *peer, char *out, size_t out_len) +static void format_peer_id_text(const struct lantern_peer_id *peer, char *out, size_t out_len) { if (!out || out_len == 0) { @@ -90,7 +55,7 @@ static void format_peer_id_text(const peer_id_t *peer, char *out, size_t out_len return; } - if (write_legacy_peer_id_text(peer, out, out_len) < 0) + if (lantern_peer_id_to_text(peer, out, out_len) < 0) { out[0] = '\0'; } @@ -103,24 +68,21 @@ static lean_metrics_direction_t metrics_direction_from_inbound(bool inbound) static lean_metrics_connection_result_t metrics_connection_result_from_code(int code) { - return (code == LIBP2P_ERR_TIMEOUT) ? LEAN_METRICS_CONN_RESULT_TIMEOUT : LEAN_METRICS_CONN_RESULT_ERROR; + (void)code; + return LEAN_METRICS_CONN_RESULT_ERROR; } static lean_metrics_disconnection_reason_t metrics_disconnection_reason_from_code(int reason) { - switch (reason) - { - case LIBP2P_ERR_TIMEOUT: - return LEAN_METRICS_DISCONNECT_TIMEOUT; - case LIBP2P_ERR_EOF: - case LIBP2P_ERR_RESET: - return LEAN_METRICS_DISCONNECT_REMOTE_CLOSE; - case 0: - case LIBP2P_ERR_CLOSED: - return LEAN_METRICS_DISCONNECT_LOCAL_CLOSE; - default: - return LEAN_METRICS_DISCONNECT_ERROR; + if (reason == LIBP2P_HOST_OK) + { + return LEAN_METRICS_DISCONNECT_LOCAL_CLOSE; + } + if (reason == LIBP2P_HOST_ERR_CLOSED) + { + return LEAN_METRICS_DISCONNECT_REMOTE_CLOSE; } + return LEAN_METRICS_DISCONNECT_ERROR; } /* ============================================================================ @@ -145,6 +107,8 @@ void connection_counter_reset(struct lantern_client *client) client->connected_peers = 0; lantern_string_list_reset(&client->connected_peer_ids); lantern_string_list_init(&client->connected_peer_ids); + lantern_string_list_reset(&client->connected_peer_refs); + lantern_string_list_init(&client->connected_peer_refs); lantern_string_list_reset(&client->inbound_peer_ids); lantern_string_list_init(&client->inbound_peer_ids); return; @@ -154,6 +118,8 @@ void connection_counter_reset(struct lantern_client *client) client->connected_peers = 0; lantern_string_list_reset(&client->connected_peer_ids); lantern_string_list_init(&client->connected_peer_ids); + lantern_string_list_reset(&client->connected_peer_refs); + lantern_string_list_init(&client->connected_peer_refs); lantern_string_list_reset(&client->inbound_peer_ids); lantern_string_list_init(&client->inbound_peer_ids); pthread_mutex_unlock(&client->connection_lock); @@ -163,6 +129,8 @@ void connection_counter_reset(struct lantern_client *client) client->connected_peers = 0; lantern_string_list_reset(&client->connected_peer_ids); lantern_string_list_init(&client->connected_peer_ids); + lantern_string_list_reset(&client->connected_peer_refs); + lantern_string_list_init(&client->connected_peer_refs); lantern_string_list_reset(&client->inbound_peer_ids); lantern_string_list_init(&client->inbound_peer_ids); } @@ -183,7 +151,7 @@ void connection_counter_reset(struct lantern_client *client) void connection_counter_update( struct lantern_client *client, int delta, - const peer_id_t *peer, + const struct lantern_peer_id *peer, bool inbound, int reason) { @@ -203,6 +171,7 @@ void connection_counter_update( { if (delta > 0) { + (void)lantern_string_list_append(&client->connected_peer_refs, peer_text); if (!string_list_contains(&client->connected_peer_ids, peer_text)) { (void)lantern_string_list_append(&client->connected_peer_ids, peer_text); @@ -216,7 +185,9 @@ void connection_counter_update( } else { - string_list_remove(&client->inbound_peer_ids, peer_text); + /* Keep an existing inbound mark while any connection for + * the peer remains; c-lean-libp2p close events do not + * expose the original connection direction. */ } } else if (delta < 0) @@ -224,9 +195,13 @@ void connection_counter_update( if (string_list_contains(&client->connected_peer_ids, peer_text)) { was_inbound = string_list_contains(&client->inbound_peer_ids, peer_text); - string_list_remove(&client->connected_peer_ids, peer_text); - string_list_remove(&client->inbound_peer_ids, peer_text); - record_disconnect = true; + string_list_remove(&client->connected_peer_refs, peer_text); + if (!string_list_contains(&client->connected_peer_refs, peer_text)) + { + string_list_remove(&client->connected_peer_ids, peer_text); + string_list_remove(&client->inbound_peer_ids, peer_text); + record_disconnect = true; + } } } client->connected_peers = client->connected_peer_ids.len; @@ -268,7 +243,7 @@ void connection_counter_update( if (total == 0 && client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) { - client->sync_state = LANTERN_SYNC_STATE_IDLE; + lantern_client_set_sync_state_logged(client, LANTERN_SYNC_STATE_IDLE, "no peers"); pthread_mutex_unlock(&client->status_lock); } lantern_log_trace( @@ -345,31 +320,12 @@ bool lantern_client_is_peer_connected(struct lantern_client *client, const char * * @note Thread safety: This function acquires status_lock */ -void request_status_now(struct lantern_client *client, const peer_id_t *peer, const char *peer_text) +void request_status_now(struct lantern_client *client, const struct lantern_peer_id *peer, const char *peer_text) { if (!client || !client->reqresp_running) { return; } - uint64_t genesis_time = client->genesis.chain_config.genesis_time; - if (genesis_time > 0) - { - uint64_t now = validator_wall_time_now_seconds(); - if (now > 0 && now < genesis_time) - { - struct lantern_log_metadata meta = { - .validator = client->node_id, - .peer = (peer_text && peer_text[0]) ? peer_text : NULL, - }; - lantern_log_trace( - "reqresp", - &meta, - "skipping status request before genesis now=%" PRIu64 " genesis_time=%" PRIu64, - now, - genesis_time); - return; - } - } char peer_buffer[128]; peer_buffer[0] = '\0'; const char *status_peer = (peer_text && peer_text[0]) ? peer_text : NULL; @@ -561,36 +517,13 @@ void identify_dial_multiaddr( return; } - libp2p_stream_t *stream = NULL; - int rc = libp2p_host_dial_protocol_blocking( - client->network.host, - multiaddr, - LIBP2P_IDENTIFY_PROTO_ID, - LANTERN_PEER_DIAL_TIMEOUT_MS, - &stream); - - if (rc == 0 && stream) - { - libp2p_stream_free(stream); - lantern_log_debug( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_label, - }, - "identify dial succeeded addr=%s", - multiaddr); - return; - } - lantern_log_trace( "network", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = peer_label, }, - "identify dial failed rc=%d addr=%s", - rc, + "identify query will run after connection opens addr=%s", multiaddr); } @@ -633,7 +566,7 @@ void peer_dialer_sleep(struct lantern_client *client, unsigned seconds) * * @note Thread safety: This function acquires connection_lock */ -void redial_peer_on_timeout(struct lantern_client *client, const peer_id_t *peer) +void redial_peer_on_timeout(struct lantern_client *client, const struct lantern_peer_id *peer) { if (!client || !client->network.host || !peer) { @@ -673,7 +606,7 @@ void redial_peer_on_timeout(struct lantern_client *client, const peer_id_t *peer } char multiaddr[256]; - peer_id_t *enr_peer_id = NULL; + struct lantern_peer_id enr_peer_id; if (lantern_libp2p_enr_to_multiaddr( record, multiaddr, @@ -683,10 +616,7 @@ void redial_peer_on_timeout(struct lantern_client *client, const peer_id_t *peer continue; } - int eq = peer_id_equal(peer, enr_peer_id); - peer_id_free(enr_peer_id); - - if (eq == 1) + if (lantern_peer_id_equal(peer, &enr_peer_id)) { /* Found matching peer in genesis, redial */ lantern_log_info( @@ -698,10 +628,7 @@ void redial_peer_on_timeout(struct lantern_client *client, const peer_id_t *peer "redialing peer after disconnect addr=%s", multiaddr); - (void)lantern_libp2p_host_add_enr_peer( - &client->network, - record, - LANTERN_LIBP2P_DEFAULT_PEER_TTL_MS); + (void)lantern_libp2p_host_dial_multiaddr(&client->network, multiaddr); identify_dial_multiaddr(client, multiaddr, peer_text[0] ? peer_text : record->encoded); return; } @@ -769,20 +696,17 @@ static size_t snapshot_connected_peers( * * @note Thread safety: This function is thread-safe */ -static peer_id_t *get_local_peer_id(struct lantern_client *client) +static bool get_local_peer_id(struct lantern_client *client, struct lantern_peer_id *out_peer) { - if (!client || !client->network.host) - { - return NULL; - } - - peer_id_t *local_peer = NULL; - if (libp2p_host_get_peer_id(client->network.host, &local_peer) != 0) + if (!client || !out_peer || client->network.local_peer_id_len == 0 + || client->network.local_peer_id_len > sizeof(out_peer->bytes)) { - return NULL; + return false; } - return local_peer; + memcpy(out_peer->bytes, client->network.local_peer_id, client->network.local_peer_id_len); + out_peer->len = client->network.local_peer_id_len; + return true; } @@ -797,7 +721,7 @@ static peer_id_t *get_local_peer_id(struct lantern_client *client) */ static size_t compute_peer_dial_target( const struct lantern_enr_record_list *enrs, - const peer_id_t *local_peer) + const struct lantern_peer_id *local_peer) { if (!enrs || enrs->count == 0) { @@ -827,7 +751,7 @@ static size_t compute_peer_dial_target( static void peer_dialer_handle_record( struct lantern_client *client, const struct lantern_enr_record *record, - const peer_id_t *local_peer, + const struct lantern_peer_id *local_peer, const struct lantern_string_list *connected_snapshot) { if (!client || !record || !record->encoded) @@ -836,66 +760,34 @@ static void peer_dialer_handle_record( } char multiaddr[256]; - peer_id_t *peer_id = NULL; + struct lantern_peer_id peer_id; if (lantern_libp2p_enr_to_multiaddr(record, multiaddr, sizeof(multiaddr), &peer_id) != 0) { return; } - if (local_peer && peer_id_equal(local_peer, peer_id) == 1) + if (local_peer && lantern_peer_id_equal(local_peer, &peer_id)) { - peer_id_free(peer_id); return; } char peer_text[128]; - format_peer_id_text(peer_id, peer_text, sizeof(peer_text)); + format_peer_id_text(&peer_id, peer_text, sizeof(peer_text)); if (peer_text[0] && connected_snapshot && string_list_contains(connected_snapshot, peer_text)) { - peer_id_free(peer_id); return; } - (void)lantern_libp2p_host_add_enr_peer( - &client->network, - record, - LANTERN_LIBP2P_DEFAULT_PEER_TTL_MS); + (void)lantern_libp2p_host_dial_multiaddr(&client->network, multiaddr); const char *peer_label = peer_text[0] ? peer_text : record->encoded; identify_dial_multiaddr(client, multiaddr, peer_label); - if (client->gossip_running && client->gossip.gossipsub) + if (peer_text[0] && !string_list_contains(&client->dialer_peers, peer_text)) { - bool already_added = false; - if (peer_text[0]) - { - already_added = string_list_contains(&client->dialer_peers, peer_text); - } - - if (!already_added) - { - libp2p_err_t perr = libp2p_gossipsub_peering_add( - client->gossip.gossipsub, - peer_id); - if (perr == LIBP2P_ERR_OK) - { - if (peer_text[0]) - { - (void)lantern_string_list_append(&client->dialer_peers, peer_text); - } - lantern_log_trace( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_label, - }, - "dialer added peer to gossipsub peering"); - } - } + (void)lantern_string_list_append(&client->dialer_peers, peer_text); } - - peer_id_free(peer_id); } @@ -922,7 +814,8 @@ void peer_dialer_attempt(struct lantern_client *client) struct lantern_string_list connected_snapshot; lantern_string_list_init(&connected_snapshot); size_t connected_unique = snapshot_connected_peers(client, &connected_snapshot); - peer_id_t *local_peer = get_local_peer_id(client); + struct lantern_peer_id local_peer_value; + struct lantern_peer_id *local_peer = get_local_peer_id(client, &local_peer_value) ? &local_peer_value : NULL; size_t target = compute_peer_dial_target(enrs, local_peer); if (target > 0 && connected_unique >= target) @@ -945,11 +838,6 @@ void peer_dialer_attempt(struct lantern_client *client) } cleanup: - if (local_peer) - { - peer_id_free(local_peer); - } - lantern_string_list_reset(&connected_snapshot); } @@ -1033,404 +921,6 @@ void stop_peer_dialer(struct lantern_client *client) } -/* ============================================================================ - * Ping Service - * ============================================================================ */ - -static uint32_t record_peer_ping_failure(struct lantern_client *client, const char *peer_text) -{ - if (!client || !peer_text || peer_text[0] == '\0') - { - return 0; - } - if (!client->status_lock_initialized) - { - return 0; - } - if (pthread_mutex_lock(&client->status_lock) != 0) - { - return 0; - } - - uint32_t failures = 0; - struct lantern_peer_status_entry *entry = - lantern_client_ensure_status_entry_locked(client, peer_text); - if (entry) - { - if (entry->consecutive_ping_failures < UINT32_MAX) - { - entry->consecutive_ping_failures += 1u; - } - failures = entry->consecutive_ping_failures; - } - pthread_mutex_unlock(&client->status_lock); - return failures; -} - -static void clear_peer_ping_failures(struct lantern_client *client, const char *peer_text) -{ - if (!client || !peer_text || peer_text[0] == '\0') - { - return; - } - if (!client->status_lock_initialized) - { - return; - } - if (pthread_mutex_lock(&client->status_lock) != 0) - { - return; - } - struct lantern_peer_status_entry *entry = - lantern_client_find_status_entry_locked(client, peer_text); - if (entry) - { - entry->consecutive_ping_failures = 0; - } - pthread_mutex_unlock(&client->status_lock); -} - -static bool ping_error_is_disconnect(int err) -{ - switch (err) - { - case LIBP2P_ERR_TIMEOUT: - case LIBP2P_ERR_EOF: - case LIBP2P_ERR_CLOSED: - case LIBP2P_ERR_RESET: - return true; - default: - return false; - } -} - -/** - * Callback context for async ping dial. - */ -struct ping_dial_ctx -{ - struct lantern_client *client; /**< Client instance */ - char peer_text[128]; /**< Peer ID as text */ -}; - - -/** - * Callback when ping stream is opened. - * - * @param s Opened stream - * @param user_data Ping dial context - * @param err Error code - * - * @note Thread safety: This function is called from libp2p thread - */ -static void ping_on_stream_open(libp2p_stream_t *s, void *user_data, int err) -{ - struct ping_dial_ctx *ctx = (struct ping_dial_ctx *)user_data; - if (!ctx) - { - if (s) - { - libp2p_stream_close(s); - libp2p_stream_free(s); - } - return; - } - struct lantern_client *client = ctx->client; - const char *peer_text = ctx->peer_text; - if (err || !s) - { - lantern_log_trace( - "network", - &(const struct lantern_log_metadata){ - .validator = client ? client->node_id : NULL, - .peer = peer_text[0] ? peer_text : NULL, - }, - "ping dial failed err=%d", - err); - if (client && peer_text[0]) - { - int reason = err != 0 ? err : LIBP2P_ERR_INTERNAL; - if (!ping_error_is_disconnect(reason)) - { - clear_peer_ping_failures(client, peer_text); - free(ctx); - return; - } - uint32_t failures = record_peer_ping_failure(client, peer_text); - if (failures >= LANTERN_PING_FAILURES_BEFORE_DISCONNECT) - { - peer_id_t *peer_id = NULL; - if (peer_id_new_from_text(peer_text, &peer_id) == PEER_ID_OK && peer_id) - { - connection_counter_update(client, -1, peer_id, false, reason); - peer_id_free(peer_id); - } - clear_peer_ping_failures(client, peer_text); - } - } - free(ctx); - return; - } - /* Perform the ping roundtrip. */ - uint64_t rtt_ms = 0; - libp2p_ping_err_t rc = libp2p_ping_roundtrip_stream(s, LANTERN_PING_TIMEOUT_MS, &rtt_ms); - if (rc == LIBP2P_PING_OK) - { - lantern_log_trace( - "network", - &(const struct lantern_log_metadata){ - .validator = client ? client->node_id : NULL, - .peer = peer_text[0] ? peer_text : NULL, - }, - "ping ok rtt_ms=%llu", - (unsigned long long)rtt_ms); - clear_peer_ping_failures(client, peer_text); - } - else - { - lantern_log_trace( - "network", - &(const struct lantern_log_metadata){ - .validator = client ? client->node_id : NULL, - .peer = peer_text[0] ? peer_text : NULL, - }, - "ping failed rc=%d", - (int)rc); - if (client && peer_text[0]) - { - int reason = (rc == LIBP2P_PING_ERR_TIMEOUT) ? LIBP2P_ERR_TIMEOUT : LIBP2P_ERR_RESET; - if (!ping_error_is_disconnect(reason)) - { - clear_peer_ping_failures(client, peer_text); - libp2p_stream_close(s); - libp2p_stream_free(s); - free(ctx); - return; - } - uint32_t failures = record_peer_ping_failure(client, peer_text); - if (failures >= LANTERN_PING_FAILURES_BEFORE_DISCONNECT) - { - peer_id_t *peer_id = NULL; - if (peer_id_new_from_text(peer_text, &peer_id) == PEER_ID_OK && peer_id) - { - connection_counter_update(client, -1, peer_id, false, reason); - peer_id_free(peer_id); - } - clear_peer_ping_failures(client, peer_text); - } - } - } - libp2p_stream_close(s); - libp2p_stream_free(s); - free(ctx); -} - -static bool peer_status_needs_refresh( - struct lantern_client *client, - const char *peer_id, - uint64_t now_ms) -{ - if (!client || !peer_id || peer_id[0] == '\0') - { - return false; - } - if (!client->status_lock_initialized) - { - return true; - } - if (pthread_mutex_lock(&client->status_lock) != 0) - { - return true; - } - struct lantern_peer_status_entry *entry = - lantern_client_find_status_entry_locked(client, peer_id); - bool needs_refresh = true; - if (entry) - { - if (entry->status_request_inflight) - { - needs_refresh = false; - } - else if (entry->has_status && entry->last_status_ms != 0 && now_ms >= entry->last_status_ms) - { - needs_refresh = (now_ms - entry->last_status_ms) > LANTERN_PEER_STATUS_STALE_MS; - } - } - pthread_mutex_unlock(&client->status_lock); - return needs_refresh; -} - - -/** - * Ping all connected peers. - * - * @param client Client instance - * - * @note Thread safety: This function acquires connection_lock - */ -static void ping_all_peers(struct lantern_client *client) -{ - if (!client || !client->network.host || !client->connection_lock_initialized) - { - return; - } - /* Take a snapshot of connected peer IDs. */ - struct lantern_string_list peers = {0}; - lantern_string_list_init(&peers); - if (pthread_mutex_lock(&client->connection_lock) != 0) - { - lantern_string_list_reset(&peers); - return; - } - if (lantern_string_list_copy(&peers, &client->connected_peer_ids) != 0) - { - pthread_mutex_unlock(&client->connection_lock); - lantern_string_list_reset(&peers); - return; - } - pthread_mutex_unlock(&client->connection_lock); - - uint64_t now_ms = monotonic_millis(); - for (size_t i = 0; i < peers.len; i++) - { - const char *peer_str = peers.items[i]; - if (!peer_str || peer_str[0] == '\0') - { - continue; - } - peer_id_t *peer = NULL; - if (peer_id_new_from_text(peer_str, &peer) != PEER_ID_OK || !peer) - { - continue; - } - if (peer_status_needs_refresh(client, peer_str, now_ms)) - { - request_status_now(client, peer, peer_str); - } - struct ping_dial_ctx *ctx = (struct ping_dial_ctx *)calloc(1, sizeof(*ctx)); - if (!ctx) - { - peer_id_free(peer); - continue; - } - ctx->client = client; - strncpy(ctx->peer_text, peer_str, sizeof(ctx->peer_text) - 1); - ctx->peer_text[sizeof(ctx->peer_text) - 1] = '\0'; - int rc = libp2p_host_open_stream_async( - client->network.host, - peer, - LIBP2P_PING_PROTO_ID, - ping_on_stream_open, - ctx); - if (rc != 0) - { - lantern_log_trace( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_str, - }, - "ping dial request failed rc=%d", - rc); - free(ctx); - } - peer_id_free(peer); - } - lantern_string_list_reset(&peers); -} - - -/** - * Ping service thread function. - * - * @param arg Client instance as void pointer - * @return NULL - * - * @note Thread safety: This function runs in its own thread - */ -static void *ping_thread(void *arg) -{ - struct lantern_client *client = (struct lantern_client *)arg; - if (!client) - { - return NULL; - } - lantern_log_info( - "network", - &(const struct lantern_log_metadata){.validator = client->node_id}, - "ping service started interval=%us", - LANTERN_PING_INTERVAL_SECONDS); - while (__atomic_load_n(&client->ping_stop_flag, __ATOMIC_RELAXED) == 0) - { - ping_all_peers(client); - /* Sleep in small increments to allow quick shutdown. */ - for (unsigned i = 0; - i < LANTERN_PING_INTERVAL_SECONDS - && __atomic_load_n(&client->ping_stop_flag, __ATOMIC_RELAXED) == 0; - i++) - { - struct timespec ts = {.tv_sec = 1, .tv_nsec = 0}; - nanosleep(&ts, NULL); - } - } - return NULL; -} - - -/** - * Start the ping service. - * - * @param client Client instance - * @return 0 on success, -1 on failure - * - * @note Thread safety: This function is thread-safe - */ -int start_ping_service(struct lantern_client *client) -{ - if (!client) - { - return -1; - } - if (client->ping_thread_started) - { - return 0; - } - __atomic_store_n(&client->ping_stop_flag, 0, __ATOMIC_RELAXED); - int rc = pthread_create(&client->ping_thread, NULL, ping_thread, client); - if (rc != 0) - { - __atomic_store_n(&client->ping_stop_flag, 1, __ATOMIC_RELAXED); - return -1; - } - client->ping_thread_started = true; - return 0; -} - - -/** - * Stop the ping service. - * - * @param client Client instance - * - * @note Thread safety: This function is thread-safe - */ -void stop_ping_service(struct lantern_client *client) -{ - if (!client) - { - return; - } - if (!client->ping_thread_started) - { - __atomic_store_n(&client->ping_stop_flag, 1, __ATOMIC_RELAXED); - return; - } - __atomic_store_n(&client->ping_stop_flag, 1, __ATOMIC_RELAXED); - (void)pthread_join(client->ping_thread, NULL); - client->ping_thread_started = false; -} - - /* ============================================================================ * Connection Events * ============================================================================ */ @@ -1446,7 +936,7 @@ void stop_ping_service(struct lantern_client *client) */ static void handle_connection_opened_event( struct lantern_client *client, - const peer_id_t *peer, + const struct lantern_peer_id *peer, bool inbound) { if (!client) @@ -1460,6 +950,19 @@ static void handle_connection_opened_event( { return; } + if (inbound) + { + char peer_text[128]; + format_peer_id_text(peer, peer_text, sizeof(peer_text)); + lantern_log_trace( + "reqresp", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_text[0] ? peer_text : NULL, + }, + "inbound connection opened; waiting for peer status request"); + return; + } char peer_text[128]; format_peer_id_text(peer, peer_text, sizeof(peer_text)); @@ -1478,8 +981,10 @@ static void handle_connection_opened_event( */ static void handle_connection_closed_event( struct lantern_client *client, - const peer_id_t *peer, - int reason) + const struct lantern_peer_id *peer, + int reason, + uint64_t app_error_code, + uint64_t transport_error_code) { if (!client) { @@ -1495,59 +1000,38 @@ static void handle_connection_closed_event( char peer_text[128]; format_peer_id_text(peer, peer_text, sizeof(peer_text)); - lantern_log_info( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_text[0] ? peer_text : NULL, - }, - "connection closed reason=%d (%s)", - reason, - connection_reason_text(reason)); - - /* If disconnected unexpectedly, attempt to redial the peer. - * We redial for timeout, reset, EOF, and closed reasons since the remote - * peer may have closed due to their own timeout or network issues. */ - if (reason == LIBP2P_ERR_TIMEOUT || - reason == LIBP2P_ERR_RESET || - reason == LIBP2P_ERR_EOF || - reason == LIBP2P_ERR_CLOSED) + bool peer_still_connected = peer_text[0] && lantern_client_is_peer_connected(client, peer_text); + const struct lantern_log_metadata meta = { + .validator = client->node_id, + .peer = peer_text[0] ? peer_text : NULL, + }; + if (reason == LIBP2P_HOST_OK && peer_still_connected) { - redial_peer_on_timeout(client, peer); + lantern_log_debug( + "network", + &meta, + "duplicate connection closed reason=%d (%s) app_error=%" PRIu64 " transport_error=%" PRIu64, + reason, + connection_reason_text(reason), + app_error_code, + transport_error_code); } -} - - -/** - * Handle dialing events. - * - * @param client Client instance - * @param peer Peer ID (may be NULL) - * @param addr Multiaddr being dialed (may be NULL) - * - * @note Thread safety: This function is called from libp2p thread - */ -static void handle_dialing_event( - struct lantern_client *client, - const peer_id_t *peer, - const char *addr) -{ - if (!client) + else { - return; + lantern_log_info( + "network", + &meta, + "connection closed reason=%d (%s) app_error=%" PRIu64 " transport_error=%" PRIu64, + reason, + connection_reason_text(reason), + app_error_code, + transport_error_code); } - char peer_text[128]; - format_peer_id_text(peer, peer_text, sizeof(peer_text)); - - lantern_log_debug( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_text[0] ? peer_text : NULL, - }, - "dialing peer addr=%s", - addr ? addr : "-"); + if (reason != LIBP2P_HOST_OK) + { + redial_peer_on_timeout(client, peer); + } } @@ -1563,9 +1047,11 @@ static void handle_dialing_event( */ static void handle_outgoing_connection_error_event( struct lantern_client *client, - const peer_id_t *peer, + const struct lantern_peer_id *peer, int code, - const char *msg) + const char *msg, + uint64_t app_error_code, + uint64_t transport_error_code) { if (!client) { @@ -1581,10 +1067,12 @@ static void handle_outgoing_connection_error_event( .validator = client->node_id, .peer = peer_text[0] ? peer_text : NULL, }, - "outgoing connection error code=%d (%s) msg=%s", + "outgoing connection error code=%d (%s) msg=%s app_error=%" PRIu64 " transport_error=%" PRIu64, code, connection_reason_text(code), - msg ? msg : "-"); + msg ? msg : "-", + app_error_code, + transport_error_code); lean_metrics_record_peer_connection( LEAN_METRICS_DIR_OUTBOUND, @@ -1592,95 +1080,74 @@ static void handle_outgoing_connection_error_event( } -/** - * Handle incoming connection error events. - * - * @param client Client instance - * @param peer Peer ID (may be NULL) - * @param code Error code - * @param msg Error message (may be NULL) - * - * @note Thread safety: This function is called from libp2p thread - */ -static void handle_incoming_connection_error_event( - struct lantern_client *client, - const peer_id_t *peer, - int code, - const char *msg) +static bool connection_event_peer_id( + const libp2p_host_conn_t *conn, + struct lantern_peer_id *out_peer) { - if (!client) + if (!conn || !out_peer) { - return; + return false; } - - char peer_text[128]; - format_peer_id_text(peer, peer_text, sizeof(peer_text)); - - lantern_log_warn( - "network", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_text[0] ? peer_text : NULL, - }, - "incoming connection error code=%d (%s) msg=%s", - code, - connection_reason_text(code), - msg ? msg : "-"); - - lean_metrics_record_peer_connection( - LEAN_METRICS_DIR_INBOUND, - metrics_connection_result_from_code(code)); + size_t written = 0; + if (libp2p_host_conn_peer_id(conn, out_peer->bytes, sizeof(out_peer->bytes), &written) != LIBP2P_HOST_OK || + written == 0 || written > sizeof(out_peer->bytes)) + { + memset(out_peer, 0, sizeof(*out_peer)); + return false; + } + out_peer->len = written; + return true; } /** - * Connection event callback for libp2p host. + * Connection event callback for c-lean-libp2p host. * + * @param network Lantern host wrapper * @param evt Event details * @param user_data Client instance * * @note Thread safety: This function is called from libp2p thread */ -void connection_events_cb(const libp2p_event_t *evt, void *user_data) +void connection_events_cb( + struct lantern_libp2p_host *network, + const libp2p_host_event_t *evt, + void *user_data) { - if (!evt || !user_data) + if (!network || !evt || !user_data) { return; } struct lantern_client *client = (struct lantern_client *)user_data; - switch (evt->kind) + struct lantern_peer_id peer; + const bool has_peer = connection_event_peer_id(evt->conn, &peer); + const struct lantern_peer_id *peer_ptr = has_peer ? &peer : NULL; + + switch (evt->type) { - case LIBP2P_EVT_CONN_OPENED: - handle_connection_opened_event( - client, - evt->u.conn_opened.peer, - evt->u.conn_opened.inbound); + case LIBP2P_HOST_EVENT_CONN_ESTABLISHED: + handle_connection_opened_event(client, peer_ptr, evt->dial == NULL); + if (network->host && evt->conn) + { + (void)libp2p_identify_query(&network->identify, network->host, evt->conn, NULL, NULL); + } break; - case LIBP2P_EVT_CONN_CLOSED: + case LIBP2P_HOST_EVENT_CONN_CLOSED: handle_connection_closed_event( client, - evt->u.conn_closed.peer, - evt->u.conn_closed.reason); - break; - case LIBP2P_EVT_DIALING: - handle_dialing_event( - client, - evt->u.dialing.peer, - evt->u.dialing.addr); + peer_ptr, + (int)evt->reason, + evt->app_error_code, + evt->transport_error_code); break; - case LIBP2P_EVT_OUTGOING_CONNECTION_ERROR: + case LIBP2P_HOST_EVENT_DIAL_FAILED: handle_outgoing_connection_error_event( client, - evt->u.outgoing_conn_error.peer, - evt->u.outgoing_conn_error.code, - evt->u.outgoing_conn_error.msg); - break; - case LIBP2P_EVT_INCOMING_CONNECTION_ERROR: - handle_incoming_connection_error_event( - client, - evt->u.incoming_conn_error.peer, - evt->u.incoming_conn_error.code, - evt->u.incoming_conn_error.msg); + peer_ptr, + (int)evt->reason, + "dial failed", + evt->app_error_code, + evt->transport_error_code); break; default: break; diff --git a/src/core/client_network_internal.h b/src/core/client_network_internal.h index 9cc0be4..b3976f1 100644 --- a/src/core/client_network_internal.h +++ b/src/core/client_network_internal.h @@ -27,8 +27,6 @@ #include "lantern/core/client.h" #include "lantern/consensus/containers.h" -#include - #include #include #include @@ -86,7 +84,6 @@ struct lantern_peer_status_entry bool has_status; /**< True if status has been received */ uint64_t last_status_ms; /**< Timestamp of last status message */ bool status_request_inflight; /**< True if status request is pending */ - bool reqresp_legacy_len; /**< True if peer uses legacy reqresp length framing */ uint32_t consecutive_blocks_failures; /**< Count of consecutive request failures */ uint32_t outstanding_status_requests; /**< Number of outstanding status requests */ uint32_t consecutive_ping_failures; /**< Count of consecutive ping failures */ @@ -101,7 +98,7 @@ struct block_request_ctx { struct lantern_client *client; /**< Client instance */ uint64_t request_id; /**< Internal request tracking ID */ - peer_id_t *peer_id; /**< Peer ID structure */ + struct lantern_peer_id *peer_id; /**< Peer ID structure */ char peer_text[128]; /**< Peer ID as text */ LanternRoot *roots; /**< Roots being requested */ uint32_t *depths; /**< Backfill depth per root */ @@ -116,7 +113,7 @@ struct block_request_ctx struct block_request_worker_args { struct block_request_ctx *ctx; /**< Request context */ - libp2p_stream_t *stream; /**< libp2p stream */ + struct lantern_reqresp_stream *stream; /**< libp2p stream */ }; @@ -276,34 +273,6 @@ void lantern_client_status_request_failed( struct lantern_client *client, const char *peer_id); -/** - * Mark a peer as using legacy reqresp length framing. - * - * Legacy peers encode the varint length as the compressed payload size. - * - * @param client Client instance - * @param peer_id Peer ID to mark - * - * @note Thread safety: This function acquires status_lock - */ -void lantern_client_mark_peer_reqresp_legacy( - struct lantern_client *client, - const char *peer_id); - -/** - * Check whether a peer uses legacy reqresp length framing. - * - * @param client Client instance - * @param peer_id Peer ID to check - * @return true if peer is marked legacy, false otherwise - * - * @note Thread safety: This function acquires status_lock - */ -bool lantern_client_peer_reqresp_legacy( - struct lantern_client *client, - const char *peer_id); - - /** * Update status request tracking counters. * @@ -353,7 +322,7 @@ void connection_counter_reset(struct lantern_client *client); void connection_counter_update( struct lantern_client *client, int delta, - const peer_id_t *peer, + const struct lantern_peer_id *peer, bool inbound, int reason); @@ -381,7 +350,7 @@ bool lantern_client_is_peer_connected(struct lantern_client *client, const char * * @note Thread safety: This function acquires status_lock */ -void request_status_now(struct lantern_client *client, const peer_id_t *peer, const char *peer_text); +void request_status_now(struct lantern_client *client, const struct lantern_peer_id *peer, const char *peer_text); /** @@ -438,7 +407,7 @@ void peer_dialer_sleep(struct lantern_client *client, unsigned seconds); * * @note Thread safety: This function acquires connection_lock */ -void redial_peer_on_timeout(struct lantern_client *client, const peer_id_t *peer); +void redial_peer_on_timeout(struct lantern_client *client, const struct lantern_peer_id *peer); /** @@ -475,35 +444,18 @@ void stop_peer_dialer(struct lantern_client *client); /** - * Start the ping service. - * - * @param client Client instance - * @return 0 on success, -1 on failure - * - * @note Thread safety: This function is thread-safe - */ -int start_ping_service(struct lantern_client *client); - - -/** - * Stop the ping service. - * - * @param client Client instance - * - * @note Thread safety: This function is thread-safe - */ -void stop_ping_service(struct lantern_client *client); - - -/** - * Connection event callback for libp2p host. + * Connection event callback for the c-lean-libp2p host. * + * @param network Lantern host wrapper * @param evt Event details * @param user_data Client instance * * @note Thread safety: This function is called from libp2p thread */ -void connection_events_cb(const libp2p_event_t *evt, void *user_data); +void connection_events_cb( + struct lantern_libp2p_host *network, + const libp2p_host_event_t *evt, + void *user_data); #ifdef __cplusplus diff --git a/src/core/client_peers.c b/src/core/client_peers.c index a2c714f..b7e3379 100644 --- a/src/core/client_peers.c +++ b/src/core/client_peers.c @@ -20,6 +20,8 @@ #include #include +#include "lantern/support/strings.h" + /* ============================================================================ * Peer ID Utilities @@ -131,8 +133,7 @@ struct lantern_peer_status_entry *lantern_client_ensure_status_entry_locked( memset(entry, 0, sizeof(*entry)); const size_t peer_cap = lantern_peer_id_capacity(); - strncpy(entry->peer_id, peer_id, peer_cap - 1); - entry->peer_id[peer_cap - 1] = '\0'; + (void)lantern_string_copy(entry->peer_id, peer_cap, peer_id); lantern_client_register_vote_peer(client, peer_id); @@ -234,8 +235,7 @@ struct lantern_peer_vote_metric *lantern_client_ensure_vote_metric_locked( memset(entry, 0, sizeof(*entry)); const size_t peer_cap = sizeof(entry->peer_id); - strncpy(entry->peer_id, peer_id, peer_cap - 1u); - entry->peer_id[peer_cap - 1u] = '\0'; + (void)lantern_string_copy(entry->peer_id, peer_cap, peer_id); return entry; } @@ -486,70 +486,6 @@ void lantern_client_status_request_failed( pthread_mutex_unlock(&client->status_lock); } -/** - * Mark a peer as using legacy reqresp length framing. - * - * @param client Client instance - * @param peer_id Peer ID to mark - * - * @note Thread safety: This function acquires status_lock - */ -void lantern_client_mark_peer_reqresp_legacy( - struct lantern_client *client, - const char *peer_id) -{ - if (!client || !peer_id || !peer_id[0] || !client->status_lock_initialized) - { - return; - } - - if (pthread_mutex_lock(&client->status_lock) != 0) - { - return; - } - - struct lantern_peer_status_entry *entry = - lantern_client_ensure_status_entry_locked(client, peer_id); - if (entry) - { - entry->reqresp_legacy_len = true; - } - - pthread_mutex_unlock(&client->status_lock); -} - -/** - * Check whether a peer uses legacy reqresp length framing. - * - * @param client Client instance - * @param peer_id Peer ID to check - * @return true if peer is marked legacy, false otherwise - * - * @note Thread safety: This function acquires status_lock - */ -bool lantern_client_peer_reqresp_legacy( - struct lantern_client *client, - const char *peer_id) -{ - if (!client || !peer_id || !peer_id[0] || !client->status_lock_initialized) - { - return false; - } - - if (pthread_mutex_lock(&client->status_lock) != 0) - { - return false; - } - - struct lantern_peer_status_entry *entry = - lantern_client_find_status_entry_locked(client, peer_id); - bool legacy = entry ? entry->reqresp_legacy_len : false; - - pthread_mutex_unlock(&client->status_lock); - return legacy; -} - - /** * Update status request tracking counters. * diff --git a/src/core/client_pending.c b/src/core/client_pending.c index 5d15b0d..7529f43 100644 --- a/src/core/client_pending.c +++ b/src/core/client_pending.c @@ -16,6 +16,8 @@ #include #include +#include "lantern/support/strings.h" + enum { LANTERN_CLIENT_PENDING_OK = 0, @@ -568,8 +570,7 @@ struct lantern_pending_vote *pending_vote_list_append( entry->vote = *vote; if (peer_text && *peer_text) { - strncpy(entry->peer_text, peer_text, sizeof(entry->peer_text) - 1u); - entry->peer_text[sizeof(entry->peer_text) - 1u] = '\0'; + (void)lantern_string_copy(entry->peer_text, sizeof(entry->peer_text), peer_text); } list->length += 1u; @@ -612,8 +613,6 @@ int clone_signed_block(const LanternSignedBlock *source, LanternSignedBlock *des lantern_signed_block_with_attestation_reset(dest); return LANTERN_CLIENT_PENDING_ERR_COPY; } - dest->block.body.legacy_plain_attestation_layout = - source->block.body.legacy_plain_attestation_layout; if (lantern_block_signatures_copy(&dest->signatures, &source->signatures) != 0) { @@ -900,8 +899,7 @@ struct lantern_pending_block *pending_block_list_append( if (peer_text && *peer_text) { - strncpy(entry->peer_text, peer_text, sizeof(entry->peer_text) - 1u); - entry->peer_text[sizeof(entry->peer_text) - 1u] = '\0'; + (void)lantern_string_copy(entry->peer_text, sizeof(entry->peer_text), peer_text); } pending_parent_index_add_child(&list->parent_index, parent_root, block_root); diff --git a/src/core/client_reqresp.c b/src/core/client_reqresp.c index 4a0a82d..405ca3d 100644 --- a/src/core/client_reqresp.c +++ b/src/core/client_reqresp.c @@ -28,9 +28,6 @@ #include #include -#include "libp2p/errors.h" -#include "peer_id/peer_id.h" - #include "lantern/consensus/hash.h" #include "lantern/networking/messages.h" #include "lantern/networking/reqresp_service.h" @@ -40,6 +37,18 @@ enum { ROOT_HEX_BUFFER_LEN = (LANTERN_ROOT_SIZE * 2u) + 3u, + ASYNC_BLOCK_IMPORT_QUEUE_LIMIT = 128u, +}; + +struct lantern_async_block_import_job +{ + struct lantern_client *client; + LanternSignedBlock block; + LanternRoot block_root; + char peer_id[128]; + uint8_t *raw_block_ssz; + size_t raw_block_ssz_len; + struct lantern_async_block_import_job *next; }; @@ -131,7 +140,11 @@ static const char *lantern_blocks_request_outcome_text(enum lantern_blocks_reque static void lantern_client_handle_pending_parent_request_result( struct lantern_client *client, const LanternRoot *request_roots, - size_t root_count); + size_t root_count, + enum lantern_blocks_request_outcome outcome); +static void reqresp_alias_anchor_checkpoint_if_unset( + struct lantern_client *client, + LanternStatusMessage *status); /* ============================================================================ @@ -158,8 +171,7 @@ static void copy_peer_id_text(const char *peer_id, char *out, size_t out_len) return; } - strncpy(out, peer_id, out_len - 1); - out[out_len - 1] = '\0'; + (void)lantern_string_copy(out, out_len, peer_id); } @@ -224,7 +236,9 @@ static void log_status_failure( .peer = peer_id_text && peer_id_text[0] ? peer_id_text : NULL, }; - if (error == LIBP2P_ERR_PROTO_NEGOTIATION_FAILED || error == LIBP2P_ERR_UNSUPPORTED) + if (error == LIBP2P_HOST_ERR_NEGOTIATION || + error == LIBP2P_HOST_ERR_UNSUPPORTED || + error == LIBP2P_HOST_ERR_NOT_FOUND) { if (first_failure) { @@ -249,7 +263,7 @@ static void log_status_failure( return; } - if (error == LIBP2P_ERR_TIMEOUT) + if (error == LIBP2P_HOST_ERR_WOULD_BLOCK) { if (first_failure) { @@ -515,77 +529,29 @@ static void maybe_seed_head_request_from_status( local_finalized_slot); } -static bool peer_status_is_eligible( +static void network_view_snapshot_locked( struct lantern_client *client, - const struct lantern_peer_status_entry *entry, - uint64_t now_ms) + uint64_t *out_head_slot, + bool *out_has_head_slot, + uint64_t *out_finalized_slot, + bool *out_has_finalized_slot) { - if (!client || !entry || !entry->has_status) + if (out_head_slot) { - return false; + *out_head_slot = client ? client->network_view.latest_observed_head_slot : 0u; } - if (entry->last_status_ms == 0 || now_ms < entry->last_status_ms) + if (out_has_head_slot) { - return false; + *out_has_head_slot = client && client->network_view.has_latest_observed_head_slot; } - if (!client->connection_lock_initialized) + if (out_finalized_slot) { - return true; + *out_finalized_slot = client ? client->network_view.network_finalized_slot : 0u; } - return lantern_client_is_peer_connected(client, entry->peer_id); -} - -static bool network_finalized_slot_locked( - struct lantern_client *client, - uint64_t now_ms, - uint64_t *out_slot) -{ - if (!client || !out_slot) + if (out_has_finalized_slot) { - return false; + *out_has_finalized_slot = client && client->network_view.has_network_finalized_slot; } - - bool found = false; - uint64_t mode_slot = 0; - size_t mode_count = 0; - - for (size_t i = 0; i < client->peer_status_count; ++i) - { - const struct lantern_peer_status_entry *entry = &client->peer_status_entries[i]; - if (!peer_status_is_eligible(client, entry, now_ms)) - { - continue; - } - - uint64_t slot = entry->status.finalized.slot; - size_t count = 0; - for (size_t j = 0; j < client->peer_status_count; ++j) - { - const struct lantern_peer_status_entry *other = &client->peer_status_entries[j]; - if (!peer_status_is_eligible(client, other, now_ms)) - { - continue; - } - if (other->status.finalized.slot == slot) - { - count += 1u; - } - } - - if (!found || count > mode_count) - { - mode_slot = slot; - mode_count = count; - found = true; - } - } - - if (found) - { - *out_slot = mode_slot; - } - - return found; } static void format_duration_seconds(uint64_t seconds, char *out, size_t out_len) @@ -620,6 +586,8 @@ static void format_duration_seconds(uint64_t seconds, char *out, size_t out_len) static void maybe_log_sync_progress( struct lantern_client *client, uint64_t local_slot, + uint64_t network_head_slot, + bool has_network_head, uint64_t network_finalized, bool has_network_finalized, bool allow_sync_complete) @@ -629,19 +597,11 @@ static void maybe_log_sync_progress( return; } - if (!has_network_finalized) - { - return; - } - - bool was_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; - if (was_idle) - { - client->sync_state = LANTERN_SYNC_STATE_SYNCING; - } + uint64_t now_ms = monotonic_millis(); size_t pending = 0; size_t orphan_count = 0; + uint64_t local_head_slot = local_slot; char pending_peer[sizeof(((struct lantern_pending_block *)0)->peer_text)]; pending_peer[0] = '\0'; char orphan_peer[sizeof(((struct lantern_pending_block *)0)->peer_text)]; @@ -657,8 +617,7 @@ static void maybe_log_sync_progress( const struct lantern_pending_block *entry = &client->pending_blocks.items[i]; if (pending_peer[0] == '\0' && entry->peer_text[0] != '\0') { - strncpy(pending_peer, entry->peer_text, sizeof(pending_peer) - 1u); - pending_peer[sizeof(pending_peer) - 1u] = '\0'; + (void)lantern_string_copy(pending_peer, sizeof(pending_peer), entry->peer_text); } if (lantern_root_is_zero(&entry->parent_root)) { @@ -681,26 +640,49 @@ static void maybe_log_sync_progress( orphan_count += 1u; if (orphan_peer[0] == '\0' && entry->peer_text[0] != '\0') { - strncpy(orphan_peer, entry->peer_text, sizeof(orphan_peer) - 1u); - orphan_peer[sizeof(orphan_peer) - 1u] = '\0'; + (void)lantern_string_copy(orphan_peer, sizeof(orphan_peer), entry->peer_text); } } } } + if (state_locked && client->has_fork_choice) + { + LanternRoot local_head_root = {0}; + if (lantern_fork_choice_current_head(&client->fork_choice, &local_head_root) == 0) + { + uint64_t fork_slot = 0; + if (lantern_fork_choice_block_info( + &client->fork_choice, + &local_head_root, + &fork_slot, + NULL, + NULL) + == 0) + { + local_head_slot = fork_slot; + } + } + } lantern_client_unlock_pending(client, pending_locked); lantern_client_unlock_state(client, state_locked); } - bool has_orphans = orphan_count > 0; - bool behind_finalized = network_finalized > local_slot; - bool one_slot_skew = behind_finalized && (network_finalized - local_slot == 1u); - if (one_slot_skew && !has_orphans) + + if (!has_network_finalized && !has_network_head) { - /* Treat single-slot finalized lead as transient to avoid unnecessary sync mode churn. */ - behind_finalized = false; + return; } - bool syncing = behind_finalized; - uint64_t now_ms = monotonic_millis(); + bool was_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + if (was_idle) + { + lantern_client_set_sync_state_logged(client, LANTERN_SYNC_STATE_SYNCING, "first peer status"); + } + + bool has_orphans = orphan_count > 0; + bool behind_finalized = has_network_finalized && network_finalized > local_head_slot; + bool synced = has_network_finalized && !has_orphans && !behind_finalized; + bool syncing = !synced; + struct lantern_log_metadata meta = {.validator = client->node_id}; bool should_request_parent = false; const char *request_peer = orphan_peer[0] ? orphan_peer : pending_peer; @@ -712,19 +694,6 @@ static void maybe_log_sync_progress( bool allow_complete = allow_sync_complete && client->sync_state == LANTERN_SYNC_STATE_SYNCING; if (!syncing) { - if (was_idle) - { - /* Hold SYNCING for one status update to honor IDLE -> SYNCING transition. */ - maybe_retry_orphan_parent_request( - client, - now_ms, - should_request_parent, - request_peer, - pending, - orphan_count, - &meta); - return; - } if (!allow_complete) { maybe_retry_orphan_parent_request( @@ -737,7 +706,7 @@ static void maybe_log_sync_progress( &meta); return; } - client->sync_state = LANTERN_SYNC_STATE_SYNCED; + lantern_client_set_sync_state_logged(client, LANTERN_SYNC_STATE_SYNCED, "head caught finalized"); if (client->sync_in_progress) { uint64_t target_slot = @@ -753,7 +722,7 @@ static void maybe_log_sync_progress( "sync", &meta, "sync complete local_slot=%" PRIu64 " target_slot=%" PRIu64 " duration=%s imported=%" PRIu64, - local_slot, + local_head_slot, target_slot, duration, client->sync_imported_blocks); @@ -774,7 +743,7 @@ static void maybe_log_sync_progress( return; } - client->sync_state = LANTERN_SYNC_STATE_SYNCING; + lantern_client_set_sync_state_logged(client, LANTERN_SYNC_STATE_SYNCING, "sync incomplete"); if (!client->sync_in_progress) { client->sync_in_progress = true; @@ -782,13 +751,13 @@ static void maybe_log_sync_progress( client->sync_last_log_ms = 0; client->sync_last_imported_blocks = 0; client->sync_imported_blocks = 0; - client->sync_target_slot = network_finalized; + client->sync_target_slot = has_network_head ? network_head_slot : network_finalized; lantern_log_info( "sync", &meta, "sync starting local_slot=%" PRIu64 " target_slot=%" PRIu64 " pending=%zu", - local_slot, - network_finalized, + local_head_slot, + client->sync_target_slot, pending); } @@ -799,8 +768,10 @@ static void maybe_log_sync_progress( } uint64_t target_slot = - client->sync_target_slot != 0 ? client->sync_target_slot : network_finalized; - uint64_t remaining = (target_slot > local_slot) ? (target_slot - local_slot) : 0; + client->sync_target_slot != 0 + ? client->sync_target_slot + : (has_network_head ? network_head_slot : network_finalized); + uint64_t remaining = (target_slot > local_head_slot) ? (target_slot - local_head_slot) : 0; if (pending > 0) { @@ -809,7 +780,7 @@ static void maybe_log_sync_progress( &meta, "sync progress local_slot=%" PRIu64 " target_slot=%" PRIu64 " remaining=%" PRIu64 " pending=%zu", - local_slot, + local_head_slot, target_slot, remaining, pending); @@ -820,7 +791,7 @@ static void maybe_log_sync_progress( "sync", &meta, "sync progress local_slot=%" PRIu64 " target_slot=%" PRIu64 " remaining=%" PRIu64, - local_slot, + local_head_slot, target_slot, remaining); } @@ -862,15 +833,27 @@ void lantern_client_update_sync_progress( return; } + uint64_t network_head = 0; + bool has_network_head = false; uint64_t network_finalized = 0; - bool has_network_finalized = network_finalized_slot_locked( + bool has_network_finalized = false; + network_view_snapshot_locked( client, - monotonic_millis(), - &network_finalized); + &network_head, + &has_network_head, + &network_finalized, + &has_network_finalized); pthread_mutex_unlock(&client->status_lock); - maybe_log_sync_progress(client, local_slot, network_finalized, has_network_finalized, true); + maybe_log_sync_progress( + client, + local_slot, + network_head, + has_network_head, + network_finalized, + has_network_finalized, + true); } @@ -970,15 +953,19 @@ static void lantern_client_peer_status_update( return; } - uint64_t network_finalized = 0; - bool has_network_finalized = network_finalized_slot_locked( - client, - monotonic_millis(), - &network_finalized); + uint64_t network_head_slot = peer_status->head.slot; + uint64_t network_finalized_slot = peer_status->finalized.slot; pthread_mutex_unlock(&client->status_lock); - maybe_log_sync_progress(client, local_slot, network_finalized, has_network_finalized, false); + maybe_log_sync_progress( + client, + local_slot, + network_head_slot, + true, + network_finalized_slot, + true, + false); } @@ -1136,7 +1123,8 @@ static const char *lantern_blocks_request_outcome_text(enum lantern_blocks_reque static void lantern_client_handle_pending_parent_request_result( struct lantern_client *client, const LanternRoot *request_roots, - size_t root_count) + size_t root_count, + enum lantern_blocks_request_outcome outcome) { if (!client || !request_roots || root_count == 0) { @@ -1150,6 +1138,9 @@ static void lantern_client_handle_pending_parent_request_result( } struct lantern_pending_block_list *list = &client->pending_blocks; + bool retry_later = + outcome == LANTERN_BLOCKS_REQUEST_FAILED || outcome == LANTERN_BLOCKS_REQUEST_EMPTY; + uint64_t retry_after_ms = retry_later ? monotonic_millis() : 0u; for (size_t i = 0; i < list->length; ++i) { @@ -1172,8 +1163,16 @@ static void lantern_client_handle_pending_parent_request_result( continue; } - entry->parent_requested = false; - entry->parent_requested_ms = 0; + if (retry_later) + { + entry->parent_requested = true; + entry->parent_requested_ms = retry_after_ms; + } + else + { + entry->parent_requested = false; + entry->parent_requested_ms = 0; + } } lantern_client_unlock_pending(client, locked); @@ -1206,7 +1205,7 @@ int reqresp_build_status(void *context, LanternStatusMessage *out_status) memset(out_status, 0, sizeof(*out_status)); if (!client->has_state) { - return LANTERN_CLIENT_OK; + return LANTERN_CLIENT_ERR_GENESIS; } out_status->finalized = client->state.latest_finalized; @@ -1246,15 +1245,45 @@ int reqresp_build_status(void *context, LanternStatusMessage *out_status) if (lantern_hash_tree_root_block_header( &client->state.latest_block_header, &out_status->head.root) - != 0) + != SSZ_SUCCESS) { memset(&out_status->head.root, 0, sizeof(out_status->head.root)); } } + reqresp_alias_anchor_checkpoint_if_unset(client, out_status); return LANTERN_CLIENT_OK; } +static void reqresp_alias_anchor_checkpoint_if_unset( + struct lantern_client *client, + LanternStatusMessage *status) +{ + if (!client || !status || !client->has_fork_choice) + { + return; + } + + const LanternRoot *anchor_root = lantern_fork_choice_anchor_root(&client->fork_choice); + uint64_t anchor_slot = 0; + if (!anchor_root + || lantern_fork_choice_anchor_slot(&client->fork_choice, &anchor_slot) != 0) + { + return; + } + + if (status->head.slot == anchor_slot && lantern_root_is_zero(&status->head.root)) + { + status->head.root = *anchor_root; + } + if (status->finalized.slot == anchor_slot + && lantern_root_is_zero(&status->finalized.root)) + { + status->finalized.root = *anchor_root; + } +} + + /** * Handle an incoming status message from a peer. * @@ -1331,7 +1360,7 @@ void reqresp_status_failure(void *context, const char *peer_id, int error) copy_peer_id_text(peer_id, peer_copy, sizeof(peer_copy)); if (error == 0) { - error = LIBP2P_ERR_INTERNAL; + error = LIBP2P_HOST_ERR_INTERNAL; } if (peer_copy[0] != '\0') @@ -1353,7 +1382,7 @@ static bool signed_block_matches_root( return false; } LanternRoot block_root = {0}; - if (lantern_hash_tree_root_block(&block->block, &block_root) != 0) + if (lantern_hash_tree_root_block(&block->block, &block_root) != SSZ_SUCCESS) { return false; } @@ -1385,8 +1414,6 @@ static int signed_block_copy( lantern_signed_block_init(dst); return -1; } - dst->block.body.legacy_plain_attestation_layout = - src->block.body.legacy_plain_attestation_layout; if (lantern_block_signatures_copy( &dst->signatures, @@ -1476,6 +1503,336 @@ static bool append_pending_block_for_root( return appended; } +static bool block_response_was_accepted( + struct lantern_client *client, + const LanternRoot *block_root, + bool *out_pending, + bool *out_known) +{ + if (out_pending) + { + *out_pending = false; + } + if (out_known) + { + *out_known = false; + } + if (!client || !block_root) + { + return false; + } + + bool pending_locked = lantern_client_lock_pending(client); + if (pending_locked) + { + bool pending = pending_block_list_find(&client->pending_blocks, block_root) != NULL; + lantern_client_unlock_pending(client, pending_locked); + if (pending) + { + if (out_pending) + { + *out_pending = true; + } + return true; + } + } + + bool state_locked = lantern_client_lock_state(client); + if (!state_locked) + { + return false; + } + bool known = lantern_client_block_known_locked(client, block_root, NULL); + lantern_client_unlock_state(client, state_locked); + if (known && out_known) + { + *out_known = true; + } + return known; +} + + +static int import_block_response_now( + struct lantern_client *client, + const LanternSignedBlock *block, + const LanternRoot *block_root, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + const char *peer_id) +{ + struct lantern_log_metadata meta = { + .validator = client ? client->node_id : NULL, + .peer = peer_id && peer_id[0] ? peer_id : NULL, + .has_slot = block != NULL, + .slot = block ? block->block.slot : 0u, + }; + if (!client || !block || !block_root) + { + return LANTERN_CLIENT_ERR_INVALID_PARAM; + } + + char root_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(block_root, root_hex, sizeof(root_hex)); + bool imported = lantern_client_import_block( + client, + block, + block_root, + &meta, + 0u, + true, + raw_block_ssz, + raw_block_ssz_len); + bool pending = false; + bool known = false; + bool accepted = imported || block_response_was_accepted(client, block_root, &pending, &known); + if (accepted) + { + lantern_log_info( + "backfill", + &meta, + "slot %" PRIu64 ", %s, response %s", + block->block.slot, + root_hex[0] ? root_hex : "0x0", + imported ? "imported" : (pending ? "queued" : (known ? "duplicate" : "accepted"))); + return LANTERN_CLIENT_OK; + } + + lantern_log_warn( + "backfill", + &meta, + "slot %" PRIu64 ", %s, response rejected", + block->block.slot, + root_hex[0] ? root_hex : "0x0"); + return LANTERN_CLIENT_ERR_RUNTIME; +} + +static void block_import_job_free(struct lantern_async_block_import_job *job) +{ + if (!job) + { + return; + } + lantern_signed_block_reset(&job->block); + free(job->raw_block_ssz); + free(job); +} + +static struct lantern_async_block_import_job *block_import_job_new( + struct lantern_client *client, + const LanternSignedBlock *block, + const LanternRoot *block_root, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + const char *peer_id) +{ + if (!client || !block || !block_root || (!raw_block_ssz && raw_block_ssz_len > 0u)) + { + return NULL; + } + + struct lantern_async_block_import_job *job = calloc(1u, sizeof(*job)); + if (!job) + { + return NULL; + } + job->client = client; + job->block_root = *block_root; + snprintf(job->peer_id, sizeof(job->peer_id), "%s", peer_id ? peer_id : ""); + if (signed_block_copy(&job->block, block) != 0) + { + block_import_job_free(job); + return NULL; + } + if (raw_block_ssz_len > 0u) + { + job->raw_block_ssz = malloc(raw_block_ssz_len); + if (!job->raw_block_ssz) + { + block_import_job_free(job); + return NULL; + } + memcpy(job->raw_block_ssz, raw_block_ssz, raw_block_ssz_len); + job->raw_block_ssz_len = raw_block_ssz_len; + } + return job; +} + +static void *block_import_worker_main(void *arg) +{ + struct lantern_client *client = (struct lantern_client *)arg; + if (!client) + { + return NULL; + } + + for (;;) + { + if (pthread_mutex_lock(&client->block_import_lock) != 0) + { + break; + } + while (!client->block_import_stop && !client->block_import_head) + { + (void)pthread_cond_wait(&client->block_import_cond, &client->block_import_lock); + } + struct lantern_async_block_import_job *job = client->block_import_head; + if (!job) + { + pthread_mutex_unlock(&client->block_import_lock); + break; + } + client->block_import_head = job->next; + if (!client->block_import_head) + { + client->block_import_tail = NULL; + } + if (client->block_import_queue_len > 0u) + { + client->block_import_queue_len -= 1u; + } + pthread_mutex_unlock(&client->block_import_lock); + + int rc = import_block_response_now( + job->client, + &job->block, + &job->block_root, + job->raw_block_ssz, + job->raw_block_ssz_len, + job->peer_id); + if (rc != LANTERN_CLIENT_OK) + { + lantern_log_warn( + "reqresp", + &(const struct lantern_log_metadata){ + .validator = job->client ? job->client->node_id : NULL, + .peer = job->peer_id[0] ? job->peer_id : NULL, + .has_slot = true, + .slot = job->block.block.slot, + }, + "async blocks_by_root import rejected"); + } + block_import_job_free(job); + } + + return NULL; +} + +lantern_client_error lantern_client_block_importer_start(struct lantern_client *client) +{ + if (!client) + { + return LANTERN_CLIENT_ERR_INVALID_PARAM; + } + if (client->block_import_thread_started) + { + return LANTERN_CLIENT_OK; + } + if (pthread_mutex_init(&client->block_import_lock, NULL) != 0) + { + return LANTERN_CLIENT_ERR_RUNTIME; + } + client->block_import_lock_initialized = true; + if (pthread_cond_init(&client->block_import_cond, NULL) != 0) + { + pthread_mutex_destroy(&client->block_import_lock); + client->block_import_lock_initialized = false; + return LANTERN_CLIENT_ERR_RUNTIME; + } + client->block_import_cond_initialized = true; + client->block_import_stop = false; + if (pthread_create(&client->block_import_thread, NULL, block_import_worker_main, client) != 0) + { + client->block_import_stop = true; + pthread_cond_destroy(&client->block_import_cond); + pthread_mutex_destroy(&client->block_import_lock); + client->block_import_cond_initialized = false; + client->block_import_lock_initialized = false; + return LANTERN_CLIENT_ERR_RUNTIME; + } + client->block_import_thread_started = true; + return LANTERN_CLIENT_OK; +} + +void lantern_client_block_importer_stop(struct lantern_client *client) +{ + if (!client) + { + return; + } + if (client->block_import_lock_initialized + && pthread_mutex_lock(&client->block_import_lock) == 0) + { + client->block_import_stop = true; + if (client->block_import_cond_initialized) + { + pthread_cond_broadcast(&client->block_import_cond); + } + pthread_mutex_unlock(&client->block_import_lock); + } + if (client->block_import_thread_started) + { + pthread_join(client->block_import_thread, NULL); + client->block_import_thread_started = false; + } + + struct lantern_async_block_import_job *job = client->block_import_head; + while (job) + { + struct lantern_async_block_import_job *next = job->next; + block_import_job_free(job); + job = next; + } + client->block_import_head = NULL; + client->block_import_tail = NULL; + client->block_import_queue_len = 0u; + + if (client->block_import_cond_initialized) + { + pthread_cond_destroy(&client->block_import_cond); + client->block_import_cond_initialized = false; + } + if (client->block_import_lock_initialized) + { + pthread_mutex_destroy(&client->block_import_lock); + client->block_import_lock_initialized = false; + } + client->block_import_stop = true; +} + +static int enqueue_block_import_job( + struct lantern_client *client, + struct lantern_async_block_import_job *job) +{ + if (!client || !job || !client->block_import_thread_started) + { + return -1; + } + if (pthread_mutex_lock(&client->block_import_lock) != 0) + { + return -1; + } + if (client->block_import_stop + || client->block_import_queue_len >= ASYNC_BLOCK_IMPORT_QUEUE_LIMIT) + { + pthread_mutex_unlock(&client->block_import_lock); + return -1; + } + job->next = NULL; + if (client->block_import_tail) + { + client->block_import_tail->next = job; + } + else + { + client->block_import_head = job; + } + client->block_import_tail = job; + client->block_import_queue_len += 1u; + pthread_cond_signal(&client->block_import_cond); + pthread_mutex_unlock(&client->block_import_lock); + return 0; +} + /** * Collect blocks for a blocks_by_root request. * @@ -1571,6 +1928,79 @@ int reqresp_collect_blocks( return LANTERN_CLIENT_OK; } +int reqresp_handle_block_response( + void *context, + const LanternSignedBlock *block, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + const char *peer_id) +{ + if (!context || !block) + { + return LANTERN_CLIENT_ERR_INVALID_PARAM; + } + + struct lantern_client *client = context; + LanternRoot block_root; + if (lantern_hash_tree_root_block(&block->block, &block_root) != SSZ_SUCCESS) + { + return LANTERN_CLIENT_ERR_RUNTIME; + } + if (!client->block_import_thread_started) + { + if (client->block_import_stop) + { + return LANTERN_CLIENT_ERR_RUNTIME; + } + return import_block_response_now( + client, + block, + &block_root, + raw_block_ssz, + raw_block_ssz_len, + peer_id); + } + + struct lantern_async_block_import_job *job = block_import_job_new( + client, + block, + &block_root, + raw_block_ssz, + raw_block_ssz_len, + peer_id); + if (!job) + { + return LANTERN_CLIENT_ERR_ALLOC; + } + if (enqueue_block_import_job(client, job) == 0) + { + return LANTERN_CLIENT_OK; + } + block_import_job_free(job); + return LANTERN_CLIENT_ERR_RUNTIME; +} + +void reqresp_blocks_request_complete( + void *context, + const char *peer_id, + const LanternRoot *roots, + size_t root_count, + uint64_t request_id, + int success) +{ + if (!context) + { + return; + } + lantern_client_on_blocks_request_complete_batch_with_id( + (struct lantern_client *)context, + request_id, + peer_id, + roots, + root_count, + success ? LANTERN_BLOCKS_REQUEST_SUCCESS : LANTERN_BLOCKS_REQUEST_FAILED); +} + /* ============================================================================ * Peer Status Processing @@ -1795,12 +2225,11 @@ void lantern_client_on_blocks_request_complete_batch_with_id( if (outcome == LANTERN_BLOCKS_REQUEST_SUCCESS && client->sync_in_progress) { lantern_log_debug( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = peer_for_logs && peer_for_logs[0] ? peer_for_logs : NULL}, - "blocks_by_root complete outcome=%s roots=%zu first_root=%s entry_found=%s " - "consecutive_failures=%" PRIu32, + "response %s, roots %zu, first_root %s, entry_found %s, consecutive_failures %" PRIu32, outcome_text, root_count, root_hex[0] ? root_hex : "0x0", @@ -1809,24 +2238,42 @@ void lantern_client_on_blocks_request_complete_batch_with_id( } else { - lantern_log_info( - "reqresp", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_for_logs && peer_for_logs[0] ? peer_for_logs : NULL}, - "blocks_by_root complete outcome=%s roots=%zu first_root=%s entry_found=%s " - "consecutive_failures=%" PRIu32, - outcome_text, - root_count, - root_hex[0] ? root_hex : "0x0", - entry_found ? "true" : "false", - failure_count); + if (outcome == LANTERN_BLOCKS_REQUEST_SUCCESS) + { + lantern_log_info( + "backfill", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_for_logs && peer_for_logs[0] ? peer_for_logs : NULL}, + "response %s, roots %zu, first_root %s, entry_found %s, consecutive_failures %" PRIu32, + outcome_text, + root_count, + root_hex[0] ? root_hex : "0x0", + entry_found ? "true" : "false", + failure_count); + } + else + { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_for_logs && peer_for_logs[0] ? peer_for_logs : NULL}, + "request failed, peer %s, reason: %s, roots %zu, first_root %s" + ", consecutive_failures %" PRIu32, + peer_for_logs && peer_for_logs[0] ? peer_for_logs : "-", + outcome_text, + root_count, + root_hex[0] ? root_hex : "0x0", + failure_count); + } } lantern_client_handle_pending_parent_request_result( client, request_roots, - root_count); + root_count, + outcome); if (outcome == LANTERN_BLOCKS_REQUEST_SUCCESS && peer_for_logs && peer_for_logs[0] != '\0') { diff --git a/src/core/client_reqresp_blocks.c b/src/core/client_reqresp_blocks.c index 08f9fab..24d61ee 100644 --- a/src/core/client_reqresp_blocks.c +++ b/src/core/client_reqresp_blocks.c @@ -1,826 +1,26 @@ -/** - * @file client_reqresp_blocks.c - * @brief Block request operations for reqresp protocol - * - * @spec subspecs/networking/reqresp in tools/leanSpec - * - * Implements blocks_by_root request handling including request encoding, - * response processing, and block import coordination. - * - * @note Lock ordering (acquire in this order to prevent deadlocks): - * 1. state_lock - * 2. status_lock - * 3. pending_lock - * 4. validator_lock - * 5. connection_lock - * 6. peer_vote_lock - */ - #include "client_internal.h" -#include "lantern/consensus/hash.h" -#include "lantern/consensus/ssz.h" -#include "lantern/encoding/snappy.h" -#include "lantern/networking/messages.h" -#include "lantern/networking/reqresp_service.h" -#include "lantern/support/strings.h" #include "lantern/support/log.h" -#include "libp2p/errors.h" -#include "libp2p/host.h" -#include "libp2p/stream.h" -#include "multiformats/unsigned_varint/unsigned_varint.h" -#include "peer_id/peer_id.h" - -#include -#include -#include -#include - - -/* ============================================================================ - * Constants - * ============================================================================ */ -enum -{ - ROOT_HEX_BUFFER_LEN = (LANTERN_ROOT_SIZE * 2u) + 3u, -}; - -/* ============================================================================ - * Forward Declarations - * ============================================================================ */ - -static void block_request_ctx_free(struct block_request_ctx *ctx); -static bool lantern_client_process_stream_block_chunk( - struct block_request_ctx *ctx, - uint8_t *chunk, - size_t chunk_len, - const struct lantern_log_metadata *meta, - bool *saw_block); -static void close_and_free_stream(libp2p_stream_t *stream); -static void *block_request_worker(void *arg); -static void block_request_on_open(libp2p_stream_t *stream, void *user_data, int err); -static int schedule_blocks_request_batch( - struct lantern_client *client, - const char *peer_id_text, - const LanternRoot *roots, - const uint32_t *depths, - size_t root_count, - uint64_t request_id); - - -/* ============================================================================ - * Block Request Context Management - * ============================================================================ */ - -/** - * Free a block request context. - * - * @param ctx Context to free - * - * @note Thread safety: This function is thread-safe - */ -static void block_request_ctx_free(struct block_request_ctx *ctx) -{ - if (!ctx) - { - return; - } - peer_id_free(ctx->peer_id); - free(ctx->roots); - free(ctx->depths); - free(ctx); -} - -static void close_and_free_stream(libp2p_stream_t *stream) -{ - if (!stream) - { - return; - } - (void)libp2p_stream_close(stream); - libp2p_stream_free(stream); -} - -/* ============================================================================ - * Block Chunk Processing - * ============================================================================ */ - -/** - * Process a block chunk from a stream. - * - * @spec subspecs/containers/block/block.py - SignedBlock decoding - * @spec subspecs/ssz - SSZ deserialization - * - * Decompresses a snappy-encoded block chunk, decodes the SSZ block, - * validates the block root, and records it to the client state. - * - * @param ctx Block request context - * @param chunk Compressed chunk data (will be freed) - * @param chunk_len Length of chunk - * @param meta Log metadata - * @param saw_block Output flag set if a block was processed - * @return true on success, false on failure - * - * @note Thread safety: This function acquires state_lock via lantern_client_record_block - */ -static bool lantern_client_process_stream_block_chunk( - struct block_request_ctx *ctx, - uint8_t *chunk, - size_t chunk_len, - const struct lantern_log_metadata *meta, - bool *saw_block) -{ - if (!chunk || chunk_len == 0) - { - free(chunk); - return true; - } - if (!ctx) - { - free(chunk); - return false; - } - - size_t raw_len = 0; - int snappy_len_rc = lantern_snappy_uncompressed_length(chunk, chunk_len, &raw_len); - if (snappy_len_rc != LANTERN_SNAPPY_OK || raw_len == 0) - { - lantern_log_error( - "reqresp", - meta, - "blocks_by_root chunk snappy length failed bytes=%zu", - chunk_len); - free(chunk); - return false; - } - uint8_t *raw_block = (uint8_t *)malloc(raw_len); - if (!raw_block) - { - lantern_log_error( - "reqresp", - meta, - "blocks_by_root chunk allocation failed bytes=%zu", - raw_len); - free(chunk); - return false; - } - size_t written = raw_len; - int snappy_rc = lantern_snappy_decompress(chunk, chunk_len, raw_block, raw_len, &written); - if (snappy_rc != LANTERN_SNAPPY_OK) - { - lantern_log_error( - "reqresp", - meta, - "blocks_by_root chunk decompress failed bytes=%zu", - chunk_len); - free(raw_block); - free(chunk); - return false; - } - - LanternSignedBlock streamed_block; - lantern_signed_block_init(&streamed_block); - int decode_rc = lantern_ssz_decode_signed_block_strict(&streamed_block, raw_block, written); - if (decode_rc != 0) - { - lantern_log_error( - "reqresp", - meta, - "blocks_by_root chunk decode failed bytes=%zu", - written); - lantern_signed_block_reset(&streamed_block); - free(raw_block); - free(chunk); - return false; - } +#include - LanternRoot computed = {{0}}; - if (lantern_hash_tree_root_block(&streamed_block.block, &computed) != 0) - { - lantern_log_warn( - "reqresp", - meta, - "failed to hash streamed block slot=%" PRIu64, - streamed_block.block.slot); - lantern_signed_block_reset(&streamed_block); - free(raw_block); - free(chunk); - return true; - } - - char computed_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - format_root_hex(&computed, computed_hex, sizeof(computed_hex)); - bool matches = false; - uint32_t backfill_depth = 0; - if (ctx->roots && ctx->root_count > 0) - { - for (size_t i = 0; i < ctx->root_count; ++i) - { - if (memcmp(computed.bytes, ctx->roots[i].bytes, LANTERN_ROOT_SIZE) == 0) - { - matches = true; - if (ctx->depths) - { - backfill_depth = ctx->depths[i]; - } - break; - } - } - } - bool quiet_log = ctx->client && ctx->client->sync_in_progress; - if (quiet_log) - { - lantern_log_debug( - "reqresp", - meta, - "streamed block slot=%" PRIu64 " proposer=%" PRIu64 " root=%s match=%s depth=%" PRIu32 - " attestations=%zu", - streamed_block.block.slot, - streamed_block.block.proposer_index, - computed_hex[0] ? computed_hex : "0x0", - matches ? "true" : "false", - backfill_depth, - streamed_block.block.body.attestations.length); - } - else - { - lantern_log_info( - "reqresp", - meta, - "streamed block slot=%" PRIu64 " proposer=%" PRIu64 " root=%s match=%s depth=%" PRIu32 - " attestations=%zu", - streamed_block.block.slot, - streamed_block.block.proposer_index, - computed_hex[0] ? computed_hex : "0x0", - matches ? "true" : "false", - backfill_depth, - streamed_block.block.body.attestations.length); - } - - bool handled_by_backfill = - matches - && lantern_client_backfill_process_block( - ctx->client, - &streamed_block, - &computed, - ctx->peer_text[0] ? ctx->peer_text : NULL, - backfill_depth); - if (!handled_by_backfill) - { - lantern_client_record_block( - ctx->client, - &streamed_block, - &computed, - ctx->peer_text[0] ? ctx->peer_text : NULL, - "reqresp", - backfill_depth, - true, - raw_block, - written); - } - lantern_signed_block_reset(&streamed_block); - free(raw_block); - if (saw_block) - { - *saw_block = true; - } - free(chunk); - return true; -} - - -/* ============================================================================ - * Block Request Worker Thread - * ============================================================================ */ - -/** - * Worker thread for processing block requests. - * - * @spec subspecs/networking/reqresp/message.py - BlocksByRoot request/response - * - * Handles the complete blocks_by_root request lifecycle: - * 1. Encodes and sends the request (SSZ + snappy + varint header) - * 2. Reads response chunks (response code + varint payload) - * 3. Decodes and imports received blocks - * - * @param arg block_request_worker_args pointer - * @return NULL - * - * @note Thread safety: This function runs in a separate thread - */ -static void *block_request_worker(void *arg) -{ - struct block_request_worker_args *worker = (struct block_request_worker_args *)arg; - if (!worker) - { - return NULL; - } - struct block_request_ctx *ctx = worker->ctx; - libp2p_stream_t *stream = worker->stream; - free(worker); - if (!ctx || !stream) - { - if (stream) - { - close_and_free_stream(stream); - } - block_request_ctx_free(ctx); - return NULL; - } - - struct lantern_log_metadata meta = { - .validator = ctx->client ? ctx->client->node_id : NULL, - .peer = ctx->peer_text[0] ? ctx->peer_text : NULL, - }; - - LanternBlocksByRootRequest request; - lantern_blocks_by_root_request_init(&request); - uint8_t *raw_request = NULL; - uint8_t *payload = NULL; - enum lantern_blocks_request_outcome outcome = LANTERN_BLOCKS_REQUEST_FAILED; - bool completed = false; - - char root_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (!ctx->roots || ctx->root_count == 0) - { - lantern_log_error( - "reqresp", - &meta, - "blocks_by_root request missing roots"); - goto cleanup; - } - format_root_hex(&ctx->roots[0], root_hex, sizeof(root_hex)); - - if (lantern_root_list_resize(&request.roots, ctx->root_count) != 0) - { - lantern_log_error( - "reqresp", - &meta, - "failed to size blocks_by_root request"); - goto cleanup; - } - for (size_t i = 0; i < ctx->root_count; ++i) - { - request.roots.items[i] = ctx->roots[i]; - } - - if (request.roots.length > (SIZE_MAX - sizeof(uint32_t)) / LANTERN_ROOT_SIZE) - { - lantern_log_error( - "reqresp", - &meta, - "blocks_by_root request exceeds size limits"); - goto cleanup; - } - size_t roots_bytes = request.roots.length * LANTERN_ROOT_SIZE; - size_t raw_size = sizeof(uint32_t) + roots_bytes; - raw_request = (uint8_t *)malloc(raw_size > 0 ? raw_size : 1u); - if (!raw_request) - { - lantern_log_error( - "reqresp", - &meta, - "out of memory building blocks_by_root request"); - goto cleanup; - } - - size_t raw_written = 0; - if (lantern_network_blocks_by_root_request_encode(&request, raw_request, raw_size, &raw_written) != 0 - || raw_written == 0) - { - lantern_log_error( - "reqresp", - &meta, - "failed to encode blocks_by_root request"); - goto cleanup; - } - - size_t max_payload = 0; - int max_rc = lantern_snappy_max_compressed_size(raw_written, &max_payload); - if (max_rc != LANTERN_SNAPPY_OK) - { - lantern_log_error( - "reqresp", - &meta, - "failed to compute snappy size for blocks_by_root request"); - goto cleanup; - } - - payload = (uint8_t *)malloc(max_payload > 0 ? max_payload : 1u); - if (!payload) - { - lantern_log_error( - "reqresp", - &meta, - "out of memory building blocks_by_root request"); - goto cleanup; - } - - size_t payload_len = 0; - int snappy_rc = lantern_snappy_compress(raw_request, raw_written, payload, max_payload, &payload_len); - if (snappy_rc != LANTERN_SNAPPY_OK || payload_len == 0) - { - lantern_log_error( - "reqresp", - &meta, - "failed to encode blocks_by_root request"); - goto cleanup; - } - - if (raw_written > 0) - { - size_t plain_preview = raw_written < LANTERN_STATUS_PREVIEW_BYTES - ? raw_written - : LANTERN_STATUS_PREVIEW_BYTES; - if (plain_preview > 0) - { - char plain_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - if (lantern_bytes_to_hex( - raw_request, - plain_preview, - plain_hex, - sizeof(plain_hex), - 0) - == 0) - { - lantern_log_trace( - "reqresp", - &meta, - "blocks_by_root request roots_hex=%s%s", - plain_hex, - (raw_written > plain_preview) ? "..." : ""); - } - } - } - - size_t payload_preview = payload_len < LANTERN_STATUS_PREVIEW_BYTES ? payload_len : LANTERN_STATUS_PREVIEW_BYTES; - if (payload_preview > 0) - { - char payload_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - if (lantern_bytes_to_hex( - payload, - payload_preview, - payload_hex, - sizeof(payload_hex), - 0) - == 0) - { - lantern_log_trace( - "reqresp", - &meta, - "blocks_by_root request snappy_hex=%s%s", - payload_hex, - (payload_len > payload_preview) ? "..." : ""); - } - } - - bool use_legacy_len = false; - if (ctx->client && ctx->peer_text[0]) - { - use_legacy_len = lantern_client_peer_reqresp_legacy(ctx->client, ctx->peer_text); - } - size_t declared_len = use_legacy_len ? payload_len : raw_written; - uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; - size_t header_len = 0; - if (unsigned_varint_encode(declared_len, header, sizeof(header), &header_len) != UNSIGNED_VARINT_OK) - { - lantern_log_error( - "reqresp", - &meta, - "failed to encode blocks_by_root header length=%zu", - declared_len); - goto cleanup; - } - - lantern_log_debug( - "reqresp", - &meta, - "sending %s request roots=%zu first_root=%s declared_bytes=%zu raw_bytes=%zu compressed_bytes=%zu legacy_len=%s", - ctx->protocol_id, - ctx->root_count, - root_hex[0] ? root_hex : "0x0", - declared_len, - raw_written, - payload_len, - use_legacy_len ? "true" : "false"); - - ssize_t write_err = 0; - if (stream_write_all(stream, header, header_len, &write_err) != 0 - || stream_write_all(stream, payload, payload_len, &write_err) != 0) - { - lantern_log_error( - "reqresp", - &meta, - "failed to write blocks_by_root request err=%zd", - write_err); - goto cleanup; - } - - /* Half-close write side so responder can finalize req/resp stream lifecycle. */ - int shutdown_rc = libp2p_stream_shutdown_write(stream); - if (shutdown_rc != 0) - { - lantern_log_error( - "reqresp", - &meta, - "failed to half-close blocks_by_root stream rc=%d", - shutdown_rc); - goto cleanup; - } - - struct lantern_reqresp_service *service = ctx->client ? &ctx->client->reqresp : NULL; - bool saw_block = false; - - while (true) - { - uint8_t *chunk = NULL; - size_t chunk_len = 0; - ssize_t read_err = 0; - uint8_t chunk_code = LANTERN_REQRESP_RESPONSE_SUCCESS; - int chunk_rc = lantern_reqresp_read_response_chunk( - service, - stream, - LANTERN_REQRESP_PROTOCOL_BLOCKS_BY_ROOT, - &chunk, - &chunk_len, - &read_err, - &chunk_code, - NULL); - if (chunk_rc != 0) - { - if (read_err == (ssize_t)LIBP2P_ERR_EOF) - { - read_err = 0; - break; - } - lantern_log_error( - "reqresp", - &meta, - "failed to read blocks_by_root chunk err=%zd", - read_err); - free(chunk); - goto cleanup; - } - if (chunk_code != LANTERN_REQRESP_RESPONSE_SUCCESS) - { - lantern_log_error( - "reqresp", - &meta, - "blocks_by_root chunk returned code=%u payload_len=%zu", - (unsigned)chunk_code, - chunk_len); - if (chunk && chunk_len > 0) - { - char message_preview[128]; - size_t copy_len = chunk_len < (sizeof(message_preview) - 1u) - ? chunk_len - : (sizeof(message_preview) - 1u); - memcpy(message_preview, chunk, copy_len); - message_preview[copy_len] = '\0'; - for (size_t i = 0; i < copy_len; ++i) - { - unsigned char c = (unsigned char)message_preview[i]; - if (c < 0x20u || c > 0x7eu) - { - message_preview[i] = '.'; - } - } - lantern_log_error( - "reqresp", - &meta, - "blocks_by_root error payload=\"%s\" bytes=%zu", - message_preview, - chunk_len); - - size_t hex_preview = chunk_len < LANTERN_STATUS_PREVIEW_BYTES - ? chunk_len - : LANTERN_STATUS_PREVIEW_BYTES; - if (hex_preview > 0) - { - char hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - if (lantern_bytes_to_hex(chunk, hex_preview, hex, sizeof(hex), 0) == 0) - { - lantern_log_trace( - "reqresp", - &meta, - "blocks_by_root error payload_hex=%s%s", - hex, - (chunk_len > hex_preview) ? "..." : ""); - } - } - } - free(chunk); - goto cleanup; - } - if (chunk_len == 0 || !chunk) - { - free(chunk); - break; - } - - if (!lantern_client_process_stream_block_chunk(ctx, chunk, chunk_len, &meta, &saw_block)) - { - goto cleanup; - } - } - completed = true; - outcome = saw_block ? LANTERN_BLOCKS_REQUEST_SUCCESS : LANTERN_BLOCKS_REQUEST_EMPTY; - -cleanup: - free(payload); - free(raw_request); - lantern_blocks_by_root_request_reset(&request); - close_and_free_stream(stream); - if (ctx->client) - { - lantern_client_on_blocks_request_complete_batch_with_id( - ctx->client, - ctx->request_id, - ctx->peer_text, - ctx->roots, - ctx->root_count, - completed ? outcome : LANTERN_BLOCKS_REQUEST_FAILED); - } - - block_request_ctx_free(ctx); - return NULL; -} - - -/* ============================================================================ - * Stream Open Callback - * ============================================================================ */ - -/** - * Callback when a block request stream opens. - * - * @spec subspecs/networking/reqresp - Stream lifecycle - * - * Called by libp2p when the stream dial completes. Spawns a worker - * thread to handle the request/response exchange. - * - * @param stream libp2p stream - * @param user_data Block request context - * @param err Error code (0 on success) - * - * @note Thread safety: This function is called from libp2p thread - */ -static void block_request_on_open(libp2p_stream_t *stream, void *user_data, int err) -{ - struct block_request_ctx *ctx = (struct block_request_ctx *)user_data; - if (!ctx) - { - if (stream) - { - close_and_free_stream(stream); - } - return; - } - struct lantern_log_metadata meta = { - .validator = ctx->client ? ctx->client->node_id : NULL, - .peer = ctx->peer_text[0] ? ctx->peer_text : NULL, - }; - - lantern_log_debug( - "reqresp", - &meta, - "block request stream opened protocol=%s err=%d", - ctx->protocol_id ? ctx->protocol_id : "(unknown)", - err); - if (err != 0 || !stream) - { - char root_hex[ROOT_HEX_BUFFER_LEN]; - root_hex[0] = '\0'; - if (ctx->root_count > 0) - { - format_root_hex(&ctx->roots[0], root_hex, sizeof(root_hex)); - } - const char *reason = connection_reason_text(err); - lantern_log_warn( - "reqresp", - &meta, - "failed to open %s stream err=%d (%s) roots=%zu first_root=%s", - ctx->protocol_id, - err, - reason ? reason : "-", - ctx->root_count, - root_hex[0] ? root_hex : "0x0"); - if (stream) - { - close_and_free_stream(stream); - stream = NULL; - } - if (ctx->client) - { - lantern_client_on_blocks_request_complete_batch_with_id( - ctx->client, - ctx->request_id, - ctx->peer_text, - ctx->roots, - ctx->root_count, - LANTERN_BLOCKS_REQUEST_FAILED); - } - block_request_ctx_free(ctx); - return; - } - - struct block_request_worker_args *worker = (struct block_request_worker_args *)malloc(sizeof(*worker)); - if (!worker) - { - lantern_log_error( - "reqresp", - &meta, - "failed to allocate worker for %s stream", - ctx->protocol_id); - close_and_free_stream(stream); - if (ctx->client) - { - lantern_client_on_blocks_request_complete_batch_with_id( - ctx->client, - ctx->request_id, - ctx->peer_text, - ctx->roots, - ctx->root_count, - LANTERN_BLOCKS_REQUEST_FAILED); - } - block_request_ctx_free(ctx); - return; - } - worker->ctx = ctx; - worker->stream = stream; - - pthread_t thread; - if (pthread_create(&thread, NULL, block_request_worker, worker) != 0) - { - lantern_log_error( - "reqresp", - &meta, - "failed to spawn blocks_by_root worker"); - free(worker); - close_and_free_stream(stream); - if (ctx->client) - { - lantern_client_on_blocks_request_complete_batch_with_id( - ctx->client, - ctx->request_id, - ctx->peer_text, - ctx->roots, - ctx->root_count, - LANTERN_BLOCKS_REQUEST_FAILED); - } - block_request_ctx_free(ctx); - return; - } - lantern_log_debug( - "reqresp", - &meta, - "spawned blocks_by_root worker protocol=%s", - ctx->protocol_id ? ctx->protocol_id : "(unknown)"); - pthread_detach(thread); -} - - -/* ============================================================================ - * Public Block Request API - * ============================================================================ */ - -static int schedule_blocks_request_batch( +int lantern_client_schedule_blocks_request_batch( struct lantern_client *client, const char *peer_id_text, const LanternRoot *roots, const uint32_t *depths, size_t root_count, - uint64_t request_id) -{ - if (!client || !peer_id_text || !roots || root_count == 0) - { + uint64_t request_id) { + (void)depths; + if (!client || !peer_id_text || !roots || root_count == 0 || root_count > LANTERN_MAX_REQUEST_BLOCKS) { return LANTERN_CLIENT_ERR_INVALID_PARAM; } - if (root_count > LANTERN_MAX_REQUEST_BLOCKS) - { - return LANTERN_CLIENT_ERR_INVALID_PARAM; - } - if (!client->network.host) - { - return LANTERN_CLIENT_ERR_NETWORK; - } - for (size_t i = 0; i < root_count; ++i) - { - if (lantern_root_is_zero(&roots[i])) - { + for (size_t i = 0; i < root_count; ++i) { + if (lantern_root_is_zero(&roots[i])) { return LANTERN_CLIENT_ERR_INVALID_PARAM; } } - - if (client->debug_disable_block_requests) - { - lantern_log_debug( - "reqresp", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_id_text}, - "skipping blocks_by_root dial for test run"); + if (client->debug_disable_block_requests) { lantern_client_on_blocks_request_complete_batch_with_id( client, request_id, @@ -831,115 +31,34 @@ static int schedule_blocks_request_batch( return 0; } - struct block_request_ctx *ctx = (struct block_request_ctx *)calloc(1, sizeof(*ctx)); - if (!ctx) - { - return LANTERN_CLIENT_ERR_ALLOC; - } - ctx->roots = (LanternRoot *)calloc(root_count, sizeof(*ctx->roots)); - if (!ctx->roots) - { - block_request_ctx_free(ctx); - return LANTERN_CLIENT_ERR_ALLOC; - } - ctx->depths = (uint32_t *)calloc(root_count, sizeof(*ctx->depths)); - if (!ctx->depths) - { - block_request_ctx_free(ctx); - return LANTERN_CLIENT_ERR_ALLOC; - } - - ctx->client = client; - ctx->request_id = request_id; - ctx->root_count = root_count; - ctx->protocol_id = LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID; - strncpy(ctx->peer_text, peer_id_text, sizeof(ctx->peer_text) - 1); - ctx->peer_text[sizeof(ctx->peer_text) - 1] = '\0'; - - for (size_t i = 0; i < root_count; ++i) - { - ctx->roots[i] = roots[i]; - if (depths) - { - ctx->depths[i] = depths[i]; - } - } - - if (peer_id_new_from_text(peer_id_text, &ctx->peer_id) != PEER_ID_OK || !ctx->peer_id) - { - lantern_log_warn( - "reqresp", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = peer_id_text}, - "failed to parse peer id for blocks_by_root request"); - block_request_ctx_free(ctx); + struct lantern_peer_id peer_id; + if (lantern_peer_id_from_text(peer_id_text, &peer_id) != 0) { + lantern_client_on_blocks_request_complete_batch_with_id( + client, + request_id, + peer_id_text, + roots, + root_count, + LANTERN_BLOCKS_REQUEST_FAILED); return LANTERN_CLIENT_ERR_INVALID_PARAM; } - char root_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - format_root_hex(&roots[0], root_hex, sizeof(root_hex)); - lantern_log_debug( - "reqresp", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = ctx->peer_text[0] ? ctx->peer_text : NULL}, - "dialing peer for %s roots=%zu first_root=%s", - ctx->protocol_id, - root_count, - root_hex[0] ? root_hex : "0x0"); - - int rc = libp2p_host_open_stream_async( - client->network.host, - ctx->peer_id, - ctx->protocol_id, - block_request_on_open, - ctx); - if (rc != 0) - { - const char *reason = connection_reason_text(rc); - lantern_log_warn( - "reqresp", - &(const struct lantern_log_metadata){ - .validator = client->node_id, - .peer = ctx->peer_text[0] ? ctx->peer_text : NULL}, - "libp2p open stream failed rc=%d (%s)", - rc, - reason ? reason : "-"); - block_request_ctx_free(ctx); + if (lantern_reqresp_service_request_blocks( + &client->reqresp, + &peer_id, + peer_id_text, + roots, + root_count, + request_id) + != 0) { + lantern_client_on_blocks_request_complete_batch_with_id( + client, + request_id, + peer_id_text, + roots, + root_count, + LANTERN_BLOCKS_REQUEST_FAILED); return LANTERN_CLIENT_ERR_NETWORK; } - return 0; -} - -/** - * Schedule a blocks_by_root request to a peer. - * - * @spec subspecs/networking/reqresp/message.py - BlocksByRoot protocol - * - * Initiates an async stream dial to the peer for the blocks_by_root - * protocol. The request will be processed in block_request_on_open - * when the dial completes. - * - * @param client Client instance - * @param peer_id_text Peer ID string - * @param roots Block roots to request - * @param depths Backfill depth per root (may be NULL for zeros) - * @param root_count Number of roots - * @return 0 on success - * @return LANTERN_CLIENT_ERR_INVALID_PARAM if parameters are invalid, the peer ID is invalid, or any root is zero - * @return LANTERN_CLIENT_ERR_ALLOC if allocation fails - * @return LANTERN_CLIENT_ERR_NETWORK if stream dialing fails or networking is unavailable - * - * @note Thread safety: This function is thread-safe - */ -int lantern_client_schedule_blocks_request_batch( - struct lantern_client *client, - const char *peer_id_text, - const LanternRoot *roots, - const uint32_t *depths, - size_t root_count, - uint64_t request_id) -{ - return schedule_blocks_request_batch(client, peer_id_text, roots, depths, root_count, request_id); + return LANTERN_CLIENT_OK; } diff --git a/src/core/client_reqresp_stream.c b/src/core/client_reqresp_stream.c index 430d481..ab3da3f 100644 --- a/src/core/client_reqresp_stream.c +++ b/src/core/client_reqresp_stream.c @@ -1,1926 +1,7 @@ -/** - * @file client_reqresp_stream.c - * @brief Low-level stream I/O utilities for reqresp protocol - * - * @spec subspecs/networking/reqresp in tools/leanSpec - * - * Implements stream read/write utilities for the request/response protocol, - * including varint decoding, payload reading, and response chunk handling. - * - * @note Lock ordering (acquire in this order to prevent deadlocks): - * 1. state_lock - * 2. status_lock - * 3. pending_lock - * 4. validator_lock - * 5. connection_lock - * 6. peer_vote_lock - */ - #include "client_internal.h" -#include -#include -#include -#include -#include -#include -#include - -#include "libp2p/errors.h" -#include "libp2p/stream.h" -#include "multiformats/unsigned_varint/unsigned_varint.h" -#include "peer_id/peer_id.h" - -#include "lantern/encoding/snappy.h" -#include "lantern/networking/reqresp_service.h" -#include "lantern/support/log.h" -#include "lantern/support/strings.h" -#include "lantern/support/time.h" - - -/* ============================================================================ - * Constants - * ============================================================================ */ - -enum -{ - /** Peer ID string buffer size */ - LANTERN_REQRESP_PEER_TEXT_BYTES = 128, - - /** Payload length threshold for additional warning logs */ - LANTERN_REQRESP_SUSPICIOUS_PAYLOAD_BYTES = 512, -}; - -static uint64_t reqresp_now_ms(void) -{ - double now_sec = lantern_time_now_seconds(); - if (now_sec <= 0.0) - { - return 0; - } - double now_ms = now_sec * 1000.0; - if (now_ms >= (double)UINT64_MAX) - { - return UINT64_MAX; - } - return (uint64_t)now_ms; -} - -static void log_snappy_frame_summary( - const char *stage, - const struct lantern_log_metadata *meta, - const uint8_t *data, - size_t length) -{ - if (!data || !meta) - { - return; - } - - bool framed = lantern_snappy_is_framed(data, length); - bool have_first = false; - bool have_second = false; - uint8_t first_type = 0; - uint32_t first_len = 0; - uint8_t second_type = 0; - uint32_t second_len = 0; - size_t second_offset = 0; - - if (length >= 4u) - { - have_first = true; - first_type = data[0]; - first_len = (uint32_t)data[1] | ((uint32_t)data[2] << 8u) | ((uint32_t)data[3] << 16u); - second_offset = 4u + (size_t)first_len; - if (second_offset + 4u <= length) - { - have_second = true; - second_type = data[second_offset]; - second_len = (uint32_t)data[second_offset + 1u] - | ((uint32_t)data[second_offset + 2u] << 8u) - | ((uint32_t)data[second_offset + 3u] << 16u); - } - } - - size_t preview_len = length < 24u ? length : 24u; - char preview_hex[(24u * 2u) + 1u]; - if (preview_len > 0) - { - if (lantern_bytes_to_hex(data, preview_len, preview_hex, sizeof(preview_hex), 0) != 0) - { - preview_hex[0] = '\0'; - } - } - else - { - preview_hex[0] = '\0'; - } - const char *ellipsis = length > preview_len ? "..." : ""; - - lantern_log_warn( - "reqresp", - meta, - "%s snappy summary framed=%s len=%zu first_ok=%s first_type=0x%02x first_len=%u " - "second_ok=%s second_type=0x%02x second_len=%u preview=%s%s", - stage ? stage : "payload", - framed ? "true" : "false", - length, - have_first ? "true" : "false", - (unsigned)first_type, - first_len, - have_second ? "true" : "false", - (unsigned)second_type, - second_len, - preview_hex[0] ? preview_hex : "-", - ellipsis); -} - - -/* ============================================================================ - * Forward Declarations - * ============================================================================ */ - -static void init_peer_log_metadata( - libp2p_stream_t *stream, - char *peer_text, - size_t peer_text_len, - struct lantern_log_metadata *out_meta); -static bool protocol_expects_response_code(enum lantern_reqresp_protocol_kind protocol); -static int set_stream_deadline( - libp2p_stream_t *stream, - uint64_t deadline_ms, - const struct lantern_log_metadata *meta, - const char *label, - ssize_t *out_err); -static int read_stream_byte_with_retry( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms, - uint8_t *out_byte, - ssize_t *out_err); -static int read_response_code_prefix( - struct lantern_reqresp_service *service, - libp2p_stream_t *stream, - bool expect_code, - const struct lantern_log_metadata *meta, - const char *peer_text, - uint64_t deadline_ms, - uint8_t *out_frame_code, - uint8_t *out_response_code_byte, - uint8_t *out_response_code, - bool *out_missing_response_code, - uint8_t *out_header_first_byte, - ssize_t *out_err); -static int read_payload_header_first_byte( - libp2p_stream_t *stream, - bool expect_code, - const struct lantern_log_metadata *meta, - uint64_t deadline_ms, - uint8_t *out_header_first_byte, - ssize_t *out_err); -static int read_stream_exact( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint8_t *buffer, - size_t buffer_len, - uint64_t deadline_ms, - size_t *out_read, - ssize_t *out_err); -static int read_snappy_framed_payload( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t declared_len, - uint64_t deadline_ms, - uint8_t **out_buffer, - size_t *out_len, - bool *out_legacy_len, - ssize_t *out_err); -static int read_varint_header_from_first_byte( - libp2p_stream_t *stream, - uint8_t first_byte, - uint8_t *header, - size_t header_len, - uint64_t *out_value, - size_t *out_consumed, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms); -static void log_varint_header_details( - const uint8_t *header, - size_t consumed, - uint64_t payload_len, - const struct lantern_log_metadata *meta, - const char *label); -static int validate_payload_len( - uint64_t payload_len, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label); -static int read_payload_bytes( - libp2p_stream_t *stream, - size_t payload_size, - uint8_t **out_buffer, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms); -static void log_payload_read_complete( - const uint8_t *buffer, - size_t payload_size, - const struct lantern_log_metadata *meta, - const char *label); -static int read_varint_payload_chunk( - libp2p_stream_t *stream, - uint8_t first_byte, - uint8_t **out_data, - size_t *out_len, - bool *out_legacy_len, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms); - - -/* ============================================================================ - * Internal Helpers - * ============================================================================ */ - -/** - * @brief Builds peer log metadata for a stream. - */ -static void init_peer_log_metadata( - libp2p_stream_t *stream, - char *peer_text, - size_t peer_text_len, - struct lantern_log_metadata *out_meta) -{ - if (!peer_text || peer_text_len == 0 || !out_meta) - { - return; - } - - peer_text[0] = '\0'; - if (stream) - { - const peer_id_t *peer = libp2p_stream_remote_peer(stream); - if (peer) - { - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - peer, - PEER_ID_TEXT_LEGACY_BASE58, - peer_text, - peer_text_len, - &written); - if (rc != PEER_ID_OK) - { - peer_text[0] = '\0'; - } - } - } - - *out_meta = (struct lantern_log_metadata){.peer = peer_text[0] ? peer_text : NULL}; -} - - -/** - * @brief Returns whether a protocol expects a response code prefix. - */ -static bool protocol_expects_response_code(enum lantern_reqresp_protocol_kind protocol) -{ - return (protocol == LANTERN_REQRESP_PROTOCOL_STATUS) - || (protocol == LANTERN_REQRESP_PROTOCOL_BLOCKS_BY_ROOT); -} - - -/** - * @brief Sets a stream deadline and validates the result. - */ -static int set_stream_deadline( - libp2p_stream_t *stream, - uint64_t deadline_ms, - const struct lantern_log_metadata *meta, - const char *label, - ssize_t *out_err) -{ - if (!stream) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - int rc = libp2p_stream_set_deadline(stream, deadline_ms); - if (rc != 0) - { - if (out_err) - { - *out_err = (ssize_t)rc; - } - lantern_log_warn( - "reqresp", - meta, - "%s failed to set deadline_ms=%" PRIu64 " err=%d", - label ? label : "stream", - deadline_ms, - rc); - return LANTERN_REQRESP_ERR_SET_DEADLINE; - } - if (out_err) - { - *out_err = 0; - } - return 0; -} - -static int set_stream_deadline_remaining( - libp2p_stream_t *stream, - uint64_t deadline_ms, - const struct lantern_log_metadata *meta, - const char *label, - ssize_t *out_err) -{ - uint64_t now_ms = reqresp_now_ms(); - if (now_ms >= deadline_ms) - { - if (out_err) - { - *out_err = LIBP2P_ERR_TIMEOUT; - } - lantern_log_warn( - "reqresp", - meta, - "%s timed out", - label ? label : "stream"); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - return set_stream_deadline(stream, deadline_ms - now_ms, meta, label, out_err); -} - -static bool accept_legacy_declared_len( - const uint8_t *buffer, - size_t offset, - uint64_t declared_len, - size_t *out_uncompressed) -{ - if (!buffer || offset == 0 || declared_len == 0) - { - return false; - } - if (offset != (size_t)declared_len) - { - return false; - } - size_t raw_len = 0; - if (lantern_snappy_uncompressed_length(buffer, offset, &raw_len) != LANTERN_SNAPPY_OK) - { - return false; - } - if (out_uncompressed) - { - *out_uncompressed = raw_len; - } - return true; -} - -/** - * @brief Reads a framed Snappy payload with declared uncompressed length. - */ -static int read_snappy_framed_payload( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t declared_len, - uint64_t deadline_ms, - uint8_t **out_buffer, - size_t *out_len, - bool *out_legacy_len, - ssize_t *out_err) -{ - if (!stream || !out_buffer || !out_len) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - if (out_legacy_len) - { - *out_legacy_len = false; - } - if (declared_len > (uint64_t)LANTERN_REQRESP_MAX_CHUNK_BYTES - || declared_len > (uint64_t)SIZE_MAX) - { - if (out_err) - { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; - } - lantern_log_warn( - "reqresp", - meta, - "%s declared uncompressed length invalid=%" PRIu64, - label ? label : "chunk", - declared_len); - return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; - } - - if (declared_len == 0) - { - uint8_t header[4]; - size_t read_len = 0; - ssize_t read_err = 0; - if (read_stream_exact( - stream, - meta, - label ? label : "chunk", - header, - sizeof(header), - deadline_ms, - &read_len, - &read_err) - != 0) - { - if (out_err) - { - *out_err = read_err; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } - uint8_t chunk_type = header[0]; - uint32_t chunk_len = (uint32_t)header[1] - | ((uint32_t)header[2] << 8u) - | ((uint32_t)header[3] << 16u); - if (chunk_type != 0xffu || chunk_len != 6u) - { - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy stream identifier invalid type=0x%02x len=%u", - label ? label : "chunk", - (unsigned)chunk_type, - chunk_len); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - uint8_t payload[6]; - if (read_stream_exact( - stream, - meta, - label ? label : "chunk", - payload, - sizeof(payload), - deadline_ms, - &read_len, - &read_err) - != 0) - { - if (out_err) - { - *out_err = read_err; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } - uint8_t *buffer = malloc(10u); - if (!buffer) - { - if (out_err) - { - *out_err = -ENOMEM; - } - lantern_log_error( - "reqresp", - meta, - "%s payload allocation failed bytes=10", - label ? label : "chunk"); - return LANTERN_REQRESP_ERR_ALLOC; - } - memcpy(buffer, header, sizeof(header)); - memcpy(buffer + sizeof(header), payload, sizeof(payload)); - if (!lantern_snappy_is_framed(buffer, 10u)) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s payload missing snappy framing bytes=10", - label ? label : "chunk"); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - if (out_err) - { - *out_err = 0; - } - *out_buffer = buffer; - *out_len = 10u; - return 0; - } - - size_t max_compressed = 0; - if (lantern_snappy_max_compressed_size((size_t)declared_len, &max_compressed) != LANTERN_SNAPPY_OK - || max_compressed == 0) - { - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s failed to compute snappy max size declared_uncompressed=%" PRIu64, - label ? label : "chunk", - declared_len); - return LANTERN_REQRESP_ERR_ALLOC; - } - - uint8_t *buffer = malloc(max_compressed); - if (!buffer) - { - if (out_err) - { - *out_err = -ENOMEM; - } - lantern_log_error( - "reqresp", - meta, - "%s payload allocation failed bytes=%zu", - label ? label : "chunk", - max_compressed); - return LANTERN_REQRESP_ERR_ALLOC; - } - - size_t offset = 0; - uint64_t uncompressed_total = 0; - bool saw_data_chunk = false; - size_t chunk_index = 0; - const uint32_t max_chunk_len = 0x00ffffffu; - - while (!saw_data_chunk || uncompressed_total < declared_len) - { - uint8_t header[4]; - size_t read_len = 0; - ssize_t read_err = 0; - if (read_stream_exact( - stream, - meta, - label ? label : "chunk", - header, - sizeof(header), - deadline_ms, - &read_len, - &read_err) - != 0) - { - size_t legacy_uncompressed = 0; - if ((read_err == (ssize_t)LIBP2P_ERR_EOF - || read_err == (ssize_t)LIBP2P_ERR_CLOSED - || read_err == (ssize_t)LIBP2P_ERR_RESET) - && accept_legacy_declared_len(buffer, offset, declared_len, &legacy_uncompressed)) - { - lantern_log_warn( - "reqresp", - meta, - "%s legacy reqresp length declared=%" PRIu64 " compressed=%zu uncompressed=%zu", - label ? label : "chunk", - declared_len, - offset, - legacy_uncompressed); - if (out_legacy_len) - { - *out_legacy_len = true; - } - if (out_err) - { - *out_err = 0; - } - *out_buffer = buffer; - *out_len = offset; - return 0; - } - free(buffer); - if (out_err) - { - *out_err = read_err; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } - - uint8_t chunk_type = header[0]; - uint32_t chunk_len = (uint32_t)header[1] - | ((uint32_t)header[2] << 8u) - | ((uint32_t)header[3] << 16u); - - if (chunk_len > max_chunk_len) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy chunk length invalid=%u", - label ? label : "chunk", - chunk_len); - return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; - } - - if (offset + sizeof(header) + (size_t)chunk_len > max_compressed) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy payload exceeds max_compressed bytes=%zu", - label ? label : "chunk", - max_compressed); - return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; - } - - memcpy(buffer + offset, header, sizeof(header)); - offset += sizeof(header); - - if (chunk_len > 0) - { - if (read_stream_exact( - stream, - meta, - label ? label : "chunk", - buffer + offset, - (size_t)chunk_len, - deadline_ms, - &read_len, - &read_err) - != 0) - { - free(buffer); - if (out_err) - { - *out_err = read_err; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } - } - - const uint8_t *chunk_payload = buffer + offset; - offset += (size_t)chunk_len; - chunk_index++; - - uint64_t chunk_uncompressed = 0; - bool contributes = false; - switch (chunk_type) - { - case 0xffu: /* stream identifier */ - if (chunk_len != 6u) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy stream identifier length invalid=%u", - label ? label : "chunk", - chunk_len); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - break; - case 0x00u: /* compressed */ - if (chunk_len < 4u) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy compressed chunk too short len=%u", - label ? label : "chunk", - chunk_len); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - { - size_t raw_len = 0; - int rc = lantern_snappy_uncompressed_length_raw( - chunk_payload + 4u, - (size_t)chunk_len - 4u, - &raw_len); - if (rc != LANTERN_SNAPPY_OK) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy compressed chunk length decode failed rc=%d len=%u", - label ? label : "chunk", - rc, - chunk_len); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - chunk_uncompressed = (uint64_t)raw_len; - contributes = true; - } - break; - case 0x01u: /* uncompressed */ - if (chunk_len < 4u) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy uncompressed chunk too short len=%u", - label ? label : "chunk", - chunk_len); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - chunk_uncompressed = (uint64_t)(chunk_len - 4u); - contributes = true; - break; - default: - if (chunk_type >= 0x80u && chunk_type <= 0xfeu) - { - /* skippable chunk - ignore */ - break; - } - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy chunk type unsupported=0x%02x", - label ? label : "chunk", - (unsigned)chunk_type); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - - if (contributes) - { - saw_data_chunk = true; - if (chunk_uncompressed > UINT64_MAX - uncompressed_total) - { - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy uncompressed length overflow", - label ? label : "chunk"); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - uncompressed_total += chunk_uncompressed; - } - - lantern_log_trace( - "reqresp", - meta, - "%s snappy chunk[%zu] type=0x%02x len=%u uncompressed=%" PRIu64 " total=%" PRIu64 "/%" PRIu64, - label ? label : "chunk", - chunk_index, - (unsigned)chunk_type, - chunk_len, - chunk_uncompressed, - uncompressed_total, - declared_len); - - if (uncompressed_total > declared_len) - { - size_t legacy_uncompressed = 0; - if (accept_legacy_declared_len(buffer, offset, declared_len, &legacy_uncompressed)) - { - lantern_log_warn( - "reqresp", - meta, - "%s legacy reqresp length declared=%" PRIu64 " compressed=%zu uncompressed=%zu", - label ? label : "chunk", - declared_len, - offset, - legacy_uncompressed); - if (out_legacy_len) - { - *out_legacy_len = true; - } - if (out_err) - { - *out_err = 0; - } - *out_buffer = buffer; - *out_len = offset; - return 0; - } - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy uncompressed length exceeded declared=%" PRIu64 " got=%" PRIu64, - label ? label : "chunk", - declared_len, - uncompressed_total); - return LANTERN_REQRESP_ERR_STREAM_READ; - } - } - - if (!lantern_snappy_is_framed(buffer, offset)) - { - lantern_log_warn( - "reqresp", - meta, - "%s payload missing snappy framing bytes=%zu", - label ? label : "chunk", - offset); - log_snappy_frame_summary(label, meta, buffer, offset); - free(buffer); - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } - - lantern_log_debug( - "reqresp", - meta, - "%s snappy payload read compressed=%zu uncompressed=%" PRIu64, - label ? label : "chunk", - offset, - uncompressed_total); - - if (out_err) - { - *out_err = 0; - } - *out_buffer = buffer; - *out_len = offset; - return 0; -} - - -/** - * @brief Reads a single byte from a stream, retrying on AGAIN. - */ -static int read_stream_byte_with_retry( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms, - uint8_t *out_byte, - ssize_t *out_err) -{ - if (!stream || !out_byte) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - while (true) - { - int rc = set_stream_deadline_remaining( - stream, - deadline_ms, - meta, - label, - out_err); - if (rc != 0) - { - return rc; - } - - ssize_t n = libp2p_stream_read(stream, out_byte, 1); - if (n == 1) - { - if (set_stream_deadline(stream, 0, meta, label, NULL) != 0) - { - /* Best-effort: already logged */ - } - if (out_err) - { - *out_err = 0; - } - return 0; - } - if (n == (ssize_t)LIBP2P_ERR_AGAIN) - { - continue; - } - - if (set_stream_deadline(stream, 0, meta, label, NULL) != 0) - { - /* Best-effort: already logged */ - } - if (out_err) - { - *out_err = (n == 0) ? (ssize_t)LIBP2P_ERR_EOF : n; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } -} - - -/** - * @brief Reads and interprets the response code prefix for a chunk. +/* + * Req/resp stream framing now lives with the networking reqresp service so it + * can operate on the c-lean-libp2p host stream surface and the unit-test stream + * adapter through one Lantern-owned abstraction. */ -static int read_response_code_prefix( - struct lantern_reqresp_service *service, - libp2p_stream_t *stream, - bool expect_code, - const struct lantern_log_metadata *meta, - const char *peer_text, - uint64_t deadline_ms, - uint8_t *out_frame_code, - uint8_t *out_response_code_byte, - uint8_t *out_response_code, - bool *out_missing_response_code, - uint8_t *out_header_first_byte, - ssize_t *out_err) -{ - (void)peer_text; - if (!out_frame_code || !out_response_code_byte) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - *out_frame_code = 0; - *out_response_code_byte = 0; - if (out_missing_response_code) - { - *out_missing_response_code = false; - } - if (out_header_first_byte) - { - *out_header_first_byte = 0; - } - - if (!expect_code) - { - if (out_response_code) - { - *out_response_code = LANTERN_REQRESP_RESPONSE_SUCCESS; - } - if (out_err) - { - *out_err = 0; - } - return 0; - } - - uint8_t response_code_byte = 0; - ssize_t read_err = 0; - int rc = read_stream_byte_with_retry( - stream, - meta, - "response code", - deadline_ms, - &response_code_byte, - &read_err); - if (rc != 0) - { - if (out_err) - { - *out_err = read_err; - } - lantern_log_trace("reqresp", meta, "response code read failed err=%zd", read_err); - return rc; - } - - *out_frame_code = response_code_byte; - *out_response_code_byte = response_code_byte; - - bool allow_missing_response_code = - service != NULL && service->callbacks.context != NULL; - - if (response_code_byte > LANTERN_REQRESP_RESPONSE_RESOURCE_UNAVAILABLE - && allow_missing_response_code) - { - if (out_missing_response_code) - { - *out_missing_response_code = true; - } - if (out_header_first_byte) - { - *out_header_first_byte = response_code_byte; - } - if (out_response_code) - { - *out_response_code = LANTERN_REQRESP_RESPONSE_SUCCESS; - } - if (out_err) - { - *out_err = 0; - } - lantern_log_warn( - "reqresp", - meta, - "response code missing, using legacy no-code framing first_byte=0x%02x", - (unsigned)response_code_byte); - return 0; - } - - uint8_t mapped_code = response_code_byte; - if (response_code_byte > LANTERN_REQRESP_RESPONSE_RESOURCE_UNAVAILABLE) - { - mapped_code = (response_code_byte <= 127u) - ? LANTERN_REQRESP_RESPONSE_SERVER_ERROR - : LANTERN_REQRESP_RESPONSE_INVALID_REQUEST; - } - - if (out_response_code) - { - *out_response_code = mapped_code; - } - if (mapped_code == LANTERN_REQRESP_RESPONSE_SUCCESS) - { - lantern_log_debug( - "reqresp", - meta, - "response code=%u mapped=%u", - (unsigned)response_code_byte, - (unsigned)mapped_code); - } - else - { - lantern_log_info( - "reqresp", - meta, - "response code=%u mapped=%u", - (unsigned)response_code_byte, - (unsigned)mapped_code); - } - - if (out_err) - { - *out_err = 0; - } - return 0; -} - - -/** - * @brief Reads the first byte of the varint payload header for a chunk. - */ -static int read_payload_header_first_byte( - libp2p_stream_t *stream, - bool expect_code, - const struct lantern_log_metadata *meta, - uint64_t deadline_ms, - uint8_t *out_header_first_byte, - ssize_t *out_err) -{ - if (!stream || !out_header_first_byte) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - (void)expect_code; - - ssize_t read_err = 0; - int rc = read_stream_byte_with_retry( - stream, - meta, - "payload header", - deadline_ms, - out_header_first_byte, - &read_err); - if (rc != 0) - { - if (out_err) - { - *out_err = read_err; - } - lantern_log_trace("reqresp", meta, "response payload header read failed err=%zd", read_err); - return rc; - } - - if (out_err) - { - *out_err = 0; - } - return 0; -} - - -/** - * @brief Reads exactly buffer_len bytes from a stream. - */ -static int read_stream_exact( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint8_t *buffer, - size_t buffer_len, - uint64_t deadline_ms, - size_t *out_read, - ssize_t *out_err) -{ - if (!stream || (!buffer && buffer_len > 0)) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - size_t collected = 0; - while (collected < buffer_len) - { - int rc = set_stream_deadline_remaining( - stream, - deadline_ms, - meta, - label, - out_err); - if (rc != 0) - { - if (out_read) - { - *out_read = collected; - } - return rc; - } - - ssize_t n = libp2p_stream_read(stream, buffer + collected, buffer_len - collected); - if (n > 0) - { - collected += (size_t)n; - continue; - } - if (n == (ssize_t)LIBP2P_ERR_AGAIN) - { - continue; - } - - if (set_stream_deadline(stream, 0, meta, label, NULL) != 0) - { - /* Best-effort: already logged */ - } - if (out_read) - { - *out_read = collected; - } - if (out_err) - { - *out_err = (n == 0) ? (ssize_t)LIBP2P_ERR_EOF : n; - } - return LANTERN_REQRESP_ERR_STREAM_READ; - } - if (set_stream_deadline(stream, 0, meta, label, NULL) != 0) - { - /* Best-effort: already logged */ - } - if (out_read) - { - *out_read = collected; - } - if (out_err) - { - *out_err = 0; - } - return 0; -} - - -/** - * @brief Reads and decodes a varint header after the first byte. - */ -static int read_varint_header_from_first_byte( - libp2p_stream_t *stream, - uint8_t first_byte, - uint8_t *header, - size_t header_len, - uint64_t *out_value, - size_t *out_consumed, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms) -{ - if (!stream || !header || !out_value || !out_consumed) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - size_t used = 0; - size_t consumed = 0; - uint64_t value = 0; - header[used++] = first_byte; - - while (unsigned_varint_decode(header, used, &value, &consumed) != UNSIGNED_VARINT_OK) - { - if (used == header_len) - { - if (out_err) - { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s varint header exceeded limit", - label ? label : "chunk"); - return LANTERN_REQRESP_ERR_VARINT_HEADER_TOO_LONG; - } - - uint8_t next_byte = 0; - ssize_t read_err = 0; - int rc = read_stream_byte_with_retry(stream, meta, label, deadline_ms, &next_byte, &read_err); - if (rc != 0) - { - if (out_err) - { - *out_err = read_err; - } - lantern_log_warn( - "reqresp", - meta, - "%s header read failed err=%zd", - label ? label : "chunk", - read_err); - return rc; - } - - header[used++] = next_byte; - } - - *out_value = value; - *out_consumed = consumed; - if (out_err) - { - *out_err = 0; - } - return 0; -} - - -/** - * @brief Logs decoded varint header details. - */ -static void log_varint_header_details( - const uint8_t *header, - size_t consumed, - uint64_t payload_len, - const struct lantern_log_metadata *meta, - const char *label) -{ - char header_hex[(LANTERN_REQRESP_HEADER_MAX_BYTES * 2) + 1]; - header_hex[0] = '\0'; - if (lantern_bytes_to_hex(header, consumed, header_hex, sizeof(header_hex), 0) != 0) - { - header_hex[0] = '\0'; - } - - lantern_log_debug( - "reqresp", - meta, - "%s payload_uncompressed_len=%" PRIu64 " header_hex=%s", - label ? label : "chunk", - payload_len, - header_hex[0] ? header_hex : "-"); -} - - -/** - * @brief Validates a decoded payload length. - */ -static int validate_payload_len( - uint64_t payload_len, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label) -{ - if ((payload_len > (uint64_t)LANTERN_REQRESP_MAX_CHUNK_BYTES) - || (payload_len > (uint64_t)SIZE_MAX)) - { - if (out_err) - { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; - } - lantern_log_warn( - "reqresp", - meta, - "%s payload too large=%" PRIu64, - label ? label : "chunk", - payload_len); - return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; - } - return 0; -} - - -/** - * @brief Allocates and reads a payload buffer. - */ -static int read_payload_bytes( - libp2p_stream_t *stream, - size_t payload_size, - uint8_t **out_buffer, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms) -{ - if (!stream || !out_buffer) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - uint8_t *buffer = malloc(payload_size); - if (!buffer) - { - if (out_err) - { - *out_err = -ENOMEM; - } - lantern_log_error( - "reqresp", - meta, - "%s payload allocation failed bytes=%zu", - label ? label : "chunk", - payload_size); - return LANTERN_REQRESP_ERR_ALLOC; - } - - size_t collected = 0; - ssize_t read_err = 0; - int rc = read_stream_exact( - stream, - meta, - label, - buffer, - payload_size, - deadline_ms, - &collected, - &read_err); - if (rc != 0) - { - if (collected > 0) - { - char partial_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - size_t preview_max = (size_t)LANTERN_STATUS_PREVIEW_BYTES; - size_t preview_len = collected < preview_max ? collected : preview_max; - if (lantern_bytes_to_hex(buffer, preview_len, partial_hex, sizeof(partial_hex), 0) != 0) - { - partial_hex[0] = '\0'; - } - lantern_log_trace( - "reqresp", - meta, - "%s payload partial hex=%s%s", - label ? label : "chunk", - partial_hex[0] ? partial_hex : "-", - (collected > preview_len) ? "..." : ""); - } - - free(buffer); - if (out_err) - { - *out_err = read_err; - } - lantern_log_warn( - "reqresp", - meta, - "%s payload read failed err=%zd collected=%zu/%zu", - label ? label : "chunk", - read_err, - collected, - payload_size); - return rc; - } - - if (out_err) - { - *out_err = 0; - } - *out_buffer = buffer; - return 0; -} - - -/** - * @brief Logs a completed payload read with a hex preview. - */ -static void log_payload_read_complete( - const uint8_t *buffer, - size_t payload_size, - const struct lantern_log_metadata *meta, - const char *label) -{ - char payload_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - payload_hex[0] = '\0'; - size_t preview = payload_size < (size_t)LANTERN_STATUS_PREVIEW_BYTES - ? payload_size - : (size_t)LANTERN_STATUS_PREVIEW_BYTES; - if (preview > 0 - && lantern_bytes_to_hex(buffer, preview, payload_hex, sizeof(payload_hex), 0) != 0) - { - payload_hex[0] = '\0'; - } - lantern_log_debug( - "reqresp", - meta, - "%s payload read complete bytes=%zu%s%s", - label ? label : "chunk", - payload_size, - payload_hex[0] ? " hex=" : "", - payload_hex[0] ? payload_hex : ""); -} - - -/* ============================================================================ - * Stream Write Operations - * ============================================================================ */ - -/** - * Write all bytes to a stream. - * - * @spec Ethernet 2.0 Networking Spec - ReqResp Protocol - * - * Implements reliable stream writing with retry on AGAIN/TIMEOUT errors. - * Used for sending request payloads to peers. - * - * @param stream libp2p stream - * @param data Data to write - * @param length Number of bytes to write - * @param out_err Optional output error code (may be NULL) - * @return 0 on success - * @return LANTERN_REQRESP_ERR_INVALID_PARAM if parameters are invalid - * @return LANTERN_REQRESP_ERR_STREAM_WRITE on stream write failure - * - * @note Thread safety: This function is thread-safe - */ -int stream_write_all( - libp2p_stream_t *stream, - const uint8_t *data, - size_t length, - ssize_t *out_err) -{ - if (!stream || (!data && length > 0)) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - - char peer_text[LANTERN_REQRESP_PEER_TEXT_BYTES]; - struct lantern_log_metadata meta; - init_peer_log_metadata(stream, peer_text, sizeof(peer_text), &meta); - - uint64_t deadline_ms = UINT64_MAX; - uint64_t now_ms = reqresp_now_ms(); - uint64_t stall_timeout_ms = lantern_reqresp_stall_timeout_ms(); - if (stall_timeout_ms > 0u - && now_ms != 0u - && now_ms <= UINT64_MAX - stall_timeout_ms) - { - deadline_ms = now_ms + stall_timeout_ms; - } - - size_t offset = 0; - while (offset < length) - { - if (deadline_ms != UINT64_MAX) - { - now_ms = reqresp_now_ms(); - if (now_ms != 0u && now_ms >= deadline_ms) - { - if (out_err) - { - *out_err = LIBP2P_ERR_TIMEOUT; - } - lantern_log_warn( - "reqresp", - &meta, - "stream write timed out bytes_written=%zu total_bytes=%zu", - offset, - length); - return LANTERN_REQRESP_ERR_STREAM_WRITE; - } - - uint64_t remaining_ms = (now_ms == 0u) ? stall_timeout_ms : (deadline_ms - now_ms); - if (remaining_ms == 0u) - { - remaining_ms = 1u; - } - int deadline_rc = libp2p_stream_set_deadline(stream, remaining_ms); - if (deadline_rc != 0) - { - if (out_err) - { - *out_err = (ssize_t)deadline_rc; - } - lantern_log_warn( - "reqresp", - &meta, - "failed to set write deadline err=%d", - deadline_rc); - return LANTERN_REQRESP_ERR_STREAM_WRITE; - } - } - - ssize_t written = libp2p_stream_write(stream, data + offset, length - offset); - if (written > 0) - { - offset += (size_t)written; - continue; - } - if (written == (ssize_t)LIBP2P_ERR_AGAIN || written == (ssize_t)LIBP2P_ERR_TIMEOUT) - { - continue; - } - if (out_err) - { - *out_err = (written == 0) ? (ssize_t)LIBP2P_ERR_CLOSED : written; - } - return LANTERN_REQRESP_ERR_STREAM_WRITE; - } - if (out_err) - { - *out_err = 0; - } - return 0; -} - - -/* ============================================================================ - * Response Chunk Reading - * ============================================================================ */ - -/** - * Read a response chunk from a reqresp stream. - * - * @spec subspecs/networking/reqresp/message.py - Response framing - * - * Handles response framing with a required response code byte. - * The response code byte indicates success (0), invalid request (1), - * server error (2), or resource unavailable (3). Unknown codes are - * mapped per spec. - * - * Protocol flow: - * 1. Read response code byte (if expected) - * 2. Read varint-prefixed payload - * - * @param service Reqresp service (may be NULL) - * @param stream libp2p stream - * @param protocol Protocol kind - * @param out_data Output data buffer (caller must free) - * @param out_len Output data length - * @param out_err Output error code (may be NULL) - * @param out_response_code Output response code (may be NULL) - * @param response_code_pending Tracks whether response code is still expected - * @return 0 on success - * @return LANTERN_REQRESP_ERR_INVALID_PARAM if required parameters are NULL - * @return LANTERN_REQRESP_ERR_SET_READ_INTEREST if enabling read interest fails - * @return LANTERN_REQRESP_ERR_SET_DEADLINE if setting a stream deadline fails - * @return LANTERN_REQRESP_ERR_STREAM_READ if reading from the stream fails - * @return LANTERN_REQRESP_ERR_VARINT_HEADER_TOO_LONG if the varint header exceeds limits - * @return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE if the payload length exceeds limits - * @return LANTERN_REQRESP_ERR_ALLOC if allocating the payload buffer fails - * - * @note Thread safety: This function is thread-safe - */ -int lantern_reqresp_read_response_chunk( - struct lantern_reqresp_service *service, - libp2p_stream_t *stream, - enum lantern_reqresp_protocol_kind protocol, - uint8_t **out_data, - size_t *out_len, - ssize_t *out_err, - uint8_t *out_response_code, - bool *response_code_pending) -{ - if (!stream || !out_data || !out_len) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - if (out_response_code) - { - *out_response_code = LANTERN_REQRESP_RESPONSE_SERVER_ERROR; - } - - char peer_text[LANTERN_REQRESP_PEER_TEXT_BYTES]; - struct lantern_log_metadata meta; - init_peer_log_metadata(stream, peer_text, sizeof(peer_text), &meta); - - int interest_rc = libp2p_stream_set_read_interest(stream, true); - if (interest_rc != 0) - { - if (out_err) - { - *out_err = (ssize_t)interest_rc; - } - lantern_log_warn( - "reqresp", - &meta, - "failed to set read interest err=%d", - interest_rc); - return LANTERN_REQRESP_ERR_SET_READ_INTEREST; - } - - bool expect_code = response_code_pending - ? *response_code_pending - : protocol_expects_response_code(protocol); - uint64_t start_ms = reqresp_now_ms(); - uint64_t ttfb_deadline_ms = start_ms + LANTERN_REQRESP_TTFB_TIMEOUT_MS; - uint64_t resp_deadline_ms = start_ms + LANTERN_REQRESP_RESP_TIMEOUT_MS; - uint8_t frame_code = 0; - uint8_t response_code_byte = 0; - bool missing_response_code = false; - uint8_t missing_code_header_first = 0; - int rc = read_response_code_prefix( - service, - stream, - expect_code, - &meta, - peer_text, - ttfb_deadline_ms, - &frame_code, - &response_code_byte, - out_response_code, - &missing_response_code, - &missing_code_header_first, - out_err); - if (rc != 0) - { - return rc; - } - if (response_code_pending) - { - *response_code_pending = false; - } - - uint8_t header_first_byte = 0; - if (missing_response_code) - { - header_first_byte = missing_code_header_first; - } - else - { - uint64_t header_deadline_ms = expect_code ? resp_deadline_ms : ttfb_deadline_ms; - rc = read_payload_header_first_byte( - stream, - expect_code, - &meta, - header_deadline_ms, - &header_first_byte, - out_err); - if (rc != 0) - { - return rc; - } - } - - lantern_log_trace( - "reqresp", - &meta, - "response using varint framing code=0x%02x header_first=0x%02x", - (unsigned)frame_code, - (unsigned)header_first_byte); - - bool legacy_len = false; - int payload_rc = 0; - if (missing_response_code) - { - uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; - uint64_t payload_len = 0; - size_t consumed = 0; - payload_rc = read_varint_header_from_first_byte( - stream, - header_first_byte, - header, - sizeof(header), - &payload_len, - &consumed, - out_err, - &meta, - "chunk", - resp_deadline_ms); - if (payload_rc == 0) - { - log_varint_header_details(header, consumed, payload_len, &meta, "chunk"); - payload_rc = validate_payload_len(payload_len, out_err, &meta, "chunk"); - } - if (payload_rc == 0) - { - if (payload_len == 0) - { - *out_data = NULL; - *out_len = 0; - } - else - { - uint8_t *buffer = NULL; - payload_rc = read_payload_bytes( - stream, - (size_t)payload_len, - &buffer, - out_err, - &meta, - "chunk", - resp_deadline_ms); - if (payload_rc == 0) - { - *out_data = buffer; - *out_len = (size_t)payload_len; - } - } - } - if (payload_rc == 0) - { - if (out_err) - { - *out_err = 0; - } - log_payload_read_complete(*out_data, *out_len, &meta, "chunk"); - legacy_len = true; - } - } - else - { - payload_rc = read_varint_payload_chunk( - stream, - header_first_byte, - out_data, - out_len, - &legacy_len, - out_err, - &meta, - "chunk", - resp_deadline_ms); - } - if (payload_rc == 0 && legacy_len && service && service->callbacks.context && peer_text[0]) - { - lantern_client_mark_peer_reqresp_legacy( - (struct lantern_client *)service->callbacks.context, - peer_text); - } - - return payload_rc; -} - - -/** - * Read a payload chunk with varint header. - * - * @spec subspecs/networking/reqresp/message.py - Varint-prefixed payload - * - * Decodes the varint length header and reads the full payload. - * Validates payload size against protocol limits. - * - * @param stream libp2p stream - * @param first_byte First byte already read - * @param out_data Output data buffer (caller must free) - * @param out_len Output data length - * @param out_err Output error code (may be NULL) - * @param meta Log metadata - * @param label Label for logging - * @return 0 on success - * @return LANTERN_REQRESP_ERR_INVALID_PARAM if required parameters are NULL - * @return LANTERN_REQRESP_ERR_SET_DEADLINE if setting a stream deadline fails - * @return LANTERN_REQRESP_ERR_STREAM_READ if reading from the stream fails - * @return LANTERN_REQRESP_ERR_VARINT_HEADER_TOO_LONG if the varint header exceeds limits - * @return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE if the payload length exceeds limits - * @return LANTERN_REQRESP_ERR_ALLOC if allocating the payload buffer fails - * - * @note Thread safety: This function is thread-safe - */ -static int read_varint_payload_chunk( - libp2p_stream_t *stream, - uint8_t first_byte, - uint8_t **out_data, - size_t *out_len, - bool *out_legacy_len, - ssize_t *out_err, - const struct lantern_log_metadata *meta, - const char *label, - uint64_t deadline_ms) -{ - if (!stream || !out_data || !out_len) - { - if (out_err) - { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return LANTERN_REQRESP_ERR_INVALID_PARAM; - } - if (out_legacy_len) - { - *out_legacy_len = false; - } - - uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; - uint64_t payload_len = 0; - size_t consumed = 0; - int rc = read_varint_header_from_first_byte( - stream, - first_byte, - header, - sizeof(header), - &payload_len, - &consumed, - out_err, - meta, - label, - deadline_ms); - if (rc != 0) - { - return rc; - } - - log_varint_header_details(header, consumed, payload_len, meta, label); - - rc = validate_payload_len(payload_len, out_err, meta, label); - if (rc != 0) - { - return rc; - } - - uint8_t *buffer = NULL; - size_t payload_size = 0; - rc = read_snappy_framed_payload( - stream, - meta, - label, - payload_len, - deadline_ms, - &buffer, - &payload_size, - out_legacy_len, - out_err); - if (rc != 0) - { - return rc; - } - - *out_data = buffer; - *out_len = payload_size; - if (out_err) - { - *out_err = 0; - } - - log_payload_read_complete(buffer, payload_size, meta, label); - return 0; -} diff --git a/src/core/client_services_internal.h b/src/core/client_services_internal.h index 54ae815..c304e91 100644 --- a/src/core/client_services_internal.h +++ b/src/core/client_services_internal.h @@ -483,6 +483,24 @@ int reqresp_collect_blocks( size_t root_count, LanternSignedBlockList *out_blocks); +int reqresp_handle_block_response( + void *context, + const LanternSignedBlock *block, + const uint8_t *raw_block_ssz, + size_t raw_block_ssz_len, + const char *peer_id); + +void reqresp_blocks_request_complete( + void *context, + const char *peer_id, + const LanternRoot *roots, + size_t root_count, + uint64_t request_id, + int success); + +lantern_client_error lantern_client_block_importer_start(struct lantern_client *client); +void lantern_client_block_importer_stop(struct lantern_client *client); + /** * Handle completion of a blocks request. @@ -573,7 +591,7 @@ bool lantern_client_import_block( */ int lantern_reqresp_read_response_chunk( struct lantern_reqresp_service *service, - libp2p_stream_t *stream, + struct lantern_reqresp_stream *stream, enum lantern_reqresp_protocol_kind protocol, uint8_t **out_data, size_t *out_len, @@ -624,7 +642,7 @@ int lantern_client_schedule_blocks_request_batch( * @note Thread safety: This function is thread-safe */ int stream_write_all( - libp2p_stream_t *stream, + struct lantern_reqresp_stream *stream, const uint8_t *data, size_t length, ssize_t *out_err); diff --git a/src/core/client_sync.c b/src/core/client_sync.c index ca736b5..f33fff8 100644 --- a/src/core/client_sync.c +++ b/src/core/client_sync.c @@ -21,8 +21,6 @@ #include #include -#include "peer_id/peer_id.h" - #include "lantern/consensus/containers.h" #include "lantern/consensus/fork_choice.h" #include "lantern/consensus/hash.h" @@ -44,6 +42,30 @@ enum VALIDATOR_PUBKEY_HEX_BUFFER_LEN = (LANTERN_VALIDATOR_PUBKEY_SIZE * 2u) + 3u, }; +void lantern_client_set_sync_state_logged( + struct lantern_client *client, + LanternSyncState new_state, + const char *reason) +{ + if (!client) + { + return; + } + LanternSyncState prev_state = client->sync_state; + if (prev_state == new_state) + { + return; + } + client->sync_state = new_state; + lantern_log_info( + "sync", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "%s → %s, %s", + lantern_sync_state_name(prev_state), + lantern_sync_state_name(new_state), + reason && reason[0] ? reason : "state change"); +} + static void backfill_session_clear_locked(struct lantern_backfill_session *session) { if (!session) @@ -95,8 +117,7 @@ static bool backfill_entry_append_locked( } if (peer_text && peer_text[0]) { - strncpy(entry->peer_text, peer_text, sizeof(entry->peer_text) - 1u); - entry->peer_text[sizeof(entry->peer_text) - 1u] = '\0'; + (void)lantern_string_copy(entry->peer_text, sizeof(entry->peer_text), peer_text); } return true; } @@ -126,8 +147,7 @@ static bool backfill_entry_append_locked( entry->depth = depth; if (peer_text && peer_text[0]) { - strncpy(entry->peer_text, peer_text, sizeof(entry->peer_text) - 1u); - entry->peer_text[sizeof(entry->peer_text) - 1u] = '\0'; + (void)lantern_string_copy(entry->peer_text, sizeof(entry->peer_text), peer_text); } return true; } @@ -386,8 +406,10 @@ bool lantern_client_maybe_start_historical_backfill( client->backfill.frontier_depth = 0; if (peer_text && peer_text[0]) { - strncpy(client->backfill.peer_text, peer_text, sizeof(client->backfill.peer_text) - 1u); - client->backfill.peer_text[sizeof(client->backfill.peer_text) - 1u] = '\0'; + (void)lantern_string_copy( + client->backfill.peer_text, + sizeof(client->backfill.peer_text), + peer_text); } lantern_client_unlock_pending(client, locked); @@ -551,14 +573,14 @@ bool lantern_client_backfill_should_drop_gossip( char root_hex[ROOT_HEX_BUFFER_LEN]; format_root_hex(root, root_hex, sizeof(root_hex)); lantern_log_info( - "sync", + "import", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = peer_text && peer_text[0] ? peer_text : NULL}, - "historical backfill dropped gossip hint root=%s slot=%" PRIu64 - " anchor_slot=%" PRIu64 " persisted=%" PRIu64 " dropped_gossip=%" PRIu64, - root_hex[0] ? root_hex : "0x0", + "slot %" PRIu64 ", %s, rejected, reason: backfill_window" + ", anchor_slot %" PRIu64 ", persisted %" PRIu64 ", dropped_gossip %" PRIu64, block->block.slot, + root_hex[0] ? root_hex : "0x0", anchor_slot, persisted, dropped); @@ -634,7 +656,7 @@ size_t lantern_client_enabled_validator_count(struct lantern_client *client) * * @note Thread safety: This function is thread-safe */ -static const char *peer_id_to_text(const peer_id_t *from, char *out, size_t out_len) +static const char *peer_id_to_text(const struct lantern_peer_id *from, char *out, size_t out_len) { if (!out || out_len == 0) { @@ -647,14 +669,7 @@ static const char *peer_id_to_text(const peer_id_t *from, char *out, size_t out_ return NULL; } - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - from, - PEER_ID_TEXT_LEGACY_BASE58, - out, - out_len, - &written); - if (rc != PEER_ID_OK) + if (lantern_peer_id_to_text(from, out, out_len) != 0) { out[0] = '\0'; return NULL; @@ -776,7 +791,7 @@ static bool validate_gossip_aggregated_attestation_data_locked( */ int gossip_block_handler( const LanternSignedBlock *block, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_block_ssz, size_t raw_block_ssz_len, void *context) @@ -786,14 +801,46 @@ int gossip_block_handler( return LANTERN_CLIENT_ERR_INVALID_PARAM; } struct lantern_client *client = context; + char peer_text[PEER_TEXT_BUFFER_LEN]; + const char *peer_id_text = peer_id_to_text(from, peer_text, sizeof(peer_text)); + bool sync_idle = false; + if (client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) + { + sync_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + pthread_mutex_unlock(&client->status_lock); + } + else + { + sync_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + } + if (sync_idle) + { + LanternRoot block_root = {0}; + char root_hex[ROOT_HEX_BUFFER_LEN]; + char parent_hex[ROOT_HEX_BUFFER_LEN]; + root_hex[0] = '\0'; + if (lantern_hash_tree_root_block(&block->block, &block_root) == SSZ_SUCCESS) + { + format_root_hex(&block_root, root_hex, sizeof(root_hex)); + } + format_root_hex(&block->block.parent_root, parent_hex, sizeof(parent_hex)); + lantern_log_info( + "import", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_id_text}, + "slot %" PRIu64 ", %s, rejected, reason: sync_state=idle, via gossip, parent %s", + block->block.slot, + root_hex[0] ? root_hex : "0x0", + parent_hex[0] ? parent_hex : "0x0"); + return LANTERN_CLIENT_ERR_IGNORED; + } + if (raw_block_ssz && raw_block_ssz_len > 0) { lean_metrics_record_gossip_block_size(raw_block_ssz_len); } - char peer_text[PEER_TEXT_BUFFER_LEN]; - const char *peer_id_text = peer_id_to_text(from, peer_text, sizeof(peer_text)); - lantern_client_record_block( client, block, @@ -827,7 +874,7 @@ int gossip_block_handler( */ int gossip_vote_handler( const LanternSignedVote *vote, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_vote_payload, size_t raw_vote_payload_len, void *context) @@ -837,6 +884,21 @@ int gossip_vote_handler( return LANTERN_CLIENT_ERR_INVALID_PARAM; } struct lantern_client *client = context; + bool sync_idle = false; + if (client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) + { + sync_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + pthread_mutex_unlock(&client->status_lock); + } + else + { + sync_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + } + if (sync_idle) + { + return LANTERN_CLIENT_ERR_IGNORED; + } + if (raw_vote_payload && raw_vote_payload_len > 0) { lean_metrics_record_gossip_attestation_size(raw_vote_payload_len); @@ -939,7 +1001,7 @@ static bool verify_and_cache_aggregated_attestation_locked( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&attestation->data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestation->data, &data_root) != SSZ_SUCCESS) { free(pubkeys); lantern_state_reset(&target_state); return false; @@ -973,7 +1035,7 @@ static bool verify_and_cache_aggregated_attestation_locked( int gossip_aggregated_attestation_handler( const LanternSignedAggregatedAttestation *attestation, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_attestation_payload, size_t raw_attestation_payload_len, void *context) @@ -982,6 +1044,21 @@ int gossip_aggregated_attestation_handler( return LANTERN_CLIENT_ERR_INVALID_PARAM; } struct lantern_client *client = context; + bool sync_idle = false; + if (client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) + { + sync_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + pthread_mutex_unlock(&client->status_lock); + } + else + { + sync_idle = client->sync_state == LANTERN_SYNC_STATE_IDLE; + } + if (sync_idle) + { + return LANTERN_CLIENT_ERR_IGNORED; + } + if (raw_attestation_payload && raw_attestation_payload_len > 0) { lean_metrics_record_gossip_aggregation_size(raw_attestation_payload_len); } @@ -1067,7 +1144,7 @@ void persist_anchor_block( const LanternRoot *root_to_log = anchor_root; if (!root_to_log) { - if (lantern_hash_tree_root_block(block, &computed_root) == 0) + if (lantern_hash_tree_root_block(block, &computed_root) == SSZ_SUCCESS) { root_to_log = &computed_root; } @@ -1146,7 +1223,7 @@ static int compute_fork_choice_anchor_roots( normalized_genesis_snapshot = true; } - if (lantern_hash_tree_root_state(state_for_hash, out_state_root) != 0) + if (lantern_hash_tree_root_state(state_for_hash, out_state_root) != SSZ_SUCCESS) { lantern_log_error("forkchoice", meta, "failed to hash anchor state"); return LANTERN_CLIENT_ERR_RUNTIME; @@ -1163,7 +1240,7 @@ static int compute_fork_choice_anchor_roots( *out_anchor_header = client->state.latest_block_header; out_anchor_header->state_root = *out_state_root; - if (lantern_hash_tree_root_block_header(out_anchor_header, out_anchor_root) != 0) + if (lantern_hash_tree_root_block_header(out_anchor_header, out_anchor_root) != SSZ_SUCCESS) { lantern_log_error("forkchoice", meta, "failed to hash anchor block header"); return LANTERN_CLIENT_ERR_RUNTIME; @@ -1396,7 +1473,7 @@ int initialize_fork_choice(struct lantern_client *client) LanternRoot synthetic_anchor_block_root = {0}; bool have_synthetic_anchor_block_root = - lantern_hash_tree_root_block(&anchor, &synthetic_anchor_block_root) == 0; + lantern_hash_tree_root_block(&anchor, &synthetic_anchor_block_root) == SSZ_SUCCESS; char synthetic_anchor_block_root_hex[ROOT_HEX_BUFFER_LEN]; format_root_hex( &synthetic_anchor_block_root, @@ -1575,7 +1652,7 @@ static bool load_restored_block_state( } lantern_state_init(out_state); - bool loaded = lantern_ssz_decode_state(out_state, state_bytes, state_len) == 0; + bool loaded = lantern_ssz_decode_state(out_state, state_bytes, state_len) == SSZ_SUCCESS; free(state_bytes); if (!loaded) { @@ -2400,8 +2477,7 @@ static bool reserve_active_blocks_request_locked( entry->request_id = request_id; entry->started_ms = now_ms; entry->deadline_ms = deadline_ms; - strncpy(entry->peer_id, peer_id, sizeof(entry->peer_id) - 1u); - entry->peer_id[sizeof(entry->peer_id) - 1u] = '\0'; + (void)lantern_string_copy(entry->peer_id, sizeof(entry->peer_id), peer_id); client->active_blocks_request_count += 1u; *out_request_id = request_id; return true; @@ -2435,12 +2511,13 @@ static void sweep_expired_active_blocks_requests_locked( } lantern_log_warn( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = request->peer_id[0] ? request->peer_id : NULL}, - "blocks_by_root request timed out request_id=%" PRIu64 - " age_ms=%" PRIu64 " keeping inflight slot until completion", + "request failed, peer %s, reason: timeout, request_id %" PRIu64 + ", age_ms %" PRIu64, + request->peer_id[0] ? request->peer_id : "-", request->request_id, age_ms); i += 1u; @@ -2448,12 +2525,13 @@ static void sweep_expired_active_blocks_requests_locked( } lantern_log_warn( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = request->peer_id[0] ? request->peer_id : NULL}, - "blocks_by_root request hard timeout request_id=%" PRIu64 - " age_ms=%" PRIu64 " releasing inflight slot", + "request failed, peer %s, reason: hard_timeout, request_id %" PRIu64 + ", age_ms %" PRIu64, + request->peer_id[0] ? request->peer_id : "-", request->request_id, age_ms); @@ -2477,8 +2555,7 @@ static void sweep_expired_active_blocks_requests_locked( * * Queues a block whose parent is not yet known. The block will be * imported once its parent arrives. When the client is syncing or - * synced, it requests the missing parent via reqresp. In IDLE, it - * relies on gossip for normal block propagation. + * synced, it requests the missing parent via reqresp. * * @param client Client instance * @param block Block to enqueue @@ -2497,14 +2574,41 @@ static bool try_schedule_blocks_request_batch( { if (!client || !roots || root_count == 0) { + if (client) + { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: invalid_request, roots %zu", + root_count); + } return false; } if (root_count > LANTERN_MAX_REQUEST_BLOCKS) { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: too_many_roots, roots %zu", + root_count); + return false; + } + if (!client->status_lock_initialized) + { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: status_lock_not_initialized, roots %zu", + root_count); return false; } if (client->debug_disable_block_requests) { + lantern_log_info( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: block_requests_disabled, roots %zu", + root_count); return false; } @@ -2529,10 +2633,21 @@ static bool try_schedule_blocks_request_batch( { if (lantern_root_is_zero(&roots[i])) { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: zero_root, roots %zu", + root_count); return false; } if (depths && depths[i] > LANTERN_MAX_BACKFILL_DEPTH) { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: depth_limit, roots %zu, depth %" PRIu32, + root_count, + depths[i]); return false; } } @@ -2546,6 +2661,11 @@ static bool try_schedule_blocks_request_batch( if (pthread_mutex_lock(&client->status_lock) != 0) { + lantern_log_warn( + "backfill", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "not scheduled, reason: status_lock_failed, roots %zu", + root_count); return false; } @@ -2573,12 +2693,14 @@ static bool try_schedule_blocks_request_batch( age_ms = now_ms - entry->last_status_ms; } lantern_log_info( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = peer_text}, - "blocks_by_root requested peer not eligible connected=%s inflight=%" PRIu32 - " failures=%" PRIu32 " has_status=%s status_age_ms=%" PRIu64, + "not scheduled, peer %s, reason: peer_not_connected, roots %zu, connected %s, inflight %" PRIu32 + ", failures %" PRIu32 ", has_status %s, status_age_ms %" PRIu64, + peer_text, + root_count, connected ? "true" : "false", inflight, entry->consecutive_blocks_failures, @@ -2592,12 +2714,13 @@ static bool try_schedule_blocks_request_batch( * different peer while the preferred one is still streaming tends to * produce alternating success/empty responses and slows convergence. */ lantern_log_debug( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = peer_text}, - "blocks_by_root preferred peer busy inflight=%" PRIu32 - " max=%" PRIu32 " roots=%zu; deferring request", + "not scheduled, peer %s, reason: request_limit_hit, inflight %" PRIu32 + ", max %" PRIu32 ", roots %zu", + peer_text, inflight, LANTERN_MAX_BLOCKS_REQUESTS_PER_PEER, root_count); @@ -2756,10 +2879,10 @@ static bool try_schedule_blocks_request_batch( } } lantern_log_info( - "reqresp", + "backfill", &(const struct lantern_log_metadata){.validator = client->node_id}, - "blocks_by_root request skipped: no eligible peers roots=%zu connected=%zu status_entries=%zu " - "connected_entries=%zu has_status=%zu fresh=%zu stale=%zu inflight_full=%zu", + "not scheduled, reason: no_eligible_peer, roots %zu, peers %zu, status_entries %zu, " + "connected_entries %zu, has_status %zu, fresh %zu, stale %zu, request_limit_hit %zu", root_count, client->connected_peers, client->peer_status_count, @@ -2777,8 +2900,7 @@ static bool try_schedule_blocks_request_batch( { copy_cap = peer_cap; } - strncpy(selected_peer, entry->peer_id, copy_cap - 1u); - selected_peer[copy_cap - 1u] = '\0'; + (void)lantern_string_copy(selected_peer, copy_cap, entry->peer_id); uint64_t request_id = 0u; if (!reserve_active_blocks_request_locked( client, @@ -2788,11 +2910,13 @@ static bool try_schedule_blocks_request_batch( &request_id)) { lantern_log_warn( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = selected_peer[0] ? selected_peer : NULL}, - "blocks_by_root request skipped: unable to reserve request tracking entry"); + "not scheduled, peer %s, reason: request_tracking_full, roots %zu", + selected_peer[0] ? selected_peer : "-", + root_count); pthread_mutex_unlock(&client->status_lock); return false; } @@ -2820,22 +2944,30 @@ static bool try_schedule_blocks_request_batch( != 0) { lantern_log_warn( - "reqresp", + "backfill", &(const struct lantern_log_metadata){ .validator = client->node_id, .peer = selected_peer}, - "blocks_by_root request scheduling failed roots=%zu", + "not scheduled, peer %s, reason: scheduler_failed, roots %zu", + selected_peer[0] ? selected_peer : "-", root_count); - lantern_client_on_blocks_request_complete_batch_with_id( - client, - request_id, - selected_peer, - roots, - root_count, - LANTERN_BLOCKS_REQUEST_ABORTED); + /* + * The reqresp scheduler completes parse/open-stream failures itself so + * the reserved request id is released exactly once. + */ return false; } + lantern_log_info( + "backfill", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = selected_peer[0] ? selected_peer : NULL}, + "request sent, parent %s, peer %s, roots %zu, request_id %" PRIu64, + first_root_hex[0] ? first_root_hex : "0x0", + selected_peer[0] ? selected_peer : "-", + root_count, + request_id); return true; } @@ -3393,6 +3525,19 @@ void lantern_client_enqueue_pending_block( LanternRoot block_root_local = *block_root; LanternRoot parent_root_local = *parent_root; + if (client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) + { + if (client->sync_state == LANTERN_SYNC_STATE_SYNCED) + { + lantern_client_set_sync_state_logged(client, LANTERN_SYNC_STATE_SYNCING, "parent missing enqueued"); + } + pthread_mutex_unlock(&client->status_lock); + } + else if (client->sync_state == LANTERN_SYNC_STATE_SYNCED) + { + lantern_client_set_sync_state_logged(client, LANTERN_SYNC_STATE_SYNCING, "parent missing enqueued"); + } + bool locked = lantern_client_lock_pending(client); if (!locked) { @@ -3402,7 +3547,6 @@ void lantern_client_enqueue_pending_block( struct lantern_pending_block_list *list = &client->pending_blocks; bool request_parent_now = request_parent || client->sync_state != LANTERN_SYNC_STATE_IDLE; - bool allow_parent_requests = client->sync_state != LANTERN_SYNC_STATE_IDLE; if (backfill_depth > LANTERN_MAX_BACKFILL_DEPTH) { backfill_depth = LANTERN_MAX_BACKFILL_DEPTH; @@ -3412,15 +3556,14 @@ void lantern_client_enqueue_pending_block( if (existing) { bool should_request = - allow_parent_requests && request_parent_now && !existing->parent_requested; + request_parent_now && !existing->parent_requested; LanternRoot request_root = existing->parent_root; bool parent_cached = pending_block_list_find(list, &request_root) != NULL; char peer_copy[PEER_TEXT_BUFFER_LEN]; peer_copy[0] = '\0'; if (peer_text && *peer_text) { - strncpy(peer_copy, peer_text, sizeof(peer_copy) - 1u); - peer_copy[sizeof(peer_copy) - 1u] = '\0'; + (void)lantern_string_copy(peer_copy, sizeof(peer_copy), peer_text); } if (backfill_depth < existing->backfill_depth) { @@ -3430,8 +3573,10 @@ void lantern_client_enqueue_pending_block( { if (existing->peer_text[0] == '\0' || strcmp(existing->peer_text, peer_text) != 0) { - strncpy(existing->peer_text, peer_text, sizeof(existing->peer_text) - 1u); - existing->peer_text[sizeof(existing->peer_text) - 1u] = '\0'; + (void)lantern_string_copy( + existing->peer_text, + sizeof(existing->peer_text), + peer_text); } } existing->received_ms = monotonic_millis(); @@ -3461,13 +3606,22 @@ void lantern_client_enqueue_pending_block( && existing_backfill_depth < LANTERN_MAX_BACKFILL_DEPTH) { uint32_t request_depth = existing_backfill_depth + 1u; - if (try_schedule_blocks_request( - client, - peer_copy[0] ? peer_copy : NULL, - &request_root, - request_depth)) + if (try_schedule_blocks_request( + client, + peer_copy[0] ? peer_copy : NULL, + &request_root, + request_depth)) { mark_pending_parent_requested(client, &request_root, true); + lantern_log_info( + "backfill", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_copy[0] ? peer_copy : NULL}, + "slot %" PRIu64 ", request sent, parent %s, peer %s", + block->block.slot, + parent_hex[0] ? parent_hex : "0x0", + peer_copy[0] ? peer_copy : "-"); } } return; @@ -3564,36 +3718,16 @@ void lantern_client_enqueue_pending_block( lantern_client_unlock_pending(client, locked); - if (client->sync_in_progress) - { - lantern_log_debug( - "state", - &meta, - "queued block slot=%" PRIu64 " root=%s waiting for parent=%s (via gossip)", - block->block.slot, - block_hex[0] ? block_hex : "0x0", - parent_hex[0] ? parent_hex : "0x0"); - } - else - { - lantern_log_info( - "state", - &meta, - "queued block slot=%" PRIu64 " root=%s waiting for parent=%s (via gossip)", - block->block.slot, - block_hex[0] ? block_hex : "0x0", - parent_hex[0] ? parent_hex : "0x0"); - } + bool request_scheduled = false; - if (allow_parent_requests && request_parent_now && !parent_cached + if (request_parent_now && !parent_cached && entry_backfill_depth < LANTERN_MAX_BACKFILL_DEPTH) { char peer_copy[PEER_TEXT_BUFFER_LEN]; peer_copy[0] = '\0'; if (peer_text && *peer_text) { - strncpy(peer_copy, peer_text, sizeof(peer_copy) - 1u); - peer_copy[sizeof(peer_copy) - 1u] = '\0'; + (void)lantern_string_copy(peer_copy, sizeof(peer_copy), peer_text); } uint32_t request_depth = entry_backfill_depth + 1u; if (try_schedule_blocks_request( @@ -3603,9 +3737,28 @@ void lantern_client_enqueue_pending_block( request_depth)) { mark_pending_parent_requested(client, &parent_root_local, true); + request_scheduled = true; + lantern_log_info( + "backfill", + &(const struct lantern_log_metadata){ + .validator = client->node_id, + .peer = peer_copy[0] ? peer_copy : NULL}, + "slot %" PRIu64 ", request sent, parent %s, peer %s", + block->block.slot, + parent_hex[0] ? parent_hex : "0x0", + peer_copy[0] ? peer_copy : "-"); } } + lantern_log_info( + "import", + &meta, + "slot %" PRIu64 ", %s, queued, reason: parent_missing, parent %s, requested %s", + block->block.slot, + block_hex[0] ? block_hex : "0x0", + parent_hex[0] ? parent_hex : "0x0", + request_scheduled ? "true" : "false"); + lantern_log_debug( "sync", &meta, @@ -3616,7 +3769,7 @@ void lantern_client_enqueue_pending_block( entry_backfill_depth, pending_len, parent_cached ? "true" : "false", - (allow_parent_requests && request_parent_now) ? "true" : "false"); + request_parent_now ? "true" : "false"); } @@ -3716,12 +3869,10 @@ void lantern_client_process_pending_children( replays[replay_count].peer_text[0] = '\0'; if (entry->peer_text[0]) { - strncpy( + (void)lantern_string_copy( replays[replay_count].peer_text, - entry->peer_text, - sizeof(replays[replay_count].peer_text) - 1u); - replays[replay_count] - .peer_text[sizeof(replays[replay_count].peer_text) - 1u] = '\0'; + sizeof(replays[replay_count].peer_text), + entry->peer_text); } replay_count += 1u; } diff --git a/src/core/client_sync_blocks.c b/src/core/client_sync_blocks.c index bdb7b55..2163104 100644 --- a/src/core/client_sync_blocks.c +++ b/src/core/client_sync_blocks.c @@ -33,7 +33,7 @@ #include "lantern/storage/storage.h" #include "lantern/support/log.h" #include "lantern/support/strings.h" -#include "ssz_constants.h" +#include "ssz.h" /* ============================================================================ @@ -58,7 +58,7 @@ static bool finalized_checkpoint_advanced( const LanternCheckpoint *previous_finalized, const LanternCheckpoint *current_finalized); static void persist_finalized_state_if_advanced_locked( - const struct lantern_client *client, + struct lantern_client *client, const LanternCheckpoint *previous_finalized, const struct lantern_log_metadata *meta); static void prune_finalized_attestation_material_if_slot_advanced_locked( @@ -81,10 +81,19 @@ static void persist_post_state_and_indices_locked( const struct lantern_log_metadata *meta); static void log_imported_block( const LanternSignedBlock *block, + const LanternRoot *block_root, const LanternRoot *head_root, uint64_t head_slot, + const char *source, + uint64_t took_ms, const struct lantern_log_metadata *meta, bool quiet); +static void log_import_rejected( + const LanternSignedBlock *block, + const LanternRoot *block_root, + const char *source, + const char *reason, + const struct lantern_log_metadata *meta); static bool compute_state_head_root_locked( struct lantern_client *client, LanternRoot *out_root); @@ -164,308 +173,64 @@ static void update_sync_progress_after_block(struct lantern_client *client) lantern_client_update_sync_progress(client, local_slot); } - -/* ============================================================================ - * External Functions (from client_sync_votes.c) - * ============================================================================ */ - -extern bool lantern_client_verify_vote_signature( - const struct lantern_client *client, - const LanternSignedVote *vote, - const LanternSignature *signature, - const LanternState *state_override, - const struct lantern_log_metadata *meta, - const char *context); - - -/* ============================================================================ - * Block Signature Verification - * ============================================================================ */ - -/** - * Verify all signatures in a signed block. - * - * @spec subspecs/containers/block/block.py - SignedBlock structure - * @spec subspecs/xmss/signature.py - XMSS signature verification - * - * Verifies all signatures in a signed block: - * 1. Proposer signature (last in the signatures array) - * 2. All attestation signatures (one per attestation in body) - * - * The signatures array must contain exactly N+1 signatures where N - * is the number of attestations in the block body. - * - * @param client Client instance - * @param block Signed block to verify - * @param meta Logging metadata - * @return true if all signatures are valid - * - * @note Thread safety: Thread-safe, reads immutable validator registry - */ -static bool signed_block_signatures_are_valid( - const struct lantern_client *client, - const LanternSignedBlock *block, - const struct lantern_log_metadata *meta, - bool *out_missing_parent_state, - LanternRoot *out_missing_parent_root) +static void update_network_view_after_import( + struct lantern_client *client, + uint64_t block_slot, + uint64_t finalized_slot) { - if (!client || !block) - { - return false; - } - if (out_missing_parent_state) - { - *out_missing_parent_state = false; - } - if (out_missing_parent_root) + if (!client) { - memset(out_missing_parent_root, 0, sizeof(*out_missing_parent_root)); + return; } - LanternState parent_state; - lantern_state_init(&parent_state); - const LanternState *state_for_sig = NULL; - const LanternRoot parent_root = block->block.parent_root; - if (lantern_root_is_zero(&parent_root)) - { - if (client->has_state) - { - state_for_sig = &client->state; - } - } - else - { - LanternRoot missing_root = {0}; - state_for_sig = lantern_client_state_for_root_local_locked( - (struct lantern_client *)client, - &parent_root, - &parent_state, - NULL); - if (!state_for_sig - && lantern_client_find_missing_state_root_locked( - (struct lantern_client *)client, - &parent_root, - &missing_root)) - { - if (out_missing_parent_state) - { - *out_missing_parent_state = true; - } - if (out_missing_parent_root && !lantern_root_is_zero(&missing_root)) - { - *out_missing_parent_root = missing_root; - } - } - else if (!state_for_sig) - { - state_for_sig = lantern_client_state_for_root_locked( - (struct lantern_client *)client, - &parent_root, - &parent_state, - NULL); - } - } - if (!state_for_sig) + bool locked = false; + if (client->status_lock_initialized) { - if (out_missing_parent_state && !lantern_root_is_zero(&parent_root)) + if (pthread_mutex_lock(&client->status_lock) != 0) { - *out_missing_parent_state = true; - } - if (out_missing_parent_root - && lantern_root_is_zero(out_missing_parent_root) - && !lantern_root_is_zero(&parent_root)) - { - *out_missing_parent_root = parent_root; + return; } - char parent_hex[ROOT_HEX_BUFFER_LEN]; - char missing_hex[ROOT_HEX_BUFFER_LEN]; - format_root_hex(&parent_root, parent_hex, sizeof(parent_hex)); - format_root_hex(out_missing_parent_root, missing_hex, sizeof(missing_hex)); - lantern_log_warn( - "state", - meta, - "missing parent state for signature verification parent_root=%s missing_root=%s", - parent_hex[0] ? parent_hex : "0x0", - missing_hex[0] ? missing_hex : "0x0"); - lantern_state_reset(&parent_state); - return false; + locked = true; } - const LanternAggregatedAttestations *attestations = &block->block.body.attestations; - const LanternAttestationSignatures *sig_groups = &block->signatures.attestation_signatures; - size_t att_count = attestations->length; - - if (!client->genesis.validator_registry.records) - { - lantern_state_reset(&parent_state); - return true; - } - if (att_count > 0 && !attestations->data) + bool changed = false; + if (!client->network_view.has_latest_observed_head_slot + || block_slot > client->network_view.latest_observed_head_slot) { - lantern_log_warn( - "state", - meta, - "signed block slot=%" PRIu64 " attestations missing data length=%zu", - block->block.slot, - att_count); - lantern_state_reset(&parent_state); - return false; + client->network_view.latest_observed_head_slot = block_slot; + client->network_view.has_latest_observed_head_slot = true; + changed = true; } - if (att_count != sig_groups->length) + if (!client->network_view.has_network_finalized_slot + || finalized_slot > client->network_view.network_finalized_slot) { - lantern_log_warn( - "state", - meta, - "signed block slot=%" PRIu64 " aggregated signature count mismatch expected=%zu actual=%zu", - block->block.slot, - att_count, - sig_groups->length); - lantern_state_reset(&parent_state); - return false; + client->network_view.network_finalized_slot = finalized_slot; + client->network_view.has_network_finalized_slot = true; + changed = true; } - if (att_count > 0 && !sig_groups->data) + uint64_t head = client->network_view.latest_observed_head_slot; + uint64_t finalized = client->network_view.network_finalized_slot; + bool has_head = client->network_view.has_latest_observed_head_slot; + bool has_finalized = client->network_view.has_network_finalized_slot; + if (changed) { - lantern_log_warn( - "state", - meta, - "signed block slot=%" PRIu64 " missing aggregated signatures length=%zu", - block->block.slot, - sig_groups->length); - lantern_state_reset(&parent_state); - return false; - } - - size_t validator_count = lantern_state_validator_count(state_for_sig); - const uint8_t **pubkeys = NULL; - - for (size_t i = 0; i < att_count; ++i) - { - const LanternAggregatedAttestation *att = &attestations->data[i]; - const LanternAggregatedSignatureProof *proof = &sig_groups->data[i]; - size_t bit_length = att->aggregation_bits.bit_length; - if (bit_length == 0 || att->aggregation_bits.bytes == NULL) - { - return false; - } - if (proof->participants.bit_length != att->aggregation_bits.bit_length) - { - lantern_state_reset(&parent_state); - return false; - } - size_t bytes = (bit_length + 7u) / 8u; - if (bytes > 0) - { - if (!proof->participants.bytes - || memcmp(proof->participants.bytes, att->aggregation_bits.bytes, bytes) != 0) - { - lantern_state_reset(&parent_state); - return false; - } - } - size_t participant_count = 0; - for (size_t v = 0; v < bit_length; ++v) - { - if (lantern_bitlist_get(&att->aggregation_bits, v)) - { - participant_count += 1u; - } - } - if (participant_count == 0) - { - lantern_state_reset(&parent_state); - return false; - } - pubkeys = calloc(participant_count, sizeof(*pubkeys)); - if (!pubkeys) - { - lantern_state_reset(&parent_state); - return false; - } - size_t idx = 0; - for (size_t v = 0; v < bit_length; ++v) - { - if (!lantern_bitlist_get(&att->aggregation_bits, v)) - { - continue; - } - if (v >= validator_count) - { - free(pubkeys); - lantern_state_reset(&parent_state); - return false; - } - const uint8_t *pubkey = lantern_state_validator_attestation_pubkey(state_for_sig, v); - if (!pubkey || lantern_validator_pubkey_is_zero(pubkey)) - { - free(pubkeys); - lantern_state_reset(&parent_state); - return false; - } - pubkeys[idx++] = pubkey; - } - - LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&att->data, &data_root) != 0) - { - free(pubkeys); - lantern_state_reset(&parent_state); - return false; - } - bool sig_ok = lantern_signature_verify_aggregated( - pubkeys, - participant_count, - &data_root, - &proof->proof_data, - att->data.slot); - free(pubkeys); - pubkeys = NULL; - if (!sig_ok) - { - lantern_state_reset(&parent_state); - return false; - } + lantern_log_info( + "status", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "network_view head %s%" PRIu64 ", finalized %s%" PRIu64, + has_head ? "" : "-", + has_head ? head : 0u, + has_finalized ? "" : "-", + has_finalized ? finalized : 0u); } - bool proposer_signature_present = !lantern_signature_is_zero(&block->signatures.proposer_signature); - if (!proposer_signature_present) + if (locked) { - lantern_log_warn( - "state", - meta, - "signed block slot=%" PRIu64 " missing proposer signature", - block->block.slot); - lantern_state_reset(&parent_state); - return false; + pthread_mutex_unlock(&client->status_lock); } - if (block->block.proposer_index >= lantern_state_validator_count(state_for_sig)) - { - lantern_state_reset(&parent_state); - return false; - } - const uint8_t *proposal_pubkey = lantern_state_validator_proposal_pubkey( - state_for_sig, - (size_t)block->block.proposer_index); - if (!proposal_pubkey || lantern_validator_pubkey_is_zero(proposal_pubkey)) - { - lantern_state_reset(&parent_state); - return false; - } - LanternRoot block_root; - if (lantern_hash_tree_root_block(&block->block, &block_root) != 0) - { - lantern_state_reset(&parent_state); - return false; - } - bool proposer_ok = lantern_signature_verify( - proposal_pubkey, - LANTERN_VALIDATOR_PUBKEY_SIZE, - block->block.slot, - &block->signatures.proposer_signature, - &block_root); - lantern_state_reset(&parent_state); - return proposer_ok; } + void lantern_client_cache_block_aggregated_proofs_locked( struct lantern_client *client, const LanternSignedBlock *block) @@ -487,7 +252,7 @@ void lantern_client_cache_block_aggregated_proofs_locked( } for (size_t i = 0; i < count; ++i) { LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) != SSZ_SUCCESS) { continue; } (void)lantern_client_add_known_aggregated_payload( @@ -588,6 +353,7 @@ int lantern_client_commit_and_publish_local_block( LanternCheckpoint pre_transition_finalized = {0}; LanternRoot head_root = {0}; uint64_t head_slot = 0u; + uint64_t committed_finalized_slot = 0u; bool committed = false; bool state_locked = lantern_client_lock_state(client); @@ -641,11 +407,12 @@ int lantern_client_commit_and_publish_local_block( } lantern_client_cache_block_aggregated_proofs_locked(client, block); + committed_finalized_slot = post_state->latest_finalized.slot; adopt_state_locked(client, post_state); lantern_state_init(post_state); get_head_info_locked(client, &head_root, &head_slot); committed = true; - log_imported_block(block, &head_root, head_slot, &meta, false); + log_imported_block(block, block_root, &head_root, head_slot, "local", 0u, &meta, false); lantern_client_unlock_state(client, state_locked); state_locked = false; @@ -682,6 +449,7 @@ int lantern_client_commit_and_publish_local_block( if (committed) { persist_block_after_import(client, block, &meta); + update_network_view_after_import(client, block->block.slot, committed_finalized_slot); if (client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) { @@ -717,12 +485,12 @@ static size_t bitlist_encoded_size_bits(size_t bit_length) static size_t signed_block_base_ssz_size(void) { - size_t block_fixed = (SSZ_BYTE_SIZE_OF_UINT64 * 2u) + size_t block_fixed = (sizeof(uint64_t) * 2u) + (LANTERN_ROOT_SIZE * 2u) - + SSZ_BYTE_SIZE_OF_UINT32; - size_t body_header = SSZ_BYTE_SIZE_OF_UINT32; - size_t message_base = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_VOTE_SSZ_SIZE + block_fixed + body_header; - size_t offsets = SSZ_BYTE_SIZE_OF_UINT32 * 2u; + + SSZ_BYTES_PER_LENGTH_OFFSET; + size_t body_header = SSZ_BYTES_PER_LENGTH_OFFSET; + size_t message_base = SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_VOTE_SSZ_SIZE + block_fixed + body_header; + size_t offsets = SSZ_BYTES_PER_LENGTH_OFFSET * 2u; return offsets + message_base; } @@ -730,16 +498,16 @@ static size_t signed_block_max_ssz_size(void) { size_t base = signed_block_base_ssz_size(); size_t att_bits_max = bitlist_encoded_size_bits(LANTERN_VALIDATOR_REGISTRY_LIMIT); - size_t att_entry_max = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE + att_bits_max; - size_t attestations_max = (size_t)LANTERN_MAX_ATTESTATIONS * (SSZ_BYTE_SIZE_OF_UINT32 + att_entry_max); + size_t att_entry_max = SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_ATTESTATION_DATA_SSZ_SIZE + att_bits_max; + size_t attestations_max = (size_t)LANTERN_MAX_ATTESTATIONS * (SSZ_BYTES_PER_LENGTH_OFFSET + att_entry_max); if (attestations_max > SIZE_MAX - base) { return SIZE_MAX; } size_t total = base + attestations_max; - size_t proof_entry_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + att_bits_max + LANTERN_AGG_PROOF_MAX_BYTES; - size_t signatures_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + LANTERN_SIGNATURE_SIZE - + ((size_t)LANTERN_MAX_BLOCK_SIGNATURES * (SSZ_BYTE_SIZE_OF_UINT32 + proof_entry_max)); + size_t proof_entry_max = (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + att_bits_max + LANTERN_AGG_PROOF_MAX_BYTES; + size_t signatures_max = (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + LANTERN_SIGNATURE_SIZE + + ((size_t)LANTERN_MAX_BLOCK_SIGNATURES * (SSZ_BYTES_PER_LENGTH_OFFSET + proof_entry_max)); if (signatures_max > SIZE_MAX - total) { return SIZE_MAX; @@ -773,7 +541,7 @@ static int encode_block_ssz( } size_t written = 0; - const int encode_rc = lantern_ssz_encode_signed_block( + const ssz_error_t encode_rc = lantern_ssz_encode_signed_block( block, buffer, encoded_capacity, @@ -871,7 +639,7 @@ static bool get_block_root_local( *out_root = *provided; return true; } - if (lantern_hash_tree_root_block(&block->block, out_root) != 0) + if (lantern_hash_tree_root_block(&block->block, out_root) != SSZ_SUCCESS) { lantern_log_warn( "state", @@ -1341,7 +1109,7 @@ static bool compute_state_head_root_locked( { return false; } - if (lantern_hash_tree_root_block_header(&client->state.latest_block_header, out_root) != 0) + if (lantern_hash_tree_root_block_header(&client->state.latest_block_header, out_root) != SSZ_SUCCESS) { return false; } @@ -1369,14 +1137,14 @@ static bool state_matches_root(const LanternState *state, const LanternRoot *roo return false; } LanternRoot state_root; - if (lantern_hash_tree_root_state(state, &state_root) != 0) + if (lantern_hash_tree_root_state(state, &state_root) != SSZ_SUCCESS) { return false; } LanternBlockHeader header = state->latest_block_header; header.state_root = state_root; LanternRoot header_root; - if (lantern_hash_tree_root_block_header(&header, &header_root) != 0) + if (lantern_hash_tree_root_block_header(&header, &header_root) != SSZ_SUCCESS) { return false; } @@ -1417,7 +1185,7 @@ static bool load_snapshot_state_for_root_locked( lantern_state_init(&decoded); bool decoded_owned = true; bool loaded = false; - if (lantern_ssz_decode_state(&decoded, state_bytes, state_len) == 0 + if (lantern_ssz_decode_state(&decoded, state_bytes, state_len) == SSZ_SUCCESS && prepare_off_head_snapshot_state(client->data_dir, root, &decoded) == 0) { lantern_state_reset(out_state); @@ -1640,7 +1408,7 @@ const LanternState *lantern_client_state_for_root_local_locked( { lantern_state_reset(scratch); lantern_state_init(scratch); - if (lantern_ssz_decode_state(scratch, state_bytes, state_len) == 0) + if (lantern_ssz_decode_state(scratch, state_bytes, state_len) == SSZ_SUCCESS) { if (prepare_off_head_snapshot_state(client->data_dir, root, scratch) == 0) { @@ -1833,6 +1601,7 @@ static void adopt_state_locked(struct lantern_client *client, LanternState *stat } LanternState previous = client->state; client->state = *state; + lantern_state_init(state); if (client->has_fork_choice) { if (lantern_fork_choice_update_checkpoints( @@ -1917,7 +1686,7 @@ static bool rebuild_state_for_root_locked( { LanternState snapshot; lantern_state_init(&snapshot); - if (lantern_ssz_decode_state(&snapshot, state_bytes, state_len) == 0) + if (lantern_ssz_decode_state(&snapshot, state_bytes, state_len) == SSZ_SUCCESS) { if (prepare_off_head_snapshot_state(client->data_dir, target_root, &snapshot) == 0) { @@ -2156,7 +1925,7 @@ static bool rebuild_state_for_root_locked( { for (size_t i = 0; i < response.length; ++i) { - if (lantern_hash_tree_root_block(&response.blocks[i].block, &found_roots[i]) == 0) + if (lantern_hash_tree_root_block(&response.blocks[i].block, &found_roots[i]) == SSZ_SUCCESS) { found_count += 1u; } @@ -2279,7 +2048,7 @@ static bool rebuild_state_for_root_locked( { LanternRoot block_root = {0}; char block_hex[ROOT_HEX_BUFFER_LEN] = {0}; - if (lantern_hash_tree_root_block(&response.blocks[i].block, &block_root) == 0) + if (lantern_hash_tree_root_block(&response.blocks[i].block, &block_root) == SSZ_SUCCESS) { format_root_hex(&block_root, block_hex, sizeof(block_hex)); } @@ -2483,7 +2252,7 @@ static enum block_parent_action handle_block_parent_locked( } else if (lantern_hash_tree_root_block_header( &client->state.latest_block_header, - &latest_header_root) == 0) + &latest_header_root) == SSZ_SUCCESS) { have_head_root = true; parent_matches_head = @@ -2542,13 +2311,16 @@ static bool add_competing_fork_block_locked( lantern_client_cache_block_aggregated_proofs_locked(client, block); char block_hex[ROOT_HEX_BUFFER_LEN]; + char parent_hex[ROOT_HEX_BUFFER_LEN]; format_root_hex(block_root, block_hex, sizeof(block_hex)); + format_root_hex(&block->block.parent_root, parent_hex, sizeof(parent_hex)); lantern_log_info( - "forkchoice", + "import", meta, - "added competing fork block to fork choice slot=%" PRIu64 " root=%s", + "slot %" PRIu64 ", %s, accepted off-head, parent %s, reason: known_off_current_head", block->block.slot, - block_hex[0] ? block_hex : "0x0"); + block_hex[0] ? block_hex : "0x0", + parent_hex[0] ? parent_hex : "0x0"); return true; } @@ -2776,25 +2548,25 @@ static void get_head_info_locked( static bool historical_import_floor_slot_locked( struct lantern_client *client, - uint64_t *out_anchor_slot) + uint64_t *out_finalized_slot) { - if (out_anchor_slot) + if (out_finalized_slot) { - *out_anchor_slot = 0; + *out_finalized_slot = 0; } - if (!client || !out_anchor_slot || !client->has_fork_choice) + if (!client || !out_finalized_slot || !client->has_fork_choice) { return false; } - uint64_t anchor_slot = 0; - if (lantern_fork_choice_anchor_slot(&client->fork_choice, &anchor_slot) != 0 - || anchor_slot == 0) + const LanternCheckpoint *latest_finalized = + lantern_fork_choice_latest_finalized(&client->fork_choice); + if (!latest_finalized) { return false; } - *out_anchor_slot = anchor_slot; + *out_finalized_slot = latest_finalized->slot; return true; } @@ -2885,7 +2657,7 @@ static void prune_finalized_fork_choice_states_if_advanced_locked( } static void persist_finalized_state_if_advanced_locked( - const struct lantern_client *client, + struct lantern_client *client, const LanternCheckpoint *previous_finalized, const struct lantern_log_metadata *meta) { @@ -2906,7 +2678,72 @@ static void persist_finalized_state_if_advanced_locked( return; } - if (lantern_storage_save_finalized_state(client->data_dir, &client->state) != 0) + char finalized_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(¤t_finalized->root, finalized_hex, sizeof(finalized_hex)); + + const LanternState *finalized_state = NULL; + LanternState loaded_finalized_state; + lantern_state_init(&loaded_finalized_state); + bool loaded_finalized_state_owned = false; + + if (client->has_fork_choice) + { + finalized_state = + lantern_fork_choice_block_state(&client->fork_choice, ¤t_finalized->root); + if (finalized_state + && !state_matches_root(finalized_state, ¤t_finalized->root)) + { + finalized_state = NULL; + } + } + if (!finalized_state + && client->has_state + && state_matches_root(&client->state, ¤t_finalized->root)) + { + finalized_state = &client->state; + } + if (!finalized_state + && load_snapshot_state_for_root_locked( + client, + ¤t_finalized->root, + &loaded_finalized_state, + NULL)) + { + loaded_finalized_state_owned = true; + if (state_matches_root(&loaded_finalized_state, ¤t_finalized->root)) + { + finalized_state = &loaded_finalized_state; + } + } + if (!finalized_state) + { + lantern_log_warn( + "storage", + log_meta, + "failed to find finalized replay state finalized_slot=%" PRIu64 " root=%s head_slot=%" PRIu64, + current_finalized->slot, + finalized_hex[0] ? finalized_hex : "0x0", + client->state.slot); + goto cleanup; + } + + if (lantern_storage_store_state_for_root( + client->data_dir, + ¤t_finalized->root, + finalized_state) + != 0) + { + lantern_log_warn( + "storage", + log_meta, + "failed to persist finalized root state finalized_slot=%" PRIu64 " root=%s head_slot=%" PRIu64, + current_finalized->slot, + finalized_hex[0] ? finalized_hex : "0x0", + client->state.slot); + goto cleanup; + } + + if (lantern_storage_save_finalized_state(client->data_dir, finalized_state) != 0) { lantern_log_warn( "storage", @@ -2914,11 +2751,9 @@ static void persist_finalized_state_if_advanced_locked( "failed to persist finalized replay state finalized_slot=%" PRIu64 " head_slot=%" PRIu64, current_finalized->slot, client->state.slot); - return; + goto cleanup; } - char finalized_hex[ROOT_HEX_BUFFER_LEN]; - format_root_hex(¤t_finalized->root, finalized_hex, sizeof(finalized_hex)); lantern_log_info( "storage", log_meta, @@ -2953,6 +2788,12 @@ static void persist_finalized_state_if_advanced_locked( finalized_hex[0] ? finalized_hex : "0x0", pruned); } + +cleanup: + if (loaded_finalized_state_owned) + { + lantern_state_reset(&loaded_finalized_state); + } } @@ -3073,38 +2914,63 @@ static void persist_post_state_and_indices_locked( */ static void log_imported_block( const LanternSignedBlock *block, + const LanternRoot *block_root, const LanternRoot *head_root, uint64_t head_slot, + const char *source, + uint64_t took_ms, const struct lantern_log_metadata *meta, bool quiet) { - if (!block || !head_root) + (void)quiet; + if (!block || !block_root || !head_root) { return; } + char block_hex[ROOT_HEX_BUFFER_LEN]; + char parent_hex[ROOT_HEX_BUFFER_LEN]; char head_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(block_root, block_hex, sizeof(block_hex)); + format_root_hex(&block->block.parent_root, parent_hex, sizeof(parent_hex)); format_root_hex(head_root, head_hex, sizeof(head_hex)); - if (quiet) - { - lantern_log_debug( - "state", - meta, - "imported block slot=%" PRIu64 " new_head_slot=%" PRIu64 " head_root=%s", - block->block.slot, - head_slot, - head_hex[0] ? head_hex : "0x0"); - } - else + lantern_log_info( + "import", + meta, + "slot %" PRIu64 ", %s, via %s, parent %s, head %" PRIu64 " %s, took_ms %" PRIu64, + block->block.slot, + block_hex[0] ? block_hex : "0x0", + source && source[0] ? source : "unknown", + parent_hex[0] ? parent_hex : "0x0", + head_slot, + head_hex[0] ? head_hex : "0x0", + took_ms); +} + +static void log_import_rejected( + const LanternSignedBlock *block, + const LanternRoot *block_root, + const char *source, + const char *reason, + const struct lantern_log_metadata *meta) +{ + if (!block) { - lantern_log_info( - "state", - meta, - "imported block slot=%" PRIu64 " new_head_slot=%" PRIu64 " head_root=%s", - block->block.slot, - head_slot, - head_hex[0] ? head_hex : "0x0"); + return; } + char block_hex[ROOT_HEX_BUFFER_LEN]; + char parent_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(block_root, block_hex, sizeof(block_hex)); + format_root_hex(&block->block.parent_root, parent_hex, sizeof(parent_hex)); + lantern_log_warn( + "import", + meta, + "slot %" PRIu64 ", %s, rejected, reason: %s, via %s, parent %s", + block->block.slot, + block_hex[0] ? block_hex : "0x0", + reason && reason[0] ? reason : "unknown", + source && source[0] ? source : "unknown", + parent_hex[0] ? parent_hex : "0x0"); } @@ -3170,6 +3036,9 @@ static bool lantern_client_import_block_internal( bool imported = false; bool children_ready = false; + uint64_t import_started_ms = monotonic_millis(); + const char *import_source = + allow_historical ? "backfill" : ((meta && meta->peer) ? "gossip" : "local"); bool state_locked = lantern_client_lock_state(client); if (!state_locked) { @@ -3204,16 +3073,18 @@ static bool lantern_client_import_block_internal( lantern_log_debug( "state", meta, - "dropping pre-anchor historical block slot=%" PRIu64 " root=%s anchor_slot=%" PRIu64, + "dropping finalized historical block slot=%" PRIu64 " root=%s finalized_slot=%" PRIu64, block->block.slot, block_hex[0] ? block_hex : "0x0", historical_floor_slot); + log_import_rejected(block, &block_root_local, import_source, "pre_finalized", meta); lantern_client_unlock_state(client, state_locked); lantern_client_pending_remove_branch_by_root(client, &block_root_local); return false; } if (root_known && allow_historical && block->block.slot <= known_slot) { + log_import_rejected(block, &block_root_local, import_source, "duplicate", meta); lantern_client_unlock_state(client, state_locked); persist_block_after_import(client, block, meta); if (drain_pending_children) @@ -3238,6 +3109,7 @@ static bool lantern_client_import_block_internal( known_slot, meta)) { + log_import_rejected(block, &block_root_local, import_source, "duplicate", meta); goto cleanup; } @@ -3255,70 +3127,6 @@ static bool lantern_client_import_block_internal( } bool parent_off_head = parent_action == BLOCK_PARENT_ACTION_KNOWN_OFF_HEAD; - bool missing_parent_state_for_signature = false; - LanternRoot missing_parent_root = {0}; - if (!signed_block_signatures_are_valid( - client, - block, - meta, - &missing_parent_state_for_signature, - &missing_parent_root)) - { - if (missing_parent_state_for_signature) - { - LanternRoot parent_root = block->block.parent_root; - LanternRoot missing_roots[LANTERN_MAX_REQUEST_BLOCKS]; - size_t missing_count = 0; - LanternState rebuild_scratch; - lantern_state_init(&rebuild_scratch); - (void)rebuild_state_for_root_locked( - client, - &parent_root, - &rebuild_scratch, - missing_roots, - LANTERN_MAX_REQUEST_BLOCKS, - &missing_count); - lantern_state_reset(&rebuild_scratch); - - const char *peer_text = meta && meta->peer ? meta->peer : NULL; - lantern_client_enqueue_pending_block( - client, - block, - &block_root_local, - &parent_root, - peer_text, - backfill_depth, - allow_historical); - - uint32_t request_depth = backfill_depth + 1u; - if (request_depth > LANTERN_MAX_BACKFILL_DEPTH) - { - request_depth = LANTERN_MAX_BACKFILL_DEPTH; - } - LanternRoot request_root = parent_root; - if (!lantern_root_is_zero(&missing_parent_root)) - { - request_root = missing_parent_root; - } - (void)lantern_client_try_schedule_blocks_request_batch( - client, - peer_text, - &request_root, - &request_depth, - 1u); - } - char root_hex[ROOT_HEX_BUFFER_LEN]; - format_root_hex(&block_root_local, root_hex, sizeof(root_hex)); - lantern_log_warn( - "state", - meta, - "signature verification failed slot=%" PRIu64 " root=%s depth=%" PRIu32, - block->block.slot, - root_hex[0] ? root_hex : "0x0", - backfill_depth); - goto cleanup; - } - if (!validate_block_vote_constraints_locked(client, block, meta)) { char root_hex[ROOT_HEX_BUFFER_LEN]; @@ -3330,6 +3138,7 @@ static bool lantern_client_import_block_internal( block->block.slot, root_hex[0] ? root_hex : "0x0", backfill_depth); + log_import_rejected(block, &block_root_local, import_source, "vote_constraints_failed", meta); goto cleanup; } @@ -3341,6 +3150,7 @@ static bool lantern_client_import_block_internal( bool have_replay_state = false; bool processed = false; bool deferred = false; + uint64_t observed_finalized_slot = 0u; LanternRoot missing_roots[LANTERN_MAX_REQUEST_BLOCKS]; size_t missing_count = 0; @@ -3368,6 +3178,10 @@ static bool lantern_client_import_block_internal( &replay_state.latest_justified, &replay_state.latest_finalized, meta); + if (processed) + { + observed_finalized_slot = replay_state.latest_finalized.slot; + } } else { @@ -3383,6 +3197,7 @@ static bool lantern_client_import_block_internal( meta, "off-head state transition failed for slot=%" PRIu64, block->block.slot); + log_import_rejected(block, &block_root_local, import_source, "state_transition_failed", meta); } lantern_store_reset(&replay_store); } @@ -3514,6 +3329,7 @@ static bool lantern_client_import_block_internal( if (processed) { persist_block_after_import(client, block, meta); + update_network_view_after_import(client, block->block.slot, observed_finalized_slot); if (drain_pending_children) { lantern_client_process_pending_children(client, &block_root_local); @@ -3533,8 +3349,10 @@ static bool lantern_client_import_block_internal( } LanternCheckpoint pre_transition_finalized = client->state.latest_finalized; + uint64_t imported_finalized_slot = 0u; if (!apply_state_transition_locked(client, block, meta)) { + log_import_rejected(block, &block_root_local, import_source, "state_transition_failed", meta); persist_invalid_block_on_state_transition_failure( client, block, @@ -3569,6 +3387,7 @@ static bool lantern_client_import_block_internal( &head_root, head_slot, meta); + imported_finalized_slot = client->state.latest_finalized.slot; imported = true; cleanup: @@ -3577,6 +3396,7 @@ static bool lantern_client_import_block_internal( if (imported) { persist_block_after_import(client, block, meta); + update_network_view_after_import(client, block->block.slot, imported_finalized_slot); bool quiet_log = false; if (client->status_lock_initialized && pthread_mutex_lock(&client->status_lock) == 0) @@ -3594,7 +3414,18 @@ static bool lantern_client_import_block_internal( { children_ready = true; } - log_imported_block(block, &head_root, head_slot, meta, quiet_log); + uint64_t import_finished_ms = monotonic_millis(); + uint64_t took_ms = + import_finished_ms >= import_started_ms ? import_finished_ms - import_started_ms : 0u; + log_imported_block( + block, + &block_root_local, + &head_root, + head_slot, + import_source, + took_ms, + meta, + quiet_log); update_sync_progress_after_block(client); lantern_client_replay_pending_gossip_votes(client); } @@ -3698,7 +3529,7 @@ void lantern_client_record_block( const LanternRoot *selected_root = root; if (!selected_root) { - if (lantern_hash_tree_root_block(&block->block, &computed_root) != 0) + if (lantern_hash_tree_root_block(&block->block, &computed_root) != SSZ_SUCCESS) { return; } diff --git a/src/core/client_sync_internal.h b/src/core/client_sync_internal.h index 2442b8f..2abf213 100644 --- a/src/core/client_sync_internal.h +++ b/src/core/client_sync_internal.h @@ -38,10 +38,6 @@ extern "C" { #endif -/* Forward declaration for peer_id_t */ -typedef struct peer_id peer_id_t; - - /* ============================================================================ * Constants * ============================================================================ */ @@ -183,6 +179,10 @@ bool lantern_client_backfill_should_drop_gossip( const char *context); void lantern_client_backfill_reset(struct lantern_client *client); +void lantern_client_set_sync_state_logged( + struct lantern_client *client, + LanternSyncState new_state, + const char *reason); /** * Get a state snapshot for a specific block root without attempting replay. @@ -607,7 +607,7 @@ void lantern_client_replay_pending_gossip_votes(struct lantern_client *client); */ int gossip_block_handler( const LanternSignedBlock *block, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_block_ssz, size_t raw_block_ssz_len, void *context); @@ -625,7 +625,7 @@ int gossip_block_handler( */ int gossip_vote_handler( const LanternSignedVote *vote, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_vote_payload, size_t raw_vote_payload_len, void *context); @@ -642,7 +642,7 @@ int gossip_vote_handler( */ int gossip_aggregated_attestation_handler( const LanternSignedAggregatedAttestation *attestation, - const peer_id_t *from, + const struct lantern_peer_id *from, const uint8_t *raw_attestation_payload, size_t raw_attestation_payload_len, void *context); diff --git a/src/core/client_sync_votes.c b/src/core/client_sync_votes.c index 9fe5093..9b69e7b 100644 --- a/src/core/client_sync_votes.c +++ b/src/core/client_sync_votes.c @@ -34,8 +34,6 @@ enum VOTE_ROOT_HEX_BUFFER_LEN = (LANTERN_ROOT_SIZE * 2u) + 3u, }; -static const size_t DEFAULT_SYNC_ATTESTATION_COMMITTEE_COUNT = 1u; - enum lantern_vote_record_status { LANTERN_VOTE_RECORD_REJECTED = 0, @@ -486,7 +484,7 @@ static bool process_vote_locked( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &data_root) == 0) + if (lantern_hash_tree_root_attestation_data(&vote->data.data, &data_root) == SSZ_SUCCESS) { const LanternSignature *signature_to_cache = lantern_client_should_cache_attestation_signature_locked(client, &vote->data) @@ -739,7 +737,7 @@ bool lantern_client_verify_vote_signature( return false; } LanternRoot vote_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != 0) + if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != SSZ_SUCCESS) { lantern_log_warn( "state", diff --git a/src/core/client_utils.c b/src/core/client_utils.c index 2b7cf07..bbca24c 100644 --- a/src/core/client_utils.c +++ b/src/core/client_utils.c @@ -32,8 +32,6 @@ #include #endif -#include - #include "lantern/consensus/fork_choice.h" #include "lantern/support/log.h" #include "lantern/support/secure_mem.h" @@ -401,8 +399,7 @@ void format_root_hex(const LanternRoot *root, char *out, size_t out_len) } if (is_all_zero) { - strncpy(out, "0x0", out_len - 1); - out[out_len - 1] = '\0'; + (void)lantern_string_copy(out, out_len, "0x0"); return; } @@ -602,6 +599,21 @@ bool lantern_client_block_known_locked( return true; } +const char *lantern_sync_state_name(LanternSyncState state) +{ + switch (state) + { + case LANTERN_SYNC_STATE_IDLE: + return "idle"; + case LANTERN_SYNC_STATE_SYNCING: + return "syncing"; + case LANTERN_SYNC_STATE_SYNCED: + return "synced"; + default: + return "unknown"; + } +} + /* ============================================================================ * String Utilities @@ -878,7 +890,7 @@ void string_list_remove(struct lantern_string_list *list, const char *value) /** * Get text description for connection reason code. * - * @param reason Reason code from libp2p + * @param reason Reason code from c-lean-libp2p * @return Static string description * * @note Thread safety: This function is thread-safe @@ -887,30 +899,36 @@ const char *connection_reason_text(int reason) { switch (reason) { - case 0: + case LIBP2P_HOST_OK: return "ok"; - case LIBP2P_ERR_NULL_PTR: - return "null_ptr"; - case LIBP2P_ERR_AGAIN: - return "again"; - case LIBP2P_ERR_EOF: - return "eof"; - case LIBP2P_ERR_TIMEOUT: - return "timeout"; - case LIBP2P_ERR_CLOSED: + case LIBP2P_HOST_ERR_INVALID_ARG: + return "invalid_arg"; + case LIBP2P_HOST_ERR_BUF_TOO_SMALL: + return "buf_too_small"; + case LIBP2P_HOST_ERR_UNSUPPORTED: + return "unsupported"; + case LIBP2P_HOST_ERR_STATE: + return "state"; + case LIBP2P_HOST_ERR_WOULD_BLOCK: + return "would_block"; + case LIBP2P_HOST_ERR_LIMIT: + return "limit"; + case LIBP2P_HOST_ERR_NOT_FOUND: + return "not_found"; + case LIBP2P_HOST_ERR_CLOSED: return "closed"; - case LIBP2P_ERR_RESET: - return "reset"; - case LIBP2P_ERR_INTERNAL: + case LIBP2P_HOST_ERR_ADDR: + return "addr"; + case LIBP2P_HOST_ERR_IDENTITY: + return "identity"; + case LIBP2P_HOST_ERR_TRANSPORT: + return "transport"; + case LIBP2P_HOST_ERR_NEGOTIATION: + return "negotiation"; + case LIBP2P_HOST_ERR_PROTOCOL: + return "protocol"; + case LIBP2P_HOST_ERR_INTERNAL: return "internal"; - case LIBP2P_ERR_PROTO_NEGOTIATION_FAILED: - return "protocol_negotiation_failed"; - case LIBP2P_ERR_MSG_TOO_LARGE: - return "msg_too_large"; - case LIBP2P_ERR_UNSUPPORTED: - return "unsupported"; - case LIBP2P_ERR_CANCELED: - return "canceled"; default: return "unknown"; } diff --git a/src/core/client_validator.c b/src/core/client_validator.c index f3796e9..643918f 100644 --- a/src/core/client_validator.c +++ b/src/core/client_validator.c @@ -23,6 +23,7 @@ #else #include #endif +#include #include #include @@ -55,12 +56,6 @@ static const uint32_t VALIDATOR_SERVICE_IDLE_SLEEP_MS = 200; /** Sleep interval between validator service iterations (ms). */ static const uint32_t VALIDATOR_SERVICE_POLL_SLEEP_MS = 50; -/** Maximum peer head lag (in slots) before pausing validator duties. */ -static const uint64_t VALIDATOR_SYNC_SLOT_LAG = 2; -/** Pending queue size that indicates we're still catching up. */ -static const size_t VALIDATOR_SYNC_PENDING_THRESHOLD = 8; -/** Wall clock lag (in slots) tolerated before treating peer status as stale. */ -static const uint64_t VALIDATOR_SYNC_WALL_CLOCK_LAG = 16; static size_t validator_attestation_committee_count(const struct lantern_client *client) { return lantern_client_attestation_committee_count(client); @@ -161,164 +156,245 @@ static void timing_service_yield(void) #endif } -static bool validator_should_pause_for_sync(const struct lantern_client *client) +static void format_slot_text(char *out, size_t out_len, bool has_slot, uint64_t slot) { - if (!client || !client->status_lock_initialized || !client->has_state) + if (!out || out_len == 0) { - return false; + return; + } + if (has_slot) + { + snprintf(out, out_len, "%" PRIu64, slot); + } + else + { + snprintf(out, out_len, "-"); + } +} + +static void validator_log_status_for_slot(struct lantern_client *client, uint64_t slot) +{ + if (!client) + { + return; } - uint64_t local_slot = client->has_state ? client->state.latest_block_header.slot : 0; - LanternRoot local_head = {0}; - bool have_local_head = false; - if (client->has_fork_choice - && lantern_fork_choice_current_head(&client->fork_choice, &local_head) == 0) + LanternRoot head_root = {0}; + uint64_t head_slot = 0u; + uint64_t justified_slot = 0u; + uint64_t finalized_slot = 0u; + bool have_head_root = false; + bool state_locked = lantern_client_lock_state(client); + if (state_locked) { - have_local_head = true; - uint64_t head_slot = 0; - if (lantern_fork_choice_block_info( + if (client->has_fork_choice + && lantern_fork_choice_current_head(&client->fork_choice, &head_root) == 0) + { + have_head_root = true; + (void)lantern_fork_choice_block_info( &client->fork_choice, - &local_head, + &head_root, &head_slot, NULL, - NULL) - == 0) + NULL); + const LanternCheckpoint *justified = + lantern_fork_choice_latest_justified(&client->fork_choice); + const LanternCheckpoint *finalized = + lantern_fork_choice_latest_finalized(&client->fork_choice); + justified_slot = justified ? justified->slot : client->state.latest_justified.slot; + finalized_slot = finalized ? finalized->slot : client->state.latest_finalized.slot; + } + else if (client->has_state) { - local_slot = head_slot; - } - } - - uint64_t max_peer_slot = 0; - bool have_fresh_status = false; - bool have_peer_at_or_ahead = false; - bool head_match_at_or_ahead = false; - bool any_head_match = false; - uint64_t now_ms = monotonic_millis(); - static LanternRoot last_head_root = {{0}}; - static bool last_head_root_set = false; - static uint64_t last_head_match_ms = 0; - if (have_local_head) - { - if (!last_head_root_set - || memcmp( - last_head_root.bytes, - local_head.bytes, - LANTERN_ROOT_SIZE) - != 0) - { - last_head_root = local_head; - last_head_root_set = true; - last_head_match_ms = now_ms; + head_slot = client->state.slot; + justified_slot = client->state.latest_justified.slot; + finalized_slot = client->state.latest_finalized.slot; } + lantern_client_unlock_state(client, state_locked); } - pthread_mutex_t *status_lock = (pthread_mutex_t *)&client->status_lock; - if (pthread_mutex_lock(status_lock) == 0) + + size_t orphan_count = 0u; + bool pending_locked = lantern_client_lock_pending(client); + if (pending_locked) { - for (size_t i = 0; i < client->peer_status_count; ++i) - { - const struct lantern_peer_status_entry *entry = &client->peer_status_entries[i]; - if (!entry->has_status) - { - continue; - } - if (entry->last_status_ms == 0 - || now_ms < entry->last_status_ms - || now_ms - entry->last_status_ms > LANTERN_PEER_STATUS_STALE_MS) - { - continue; - } - have_fresh_status = true; - if (entry->status.head.slot > max_peer_slot) - { - max_peer_slot = entry->status.head.slot; - } - if (have_local_head && entry->status.head.slot >= local_slot) - { - have_peer_at_or_ahead = true; - if (memcmp(entry->status.head.root.bytes, local_head.bytes, LANTERN_ROOT_SIZE) == 0) - { - head_match_at_or_ahead = true; - } - } - if (have_local_head - && memcmp(entry->status.head.root.bytes, local_head.bytes, LANTERN_ROOT_SIZE) == 0) - { - any_head_match = true; - } - } - pthread_mutex_unlock(status_lock); + orphan_count = client->pending_blocks.length; + lantern_client_unlock_pending(client, pending_locked); + } + + size_t peers = client->connected_peers; + if (client->connection_lock_initialized + && pthread_mutex_lock(&client->connection_lock) == 0) + { + peers = client->connected_peers; + pthread_mutex_unlock(&client->connection_lock); } - if (!have_fresh_status) + LanternSyncState sync_state = client->sync_state; + bool has_network_head = false; + bool has_network_finalized = false; + uint64_t network_head = 0u; + uint64_t network_finalized = 0u; + bool should_log = true; + if (client->status_lock_initialized + && pthread_mutex_lock(&client->status_lock) == 0) { - bool has_connections = false; - pthread_mutex_t *connection_lock = (pthread_mutex_t *)&client->connection_lock; - if (client->connection_lock_initialized - && pthread_mutex_lock(connection_lock) == 0) + if (client->has_last_status_log_slot && client->last_status_log_slot == slot) { - has_connections = client->connected_peers > 0; - pthread_mutex_unlock(connection_lock); + should_log = false; } - if (has_connections || client->bootnodes.len > 0) + else { - return true; + client->last_status_log_slot = slot; + client->has_last_status_log_slot = true; } - return false; + sync_state = client->sync_state; + has_network_head = client->network_view.has_latest_observed_head_slot; + has_network_finalized = client->network_view.has_network_finalized_slot; + network_head = client->network_view.latest_observed_head_slot; + network_finalized = client->network_view.network_finalized_slot; + pthread_mutex_unlock(&client->status_lock); + } + else if (client->has_last_status_log_slot && client->last_status_log_slot == slot) + { + should_log = false; + } + else + { + client->last_status_log_slot = slot; + client->has_last_status_log_slot = true; + has_network_head = client->network_view.has_latest_observed_head_slot; + has_network_finalized = client->network_view.has_network_finalized_slot; + network_head = client->network_view.latest_observed_head_slot; + network_finalized = client->network_view.network_finalized_slot; } - uint64_t current_slot = 0; - if (lantern_client_current_slot(client, ¤t_slot) - && current_slot > max_peer_slot + VALIDATOR_SYNC_WALL_CLOCK_LAG) + if (!should_log) + { + return; + } + + char head_root_hex[2 * LANTERN_ROOT_SIZE + 3]; + char network_head_text[32]; + char network_finalized_text[32]; + format_root_hex(have_head_root ? &head_root : NULL, head_root_hex, sizeof(head_root_hex)); + format_slot_text(network_head_text, sizeof(network_head_text), has_network_head, network_head); + format_slot_text( + network_finalized_text, + sizeof(network_finalized_text), + has_network_finalized, + network_finalized); + lantern_log_info( + "status", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", head %" PRIu64 ", head_root %s, justified %" PRIu64 + ", finalized %" PRIu64 ", %s, %zu peers, orphans %zu, net_head %s, net_finalized %s", + slot, + head_slot, + head_root_hex[0] ? head_root_hex : "0x0", + justified_slot, + finalized_slot, + lantern_sync_state_name(sync_state), + peers, + orphan_count, + network_head_text, + network_finalized_text); +} + +static const char *validator_service_skip_reason(const struct lantern_client *client) +{ + if (!client) + { + return "client_null"; + } + if (!client->has_state) + { + return "state_not_ready"; + } + if (!client->has_runtime) + { + return "runtime_not_ready"; + } + if (!client->has_fork_choice) { - return true; + return "fork_choice_not_ready"; + } + if (!client->gossip_running) + { + return "gossip_not_running"; + } + if (client->local_validator_count == 0) + { + return "no_validators"; + } + if (client->sync_state != LANTERN_SYNC_STATE_SYNCED) + { + switch (client->sync_state) + { + case LANTERN_SYNC_STATE_IDLE: + return "sync_state=idle"; + case LANTERN_SYNC_STATE_SYNCING: + return "sync_state=syncing"; + case LANTERN_SYNC_STATE_SYNCED: + break; + default: + return "sync_state=unknown"; + } } + return NULL; +} - if (have_peer_at_or_ahead && have_local_head && !head_match_at_or_ahead) +static void validator_log_duty_skipped( + struct lantern_client *client, + uint64_t slot, + const char *reason) +{ + if (!client || !reason || reason[0] == '\0') { - return true; + return; } - if (have_fresh_status && have_local_head) + bool should_log = true; + if (client->status_lock_initialized + && pthread_mutex_lock(&client->status_lock) == 0) { - if (any_head_match) + if (client->has_last_duty_skip_slot + && client->last_duty_skip_slot == slot + && client->last_duty_skip_reason + && strcmp(client->last_duty_skip_reason, reason) == 0) { - last_head_match_ms = now_ms; + should_log = false; } else { - uint64_t grace_ms = 0; - if (client->has_fork_choice && client->fork_choice.seconds_per_slot > 0) - { - uint64_t slot_ms = client->fork_choice.seconds_per_slot * 1000u; - if (slot_ms > UINT64_MAX / 2u) - { - slot_ms = UINT64_MAX / 2u; - } - grace_ms = slot_ms * 2u; - } - if (grace_ms == 0) - { - grace_ms = 8000u; - } - if (now_ms > last_head_match_ms + grace_ms) - { - return true; - } + client->last_duty_skip_slot = slot; + client->has_last_duty_skip_slot = true; + client->last_duty_skip_reason = reason; } + pthread_mutex_unlock(&client->status_lock); } - - if (max_peer_slot > local_slot + VALIDATOR_SYNC_SLOT_LAG) + else if (client->has_last_duty_skip_slot + && client->last_duty_skip_slot == slot + && client->last_duty_skip_reason + && strcmp(client->last_duty_skip_reason, reason) == 0) { - return true; + should_log = false; } - - size_t pending = lantern_client_pending_block_count(client); - if (pending >= VALIDATOR_SYNC_PENDING_THRESHOLD) + else { - return true; + client->last_duty_skip_slot = slot; + client->has_last_duty_skip_slot = true; + client->last_duty_skip_reason = reason; } - return false; + if (should_log) + { + lantern_log_info( + "duty", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", skipped, reason: %s", + slot, + reason); + } } /* ============================================================================ @@ -589,7 +665,7 @@ static lantern_client_error append_group_as_aggregated( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&group->data, &data_root) != 0) + if (lantern_hash_tree_root_attestation_data(&group->data, &data_root) != SSZ_SUCCESS) { lantern_aggregated_attestation_reset(&attestation); return LANTERN_CLIENT_ERR_VALIDATOR; @@ -1019,7 +1095,7 @@ static lantern_client_error aggregate_attestations_for_block( { aggregation_group_sort(&groups[i]); LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&groups[i].data, &data_root) != 0) + if (lantern_hash_tree_root_attestation_data(&groups[i].data, &data_root) != SSZ_SUCCESS) { rc = LANTERN_CLIENT_ERR_VALIDATOR; break; @@ -1208,7 +1284,7 @@ static lantern_client_error aggregate_attestation_signatures( for (size_t i = 0; i < out_attestations->length; ++i) { LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&out_attestations->data[i].data, &data_root) != 0) + if (lantern_hash_tree_root_attestation_data(&out_attestations->data[i].data, &data_root) != SSZ_SUCCESS) { rc = LANTERN_CLIENT_ERR_VALIDATOR; break; @@ -1373,23 +1449,7 @@ void validator_duty_state_reset(struct lantern_validator_duty_state *state) */ bool validator_service_should_run(const struct lantern_client *client) { - if (!client) - { - return false; - } - if (!client->has_state || !client->has_runtime || !client->has_fork_choice) - { - return false; - } - if (!client->gossip_running || client->local_validator_count == 0) - { - return false; - } - if (validator_should_pause_for_sync(client)) - { - return false; - } - return true; + return validator_service_skip_reason(client) == NULL; } @@ -1523,7 +1583,7 @@ int validator_sign_vote( return LANTERN_CLIENT_ERR_INVALID_PARAM; } LanternRoot vote_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != 0) + if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != SSZ_SUCCESS) { return LANTERN_CLIENT_ERR_VALIDATOR; } @@ -1806,7 +1866,7 @@ static int validator_build_block_internal( out_block->block.state_root = computed_state_root; LanternRoot block_root; - if (lantern_hash_tree_root_block(&out_block->block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&out_block->block, &block_root) != SSZ_SUCCESS) { result = LANTERN_CLIENT_ERR_RUNTIME; goto cleanup; } @@ -2007,7 +2067,7 @@ int validator_publish_vote(struct lantern_client *client, const LanternSignedVot } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &data_root) == 0) + if (lantern_hash_tree_root_attestation_data(&vote->data.data, &data_root) == SSZ_SUCCESS) { LanternSignatureKey key = { .validator_index = vote->data.validator_id, @@ -2056,12 +2116,21 @@ int validator_publish_vote(struct lantern_client *client, const LanternSignedVot vote->data.slot); return LANTERN_CLIENT_ERR_NETWORK; } + char target_hex[2 * LANTERN_ROOT_SIZE + 3]; + char source_hex[2 * LANTERN_ROOT_SIZE + 3]; + format_root_hex(&vote->data.target.root, target_hex, sizeof(target_hex)); + format_root_hex(&vote->data.source.root, source_hex, sizeof(source_hex)); lantern_log_info( - "gossip", + "attest", &meta, - "published attestation validator=%" PRIu64 " slot=%" PRIu64, + "slot %" PRIu64 ", validator %" PRIu64 ", target %s @ %" PRIu64 + ", source %s @ %" PRIu64, + vote->data.slot, vote->data.validator_id, - vote->data.slot); + target_hex[0] ? target_hex : "0x0", + vote->data.target.slot, + source_hex[0] ? source_hex : "0x0", + vote->data.source.slot); return LANTERN_CLIENT_OK; } @@ -2090,25 +2159,25 @@ int lantern_client_publish_block(struct lantern_client *client, const LanternSig if (!client->gossip_running) { lantern_log_error( - "gossip", + "propose", &(const struct lantern_log_metadata){.validator = client->node_id}, - "cannot publish block at slot %" PRIu64 ": gossip service inactive", + "slot %" PRIu64 ", skipped, reason: gossip_not_running", block->block.slot); return LANTERN_CLIENT_ERR_NETWORK; } if (lantern_gossipsub_service_publish_block(&client->gossip, block) != 0) { lantern_log_error( - "gossip", + "propose", &(const struct lantern_log_metadata){.validator = client->node_id}, - "failed to publish block at slot %" PRIu64, + "slot %" PRIu64 ", skipped, reason: publish_failed", block->block.slot); return LANTERN_CLIENT_ERR_NETWORK; } LanternRoot block_root; char root_hex[2 * LANTERN_ROOT_SIZE + 3]; - if (lantern_hash_tree_root_block(&block->block, &block_root) == 0) + if (lantern_hash_tree_root_block(&block->block, &block_root) == SSZ_SUCCESS) { format_root_hex(&block_root, root_hex, sizeof(root_hex)); } @@ -2117,10 +2186,10 @@ int lantern_client_publish_block(struct lantern_client *client, const LanternSig root_hex[0] = '\0'; } - lantern_log_info( - "gossip", + lantern_log_debug( + "propose", &(const struct lantern_log_metadata){.validator = client->node_id}, - "published block slot=%" PRIu64 " root=%s attestations=%zu", + "slot %" PRIu64 ", %s, published, attestations %zu", block->block.slot, root_hex[0] ? root_hex : "0x0", block->block.body.attestations.length); @@ -2180,8 +2249,10 @@ int validator_build_block( */ int validator_propose_block(struct lantern_client *client, uint64_t slot, size_t local_index) { - if (!validator_service_should_run(client)) + const char *skip_reason = validator_service_skip_reason(client); + if (skip_reason) { + validator_log_duty_skipped(client, slot, skip_reason); return LANTERN_CLIENT_ERR_RUNTIME; } LanternSignedBlock block; @@ -2203,20 +2274,18 @@ int validator_propose_block(struct lantern_client *client, uint64_t slot, size_t &block_root); if (rc != LANTERN_CLIENT_OK) { + lantern_log_warn( + "propose", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", skipped, reason: build_failed, rc %d", + slot, + rc); lantern_store_reset(&post_store); lantern_state_reset(&post_state); lantern_signed_block_reset(&block); return rc; } - struct lantern_log_metadata meta = {.validator = client->node_id}; - lantern_log_info( - "validator", - &meta, - "proposing block slot=%" PRIu64 " proposer=%" PRIu64, - slot, - block.block.proposer_index); - rc = lantern_client_commit_and_publish_local_block( client, &block, @@ -2234,6 +2303,27 @@ int validator_propose_block(struct lantern_client *client, uint64_t slot, size_t } unlock_mutex_with_log(&client->validator_lock, client->node_id, "validator_lock"); } + if (rc == LANTERN_CLIENT_OK) + { + char root_hex[2 * LANTERN_ROOT_SIZE + 3]; + format_root_hex(&block_root, root_hex, sizeof(root_hex)); + lantern_log_info( + "propose", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", %s, %zu attestations", + slot, + root_hex[0] ? root_hex : "0x0", + block.block.body.attestations.length); + } + else + { + lantern_log_warn( + "propose", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", skipped, reason: publish_failed, rc %d", + slot, + rc); + } lantern_signed_block_reset(&block); return rc; } @@ -2260,12 +2350,15 @@ int validator_publish_attestations(struct lantern_client *client, uint64_t slot) { lantern_client_error result = LANTERN_CLIENT_OK; - if (!validator_service_should_run(client)) + const char *skip_reason = validator_service_skip_reason(client); + if (skip_reason) { + validator_log_duty_skipped(client, slot, skip_reason); return LANTERN_CLIENT_ERR_RUNTIME; } if (!client->local_validators || client->local_validator_count == 0) { + validator_log_duty_skipped(client, slot, "validators_not_loaded"); return LANTERN_CLIENT_ERR_INVALID_PARAM; } @@ -2275,6 +2368,7 @@ int validator_publish_attestations(struct lantern_client *client, uint64_t slot) bool state_locked = lantern_client_lock_state(client); if (!state_locked) { + validator_log_duty_skipped(client, slot, "state_lock_failed"); return LANTERN_CLIENT_ERR_RUNTIME; } if (lantern_state_compute_vote_checkpoints( @@ -2286,6 +2380,7 @@ int validator_publish_attestations(struct lantern_client *client, uint64_t slot) != 0) { lantern_client_unlock_state(client, state_locked); + validator_log_duty_skipped(client, slot, "checkpoint_compute_failed"); return LANTERN_CLIENT_ERR_RUNTIME; } lantern_client_unlock_state(client, state_locked); @@ -2295,6 +2390,7 @@ int validator_publish_attestations(struct lantern_client *client, uint64_t slot) { if (pthread_mutex_lock(&client->validator_lock) != 0) { + validator_log_duty_skipped(client, slot, "validator_lock_failed"); return LANTERN_CLIENT_ERR_RUNTIME; } have_lock = true; @@ -2323,6 +2419,14 @@ int validator_publish_attestations(struct lantern_client *client, uint64_t slot) int sign_rc = validator_sign_vote(validator, slot, &vote); if (sign_rc != LANTERN_CLIENT_OK) { + lantern_log_warn( + "duty", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", skipped, reason: attestation_sign_failed, validator %" PRIu64 + ", rc %d", + slot, + validator->global_index, + sign_rc); if (result == LANTERN_CLIENT_OK) { result = (lantern_client_error)sign_rc; @@ -2332,14 +2436,36 @@ int validator_publish_attestations(struct lantern_client *client, uint64_t slot) validator->last_attested_slot = slot; int store_rc = validator_store_vote(client, &vote); - if (store_rc != LANTERN_CLIENT_OK && result == LANTERN_CLIENT_OK) + if (store_rc != LANTERN_CLIENT_OK) { - result = (lantern_client_error)store_rc; + lantern_log_warn( + "duty", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", skipped, reason: attestation_store_failed, validator %" PRIu64 + ", rc %d", + slot, + validator->global_index, + store_rc); + if (result == LANTERN_CLIENT_OK) + { + result = (lantern_client_error)store_rc; + } } int publish_rc = validator_publish_vote(client, &vote); - if (publish_rc != LANTERN_CLIENT_OK && result == LANTERN_CLIENT_OK) + if (publish_rc != LANTERN_CLIENT_OK) { - result = (lantern_client_error)publish_rc; + lantern_log_warn( + "duty", + &(const struct lantern_log_metadata){.validator = client->node_id}, + "slot %" PRIu64 ", skipped, reason: attestation_publish_failed, validator %" PRIu64 + ", rc %d", + slot, + validator->global_index, + publish_rc); + if (result == LANTERN_CLIENT_OK) + { + result = (lantern_client_error)publish_rc; + } } lean_metrics_record_attestations_production_time( lantern_time_now_seconds() - production_start); @@ -2608,6 +2734,7 @@ void *timing_thread(void *arg) } uint64_t current_interval = 0u; + uint64_t intervals_per_slot = 0u; bool state_locked = lantern_client_lock_state(client); if (!state_locked) { @@ -2615,7 +2742,12 @@ void *timing_thread(void *arg) continue; } current_interval = client->has_fork_choice ? client->fork_choice.time_intervals : 0u; + intervals_per_slot = client->has_fork_choice ? client->fork_choice.intervals_per_slot : 0u; lantern_client_unlock_state(client, state_locked); + if (intervals_per_slot > 0u) + { + validator_log_status_for_slot(client, target_interval / intervals_per_slot); + } if (current_interval >= target_interval) { @@ -2665,8 +2797,12 @@ void *validator_thread(void *arg) while (__atomic_load_n(&client->validator_stop_flag, __ATOMIC_RELAXED) == 0) { - if (!validator_service_should_run(client)) + const char *skip_reason = validator_service_skip_reason(client); + if (skip_reason) { + uint64_t skip_slot = + client->validator_duty.have_timepoint ? client->validator_duty.last_slot : 0u; + validator_log_duty_skipped(client, skip_slot, skip_reason); validator_sleep_ms(VALIDATOR_SERVICE_IDLE_SLEEP_MS); continue; } diff --git a/src/encoding/snappy.c b/src/encoding/snappy.c index c2dc225..6a23a08 100644 --- a/src/encoding/snappy.c +++ b/src/encoding/snappy.c @@ -51,9 +51,6 @@ static const uint8_t LANTERN_SNAPPY_RAW_COPY_2 = 0x02u; static const uint8_t LANTERN_SNAPPY_RAW_COPY_4 = 0x03u; static const size_t LANTERN_SNAPPY_RAW_MAX_INLINE_LITERAL = 60u; static const size_t LANTERN_SNAPPY_RAW_LITERAL_1_BYTE = 60u; -static const size_t LANTERN_SNAPPY_RAW_LITERAL_2_BYTES = 61u; -static const size_t LANTERN_SNAPPY_RAW_LITERAL_3_BYTES = 62u; -static const size_t LANTERN_SNAPPY_RAW_LITERAL_4_BYTES = 63u; static const size_t LANTERN_SNAPPY_RAW_MIN_COPY_1_LENGTH = 4u; static const size_t LANTERN_SNAPPY_RAW_MAX_COPY_1_LENGTH = 11u; static const size_t LANTERN_SNAPPY_RAW_MAX_COPY_1_OFFSET = 2047u; diff --git a/src/genesis/genesis_parse.c b/src/genesis/genesis_parse.c index 60b4b80..61d129d 100644 --- a/src/genesis/genesis_parse.c +++ b/src/genesis/genesis_parse.c @@ -21,8 +21,6 @@ #include #include -#include "peer_id/peer_id.h" - #include "internal/yaml_parser.h" #include "lantern/networking/libp2p.h" #include "lantern/support/secure_mem.h" @@ -74,28 +72,6 @@ static int parse_validator_config_entry( struct lantern_validator_config_entry *entry); static void free_validator_config_entry(struct lantern_validator_config_entry *entry); -static int write_legacy_peer_id_text(const peer_id_t *peer_id, char *buffer, size_t buffer_len) -{ - if (!peer_id || !buffer || buffer_len == 0) - { - return -1; - } - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - peer_id, - PEER_ID_TEXT_LEGACY_BASE58, - buffer, - buffer_len, - &written); - if (rc != PEER_ID_OK) - { - buffer[0] = '\0'; - return -1; - } - return (int)written; -} - - /** * Parse an unsigned 64-bit integer from a string, allowing a trailing comment. * @@ -426,40 +402,41 @@ static int derive_peer_id_from_privkey_hex(const char *hex, char **out_peer_id) return LANTERN_GENESIS_ERR_PARSE; } - uint8_t *encoded = NULL; - size_t encoded_len = 0; - if (lantern_libp2p_encode_secp256k1_private_key_proto( + uint8_t public_key[LIBP2P_PEER_ID_SECP256K1_COMPRESSED_PUBLIC_KEY_BYTES]; + size_t public_key_len = 0; + if (libp2p_peer_id_public_key_from_private_key( secret, sizeof(secret), - &encoded, - &encoded_len) - != 0) + 1, + public_key, + sizeof(public_key), + &public_key_len) + != LIBP2P_PEER_ID_OK) { lantern_secure_zero(secret, sizeof(secret)); return LANTERN_GENESIS_ERR_PARSE; } lantern_secure_zero(secret, sizeof(secret)); - peer_id_t *peer_id = NULL; - peer_id_error_t perr = peer_id_new_from_private_key_pb(encoded, encoded_len, &peer_id); - if (encoded) - { - lantern_secure_zero(encoded, encoded_len); - } - free(encoded); - - if (perr != PEER_ID_OK || !peer_id) + struct lantern_peer_id peer_id; + size_t peer_id_len = 0; + if (libp2p_peer_id_from_secp256k1_public_key( + public_key, + public_key_len, + peer_id.bytes, + sizeof(peer_id.bytes), + &peer_id_len) + != LIBP2P_PEER_ID_OK) { return LANTERN_GENESIS_ERR_PARSE; } + peer_id.len = peer_id_len; char buffer[GENESIS_PEER_ID_BUFFER_LEN]; - if (write_legacy_peer_id_text(peer_id, buffer, sizeof(buffer)) < 0) + if (lantern_peer_id_to_text(&peer_id, buffer, sizeof(buffer)) < 0) { - peer_id_free(peer_id); return LANTERN_GENESIS_ERR_PARSE; } - peer_id_free(peer_id); char *dup = lantern_string_duplicate(buffer); if (!dup) diff --git a/src/http/metrics.c b/src/http/metrics.c index 9d9ee84..f1c7cac 100644 --- a/src/http/metrics.c +++ b/src/http/metrics.c @@ -30,6 +30,7 @@ #include "lantern/http/common.h" #include "lantern/support/log.h" +#include "lantern/support/strings.h" #include "lantern/support/version.h" static const size_t LANTERN_METRICS_READ_BUFFER_SIZE = 4096; @@ -831,8 +832,7 @@ static int append_peer_vote_metrics( { const struct lantern_peer_vote_metric *metric = &snapshot->peer_vote_metrics[i]; char peer_id[sizeof(metric->peer_id)]; - strncpy(peer_id, metric->peer_id, sizeof(peer_id) - 1); - peer_id[sizeof(peer_id) - 1] = '\0'; + (void)lantern_string_copy(peer_id, sizeof(peer_id), metric->peer_id); rc = metrics_buffer_appendf( buf, @@ -858,8 +858,7 @@ static int append_peer_vote_metrics( { const struct lantern_peer_vote_metric *metric = &snapshot->peer_vote_metrics[i]; char peer_id[sizeof(metric->peer_id)]; - strncpy(peer_id, metric->peer_id, sizeof(peer_id) - 1); - peer_id[sizeof(peer_id) - 1] = '\0'; + (void)lantern_string_copy(peer_id, sizeof(peer_id), metric->peer_id); rc = metrics_buffer_appendf( buf, @@ -885,8 +884,7 @@ static int append_peer_vote_metrics( { const struct lantern_peer_vote_metric *metric = &snapshot->peer_vote_metrics[i]; char peer_id[sizeof(metric->peer_id)]; - strncpy(peer_id, metric->peer_id, sizeof(peer_id) - 1); - peer_id[sizeof(peer_id) - 1] = '\0'; + (void)lantern_string_copy(peer_id, sizeof(peer_id), metric->peer_id); rc = metrics_buffer_appendf( buf, @@ -912,8 +910,7 @@ static int append_peer_vote_metrics( { const struct lantern_peer_vote_metric *metric = &snapshot->peer_vote_metrics[i]; char peer_id[sizeof(metric->peer_id)]; - strncpy(peer_id, metric->peer_id, sizeof(peer_id) - 1); - peer_id[sizeof(peer_id) - 1] = '\0'; + (void)lantern_string_copy(peer_id, sizeof(peer_id), metric->peer_id); rc = metrics_buffer_appendf( buf, @@ -939,8 +936,7 @@ static int append_peer_vote_metrics( { const struct lantern_peer_vote_metric *metric = &snapshot->peer_vote_metrics[i]; char peer_id[sizeof(metric->peer_id)]; - strncpy(peer_id, metric->peer_id, sizeof(peer_id) - 1); - peer_id[sizeof(peer_id) - 1] = '\0'; + (void)lantern_string_copy(peer_id, sizeof(peer_id), metric->peer_id); rc = metrics_buffer_appendf( buf, @@ -1356,15 +1352,13 @@ static void peer_to_text(const struct sockaddr_in *peer_addr, char *out, size_t const char *fallback = "unknown"; if (!peer_addr) { - strncpy(out, fallback, out_len - 1); - out[out_len - 1] = '\0'; + (void)lantern_string_copy(out, out_len, fallback); return; } if (!inet_ntop(AF_INET, &peer_addr->sin_addr, out, out_len)) { - strncpy(out, fallback, out_len - 1); - out[out_len - 1] = '\0'; + (void)lantern_string_copy(out, out_len, fallback); } } diff --git a/src/http/server.c b/src/http/server.c index cb3cffe..b27a238 100644 --- a/src/http/server.c +++ b/src/http/server.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -34,8 +35,10 @@ #include "lantern/http/metrics.h" #include "lantern/support/log.h" #include "lantern/support/strings.h" +#include "test_driver/driver.h" static const size_t LANTERN_HTTP_READ_BUFFER_SIZE = 4096; +static const size_t LANTERN_HTTP_MAX_TEST_DRIVER_BODY_SIZE = 64u * 1024u * 1024u; static const int LANTERN_HTTP_LISTEN_BACKLOG = 16; static const char LANTERN_HTTP_PATH_HEALTH[] = "/lean/v0/health"; static const char LANTERN_HTTP_PATH_METRICS[] = "/metrics"; @@ -43,6 +46,12 @@ static const char LANTERN_HTTP_PATH_FINALIZED[] = "/lean/v0/states/finalized"; static const char LANTERN_HTTP_PATH_JUSTIFIED[] = "/lean/v0/checkpoints/justified"; static const char LANTERN_HTTP_PATH_FORK_CHOICE[] = "/lean/v0/fork_choice"; static const char LANTERN_HTTP_PATH_ADMIN_AGGREGATOR[] = "/lean/v0/admin/aggregator"; +static const char LANTERN_HTTP_PATH_TEST_FC_INIT[] = "/lean/v0/test_driver/fork_choice/init"; +static const char LANTERN_HTTP_PATH_TEST_FC_STEP[] = "/lean/v0/test_driver/fork_choice/step"; +static const char LANTERN_HTTP_PATH_TEST_STATE_RUN[] = + "/lean/v0/test_driver/state_transition/run"; +static const char LANTERN_HTTP_PATH_TEST_VERIFY_RUN[] = + "/lean/v0/test_driver/verify_signatures/run"; static const char LANTERN_HTTP_JSON_HEALTH[] = "{\"status\":\"healthy\",\"service\":\"lean-rpc-api\"}"; static const char LANTERN_HTTP_JSON_MALFORMED[] = "{\"error\":\"malformed request\"}"; static const char LANTERN_HTTP_JSON_UNKNOWN_ENDPOINT[] = "{\"error\":\"unknown endpoint\"}"; @@ -97,15 +106,13 @@ static void peer_to_text(const struct sockaddr_in *peer_addr, char *out, size_t const char *fallback = "unknown"; if (!peer_addr) { - strncpy(out, fallback, out_len - 1); - out[out_len - 1] = '\0'; + (void)lantern_string_copy(out, out_len, fallback); return; } if (!inet_ntop(AF_INET, &peer_addr->sin_addr, out, out_len)) { - strncpy(out, fallback, out_len - 1); - out[out_len - 1] = '\0'; + (void)lantern_string_copy(out, out_len, fallback); } } @@ -502,6 +509,151 @@ static bool http_locate_body( return false; } +static int http_parse_content_length( + const char *buffer, + size_t buffer_len, + size_t *out_content_length) +{ + if (!buffer || !out_content_length) + { + return -1; + } + *out_content_length = 0; + const char *headers_end = NULL; + static const char SEPARATOR[] = "\r\n\r\n"; + const size_t sep_len = sizeof(SEPARATOR) - 1u; + for (size_t i = 0; i + sep_len <= buffer_len; ++i) + { + if (memcmp(buffer + i, SEPARATOR, sep_len) == 0) + { + headers_end = buffer + i; + break; + } + } + if (!headers_end) + { + return -1; + } + + const char *cursor = buffer; + static const char HEADER[] = "content-length:"; + const size_t header_len = sizeof(HEADER) - 1u; + while (cursor < headers_end) + { + const char *line_end = memchr(cursor, '\n', (size_t)(headers_end - cursor)); + if (!line_end) + { + line_end = headers_end; + } + const char *line = cursor; + if (line_end > line && *(line_end - 1) == '\r') + { + line_end -= 1; + } + size_t line_len = (size_t)(line_end - line); + if (line_len >= header_len && strncasecmp(line, HEADER, header_len) == 0) + { + const char *value = line + header_len; + while (value < line_end && (*value == ' ' || *value == '\t')) + { + ++value; + } + size_t content_length = 0; + if (value == line_end) + { + return -1; + } + while (value < line_end) + { + if (*value < '0' || *value > '9') + { + return -1; + } + size_t digit = (size_t)(*value - '0'); + if (content_length > (SIZE_MAX - digit) / 10u) + { + return -1; + } + content_length = (content_length * 10u) + digit; + ++value; + } + *out_content_length = content_length; + return 0; + } + cursor = line_end; + if (cursor < headers_end && *cursor == '\r') + { + ++cursor; + } + if (cursor < headers_end && *cursor == '\n') + { + ++cursor; + } + } + return -1; +} + +static int http_read_request_body( + int client_fd, + const char *raw_buffer, + size_t raw_buffer_len, + char **out_body, + size_t *out_body_len) +{ + if (client_fd < 0 || !raw_buffer || !out_body || !out_body_len) + { + return -1; + } + *out_body = NULL; + *out_body_len = 0; + + const char *body_ptr = NULL; + size_t available_len = 0; + if (!http_locate_body(raw_buffer, raw_buffer_len, &body_ptr, &available_len)) + { + return -1; + } + + size_t content_length = 0; + if (http_parse_content_length(raw_buffer, raw_buffer_len, &content_length) != 0) + { + content_length = available_len; + } + if (content_length > LANTERN_HTTP_MAX_TEST_DRIVER_BODY_SIZE) + { + return -1; + } + + char *body = malloc(content_length + 1u); + if (!body) + { + return -1; + } + size_t copied = available_len < content_length ? available_len : content_length; + if (copied > 0) + { + memcpy(body, body_ptr, copied); + } + while (copied < content_length) + { + ssize_t received = recv(client_fd, body + copied, content_length - copied, 0); + if (received < 0 && errno == EINTR) + { + continue; + } + if (received <= 0) + { + free(body); + return -1; + } + copied += (size_t)received; + } + body[content_length] = '\0'; + *out_body = body; + *out_body_len = content_length; + return 0; +} + /** * Strictly parse `{"enabled": }` from a JSON body. @@ -743,6 +895,149 @@ static void handle_admin_aggregator( rc); } +static bool is_test_driver_path(const char *path) +{ + return path + && (strcmp(path, LANTERN_HTTP_PATH_TEST_FC_INIT) == 0 + || strcmp(path, LANTERN_HTTP_PATH_TEST_FC_STEP) == 0 + || strcmp(path, LANTERN_HTTP_PATH_TEST_STATE_RUN) == 0 + || strcmp(path, LANTERN_HTTP_PATH_TEST_VERIFY_RUN) == 0); +} + +static void handle_test_driver( + int client_fd, + const char *method, + const char *path, + const char *raw_buffer, + size_t raw_buffer_len, + const char *peer_text) +{ + if (strcmp(method, "POST") != 0) + { + int rc = send_json_error(client_fd, 405, "Method Not Allowed", LANTERN_HTTP_JSON_METHOD_NOT_ALLOWED); + lantern_log_info( + "http", + &(const struct lantern_log_metadata){.peer = peer_text}, + "%s %s -> 405 (rc=%d)", + method, + path, + rc); + return; + } + + char *request_body = NULL; + size_t request_body_len = 0; + if (http_read_request_body( + client_fd, + raw_buffer, + raw_buffer_len, + &request_body, + &request_body_len) + != 0) + { + int rc = send_json_error(client_fd, 400, "Bad Request", LANTERN_HTTP_JSON_BAD_REQUEST); + lantern_log_info( + "http", + &(const struct lantern_log_metadata){.peer = peer_text}, + "POST %s -> 400 (body read failed, rc=%d)", + path, + rc); + return; + } + + if (strcmp(path, LANTERN_HTTP_PATH_TEST_FC_INIT) == 0) + { + char *error = NULL; + int driver_rc = + lantern_test_driver_fork_choice_init(request_body, request_body_len, &error); + free(request_body); + if (driver_rc != 0) + { + int rc = send_json_error( + client_fd, + 400, + "Bad Request", + LANTERN_HTTP_JSON_BAD_REQUEST); + lantern_log_info( + "http", + &(const struct lantern_log_metadata){.peer = peer_text}, + "POST %s -> 400 (driver_rc=%d error=%s rc=%d)", + path, + driver_rc, + error ? error : "-", + rc); + free(error); + return; + } + int rc = send_http_response(client_fd, 204, "No Content", "application/json", NULL, 0); + lantern_log_info( + "http", + &(const struct lantern_log_metadata){.peer = peer_text}, + "POST %s -> 204 (rc=%d)", + path, + rc); + return; + } + + char *response_body = NULL; + size_t response_body_len = 0; + int driver_rc = -1; + if (strcmp(path, LANTERN_HTTP_PATH_TEST_FC_STEP) == 0) + { + driver_rc = lantern_test_driver_fork_choice_step( + request_body, + request_body_len, + &response_body, + &response_body_len); + } + else if (strcmp(path, LANTERN_HTTP_PATH_TEST_STATE_RUN) == 0) + { + driver_rc = lantern_test_driver_state_transition_run( + request_body, + request_body_len, + &response_body, + &response_body_len); + } + else if (strcmp(path, LANTERN_HTTP_PATH_TEST_VERIFY_RUN) == 0) + { + driver_rc = lantern_test_driver_verify_signatures_run( + request_body, + request_body_len, + &response_body, + &response_body_len); + } + free(request_body); + + if (driver_rc != 0 || !response_body) + { + free(response_body); + int rc = send_json_error(client_fd, 500, "Internal Server Error", LANTERN_HTTP_JSON_INTERNAL); + lantern_log_error( + "http", + &(const struct lantern_log_metadata){.peer = peer_text}, + "POST %s -> 500 (driver_rc=%d rc=%d)", + path, + driver_rc, + rc); + return; + } + + int rc = send_http_response( + client_fd, + 200, + "OK", + "application/json", + response_body, + response_body_len); + free(response_body); + lantern_log_info( + "http", + &(const struct lantern_log_metadata){.peer = peer_text}, + "POST %s -> 200 (rc=%d)", + path, + rc); +} + /** * Handle a single client connection. @@ -816,6 +1111,18 @@ static void handle_client_connection( return; } + if (is_test_driver_path(path)) + { + handle_test_driver( + client_fd, + method, + path, + buffer, + (size_t)received, + peer_text); + return; + } + if (strcmp(method, "GET") != 0) { int rc = send_json_error(client_fd, 404, "Not Found", LANTERN_HTTP_JSON_UNKNOWN_ENDPOINT); diff --git a/src/networking/enr.c b/src/networking/enr.c index 322a8c2..20129a2 100644 --- a/src/networking/enr.c +++ b/src/networking/enr.c @@ -3,8 +3,6 @@ #include "lantern/encoding/rlp.h" #include "lantern/support/log.h" #include "lantern/support/strings.h" -#include "multiformats/multibase/encoding/base64_url.h" -#include "tomcrypt.h" #include #include @@ -24,6 +22,181 @@ #define LANTERN_ENR_SIGNATURE_SIZE 64u #define LANTERN_ENR_MAX_SIZE 300u +static const char LANTERN_BASE64URL_ALPHABET[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + +static uint64_t load_u64_le(const uint8_t in[8]) { + uint64_t value = 0; + for (size_t i = 0; i < 8; i++) { + value |= ((uint64_t)in[i]) << (8u * i); + } + return value; +} + +static void store_u64_le(uint64_t value, uint8_t out[8]) { + for (size_t i = 0; i < 8; i++) { + out[i] = (uint8_t)((value >> (8u * i)) & 0xffu); + } +} + +static uint64_t rotl64(uint64_t value, unsigned shift) { + return (value << shift) | (value >> (64u - shift)); +} + +static void keccakf1600(uint64_t state[25]) { + static const uint64_t round_constants[24] = { + UINT64_C(0x0000000000000001), UINT64_C(0x0000000000008082), + UINT64_C(0x800000000000808a), UINT64_C(0x8000000080008000), + UINT64_C(0x000000000000808b), UINT64_C(0x0000000080000001), + UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008009), + UINT64_C(0x000000000000008a), UINT64_C(0x0000000000000088), + UINT64_C(0x0000000080008009), UINT64_C(0x000000008000000a), + UINT64_C(0x000000008000808b), UINT64_C(0x800000000000008b), + UINT64_C(0x8000000000008089), UINT64_C(0x8000000000008003), + UINT64_C(0x8000000000008002), UINT64_C(0x8000000000000080), + UINT64_C(0x000000000000800a), UINT64_C(0x800000008000000a), + UINT64_C(0x8000000080008081), UINT64_C(0x8000000000008080), + UINT64_C(0x0000000080000001), UINT64_C(0x8000000080008008), + }; + static const unsigned rotation_constants[24] = { + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44, + }; + static const unsigned pi_lanes[24] = { + 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1, + }; + + for (size_t round = 0; round < 24; round++) { + uint64_t column[5]; + for (size_t x = 0; x < 5; x++) { + column[x] = state[x] ^ state[x + 5] ^ state[x + 10] ^ state[x + 15] ^ state[x + 20]; + } + for (size_t x = 0; x < 5; x++) { + uint64_t t = column[(x + 4) % 5] ^ rotl64(column[(x + 1) % 5], 1); + for (size_t y = 0; y < 25; y += 5) { + state[y + x] ^= t; + } + } + + uint64_t t = state[1]; + for (size_t i = 0; i < 24; i++) { + unsigned lane = pi_lanes[i]; + uint64_t saved = state[lane]; + state[lane] = rotl64(t, rotation_constants[i]); + t = saved; + } + + for (size_t y = 0; y < 25; y += 5) { + for (size_t x = 0; x < 5; x++) { + column[x] = state[y + x]; + } + for (size_t x = 0; x < 5; x++) { + state[y + x] ^= (~column[(x + 1) % 5]) & column[(x + 2) % 5]; + } + } + + state[0] ^= round_constants[round]; + } +} + +static int keccak256_bytes(const uint8_t *data, size_t data_len, uint8_t out_hash[32]) { + enum { KECCAK256_RATE = 136 }; + if ((!data && data_len > 0u) || !out_hash) { + return -1; + } + + uint64_t state[25] = {0}; + while (data_len >= KECCAK256_RATE) { + for (size_t i = 0; i < KECCAK256_RATE / 8u; i++) { + state[i] ^= load_u64_le(data + (i * 8u)); + } + keccakf1600(state); + data += KECCAK256_RATE; + data_len -= KECCAK256_RATE; + } + + uint8_t block[KECCAK256_RATE]; + memset(block, 0, sizeof(block)); + if (data_len > 0u) { + memcpy(block, data, data_len); + } + block[data_len] ^= 0x01u; + block[KECCAK256_RATE - 1u] ^= 0x80u; + for (size_t i = 0; i < KECCAK256_RATE / 8u; i++) { + state[i] ^= load_u64_le(block + (i * 8u)); + } + keccakf1600(state); + + for (size_t i = 0; i < 4; i++) { + store_u64_le(state[i], out_hash + (i * 8u)); + } + return 0; +} + +static int base64url_value(char ch) { + if (ch >= 'A' && ch <= 'Z') { + return ch - 'A'; + } + if (ch >= 'a' && ch <= 'z') { + return 26 + (ch - 'a'); + } + if (ch >= '0' && ch <= '9') { + return 52 + (ch - '0'); + } + if (ch == '-') { + return 62; + } + if (ch == '_') { + return 63; + } + return -1; +} + +static int base64url_encode( + const uint8_t *input, + size_t input_len, + char *out, + size_t out_len, + size_t *written) { + if ((!input && input_len > 0u) || !out || !written) { + return -1; + } + size_t full_groups = input_len / 3u; + size_t remainder = input_len % 3u; + size_t required = (full_groups * 4u) + (remainder == 0u ? 0u : remainder + 1u); + if (out_len <= required) { + return -1; + } + + size_t in_pos = 0; + size_t out_pos = 0; + for (size_t i = 0; i < full_groups; i++) { + uint32_t value = + ((uint32_t)input[in_pos] << 16u) | + ((uint32_t)input[in_pos + 1u] << 8u) | + (uint32_t)input[in_pos + 2u]; + in_pos += 3u; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 18u) & 0x3fu]; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 12u) & 0x3fu]; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 6u) & 0x3fu]; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[value & 0x3fu]; + } + if (remainder == 1u) { + uint32_t value = (uint32_t)input[in_pos] << 16u; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 18u) & 0x3fu]; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 12u) & 0x3fu]; + } else if (remainder == 2u) { + uint32_t value = ((uint32_t)input[in_pos] << 16u) | ((uint32_t)input[in_pos + 1u] << 8u); + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 18u) & 0x3fu]; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 12u) & 0x3fu]; + out[out_pos++] = LANTERN_BASE64URL_ALPHABET[(value >> 6u) & 0x3fu]; + } + out[out_pos] = '\0'; + *written = out_pos; + return 0; +} + static void lantern_enr_key_value_reset(struct lantern_enr_key_value *pair) { if (!pair) { return; @@ -131,19 +304,58 @@ static int lantern_base64url_decode(const char *input, uint8_t **out_bytes, size return -1; } + if ((input_len % 4u) == 1u) { + return -1; + } + uint8_t *decoded = malloc(input_len); if (!decoded) { return -1; } - int written = multibase_base64_url_decode(input, input_len, decoded, input_len); - if (written < 0) { - free(decoded); - return -1; + size_t out_pos = 0; + size_t in_pos = 0; + while (input_len - in_pos >= 4u) { + int a = base64url_value(input[in_pos++]); + int b = base64url_value(input[in_pos++]); + int c = base64url_value(input[in_pos++]); + int d = base64url_value(input[in_pos++]); + if (a < 0 || b < 0 || c < 0 || d < 0) { + free(decoded); + return -1; + } + uint32_t value = + ((uint32_t)a << 18u) | ((uint32_t)b << 12u) | ((uint32_t)c << 6u) | (uint32_t)d; + decoded[out_pos++] = (uint8_t)((value >> 16u) & 0xffu); + decoded[out_pos++] = (uint8_t)((value >> 8u) & 0xffu); + decoded[out_pos++] = (uint8_t)(value & 0xffu); + } + + size_t rem = input_len - in_pos; + if (rem == 2u) { + int a = base64url_value(input[in_pos]); + int b = base64url_value(input[in_pos + 1u]); + if (a < 0 || b < 0) { + free(decoded); + return -1; + } + uint32_t value = ((uint32_t)a << 18u) | ((uint32_t)b << 12u); + decoded[out_pos++] = (uint8_t)((value >> 16u) & 0xffu); + } else if (rem == 3u) { + int a = base64url_value(input[in_pos]); + int b = base64url_value(input[in_pos + 1u]); + int c = base64url_value(input[in_pos + 2u]); + if (a < 0 || b < 0 || c < 0) { + free(decoded); + return -1; + } + uint32_t value = ((uint32_t)a << 18u) | ((uint32_t)b << 12u) | ((uint32_t)c << 6u); + decoded[out_pos++] = (uint8_t)((value >> 16u) & 0xffu); + decoded[out_pos++] = (uint8_t)((value >> 8u) & 0xffu); } *out_bytes = decoded; - *out_len = (size_t)written; + *out_len = out_pos; return 0; } @@ -354,22 +566,6 @@ static int parse_port_value(const struct lantern_enr_key_value *pair, uint16_t * return 0; } -static int keccak256_bytes(const uint8_t *data, size_t data_len, uint8_t out_hash[32]) { - if ((!data && data_len > 0u) || !out_hash) { - return -1; - } - - const struct ltc_hash_descriptor *keccak_desc = &keccak_256_desc; - hash_state keccak_state; - if (keccak_desc->init(&keccak_state) != CRYPT_OK) { - return -1; - } - if (data_len > 0u && keccak_desc->process(&keccak_state, data, (unsigned long)data_len) != CRYPT_OK) { - return -1; - } - return keccak_desc->done(&keccak_state, out_hash) == CRYPT_OK ? 0 : -1; -} - static int encode_record_content( const struct lantern_enr_record *record, struct lantern_rlp_buffer *out_content) { @@ -781,21 +977,8 @@ int lantern_enr_record_build_v4( } uint8_t message_hash[32]; - const struct ltc_hash_descriptor *keccak_desc = &keccak_256_desc; - hash_state keccak_state; - int hash_rc = keccak_desc->init(&keccak_state); - if (hash_rc != CRYPT_OK) { - lantern_log_error("enr", NULL, "keccak init rc=%d", hash_rc); - error_reason = "keccak init failed"; - goto error; - } - if (content.length > 0 - && keccak_desc->process(&keccak_state, content.data, (unsigned long)content.length) != CRYPT_OK) { - error_reason = "keccak absorb failed"; - goto error; - } - if (keccak_desc->done(&keccak_state, message_hash) != CRYPT_OK) { - error_reason = "keccak finalize failed"; + if (keccak256_bytes(content.data, content.length, message_hash) != 0) { + error_reason = "keccak failed"; goto error; } @@ -839,19 +1022,19 @@ int lantern_enr_record_build_v4( error_reason = "payload alloc failed"; goto error; } - int written = multibase_base64_url_encode( + size_t written = 0; + if (base64url_encode( signed_record.data, signed_record.length, payload, - encoded_capacity); - if (written < 0) { + encoded_capacity, + &written) != 0) { free(payload); error_reason = "base64url encode failed"; goto error; } - payload[written] = '\0'; - size_t enr_len = (size_t)written + 5; + size_t enr_len = written + 5; char *enr_text = malloc(enr_len); if (!enr_text) { free(payload); @@ -859,7 +1042,7 @@ int lantern_enr_record_build_v4( goto error; } memcpy(enr_text, "enr:", 4); - memcpy(enr_text + 4, payload, (size_t)written + 1); + memcpy(enr_text + 4, payload, written + 1u); free(payload); if (lantern_enr_record_decode(enr_text, record) != 0) { diff --git a/src/networking/gossip.c b/src/networking/gossip.c index 2afd3cf..134567d 100644 --- a/src/networking/gossip.c +++ b/src/networking/gossip.c @@ -5,7 +5,8 @@ #include #include -#include "WjCryptLib_Sha256.h" +#include + #include "lantern/encoding/snappy.h" const uint8_t LANTERN_GOSSIP_DOMAIN_INVALID[LANTERN_GOSSIP_DOMAIN_SIZE] = {0x00, 0x00, 0x00, 0x00}; @@ -264,19 +265,29 @@ int lantern_gossip_compute_message_id( } } - Sha256Context ctx; - Sha256Initialise(&ctx); - Sha256Update(&ctx, domain, LANTERN_GOSSIP_DOMAIN_SIZE); + SHA256_CTX ctx; + if (SHA256_Init(&ctx) != 1) { + return -1; + } + if (SHA256_Update(&ctx, domain, LANTERN_GOSSIP_DOMAIN_SIZE) != 1) { + return -1; + } uint8_t topic_len_encoded[8]; write_u64_le((uint64_t)topic_len, topic_len_encoded); - Sha256Update(&ctx, topic_len_encoded, sizeof(topic_len_encoded)); - Sha256Update(&ctx, topic, topic_len); - if (data_len > 0 && data_for_hash) { - Sha256Update(&ctx, data_for_hash, data_len); - } - SHA256_HASH digest; - Sha256Finalise(&ctx, &digest); - memcpy(message_id->bytes, digest.bytes, LANTERN_GOSSIP_MESSAGE_ID_SIZE); + if (SHA256_Update(&ctx, topic_len_encoded, sizeof(topic_len_encoded)) != 1) { + return -1; + } + if (topic_len > 0 && SHA256_Update(&ctx, topic, topic_len) != 1) { + return -1; + } + if (data_len > 0 && data_for_hash && SHA256_Update(&ctx, data_for_hash, data_len) != 1) { + return -1; + } + uint8_t digest[SHA256_DIGEST_LENGTH]; + if (SHA256_Final(digest, &ctx) != 1) { + return -1; + } + memcpy(message_id->bytes, digest, LANTERN_GOSSIP_MESSAGE_ID_SIZE); return 0; } diff --git a/src/networking/gossip_payloads.c b/src/networking/gossip_payloads.c index c341a85..f223f0e 100644 --- a/src/networking/gossip_payloads.c +++ b/src/networking/gossip_payloads.c @@ -8,7 +8,7 @@ #include "lantern/consensus/ssz.h" #include "lantern/encoding/snappy.h" #include "lantern/support/log.h" -#include "ssz_constants.h" +#include "ssz.h" static uint8_t *alloc_buffer(size_t size) { if (size == 0) { @@ -36,7 +36,7 @@ static size_t aggregated_attestation_encoded_size(const LanternAggregatedAttesta return 0; } size_t bits_size = bitlist_encoded_size_bits(attestation->aggregation_bits.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE; + size_t fixed_section = SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_ATTESTATION_DATA_SSZ_SIZE; if (fixed_section > SIZE_MAX - bits_size) { return 0; } @@ -53,7 +53,7 @@ static size_t aggregated_attestations_encoded_size(const LanternAggregatedAttest if (attestations->length > LANTERN_MAX_ATTESTATIONS || !attestations->data) { return 0; } - size_t offset_table = attestations->length * SSZ_BYTE_SIZE_OF_UINT32; + size_t offset_table = attestations->length * SSZ_BYTES_PER_LENGTH_OFFSET; size_t total = offset_table; for (size_t i = 0; i < attestations->length; ++i) { size_t entry = aggregated_attestation_encoded_size(&attestations->data[i]); @@ -76,7 +76,7 @@ static size_t aggregated_signature_proof_encoded_size(const LanternAggregatedSig return 0; } size_t participants_size = bitlist_encoded_size_bits(proof->participants.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; + size_t fixed_section = SSZ_BYTES_PER_LENGTH_OFFSET * 2u; if (fixed_section > SIZE_MAX - participants_size) { return 0; } @@ -96,7 +96,7 @@ static size_t attestation_signatures_encoded_size(const LanternAttestationSignat if (signatures->length > LANTERN_MAX_BLOCK_SIGNATURES || !signatures->data) { return 0; } - size_t offset_table = signatures->length * SSZ_BYTE_SIZE_OF_UINT32; + size_t offset_table = signatures->length * SSZ_BYTES_PER_LENGTH_OFFSET; size_t total = offset_table; for (size_t i = 0; i < signatures->length; ++i) { size_t entry = aggregated_signature_proof_encoded_size(&signatures->data[i]); @@ -109,27 +109,27 @@ static size_t attestation_signatures_encoded_size(const LanternAttestationSignat } static size_t signed_block_base_ssz_size(void) { - size_t block_fixed = (SSZ_BYTE_SIZE_OF_UINT64 * 2u) + size_t block_fixed = (sizeof(uint64_t) * 2u) + (LANTERN_ROOT_SIZE * 2u) - + SSZ_BYTE_SIZE_OF_UINT32; - size_t body_header = SSZ_BYTE_SIZE_OF_UINT32; /* block body attestation offset */ + + SSZ_BYTES_PER_LENGTH_OFFSET; + size_t body_header = SSZ_BYTES_PER_LENGTH_OFFSET; /* block body attestation offset */ size_t message_base = block_fixed + body_header; - size_t offsets = SSZ_BYTE_SIZE_OF_UINT32 * 2u; /* message + signatures */ + size_t offsets = SSZ_BYTES_PER_LENGTH_OFFSET * 2u; /* message + signatures */ return offsets + message_base; } static size_t signed_block_max_ssz_size(void) { size_t base = signed_block_base_ssz_size(); size_t att_bits_max = bitlist_encoded_size_bits(LANTERN_VALIDATOR_REGISTRY_LIMIT); - size_t att_entry_max = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE + att_bits_max; - size_t attestations_max = (size_t)LANTERN_MAX_ATTESTATIONS * (SSZ_BYTE_SIZE_OF_UINT32 + att_entry_max); + size_t att_entry_max = SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_ATTESTATION_DATA_SSZ_SIZE + att_bits_max; + size_t attestations_max = (size_t)LANTERN_MAX_ATTESTATIONS * (SSZ_BYTES_PER_LENGTH_OFFSET + att_entry_max); if (attestations_max > SIZE_MAX - base) { return SIZE_MAX; } size_t total = base + attestations_max; - size_t proof_entry_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + att_bits_max + LANTERN_AGG_PROOF_MAX_BYTES; - size_t signatures_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + LANTERN_SIGNATURE_SIZE - + ((size_t)LANTERN_MAX_BLOCK_SIGNATURES * (SSZ_BYTE_SIZE_OF_UINT32 + proof_entry_max)); + size_t proof_entry_max = (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + att_bits_max + LANTERN_AGG_PROOF_MAX_BYTES; + size_t signatures_max = (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + LANTERN_SIGNATURE_SIZE + + ((size_t)LANTERN_MAX_BLOCK_SIGNATURES * (SSZ_BYTES_PER_LENGTH_OFFSET + proof_entry_max)); if (signatures_max > SIZE_MAX - total) { return SIZE_MAX; } @@ -156,7 +156,7 @@ static size_t signed_block_min_capacity(const LanternSignedBlock *block) { if (sig_count > 0 && sig_list_bytes == 0) { return 0; } - size_t signature_bytes = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + LANTERN_SIGNATURE_SIZE + sig_list_bytes; + size_t signature_bytes = (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + LANTERN_SIGNATURE_SIZE + sig_list_bytes; if (signature_bytes > SIZE_MAX - total) { return 0; } @@ -227,8 +227,8 @@ static int basic_signed_aggregated_attestation_sanity( static size_t signed_aggregated_attestation_max_ssz_size(void) { size_t bits_max = bitlist_encoded_size_bits(LANTERN_VALIDATOR_REGISTRY_LIMIT); - size_t proof_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + bits_max + LANTERN_AGG_PROOF_MAX_BYTES; - size_t fixed = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT32; + size_t proof_max = (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + bits_max + LANTERN_AGG_PROOF_MAX_BYTES; + size_t fixed = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTES_PER_LENGTH_OFFSET; if (proof_max > SIZE_MAX - fixed) { return SIZE_MAX; } @@ -252,7 +252,7 @@ int lantern_gossip_encode_signed_block_snappy( return -1; } size_t raw_written = raw_capacity; - if (lantern_ssz_encode_signed_block_canonical(block, raw, raw_capacity, &raw_written) != 0) { + if (lantern_ssz_encode_signed_block(block, raw, raw_capacity, &raw_written) != SSZ_SUCCESS) { free(raw); return -1; } @@ -325,7 +325,7 @@ int lantern_gossip_decode_signed_block_snappy( free(raw); return -1; } - int decode_rc = lantern_ssz_decode_signed_block_strict(block, raw, written); + ssz_error_t decode_rc = lantern_ssz_decode_signed_block(block, raw, written); if (decode_rc != 0) { free(raw); return -1; @@ -356,7 +356,7 @@ int lantern_gossip_encode_signed_vote_snappy( } uint8_t raw[LANTERN_SIGNED_VOTE_SSZ_SIZE]; size_t raw_written = sizeof(raw); - if (lantern_ssz_encode_signed_vote(vote, raw, sizeof(raw), &raw_written) != 0) { + if (lantern_ssz_encode_signed_vote(vote, raw, sizeof(raw), &raw_written) != SSZ_SUCCESS) { return -1; } /* Use raw snappy (no framing) for gossip messages per Eth2 networking spec */ @@ -418,7 +418,7 @@ int lantern_gossip_decode_signed_vote_snappy( free(raw); return -1; } - if (lantern_ssz_decode_signed_vote(vote, raw, raw_len) != 0) { + if (lantern_ssz_decode_signed_vote(vote, raw, raw_len) != SSZ_SUCCESS) { free(raw); return -1; } @@ -444,13 +444,13 @@ int lantern_gossip_encode_signed_aggregated_attestation_snappy( if (proof_size == 0) { return -1; } - size_t raw_capacity = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT32 + proof_size; + size_t raw_capacity = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTES_PER_LENGTH_OFFSET + proof_size; uint8_t *raw = alloc_buffer(raw_capacity); if (!raw) { return -1; } size_t raw_written = raw_capacity; - if (lantern_ssz_encode_signed_aggregated_attestation(attestation, raw, raw_capacity, &raw_written) != 0) { + if (lantern_ssz_encode_signed_aggregated_attestation(attestation, raw, raw_capacity, &raw_written) != SSZ_SUCCESS) { free(raw); return -1; } @@ -472,7 +472,7 @@ int lantern_gossip_decode_signed_aggregated_attestation_snappy( return -1; } size_t max_ssz = signed_aggregated_attestation_max_ssz_size(); - size_t min_ssz = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT32 + 1u; + size_t min_ssz = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTES_PER_LENGTH_OFFSET + 1u; if (raw_len < min_ssz || raw_len > max_ssz) { return -1; } @@ -486,7 +486,7 @@ int lantern_gossip_decode_signed_aggregated_attestation_snappy( free(raw); return -1; } - if (lantern_ssz_decode_signed_aggregated_attestation(attestation, raw, raw_len) != 0) { + if (lantern_ssz_decode_signed_aggregated_attestation(attestation, raw, raw_len) != SSZ_SUCCESS) { free(raw); return -1; } diff --git a/src/networking/gossipsub_service.c b/src/networking/gossipsub_service.c index ffc0aef..39c209e 100644 --- a/src/networking/gossipsub_service.c +++ b/src/networking/gossipsub_service.c @@ -1,1255 +1,731 @@ -#ifndef _DARWIN_C_SOURCE -#define _DARWIN_C_SOURCE -#endif - #include "lantern/networking/gossipsub_service.h" -#include -#include -#include +#include #include #include -#include -#include +#include -#include "lantern/consensus/ssz.h" #include "lantern/encoding/snappy.h" -#include "lantern/metrics/lean_metrics.h" #include "lantern/networking/gossip.h" #include "lantern/networking/gossip_payloads.h" -#include "lantern/storage/storage.h" #include "lantern/support/log.h" -#include "lantern/support/strings.h" -#include "ssz_constants.h" - -#include "libp2p/errors.h" -#include "libp2p/host.h" -#include "protocol/gossipsub/gossipsub.h" -#include "src/protocol/gossipsub/core/gossipsub_internal.h" -#include "src/protocol/gossipsub/core/gossipsub_peer.h" - -#define LANTERN_GOSSIPSUB_TOPIC_CAP 128u -#define LANTERN_ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) -#define LANTERN_GOSSIPSUB_HEARTBEAT_INTERVAL_MS 700u -#define LANTERN_GOSSIPSUB_FANOUT_TTL_MS 60000u -#define LANTERN_GOSSIPSUB_MESH_D 8 -#define LANTERN_GOSSIPSUB_MESH_D_LOW 6 -#define LANTERN_GOSSIPSUB_MESH_D_HIGH 12 -#define LANTERN_GOSSIPSUB_MESH_D_LAZY 6 -#define LANTERN_GOSSIPSUB_MESSAGE_CACHE_LEN 6u -#define LANTERN_GOSSIPSUB_MESSAGE_CACHE_GOSSIP 3u -#define LANTERN_LEANSPEC_SECONDS_PER_SLOT 4u -#define LANTERN_LEANSPEC_JUSTIFICATION_LOOKBACK 3u -#define LANTERN_LEANSPEC_SEEN_TTL_FACTOR 2u -#define LANTERN_GOSSIPSUB_VALIDATION_WORKER_FALLBACK 4u -#define LANTERN_GOSSIPSUB_VALIDATION_WORKER_MAX 64u -#define LANTERN_GOSSIPSUB_VALIDATION_QUEUE_CAPACITY 128u - -static const char *const k_leanspec_gossipsub_protocols[] = { - "/meshsub/1.1.0", - "/meshsub/1.0.0", -}; - -enum lantern_gossipsub_validation_job_kind { - LANTERN_GOSSIPSUB_JOB_BLOCK = 0, - LANTERN_GOSSIPSUB_JOB_VOTE, - LANTERN_GOSSIPSUB_JOB_AGGREGATED_ATTESTATION, - LANTERN_GOSSIPSUB_JOB_UNKNOWN, -}; - -struct lantern_gossipsub_validation_job { - enum lantern_gossipsub_validation_job_kind kind; - char *topic; - uint8_t *payload; - size_t payload_len; - peer_id_t *propagation_source; - uint8_t *message_id; - size_t message_id_len; -}; - -struct lantern_gossipsub_validation_pool { - pthread_mutex_t mutex; - pthread_cond_t not_empty; - pthread_t *threads; - struct lantern_gossipsub_validation_job **queue; - size_t worker_count; - size_t queue_capacity; - size_t queue_head; - size_t queue_len; - int stopping; - int started; - struct lantern_gossipsub_service *service; -}; - -static void lantern_gossipsub_validation_job_free(struct lantern_gossipsub_validation_job *job) { - if (!job) { + +#define LANTERN_GOSSIP_ENCODE_BUFFER_BYTES (16u * 1024u * 1024u) +#define LANTERN_GOSSIP_MAX_MESSAGE_BYTES (10u * 1024u * 1024u) +#define LANTERN_GOSSIP_MAX_RPC_BYTES (LANTERN_GOSSIP_MAX_MESSAGE_BYTES + (64u * 1024u)) +#define LANTERN_GOSSIP_TX_BUFFER_BYTES (4u * LANTERN_GOSSIP_MAX_RPC_BYTES) +#define LANTERN_GOSSIP_MCACHE_BYTES (4u * LANTERN_GOSSIP_MAX_MESSAGE_BYTES) + +static int topic_eq(const libp2p_gossipsub_bytes_t topic, const char *expected) { + return expected && strlen(expected) == topic.len && memcmp(topic.data, expected, topic.len) == 0; +} + +static void peer_id_to_text_safe(const struct lantern_peer_id *peer, char *out, size_t out_len) { + if (!out || out_len == 0) { return; } - free(job->topic); - free(job->payload); - if (job->propagation_source) { - peer_id_free(job->propagation_source); + out[0] = '\0'; + if (!peer || peer->len == 0) { + return; + } + if (lantern_peer_id_to_text(peer, out, out_len) != 0) { + out[0] = '\0'; } - free(job->message_id); - free(job); } -static enum lantern_gossipsub_validation_job_kind lantern_gossipsub_classify_topic( - const struct lantern_gossipsub_service *service, - const char *topic); -static void lantern_log_invalid_gossip_topic( - const struct lantern_gossipsub_service *service, - const char *topic, - const char *reason); -static libp2p_gossipsub_validation_result_t lantern_validate_block_payload( - struct lantern_gossipsub_service *service, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source); -static libp2p_gossipsub_validation_result_t lantern_validate_vote_payload( - struct lantern_gossipsub_service *service, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source); -static libp2p_gossipsub_validation_result_t lantern_validate_aggregated_attestation_payload( - struct lantern_gossipsub_service *service, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source); -static int lantern_gossipsub_validation_pool_start(struct lantern_gossipsub_service *service); -static void lantern_gossipsub_validation_pool_stop(struct lantern_gossipsub_service *service); -static int lantern_gossipsub_validation_pool_try_enqueue( - struct lantern_gossipsub_service *service, - struct lantern_gossipsub_validation_job *job); -static void lantern_gossipsub_message_delivery_cb( - libp2p_gossipsub_t *gs, - const libp2p_gossipsub_message_t *msg, - const uint8_t *message_id, - size_t message_id_len, - const peer_id_t *propagation_source, - void *user_data); - -static bool lantern_gossipsub_mesh_peer_seen( - const peer_id_t *const *peers, - size_t peer_count, - const peer_id_t *candidate) { - if (!candidate) { - return true; - } - for (size_t i = 0; i < peer_count; ++i) { - if (gossipsub_peer_equals(peers[i], candidate)) { - return true; - } +static int peer_from_conn(libp2p_host_conn_t *conn, struct lantern_peer_id *out_peer) { + if (!conn || !out_peer) { + return -1; } - return false; + memset(out_peer, 0, sizeof(*out_peer)); + size_t written = 0; + if (libp2p_host_conn_peer_id(conn, out_peer->bytes, sizeof(out_peer->bytes), &written) != LIBP2P_HOST_OK || + written == 0 || written > sizeof(out_peer->bytes)) { + memset(out_peer, 0, sizeof(*out_peer)); + return -1; + } + out_peer->len = written; + return 0; } -static int lantern_gossipsub_mesh_peer_track( - const peer_id_t ***peers, - size_t *peer_count, - size_t *peer_cap, - const peer_id_t *candidate) { - if (!peers || !peer_count || !peer_cap || !candidate) { - return 0; +static int peer_from_gossipsub_event(const libp2p_gossipsub_event_t *event, struct lantern_peer_id *out_peer) { + if (!event || !out_peer || event->peer.len == 0 || event->peer.len > sizeof(out_peer->bytes)) { + return -1; } - if (lantern_gossipsub_mesh_peer_seen(*peers, *peer_count, candidate)) { - return 0; + memset(out_peer, 0, sizeof(*out_peer)); + memcpy(out_peer->bytes, event->peer.data, event->peer.len); + out_peer->len = event->peer.len; + return 0; +} + +static int peer_id_cmp_bytes(const uint8_t *left, size_t left_len, const uint8_t *right, size_t right_len) { + size_t min_len = left_len < right_len ? left_len : right_len; + int cmp = min_len > 0 ? memcmp(left, right, min_len) : 0; + if (cmp != 0) { + return cmp; } - if (*peer_count == *peer_cap) { - size_t next_cap = *peer_cap == 0u ? 8u : *peer_cap * 2u; - if (next_cap < *peer_cap || next_cap > SIZE_MAX / sizeof(**peers)) { - return -1; - } - const peer_id_t **grown = realloc(*peers, next_cap * sizeof(**peers)); - if (!grown) { - return -1; - } - *peers = grown; - *peer_cap = next_cap; + if (left_len < right_len) { + return -1; + } + if (left_len > right_len) { + return 1; } - (*peers)[(*peer_count)++] = candidate; return 0; } -size_t lantern_gossipsub_service_mesh_peer_count(const struct lantern_gossipsub_service *service) { - if (!service || !service->gossipsub) { - return 0u; +static int service_prefers_inbound_conn( + const struct lantern_gossipsub_service *service, + const struct lantern_peer_id *peer) { + if (!service || !service->network || !peer || service->network->local_peer_id_len == 0) { + return 0; } + return peer_id_cmp_bytes( + service->network->local_peer_id, + service->network->local_peer_id_len, + peer->bytes, + peer->len) > 0; +} - libp2p_gossipsub_t *gs = service->gossipsub; - if (pthread_mutex_lock(&gs->lock) != 0) { - return 0u; +static struct lantern_gossipsub_peer_connection_state *find_peer_state( + struct lantern_gossipsub_service *service, + const struct lantern_peer_id *peer, + int create) { + if (!service || !peer || peer->len == 0) { + return NULL; } + for (size_t i = 0; i < LANTERN_GOSSIPSUB_MAX_TRACKED_PEERS; i++) { + struct lantern_gossipsub_peer_connection_state *state = &service->peer_connections[i]; + if (state->used && lantern_peer_id_equal(&state->peer, peer)) { + return state; + } + } + if (!create) { + return NULL; + } + for (size_t i = 0; i < LANTERN_GOSSIPSUB_MAX_TRACKED_PEERS; i++) { + struct lantern_gossipsub_peer_connection_state *state = &service->peer_connections[i]; + if (!state->used) { + memset(state, 0, sizeof(*state)); + state->used = 1; + state->peer = *peer; + state->retry_backoff_us = LANTERN_GOSSIPSUB_RETRY_INITIAL_US; + return state; + } + } + return NULL; +} - size_t peer_count = 0u; - size_t peer_cap = 0u; - const peer_id_t **peers = NULL; - for (gossipsub_topic_state_t *topic = gs->topics; topic; topic = topic->next) { - if (!topic->subscribed || !topic->name) { +static struct lantern_gossipsub_peer_connection_state *find_peer_state_by_conn( + struct lantern_gossipsub_service *service, + libp2p_host_conn_t *conn) { + if (!service || !conn) { + return NULL; + } + for (size_t i = 0; i < LANTERN_GOSSIPSUB_MAX_TRACKED_PEERS; i++) { + struct lantern_gossipsub_peer_connection_state *state = &service->peer_connections[i]; + if (!state->used) { continue; } - /* - * c-libp2p keeps explicit/flood-publish peers outside topic->mesh, even - * though they are the active gossip dissemination peers in the devnet. - */ - for (gossipsub_mesh_member_t *member = topic->mesh; member; member = member->next) { - if (member->peer_entry && !member->peer_entry->connected) { - continue; - } - if (lantern_gossipsub_mesh_peer_track(&peers, &peer_count, &peer_cap, member->peer) != 0) { - free(peers); - pthread_mutex_unlock(&gs->lock); - return 0u; - } - } - for (gossipsub_fanout_peer_t *fanout = topic->fanout; fanout; fanout = fanout->next) { - if (fanout->peer_entry && !fanout->peer_entry->connected) { - continue; - } - if (lantern_gossipsub_mesh_peer_track(&peers, &peer_count, &peer_cap, fanout->peer) != 0) { - free(peers); - pthread_mutex_unlock(&gs->lock); - return 0u; + for (size_t j = 0; j < state->conn_count; j++) { + if (state->conns[j] == conn) { + return state; } } - for (gossipsub_peer_entry_t *entry = gs->peers; entry; entry = entry->next) { - if (!entry->connected || !entry->peer) { - continue; - } - if (!entry->explicit_peering && !gossipsub_peer_topic_find(entry->topics, topic->name)) { - continue; - } - if (lantern_gossipsub_mesh_peer_track(&peers, &peer_count, &peer_cap, entry->peer) != 0) { - free(peers); - pthread_mutex_unlock(&gs->lock); - return 0u; - } + if (state->writer_conn == conn || state->opening_conn == conn) { + return state; } } - - free(peers); - pthread_mutex_unlock(&gs->lock); - return peer_count; -} - -static uint32_t lantern_leanspec_seen_ttl_ms(void) { - const uint64_t ttl_seconds = (uint64_t)LANTERN_LEANSPEC_SECONDS_PER_SLOT - * (uint64_t)LANTERN_LEANSPEC_JUSTIFICATION_LOOKBACK - * (uint64_t)LANTERN_LEANSPEC_SEEN_TTL_FACTOR; - const uint64_t ttl_ms = ttl_seconds * 1000u; - return ttl_ms > UINT32_MAX ? UINT32_MAX : (uint32_t)ttl_ms; + return NULL; } -static size_t lantern_gossipsub_validation_worker_count(void) { - long processors = sysconf(_SC_NPROCESSORS_ONLN); - if (processors <= 0) { - return LANTERN_GOSSIPSUB_VALIDATION_WORKER_FALLBACK; +static ssize_t peer_state_conn_index( + const struct lantern_gossipsub_peer_connection_state *state, + libp2p_host_conn_t *conn) { + if (!state || !conn) { + return -1; } - if ((unsigned long)processors > LANTERN_GOSSIPSUB_VALIDATION_WORKER_MAX) { - return LANTERN_GOSSIPSUB_VALIDATION_WORKER_MAX; + for (size_t i = 0; i < state->conn_count; i++) { + if (state->conns[i] == conn) { + return (ssize_t)i; + } } - return (size_t)processors; + return -1; } -static size_t bitlist_encoded_size_bits(size_t bit_length) { - if (bit_length == 0) { - return 1; - } - size_t byte_len = (bit_length + 7u) / 8u; - if ((bit_length % 8u) == 0) { - return byte_len + 1u; +static void peer_state_select_primary( + struct lantern_gossipsub_service *service, + struct lantern_gossipsub_peer_connection_state *state) { + if (!service || !state || state->conn_count == 0) { + if (state) { + state->primary_conn = NULL; + state->primary_inbound = 0; + } + return; } - return byte_len; -} - -static size_t aggregated_attestation_encoded_size(const LanternAggregatedAttestation *attestation) { - if (!attestation) { - return 0; + const int prefer_inbound = service_prefers_inbound_conn(service, &state->peer); + size_t selected = LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER; + for (size_t i = 0; i < state->conn_count; i++) { + if (!state->conn_closing[i] && (state->conn_inbound[i] ? 1 : 0) == prefer_inbound) { + selected = i; + break; + } } - if (attestation->aggregation_bits.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return 0; + if (selected == LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER) { + for (size_t i = 0; i < state->conn_count; i++) { + if (!state->conn_closing[i]) { + selected = i; + break; + } + } } - size_t bits_size = bitlist_encoded_size_bits(attestation->aggregation_bits.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE; - if (fixed_section > SIZE_MAX - bits_size) { - return 0; + if (selected == LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER) { + state->primary_conn = NULL; + state->primary_inbound = 0; + return; } - return fixed_section + bits_size; + state->primary_conn = state->conns[selected]; + state->primary_inbound = state->conn_inbound[selected] ? 1 : 0; } -static size_t aggregated_attestations_encoded_size(const LanternAggregatedAttestations *attestations) { - if (!attestations) { - return 0; +static void schedule_writer_retry( + struct lantern_gossipsub_peer_connection_state *state, + libp2p_host_time_us_t now_us) { + if (!state) { + return; } - if (attestations->length == 0) { - return 0; + uint64_t delay = state->retry_backoff_us; + if (delay == 0) { + delay = LANTERN_GOSSIPSUB_RETRY_INITIAL_US; } - if (attestations->length > LANTERN_MAX_ATTESTATIONS || !attestations->data) { - return 0; + state->next_retry_us = now_us + delay; + delay *= 2; + if (delay > LANTERN_GOSSIPSUB_RETRY_MAX_US) { + delay = LANTERN_GOSSIPSUB_RETRY_MAX_US; } - size_t offset_table = attestations->length * SSZ_BYTE_SIZE_OF_UINT32; - size_t total = offset_table; - for (size_t i = 0; i < attestations->length; ++i) { - size_t entry = aggregated_attestation_encoded_size(&attestations->data[i]); - if (entry == 0 || entry > SIZE_MAX - total) { - return 0; - } - total += entry; - } - return total; + state->retry_backoff_us = delay; } -static size_t aggregated_signature_proof_encoded_size(const LanternAggregatedSignatureProof *proof) { - if (!proof) { - return 0; - } - if (proof->participants.bit_length > LANTERN_VALIDATOR_REGISTRY_LIMIT) { - return 0; - } - if (proof->proof_data.length > LANTERN_AGG_PROOF_MAX_BYTES) { - return 0; - } - size_t participants_size = bitlist_encoded_size_bits(proof->participants.bit_length); - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - if (fixed_section > SIZE_MAX - participants_size) { - return 0; - } - if (fixed_section + participants_size > SIZE_MAX - proof->proof_data.length) { - return 0; +static void reset_writer_retry(struct lantern_gossipsub_peer_connection_state *state) { + if (!state) { + return; } - return fixed_section + participants_size + proof->proof_data.length; + state->next_retry_us = 0; + state->retry_backoff_us = LANTERN_GOSSIPSUB_RETRY_INITIAL_US; } -static size_t attestation_signatures_encoded_size(const LanternAttestationSignatures *signatures) { - if (!signatures) { +static int open_primary_writer( + struct lantern_gossipsub_service *service, + struct lantern_gossipsub_peer_connection_state *state, + libp2p_host_time_us_t now_us) { + if (!service || !service->gossipsub || !service->network || !service->network->host || + !state || !state->primary_conn || state->writer_stream || state->opening_conn) { return 0; } - if (signatures->length == 0) { + if (state->next_retry_us != 0 && now_us < state->next_retry_us) { return 0; } - if (signatures->length > LANTERN_MAX_BLOCK_SIGNATURES || !signatures->data) { + + libp2p_host_stream_open_t *open = NULL; + libp2p_gossipsub_err_t err = libp2p_gossipsub_open_peer( + service->gossipsub, + service->network->host, + state->primary_conn, + LIBP2P_GOSSIPSUB_VERSION_NONE, + NULL, + &open); + if (err == LIBP2P_GOSSIPSUB_OK && open) { + char peer_text[128]; + peer_id_to_text_safe(&state->peer, peer_text, sizeof(peer_text)); + state->opening_conn = state->primary_conn; + lantern_log_debug( + "gossip", + &(const struct lantern_log_metadata){.peer = peer_text[0] ? peer_text : NULL}, + "opening gossipsub writer on primary connection inbound=%u", + (unsigned)state->primary_inbound); return 0; } - size_t offset_table = signatures->length * SSZ_BYTE_SIZE_OF_UINT32; - size_t total = offset_table; - for (size_t i = 0; i < signatures->length; ++i) { - size_t entry = aggregated_signature_proof_encoded_size(&signatures->data[i]); - if (entry == 0 || entry > SIZE_MAX - total) { - return 0; - } - total += entry; - } - return total; + + schedule_writer_retry(state, now_us); + return -1; } -static void describe_peer_id(const peer_id_t *peer, char *buffer, size_t length) { - if (!buffer || length == 0) { - return; - } - if (!peer) { - buffer[0] = '\0'; +static void maybe_repair_writer( + struct lantern_gossipsub_service *service, + struct lantern_gossipsub_peer_connection_state *state, + libp2p_host_time_us_t now_us) { + if (!service || !state || !state->used || state->conn_count == 0) { return; } - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - peer, - PEER_ID_TEXT_LEGACY_BASE58, - buffer, - length, - &written); - if (rc != PEER_ID_OK) { - buffer[0] = '\0'; - } + peer_state_select_primary(service, state); + (void)open_primary_writer(service, state, now_us); } -static void maybe_dump_invalid_gossip_payload( +static void add_peer_connection( struct lantern_gossipsub_service *service, - const char *payload_type, - const uint8_t *payload, - size_t payload_len, - const struct lantern_log_metadata *meta) { - if (!service || !service->data_dir || service->data_dir[0] == '\0' || !payload_type || !payload || payload_len == 0) { + libp2p_host_conn_t *conn, + int inbound, + libp2p_host_time_us_t now_us) { + struct lantern_peer_id peer; + if (peer_from_conn(conn, &peer) != 0) { return; } - - if (lantern_storage_store_invalid_gossip_payload( - service->data_dir, - payload_type, - payload, - payload_len) - != 0) { - lantern_log_warn( - "storage", - meta, - "failed to persist invalid gossip payload type=%s bytes=%zu", - payload_type, - payload_len); + struct lantern_gossipsub_peer_connection_state *state = find_peer_state(service, &peer, 1); + if (!state) { + (void)libp2p_host_conn_close(service->network->host, conn, 0); + return; } -} -static void lantern_gossipsub_score_update( - libp2p_gossipsub_t *gs, - const libp2p_gossipsub_score_update_t *update, - void *user_data) { - (void)gs; - struct lantern_gossipsub_service *service = (struct lantern_gossipsub_service *)user_data; - if (!update || !service) { + ssize_t existing = peer_state_conn_index(state, conn); + if (existing >= 0) { + state->conn_inbound[(size_t)existing] = inbound ? 1 : 0; + state->conn_closing[(size_t)existing] = 0; + } else if (state->conn_count < LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER) { + state->conns[state->conn_count] = conn; + state->conn_inbound[state->conn_count] = inbound ? 1 : 0; + state->conn_closing[state->conn_count] = 0; + state->conn_count++; + } else { + (void)libp2p_host_conn_close(service->network->host, conn, 0); return; } - char peer_text[128]; - describe_peer_id(update->peer, peer_text, sizeof(peer_text)); - const struct lantern_log_metadata meta = {.peer = peer_text[0] ? peer_text : NULL}; - lantern_log_debug( - "gossip", - &meta, - "gossipsub score peer=%s score=%.3f override=%d", - peer_text[0] ? peer_text : "unknown", - update->score, - update->score_override ? 1 : 0); + maybe_repair_writer(service, state, now_us); } -static size_t signed_block_min_capacity(const LanternSignedBlock *block) { - if (!block) { - return 0; +static void remove_peer_connection( + struct lantern_gossipsub_service *service, + libp2p_host_conn_t *conn, + libp2p_host_time_us_t now_us) { + struct lantern_gossipsub_peer_connection_state *state = find_peer_state_by_conn(service, conn); + if (!state) { + return; } - size_t offsets = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - size_t block_fixed = (SSZ_BYTE_SIZE_OF_UINT64 * 2u) - + (LANTERN_ROOT_SIZE * 2u) - + SSZ_BYTE_SIZE_OF_UINT32; - size_t body_header = SSZ_BYTE_SIZE_OF_UINT32; - size_t att_count = block->block.body.attestations.length; - size_t att_bytes = aggregated_attestations_encoded_size(&block->block.body.attestations); - if (att_count > 0 && att_bytes == 0) { - return 0; + ssize_t found = peer_state_conn_index(state, conn); + if (found >= 0) { + size_t index = (size_t)found; + for (size_t i = index + 1; i < state->conn_count; i++) { + state->conns[i - 1] = state->conns[i]; + state->conn_inbound[i - 1] = state->conn_inbound[i]; + state->conn_closing[i - 1] = state->conn_closing[i]; + } + state->conn_count--; + if (state->conn_count < LANTERN_GOSSIPSUB_MAX_CONNS_PER_PEER) { + state->conns[state->conn_count] = NULL; + state->conn_inbound[state->conn_count] = 0; + state->conn_closing[state->conn_count] = 0; + } } - size_t sig_count = block->signatures.attestation_signatures.length; - size_t sig_list_bytes = attestation_signatures_encoded_size(&block->signatures.attestation_signatures); - if (sig_count > 0 && sig_list_bytes == 0) { - return 0; + if (state->primary_conn == conn) { + state->primary_conn = NULL; + state->primary_inbound = 0; } - size_t signatures_bytes = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + LANTERN_SIGNATURE_SIZE + sig_list_bytes; - size_t total = offsets + block_fixed; - if (body_header > SIZE_MAX - total) { - return 0; + if (state->writer_conn == conn) { + state->writer_conn = NULL; + state->writer_stream = NULL; } - total += body_header; - if (att_bytes > SIZE_MAX - total) { - return 0; + if (state->opening_conn == conn) { + state->opening_conn = NULL; + schedule_writer_retry(state, now_us); } - total += att_bytes; - if (signatures_bytes > SIZE_MAX - total) { - return 0; + if (state->conn_count == 0) { + memset(state, 0, sizeof(*state)); + return; } - total += signatures_bytes; - return total; + maybe_repair_writer(service, state, now_us); } -static size_t signed_block_max_ssz_size(void) { - size_t offsets = SSZ_BYTE_SIZE_OF_UINT32 * 2u; - size_t block_fixed = (SSZ_BYTE_SIZE_OF_UINT64 * 2u) - + (LANTERN_ROOT_SIZE * 2u) - + SSZ_BYTE_SIZE_OF_UINT32; - size_t body_header = SSZ_BYTE_SIZE_OF_UINT32; - size_t att_bits_max = bitlist_encoded_size_bits(LANTERN_VALIDATOR_REGISTRY_LIMIT); - size_t att_entry_max = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE + att_bits_max; - size_t att_bytes = (size_t)LANTERN_MAX_ATTESTATIONS * (SSZ_BYTE_SIZE_OF_UINT32 + att_entry_max); - size_t proof_bits_max = att_bits_max; - size_t proof_entry_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + proof_bits_max + LANTERN_AGG_PROOF_MAX_BYTES; - size_t signatures_bytes = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + LANTERN_SIGNATURE_SIZE - + ((size_t)LANTERN_MAX_BLOCK_SIGNATURES * (SSZ_BYTE_SIZE_OF_UINT32 + proof_entry_max)); - size_t total = offsets + block_fixed; - if (body_header > SIZE_MAX - total) { - return 0; - } - total += body_header; - if (att_bytes > SIZE_MAX - total) { - return 0; - } - total += att_bytes; - if (signatures_bytes > SIZE_MAX - total) { - return 0; +static void handle_writer_open_failed( + struct lantern_gossipsub_service *service, + libp2p_host_conn_t *conn, + libp2p_host_time_us_t now_us) { + struct lantern_gossipsub_peer_connection_state *state = find_peer_state_by_conn(service, conn); + if (!state) { + return; } - total += signatures_bytes; - return total; -} - -static size_t signed_aggregated_attestation_max_ssz_size(void) { - size_t att_bits_max = bitlist_encoded_size_bits(LANTERN_VALIDATOR_REGISTRY_LIMIT); - size_t proof_entry_max = (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + att_bits_max + LANTERN_AGG_PROOF_MAX_BYTES; - size_t total = LANTERN_ATTESTATION_DATA_SSZ_SIZE + SSZ_BYTE_SIZE_OF_UINT32; - if (proof_entry_max > SIZE_MAX - total) { - return SIZE_MAX; + if (state->opening_conn == conn) { + state->opening_conn = NULL; + schedule_writer_retry(state, now_us); } - return total + proof_entry_max; } -static size_t gossipsub_snappy_max_uncompressed( - const struct lantern_gossipsub_service *service, - const char *topic) { - size_t block_max = signed_block_max_ssz_size(); - size_t vote_max = LANTERN_SIGNED_VOTE_SSZ_SIZE; - size_t aggregated_max = signed_aggregated_attestation_max_ssz_size(); - size_t default_max = block_max > vote_max ? block_max : vote_max; - if (aggregated_max > default_max) { - default_max = aggregated_max; - } - if (!service || !topic) { - return default_max; - } - if (service->block_topic[0] != '\0' && strcmp(topic, service->block_topic) == 0) { - return block_max; - } - if (service->vote_topic[0] != '\0' && strcmp(topic, service->vote_topic) == 0) { - return vote_max; +static void repair_all_writers(struct lantern_gossipsub_service *service, libp2p_host_time_us_t now_us) { + if (!service) { + return; } - if (service) { - if (service->vote_subnet_topic[0] != '\0' && strcmp(topic, service->vote_subnet_topic) == 0) { - return vote_max; - } - for (size_t i = 0; i < service->extra_vote_subnet_topic_count; ++i) { - if (strcmp(topic, service->extra_vote_subnet_topics[i]) == 0) { - return vote_max; - } + for (size_t i = 0; i < LANTERN_GOSSIPSUB_MAX_TRACKED_PEERS; i++) { + if (service->peer_connections[i].used) { + maybe_repair_writer(service, &service->peer_connections[i], now_us); } } - if (service->aggregated_attestation_topic[0] != '\0' && strcmp(topic, service->aggregated_attestation_topic) == 0) - { - return aggregated_max; - } - return default_max; } -static libp2p_err_t lantern_gossipsub_message_id_cb( - const libp2p_gossipsub_message_t *msg, - uint8_t **out_id, - size_t *out_len, +static libp2p_gossipsub_err_t lantern_gossipsub_message_id( + const libp2p_gossipsub_message_t *message, + uint8_t *out, + size_t out_len, + size_t *written, void *user_data) { - if (!msg || !out_id || !out_len) { - return LIBP2P_ERR_NULL_PTR; + (void)user_data; + if (!message || !written) { + return LIBP2P_GOSSIPSUB_ERR_INVALID_ARG; } - struct lantern_gossipsub_service *service = (struct lantern_gossipsub_service *)user_data; - if (!service) { - return LIBP2P_ERR_NULL_PTR; - } - const char *topic = msg->topic.topic; - if (!topic) { - return LIBP2P_ERR_INTERNAL; - } - - uint8_t *scratch = NULL; - size_t scratch_len = 0; - if (msg->data && msg->data_len > 0) { - size_t expected = 0; - if (lantern_snappy_uncompressed_length_raw(msg->data, msg->data_len, &expected) == LANTERN_SNAPPY_OK - && expected > 0) { - size_t max_expected = gossipsub_snappy_max_uncompressed(service, topic); - if (max_expected > 0 && expected > max_expected) { - expected = 0; - } - } - if (expected > 0) { - scratch = (uint8_t *)malloc(expected); - if (!scratch) { - return LIBP2P_ERR_INTERNAL; - } - scratch_len = expected; - } + *written = LANTERN_GOSSIP_MESSAGE_ID_SIZE; + if (!out || out_len < LANTERN_GOSSIP_MESSAGE_ID_SIZE) { + return LIBP2P_GOSSIPSUB_ERR_BUF_TOO_SMALL; } LanternGossipMessageId id; - if (lantern_gossip_compute_message_id( + uint8_t stack_scratch[4096]; + size_t required = 0; + int rc = lantern_gossip_compute_message_id( + &id, + message->topic.data, + message->topic.len, + message->data.data, + message->data.len, + stack_scratch, + sizeof(stack_scratch), + &required); + if (rc != 0 && required > sizeof(stack_scratch)) { + uint8_t *scratch = (uint8_t *)malloc(required); + if (!scratch) { + return LIBP2P_GOSSIPSUB_ERR_INTERNAL; + } + rc = lantern_gossip_compute_message_id( &id, - (const uint8_t *)topic, - strlen(topic), - msg->data, - msg->data_len, + message->topic.data, + message->topic.len, + message->data.data, + message->data.len, scratch, - scratch_len, - NULL) - != 0) { + required, + NULL); free(scratch); - return LIBP2P_ERR_INTERNAL; } - - free(scratch); - uint8_t *buffer = (uint8_t *)malloc(LANTERN_GOSSIP_MESSAGE_ID_SIZE); - if (!buffer) { - return LIBP2P_ERR_INTERNAL; - } - memcpy(buffer, id.bytes, LANTERN_GOSSIP_MESSAGE_ID_SIZE); - *out_id = buffer; - *out_len = LANTERN_GOSSIP_MESSAGE_ID_SIZE; - - /* Debug: log message ID computation */ - char id_hex[LANTERN_GOSSIP_MESSAGE_ID_SIZE * 2 + 1]; - for (size_t i = 0; i < LANTERN_GOSSIP_MESSAGE_ID_SIZE; ++i) { - snprintf(id_hex + (i * 2), 3, "%02x", id.bytes[i]); + if (rc != 0) { + return LIBP2P_GOSSIPSUB_ERR_INTERNAL; } - lantern_log_debug( - "gossip", - NULL, - "message_id_cb called topic=%s data_len=%zu msg_id=%s", - topic, - msg->data_len, - id_hex); - - return LIBP2P_ERR_OK; + memcpy(out, id.bytes, LANTERN_GOSSIP_MESSAGE_ID_SIZE); + return LIBP2P_GOSSIPSUB_OK; } -static int subscribe_topic( - struct lantern_gossipsub_service *service, - const char *topic) { - if (!service || !service->gossipsub || !topic) { +static int encode_payload_block(const LanternSignedBlock *block, uint8_t **out, size_t *out_len) { + if (!block || !out || !out_len) { return -1; } - libp2p_gossipsub_topic_config_t cfg; - memset(&cfg, 0, sizeof(cfg)); - cfg.struct_size = sizeof(cfg); - cfg.descriptor.struct_size = sizeof(cfg.descriptor); - cfg.descriptor.topic = topic; - cfg.message_id_fn = lantern_gossipsub_message_id_cb; - cfg.message_id_user_data = service; - libp2p_err_t err = libp2p_gossipsub_subscribe(service->gossipsub, &cfg); - return err == LIBP2P_ERR_OK ? 0 : -1; -} - -static enum lantern_gossipsub_validation_job_kind lantern_gossipsub_classify_topic( - const struct lantern_gossipsub_service *service, - const char *topic) { - if (!service || !topic) { - return LANTERN_GOSSIPSUB_JOB_UNKNOWN; - } - struct lantern_gossip_parsed_topic parsed; - if (lantern_gossip_topic_parse(topic, &parsed) != 0) { - const char *reason = "topic is not a valid lean consensus gossip topic"; - lantern_log_invalid_gossip_topic(service, topic, reason); - return LANTERN_GOSSIPSUB_JOB_UNKNOWN; - } - if (strcmp(parsed.network_name, service->topic_network_name) != 0) { - lantern_log_invalid_gossip_topic(service, topic, "topic network slot does not match local configuration"); - return LANTERN_GOSSIPSUB_JOB_UNKNOWN; - } - switch (parsed.kind) { - case LANTERN_GOSSIP_TOPIC_BLOCK: - return LANTERN_GOSSIPSUB_JOB_BLOCK; - case LANTERN_GOSSIP_TOPIC_AGGREGATED_ATTESTATION: - return LANTERN_GOSSIPSUB_JOB_AGGREGATED_ATTESTATION; - case LANTERN_GOSSIP_TOPIC_VOTE_SUBNET: - return LANTERN_GOSSIPSUB_JOB_VOTE; - default: - break; - } - return LANTERN_GOSSIPSUB_JOB_UNKNOWN; -} - -static void lantern_log_invalid_gossip_topic( - const struct lantern_gossipsub_service *service, - const char *topic, - const char *reason) -{ - lantern_log_warn( - "gossip", - &(const struct lantern_log_metadata){.peer = service ? service->devnet : NULL}, - "rejecting gossip topic=%s reason=%s", - topic ? topic : "(null)", - reason ? reason : "invalid topic"); -} - -static libp2p_gossipsub_validation_result_t lantern_validate_block_payload( - struct lantern_gossipsub_service *service, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source) { - if (!service) { - return LIBP2P_GOSSIPSUB_VALIDATION_REJECT; - } - if (!service->block_handler || !payload || payload_len == 0) { - return LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; - } - - LanternSignedBlock block; - lantern_signed_block_init(&block); - uint8_t *raw_block_ssz = NULL; - size_t raw_block_ssz_len = 0; - - libp2p_gossipsub_validation_result_t result = LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; - char peer_text[128]; - describe_peer_id(propagation_source, peer_text, sizeof(peer_text)); - const struct lantern_log_metadata meta = {.peer = peer_text[0] ? peer_text : NULL}; - - lantern_log_debug( - "gossip", - &meta, - "block gossip message received bytes=%zu from_peer=%s", - payload_len, - peer_text[0] ? peer_text : "(local)"); - if (lantern_gossip_decode_signed_block_snappy( - &block, - payload, - payload_len, - &raw_block_ssz, - &raw_block_ssz_len) - != 0) { - lantern_log_warn( - "gossip", - &meta, - "failed to decode gossip block payload bytes=%zu", - payload_len); - maybe_dump_invalid_gossip_payload(service, "block", payload, payload_len, &meta); - result = LIBP2P_GOSSIPSUB_VALIDATION_REJECT; - goto cleanup; + uint8_t *buffer = (uint8_t *)malloc(LANTERN_GOSSIP_ENCODE_BUFFER_BYTES); + if (!buffer) { + return -1; } - - if (service->block_handler( - &block, - propagation_source, - raw_block_ssz, - raw_block_ssz_len, - service->block_handler_user_data) - != 0) { - result = LIBP2P_GOSSIPSUB_VALIDATION_IGNORE; - } - char block_root_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (lantern_bytes_to_hex( - block.block.parent_root.bytes, - LANTERN_ROOT_SIZE, - block_root_hex, - sizeof(block_root_hex), - 1) + size_t written = 0; + if (lantern_gossip_encode_signed_block_snappy( + block, + buffer, + LANTERN_GOSSIP_ENCODE_BUFFER_BYTES, + &written) != 0) { - block_root_hex[0] = '\0'; - } - if (result == LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT) { - lantern_log_debug( - "gossip", - &meta, - "accepted block gossip slot=%" PRIu64 " proposer=%" PRIu64 " parent=%s", - block.block.slot, - block.block.proposer_index, - block_root_hex[0] ? block_root_hex : "0x0"); - } else if (result == LIBP2P_GOSSIPSUB_VALIDATION_IGNORE) { - lantern_log_debug( - "gossip", - &meta, - "ignored block gossip slot=%" PRIu64 " proposer=%" PRIu64 " parent=%s", - block.block.slot, - block.block.proposer_index, - block_root_hex[0] ? block_root_hex : "0x0"); + free(buffer); + return -1; } - -cleanup: - free(raw_block_ssz); - lantern_signed_block_reset(&block); - return result; + *out = buffer; + *out_len = written; + return 0; } -static libp2p_gossipsub_validation_result_t lantern_validate_vote_payload( - struct lantern_gossipsub_service *service, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source) { - if (!service) { - return LIBP2P_GOSSIPSUB_VALIDATION_REJECT; +static int encode_payload_vote(const LanternSignedVote *vote, uint8_t **out, size_t *out_len) { + if (!vote || !out || !out_len) { + return -1; } - if (!service->vote_handler || !payload || payload_len == 0) { - return LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; + uint8_t *buffer = (uint8_t *)malloc(LANTERN_GOSSIP_ENCODE_BUFFER_BYTES); + if (!buffer) { + return -1; } - - LanternSignedVote vote; - memset(&vote, 0, sizeof(vote)); - - libp2p_gossipsub_validation_result_t result = LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; - char peer_text[128]; - describe_peer_id(propagation_source, peer_text, sizeof(peer_text)); - const struct lantern_log_metadata meta = {.peer = peer_text[0] ? peer_text : NULL}; - - lantern_log_debug( - "gossip", - &meta, - "vote gossip message received bytes=%zu", - payload_len); - if (lantern_gossip_decode_signed_vote_snappy(&vote, payload, payload_len) != 0) { - lantern_log_warn( - "gossip", - &meta, - "failed to decode gossip vote payload bytes=%zu", - payload_len); - maybe_dump_invalid_gossip_payload(service, "vote", payload, payload_len, &meta); - result = LIBP2P_GOSSIPSUB_VALIDATION_REJECT; - goto cleanup; - } - - if (service->vote_handler( - &vote, - propagation_source, - payload, - payload_len, - service->vote_handler_user_data) - != 0) { - result = LIBP2P_GOSSIPSUB_VALIDATION_IGNORE; - } - char head_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (lantern_bytes_to_hex( - vote.data.head.root.bytes, - LANTERN_ROOT_SIZE, - head_hex, - sizeof(head_hex), - 1) + size_t written = 0; + if (lantern_gossip_encode_signed_vote_snappy( + vote, + buffer, + LANTERN_GOSSIP_ENCODE_BUFFER_BYTES, + &written) != 0) { - head_hex[0] = '\0'; - } - if (result == LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT) { - lantern_log_debug( - "gossip", - &meta, - "accepted vote gossip validator=%" PRIu64 " slot=%" PRIu64 " head=%s", - vote.data.validator_id, - vote.data.slot, - head_hex[0] ? head_hex : "0x0"); - } else if (result == LIBP2P_GOSSIPSUB_VALIDATION_IGNORE) { - lantern_log_debug( - "gossip", - &meta, - "ignored vote gossip validator=%" PRIu64 " slot=%" PRIu64 " head=%s", - vote.data.validator_id, - vote.data.slot, - head_hex[0] ? head_hex : "0x0"); + free(buffer); + return -1; } - -cleanup: - return result; + *out = buffer; + *out_len = written; + return 0; } -static libp2p_gossipsub_validation_result_t lantern_validate_aggregated_attestation_payload( - struct lantern_gossipsub_service *service, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source) { - if (!service) { - return LIBP2P_GOSSIPSUB_VALIDATION_REJECT; +static int encode_payload_aggregated_attestation( + const LanternSignedAggregatedAttestation *attestation, + uint8_t **out, + size_t *out_len) { + if (!attestation || !out || !out_len) { + return -1; } - if (!service->aggregated_attestation_handler || !payload || payload_len == 0) { - return LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; + uint8_t *buffer = (uint8_t *)malloc(LANTERN_GOSSIP_ENCODE_BUFFER_BYTES); + if (!buffer) { + return -1; } - - LanternSignedAggregatedAttestation attestation; - lantern_signed_aggregated_attestation_init(&attestation); - - libp2p_gossipsub_validation_result_t result = LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; - char peer_text[128]; - describe_peer_id(propagation_source, peer_text, sizeof(peer_text)); - const struct lantern_log_metadata meta = {.peer = peer_text[0] ? peer_text : NULL}; - - lantern_log_debug( - "gossip", - &meta, - "aggregated attestation gossip message received bytes=%zu", - payload_len); - if (lantern_gossip_decode_signed_aggregated_attestation_snappy(&attestation, payload, payload_len) != 0) { - lantern_log_warn( - "gossip", - &meta, - "failed to decode aggregated attestation gossip payload bytes=%zu", - payload_len); - maybe_dump_invalid_gossip_payload( - service, - "aggregated_attestation", - payload, - payload_len, - &meta); - result = LIBP2P_GOSSIPSUB_VALIDATION_REJECT; - goto cleanup; - } - - if (service->aggregated_attestation_handler( - &attestation, - propagation_source, - payload, - payload_len, - service->aggregated_attestation_handler_user_data) + size_t written = 0; + if (lantern_gossip_encode_signed_aggregated_attestation_snappy( + attestation, + buffer, + LANTERN_GOSSIP_ENCODE_BUFFER_BYTES, + &written) != 0) { - result = LIBP2P_GOSSIPSUB_VALIDATION_IGNORE; + free(buffer); + return -1; } - -cleanup: - lantern_signed_aggregated_attestation_reset(&attestation); - return result; + *out = buffer; + *out_len = written; + return 0; } -static libp2p_gossipsub_validation_result_t lantern_gossipsub_validation_job_run( +static int deliver_message( struct lantern_gossipsub_service *service, - const struct lantern_gossipsub_validation_job *job) { - if (!service || !job) { - return LIBP2P_GOSSIPSUB_VALIDATION_IGNORE; - } - - switch (job->kind) { - case LANTERN_GOSSIPSUB_JOB_BLOCK: - return lantern_validate_block_payload(service, job->payload, job->payload_len, job->propagation_source); - case LANTERN_GOSSIPSUB_JOB_VOTE: - return lantern_validate_vote_payload(service, job->payload, job->payload_len, job->propagation_source); - case LANTERN_GOSSIPSUB_JOB_AGGREGATED_ATTESTATION: - return lantern_validate_aggregated_attestation_payload( - service, - job->payload, - job->payload_len, - job->propagation_source); - default: - return LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT; - } -} - -static struct lantern_gossipsub_validation_job *lantern_gossipsub_validation_job_new( - enum lantern_gossipsub_validation_job_kind kind, - const char *topic, - const uint8_t *payload, - size_t payload_len, - const peer_id_t *propagation_source, - const uint8_t *message_id, - size_t message_id_len) { - if (!topic || !message_id || message_id_len == 0) { - return NULL; - } - - struct lantern_gossipsub_validation_job *job = calloc(1, sizeof(*job)); - if (!job) { - return NULL; - } - job->kind = kind; - job->topic = strdup(topic); - if (!job->topic) { - lantern_gossipsub_validation_job_free(job); - return NULL; - } - if (payload && payload_len > 0) { - job->payload = malloc(payload_len); - if (!job->payload) { - lantern_gossipsub_validation_job_free(job); - return NULL; + const libp2p_gossipsub_event_t *event) { + struct lantern_peer_id peer = {0}; + if (event->peer.len <= sizeof(peer.bytes)) { + memcpy(peer.bytes, event->peer.data, event->peer.len); + peer.len = event->peer.len; + } + + if (topic_eq(event->topic, service->block_topic)) { + LanternSignedBlock block; + lantern_signed_block_init(&block); + uint8_t *raw = NULL; + size_t raw_len = 0; + int rc = lantern_gossip_decode_signed_block_snappy( + &block, + event->message.data.data, + event->message.data.len, + &raw, + &raw_len); + if (rc == 0 && service->block_handler) { + rc = service->block_handler( + &block, + &peer, + raw, + raw_len, + service->block_handler_user_data); } - memcpy(job->payload, payload, payload_len); - job->payload_len = payload_len; - } - if (propagation_source) { - if (peer_id_clone(propagation_source, &job->propagation_source) != PEER_ID_OK || !job->propagation_source) { - lantern_gossipsub_validation_job_free(job); - return NULL; + free(raw); + lantern_signed_block_reset(&block); + return rc; + } + + if (topic_eq(event->topic, service->vote_topic) || topic_eq(event->topic, service->vote_subnet_topic)) { + LanternSignedVote vote; + memset(&vote, 0, sizeof(vote)); + int rc = lantern_gossip_decode_signed_vote_snappy(&vote, event->message.data.data, event->message.data.len); + if (rc == 0 && service->vote_handler) { + rc = service->vote_handler( + &vote, + &peer, + event->message.data.data, + event->message.data.len, + service->vote_handler_user_data); } - } - job->message_id = malloc(message_id_len); - if (!job->message_id) { - lantern_gossipsub_validation_job_free(job); - return NULL; - } - memcpy(job->message_id, message_id, message_id_len); - job->message_id_len = message_id_len; - return job; -} - -static void *lantern_gossipsub_validation_worker_main(void *user_data) { - struct lantern_gossipsub_validation_pool *pool = (struct lantern_gossipsub_validation_pool *)user_data; - if (!pool) { - return NULL; + return rc; } - for (;;) { - pthread_mutex_lock(&pool->mutex); - while (!pool->stopping && pool->queue_len == 0) { - pthread_cond_wait(&pool->not_empty, &pool->mutex); - } - if (pool->stopping && pool->queue_len == 0) { - pthread_mutex_unlock(&pool->mutex); - break; - } - - struct lantern_gossipsub_validation_job *job = pool->queue[pool->queue_head]; - pool->queue[pool->queue_head] = NULL; - pool->queue_head = (pool->queue_head + 1u) % pool->queue_capacity; - pool->queue_len--; - pthread_mutex_unlock(&pool->mutex); - - if (!job) { - continue; - } - - libp2p_gossipsub_validation_result_t result = lantern_gossipsub_validation_job_run(pool->service, job); - if (pool->service && pool->service->gossipsub) { - (void)libp2p_gossipsub_report_message_validation_result( - pool->service->gossipsub, - job->message_id, - job->message_id_len, - result); + if (topic_eq(event->topic, service->aggregated_attestation_topic)) { + LanternSignedAggregatedAttestation attestation; + lantern_signed_aggregated_attestation_init(&attestation); + int rc = lantern_gossip_decode_signed_aggregated_attestation_snappy( + &attestation, + event->message.data.data, + event->message.data.len); + if (rc == 0 && service->aggregated_attestation_handler) { + rc = service->aggregated_attestation_handler( + &attestation, + &peer, + event->message.data.data, + event->message.data.len, + service->aggregated_attestation_handler_user_data); } - lantern_gossipsub_validation_job_free(job); + lantern_signed_aggregated_attestation_reset(&attestation); + return rc; } - return NULL; + return -1; } -static int lantern_gossipsub_validation_pool_start(struct lantern_gossipsub_service *service) { - if (!service) { - return -1; - } - if (service->validation_pool) { - return 0; - } - - struct lantern_gossipsub_validation_pool *pool = calloc(1, sizeof(*pool)); - if (!pool) { - return -1; - } - pool->service = service; - pool->worker_count = lantern_gossipsub_validation_worker_count(); - pool->queue_capacity = LANTERN_GOSSIPSUB_VALIDATION_QUEUE_CAPACITY; - pool->threads = calloc(pool->worker_count, sizeof(*pool->threads)); - pool->queue = calloc(pool->queue_capacity, sizeof(*pool->queue)); - if (!pool->threads || !pool->queue) { - free(pool->threads); - free(pool->queue); - free(pool); - return -1; - } - if (pthread_mutex_init(&pool->mutex, NULL) != 0) { - free(pool->threads); - free(pool->queue); - free(pool); - return -1; +static void handle_gossipsub_peer_opened( + struct lantern_gossipsub_service *service, + const libp2p_gossipsub_event_t *event, + libp2p_host_time_us_t now_us) { + struct lantern_peer_id peer; + if (peer_from_gossipsub_event(event, &peer) != 0) { + return; } - if (pthread_cond_init(&pool->not_empty, NULL) != 0) { - pthread_mutex_destroy(&pool->mutex); - free(pool->threads); - free(pool->queue); - free(pool); - return -1; + struct lantern_gossipsub_peer_connection_state *state = find_peer_state(service, &peer, 1); + if (!state) { + return; } - size_t started = 0; - for (; started < pool->worker_count; ++started) { - if (pthread_create(&pool->threads[started], NULL, lantern_gossipsub_validation_worker_main, pool) != 0) { - pool->stopping = 1; - pthread_cond_broadcast(&pool->not_empty); - for (size_t i = 0; i < started; ++i) { - pthread_join(pool->threads[i], NULL); + if (event->direction == LIBP2P_HOST_STREAM_OUTBOUND) { + if (!state->writer_stream && (!state->opening_conn || event->conn == state->opening_conn)) { + state->writer_conn = event->conn; + state->writer_stream = event->stream; + state->opening_conn = NULL; + reset_writer_retry(state); + } else { + if (event->stream && service->network && service->network->host) { + (void)libp2p_host_stream_reset(service->network->host, event->stream, 0); + } + if (event->conn && event->conn == state->opening_conn) { + state->opening_conn = NULL; + schedule_writer_retry(state, now_us); } - pthread_cond_destroy(&pool->not_empty); - pthread_mutex_destroy(&pool->mutex); - free(pool->threads); - free(pool->queue); - free(pool); - return -1; } } - - pool->started = 1; - service->validation_pool = pool; - lean_metrics_set_gossip_validation_worker_count(pool->worker_count); - return 0; } -static void lantern_gossipsub_validation_pool_stop(struct lantern_gossipsub_service *service) { - if (!service || !service->validation_pool) { +static void handle_gossipsub_peer_closed( + struct lantern_gossipsub_service *service, + const libp2p_gossipsub_event_t *event, + libp2p_host_time_us_t now_us) { + struct lantern_gossipsub_peer_connection_state *state = NULL; + struct lantern_peer_id peer; + if (peer_from_gossipsub_event(event, &peer) == 0) { + state = find_peer_state(service, &peer, 0); + } + if (!state && event->conn) { + state = find_peer_state_by_conn(service, event->conn); + } + if (!state) { return; } - struct lantern_gossipsub_validation_pool *pool = service->validation_pool; - service->validation_pool = NULL; - - pthread_mutex_lock(&pool->mutex); - pool->stopping = 1; - pthread_cond_broadcast(&pool->not_empty); - while (pool->queue_len > 0) { - struct lantern_gossipsub_validation_job *job = pool->queue[pool->queue_head]; - pool->queue[pool->queue_head] = NULL; - pool->queue_head = (pool->queue_head + 1u) % pool->queue_capacity; - pool->queue_len--; - pthread_mutex_unlock(&pool->mutex); - - if (job) { - if (service->gossipsub) { - (void)libp2p_gossipsub_report_message_validation_result( - service->gossipsub, - job->message_id, - job->message_id_len, - LIBP2P_GOSSIPSUB_VALIDATION_IGNORE); - } - lantern_gossipsub_validation_job_free(job); + int writer_closed = + (event->stream && event->stream == state->writer_stream) || + (event->conn && event->conn == state->writer_conn); + if (writer_closed) { + if (event->stream && event->conn && event->conn != state->writer_conn && + service->network && service->network->host) { + (void)libp2p_host_stream_reset(service->network->host, event->stream, 0); } - - pthread_mutex_lock(&pool->mutex); + state->writer_conn = NULL; + state->writer_stream = NULL; } - pthread_mutex_unlock(&pool->mutex); - - if (pool->started) { - for (size_t i = 0; i < pool->worker_count; ++i) { - pthread_join(pool->threads[i], NULL); - } + if (event->conn && event->conn == state->opening_conn) { + state->opening_conn = NULL; + schedule_writer_retry(state, now_us); } - - pthread_cond_destroy(&pool->not_empty); - pthread_mutex_destroy(&pool->mutex); - free(pool->threads); - free(pool->queue); - free(pool); + maybe_repair_writer(service, state, now_us); } -static int lantern_gossipsub_validation_pool_try_enqueue( - struct lantern_gossipsub_service *service, - struct lantern_gossipsub_validation_job *job) { - if (!service || !service->validation_pool || !job) { - return -1; - } - - struct lantern_gossipsub_validation_pool *pool = service->validation_pool; - pthread_mutex_lock(&pool->mutex); - if (pool->stopping || pool->queue_len >= pool->queue_capacity) { - pthread_mutex_unlock(&pool->mutex); - return -1; +static void drain_gossipsub_events(struct lantern_gossipsub_service *service, libp2p_host_time_us_t now_us) { + libp2p_gossipsub_event_t event; + while (libp2p_gossipsub_next_event(service->gossipsub, &event) == LIBP2P_GOSSIPSUB_OK) { + if (event.type == LIBP2P_GOSSIPSUB_EVENT_MESSAGE) { + int ok = deliver_message(service, &event) == 0; + if (event.validation) { + (void)libp2p_gossipsub_report_validation( + service->gossipsub, + event.validation, + ok ? LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT : LIBP2P_GOSSIPSUB_VALIDATION_REJECT); + } + } else if (event.type == LIBP2P_GOSSIPSUB_EVENT_PEER_OPENED) { + handle_gossipsub_peer_opened(service, &event, now_us); + } else if (event.type == LIBP2P_GOSSIPSUB_EVENT_PEER_CLOSED) { + handle_gossipsub_peer_closed(service, &event, now_us); + } else if (event.type == LIBP2P_GOSSIPSUB_EVENT_PEER_FAILED) { + if (event.conn) { + handle_writer_open_failed(service, event.conn, now_us); + } + } else if (event.type == LIBP2P_GOSSIPSUB_EVENT_DROPPED || event.type == LIBP2P_GOSSIPSUB_EVENT_ERROR) { + lantern_log_debug("gossip", NULL, "gossipsub event type=%d reason=%d", (int)event.type, (int)event.reason); + } } - - size_t idx = (pool->queue_head + pool->queue_len) % pool->queue_capacity; - pool->queue[idx] = job; - pool->queue_len++; - pthread_cond_signal(&pool->not_empty); - pthread_mutex_unlock(&pool->mutex); - return 0; } -static void lantern_gossipsub_message_delivery_cb( - libp2p_gossipsub_t *gs, - const libp2p_gossipsub_message_t *msg, - const uint8_t *message_id, - size_t message_id_len, - const peer_id_t *propagation_source, +static void gossipsub_host_event( + struct lantern_libp2p_host *network, + const libp2p_host_event_t *event, void *user_data) { struct lantern_gossipsub_service *service = (struct lantern_gossipsub_service *)user_data; - if (!service || !gs || !msg || !msg->topic.topic || !message_id || message_id_len == 0) { + if (!service || !service->gossipsub || !network || !network->host || !event) { return; } - - enum lantern_gossipsub_validation_job_kind kind = lantern_gossipsub_classify_topic(service, msg->topic.topic); - int handler_missing = 0; - switch (kind) { - case LANTERN_GOSSIPSUB_JOB_BLOCK: - handler_missing = service->block_handler == NULL; - break; - case LANTERN_GOSSIPSUB_JOB_VOTE: - handler_missing = service->vote_handler == NULL; - break; - case LANTERN_GOSSIPSUB_JOB_AGGREGATED_ATTESTATION: - handler_missing = service->aggregated_attestation_handler == NULL; - break; - default: - handler_missing = 1; - break; - } - - if (handler_missing || !msg->data || msg->data_len == 0) { - (void)libp2p_gossipsub_report_message_validation_result( - gs, - message_id, - message_id_len, - LIBP2P_GOSSIPSUB_VALIDATION_ACCEPT); - return; + libp2p_host_time_us_t now_us = lantern_libp2p_now_us(); + if (event->type == LIBP2P_HOST_EVENT_CONN_ESTABLISHED && event->conn) { + add_peer_connection(service, event->conn, event->dial == NULL, now_us); + } + (void)libp2p_gossipsub_handle_host_event(service->gossipsub, network->host, event); + if (event->type == LIBP2P_HOST_EVENT_CONN_CLOSED && event->conn) { + remove_peer_connection(service, event->conn, now_us); + } else if (event->type == LIBP2P_HOST_EVENT_STREAM_OPEN_FAILED && event->conn) { + handle_writer_open_failed(service, event->conn, now_us); } +} - struct lantern_gossipsub_validation_job *job = lantern_gossipsub_validation_job_new( - kind, - msg->topic.topic, - msg->data, - msg->data_len, - propagation_source, - message_id, - message_id_len); - if (!job) { - lantern_log_warn( - "gossip", - NULL, - "failed to allocate gossip validation job topic=%s bytes=%zu; ignoring message", - msg->topic.topic, - msg->data_len); - (void)libp2p_gossipsub_report_message_validation_result( - gs, - message_id, - message_id_len, - LIBP2P_GOSSIPSUB_VALIDATION_IGNORE); +static void gossipsub_drive( + struct lantern_libp2p_host *network, + libp2p_host_time_us_t now_us, + void *user_data) { + struct lantern_gossipsub_service *service = (struct lantern_gossipsub_service *)user_data; + if (!service || !service->gossipsub || !network || !network->host) { return; } + (void)libp2p_gossipsub_drive(service->gossipsub, network->host, now_us, NULL); + drain_gossipsub_events(service, now_us); + repair_all_writers(service, now_us); +} - if (lantern_gossipsub_validation_pool_try_enqueue(service, job) != 0) { - lantern_log_warn( - "gossip", - NULL, - "gossip validation queue full or stopping topic=%s bytes=%zu; ignoring message", - msg->topic.topic, - msg->data_len); - lantern_gossipsub_validation_job_free(job); - (void)libp2p_gossipsub_report_message_validation_result( - gs, - message_id, - message_id_len, - LIBP2P_GOSSIPSUB_VALIDATION_IGNORE); - } +static int subscribe_topic(struct lantern_gossipsub_service *service, const char *topic) { + libp2p_gossipsub_topic_config_t topic_config = { + .topic = {.data = (const uint8_t *)topic, .len = strlen(topic)}, + .validation_mode = LIBP2P_GOSSIPSUB_VALIDATION_REQUIRE_APP, + .enable_idontwant = 1, + .idontwant_min_message_bytes = LIBP2P_GOSSIPSUB_DEFAULT_IDONTWANT_MIN_BYTES, + }; + return libp2p_gossipsub_subscribe(service->gossipsub, &topic_config) == LIBP2P_GOSSIPSUB_OK ? 0 : -1; } -void lantern_gossipsub_service_init(struct lantern_gossipsub_service *service) { - if (!service) { - return; - } - memset(service, 0, sizeof(*service)); +static int setup_topics(struct lantern_gossipsub_service *service, const struct lantern_gossipsub_config *config) { + snprintf(service->topic_network_name, sizeof(service->topic_network_name), "%s", config->topic_network_name); + memcpy(service->fork_digest, config->fork_digest, sizeof(service->fork_digest)); + service->attestation_subnet_id = config->attestation_subnet_id; + service->subscribe_attestation_subnet = config->subscribe_attestation_subnet ? 1 : 0; + return lantern_gossip_topic_format( + LANTERN_GOSSIP_TOPIC_BLOCK, + service->topic_network_name, + service->block_topic, + sizeof(service->block_topic)) == 0 && + lantern_gossip_topic_format( + LANTERN_GOSSIP_TOPIC_VOTE, + service->topic_network_name, + service->vote_topic, + sizeof(service->vote_topic)) == 0 && + lantern_gossip_topic_format_subnet( + LANTERN_GOSSIP_TOPIC_VOTE_SUBNET, + service->topic_network_name, + service->attestation_subnet_id, + service->vote_subnet_topic, + sizeof(service->vote_subnet_topic)) == 0 && + lantern_gossip_topic_format( + LANTERN_GOSSIP_TOPIC_AGGREGATED_ATTESTATION, + service->topic_network_name, + service->aggregated_attestation_topic, + sizeof(service->aggregated_attestation_topic)) == 0 + ? 0 + : -1; } -static void lantern_gossipsub_service_remove_validators(struct lantern_gossipsub_service *service) { - if (!service) { - return; - } - if (service->gossipsub) { - if (service->block_validator_handle) { - (void)libp2p_gossipsub_remove_validator(service->gossipsub, service->block_validator_handle); - } - if (service->vote_validator_handle) { - (void)libp2p_gossipsub_remove_validator(service->gossipsub, service->vote_validator_handle); - } - if (service->vote_subnet_validator_handle) { - (void)libp2p_gossipsub_remove_validator(service->gossipsub, service->vote_subnet_validator_handle); - } - if (service->aggregated_attestation_validator_handle) { - (void)libp2p_gossipsub_remove_validator( - service->gossipsub, - service->aggregated_attestation_validator_handle); - } - for (size_t i = 0; i < service->extra_vote_subnet_topic_count; ++i) { - if (service->extra_vote_subnet_validator_handles - && service->extra_vote_subnet_validator_handles[i]) { - (void)libp2p_gossipsub_remove_validator( - service->gossipsub, - service->extra_vote_subnet_validator_handles[i]); - } - } - } - service->block_validator_handle = NULL; - service->vote_validator_handle = NULL; - service->vote_subnet_validator_handle = NULL; - service->aggregated_attestation_validator_handle = NULL; - if (service->extra_vote_subnet_validator_handles) { - memset( - service->extra_vote_subnet_validator_handles, - 0, - service->extra_vote_subnet_topic_count * sizeof(*service->extra_vote_subnet_validator_handles)); +void lantern_gossipsub_service_init(struct lantern_gossipsub_service *service) { + if (service) { + memset(service, 0, sizeof(*service)); } } void lantern_gossipsub_service_stop(struct lantern_gossipsub_service *service) { - if (!service) { + if (!service || !service->gossipsub) { return; } - if (service->gossipsub) { - (void)libp2p_gossipsub_set_message_delivery_callback(service->gossipsub, NULL, NULL); - } - lantern_gossipsub_validation_pool_stop(service); - lantern_gossipsub_service_remove_validators(service); - if (service->gossipsub) { - libp2p_gossipsub_stop(service->gossipsub); + if (service->network && service->network->host) { + (void)libp2p_gossipsub_close(service->gossipsub, service->network->host, 0); } } @@ -1259,188 +735,119 @@ void lantern_gossipsub_service_reset(struct lantern_gossipsub_service *service) } lantern_gossipsub_service_stop(service); if (service->gossipsub) { - libp2p_gossipsub_free(service->gossipsub); - service->gossipsub = NULL; - } - memset(service->block_topic, 0, sizeof(service->block_topic)); - memset(service->vote_topic, 0, sizeof(service->vote_topic)); - memset(service->vote_subnet_topic, 0, sizeof(service->vote_subnet_topic)); - memset(service->aggregated_attestation_topic, 0, sizeof(service->aggregated_attestation_topic)); - service->data_dir = NULL; - service->devnet = NULL; - memset(service->topic_network_name, 0, sizeof(service->topic_network_name)); - memset(service->fork_digest, 0, sizeof(service->fork_digest)); - service->attestation_subnet_id = 0; - service->subscribe_attestation_subnet = 0; - service->publish_hook = NULL; - service->publish_hook_user_data = NULL; - service->loopback_only = 0; + libp2p_gossipsub_deinit(service->gossipsub); + } + free(service->gossipsub_storage); free(service->extra_vote_subnet_topics); - service->extra_vote_subnet_topics = NULL; - free(service->extra_vote_subnet_validator_handles); - service->extra_vote_subnet_validator_handles = NULL; - service->extra_vote_subnet_topic_count = 0; - service->validation_pool = NULL; + + int (*publish_hook)(const char *, const uint8_t *, size_t, void *) = service->publish_hook; + void *publish_user_data = service->publish_hook_user_data; + int loopback_only = service->loopback_only; + lantern_gossipsub_block_handler block_handler = service->block_handler; + void *block_user_data = service->block_handler_user_data; + lantern_gossipsub_vote_handler vote_handler = service->vote_handler; + void *vote_user_data = service->vote_handler_user_data; + lantern_gossipsub_aggregated_attestation_handler aggregate_handler = service->aggregated_attestation_handler; + void *aggregate_user_data = service->aggregated_attestation_handler_user_data; + + memset(service, 0, sizeof(*service)); + service->publish_hook = publish_hook; + service->publish_hook_user_data = publish_user_data; + service->loopback_only = loopback_only; + service->block_handler = block_handler; + service->block_handler_user_data = block_user_data; + service->vote_handler = vote_handler; + service->vote_handler_user_data = vote_user_data; + service->aggregated_attestation_handler = aggregate_handler; + service->aggregated_attestation_handler_user_data = aggregate_user_data; } int lantern_gossipsub_service_start( struct lantern_gossipsub_service *service, const struct lantern_gossipsub_config *config) { - if (!service || !config || !config->host || !config->topic_network_name) { + if (!service || !config || !config->topic_network_name) { return -1; } - int previous_loopback = service->loopback_only; - int (*previous_publish_hook)(const char *, const uint8_t *, size_t, void *) = service->publish_hook; - void *previous_publish_user_data = service->publish_hook_user_data; - lantern_gossipsub_block_handler previous_block_handler = service->block_handler; - void *previous_block_user_data = service->block_handler_user_data; - lantern_gossipsub_vote_handler previous_vote_handler = service->vote_handler; - void *previous_vote_user_data = service->vote_handler_user_data; - lantern_gossipsub_aggregated_attestation_handler previous_aggregated_handler = - service->aggregated_attestation_handler; - void *previous_aggregated_user_data = service->aggregated_attestation_handler_user_data; lantern_gossipsub_service_reset(service); - service->loopback_only = previous_loopback; - service->publish_hook = previous_publish_hook; - service->publish_hook_user_data = previous_publish_user_data; - service->block_handler = previous_block_handler; - service->block_handler_user_data = previous_block_user_data; - service->vote_handler = previous_vote_handler; - service->vote_handler_user_data = previous_vote_user_data; - service->aggregated_attestation_handler = previous_aggregated_handler; - service->aggregated_attestation_handler_user_data = previous_aggregated_user_data; + service->network = config->network; service->data_dir = config->data_dir; service->devnet = config->devnet; - snprintf( - service->topic_network_name, - sizeof(service->topic_network_name), - "%s", - config->topic_network_name); - memcpy(service->fork_digest, config->fork_digest, sizeof(service->fork_digest)); - service->attestation_subnet_id = config->attestation_subnet_id; - service->subscribe_attestation_subnet = config->subscribe_attestation_subnet ? 1 : 0; - - if (lantern_gossip_topic_format( - LANTERN_GOSSIP_TOPIC_BLOCK, - service->topic_network_name, - service->block_topic, - sizeof(service->block_topic)) - != 0) { - return -1; - } - if (lantern_gossip_topic_format( - LANTERN_GOSSIP_TOPIC_VOTE, - service->topic_network_name, - service->vote_topic, - sizeof(service->vote_topic)) - != 0) { - return -1; - } - if (lantern_gossip_topic_format_subnet( - LANTERN_GOSSIP_TOPIC_VOTE_SUBNET, - service->topic_network_name, - config->attestation_subnet_id, - service->vote_subnet_topic, - sizeof(service->vote_subnet_topic)) - != 0) { + if (setup_topics(service, config) != 0) { return -1; } - if (lantern_gossip_topic_format( - LANTERN_GOSSIP_TOPIC_AGGREGATED_ATTESTATION, - service->topic_network_name, - service->aggregated_attestation_topic, - sizeof(service->aggregated_attestation_topic)) - != 0) { - return -1; + if (service->loopback_only || !service->network || !service->network->host) { + return 0; } - libp2p_gossipsub_config_t cfg; - if (libp2p_gossipsub_config_default(&cfg) != LIBP2P_ERR_OK) { + libp2p_gossipsub_config_t gs_config; + if (libp2p_gossipsub_config_default(&gs_config) != LIBP2P_GOSSIPSUB_OK) { return -1; } - cfg.heartbeat_interval_ms = LANTERN_GOSSIPSUB_HEARTBEAT_INTERVAL_MS; - cfg.d = LANTERN_GOSSIPSUB_MESH_D; - cfg.d_lo = LANTERN_GOSSIPSUB_MESH_D_LOW; - cfg.d_hi = LANTERN_GOSSIPSUB_MESH_D_HIGH; - cfg.d_lazy = LANTERN_GOSSIPSUB_MESH_D_LAZY; - cfg.message_cache_length = LANTERN_GOSSIPSUB_MESSAGE_CACHE_LEN; - cfg.message_cache_gossip = LANTERN_GOSSIPSUB_MESSAGE_CACHE_GOSSIP; - cfg.seen_cache_ttl_ms = (int)lantern_leanspec_seen_ttl_ms(); - cfg.fanout_ttl_ms = LANTERN_GOSSIPSUB_FANOUT_TTL_MS; - cfg.protocol_ids = k_leanspec_gossipsub_protocols; - cfg.protocol_id_count = LANTERN_ARRAY_SIZE(k_leanspec_gossipsub_protocols); - cfg.enable_flood_publish = true; - cfg.on_score_update = lantern_gossipsub_score_update; - cfg.score_update_user_data = service; - cfg.anonymous_mode = true; /* Required for rust-libp2p compatibility (Anonymous validation mode) */ - - libp2p_gossipsub_t *gs = NULL; - if (libp2p_gossipsub_new(config->host, &cfg, &gs) != LIBP2P_ERR_OK || !gs) { + gs_config.random_fn = lantern_libp2p_gossipsub_random; + gs_config.message_id_fn = lantern_gossipsub_message_id; + /* Match leanSpec's 10 MiB max networking payload; hash-sig blocks exceed libp2p's 1 MiB defaults. */ + gs_config.limits.max_message_data_bytes = LANTERN_GOSSIP_MAX_MESSAGE_BYTES; + gs_config.limits.max_rpc_bytes = LANTERN_GOSSIP_MAX_RPC_BYTES; + gs_config.capacity.tx_buffer_bytes = LANTERN_GOSSIP_TX_BUFFER_BYTES; + gs_config.capacity.mcache_bytes = LANTERN_GOSSIP_MCACHE_BYTES; + gs_config.mesh.enable_flood_publish = 1; + gs_config.mesh.seen_ttl_us = 120000000ull; + gs_config.protocol_mask = LIBP2P_GOSSIPSUB_PROTOCOL_MASK_ALL; + gs_config.preferred_protocol = LIBP2P_GOSSIPSUB_VERSION_12; + + if (libp2p_gossipsub_storage_size(&gs_config, &service->gossipsub_storage_len) != + LIBP2P_GOSSIPSUB_OK) { return -1; } - if (libp2p_gossipsub_start(gs) != LIBP2P_ERR_OK) { - libp2p_gossipsub_free(gs); + service->gossipsub_storage = calloc(1u, service->gossipsub_storage_len); + if (!service->gossipsub_storage) { return -1; } - service->gossipsub = gs; - if (lantern_gossipsub_validation_pool_start(service) != 0) { - lantern_gossipsub_service_reset(service); + if (libp2p_gossipsub_init( + service->gossipsub_storage, + service->gossipsub_storage_len, + &gs_config, + &service->gossipsub) + != LIBP2P_GOSSIPSUB_OK) { return -1; } - if (libp2p_gossipsub_set_message_delivery_callback( + if (libp2p_gossipsub_protocols( service->gossipsub, - lantern_gossipsub_message_delivery_cb, - service) - != LIBP2P_ERR_OK) { - lantern_gossipsub_service_reset(service); + service->gossipsub_protocols, + LIBP2P_GOSSIPSUB_PROTOCOL_COUNT, + &service->gossipsub_protocol_count) + != LIBP2P_GOSSIPSUB_OK) { return -1; } - if (subscribe_topic(service, service->block_topic) != 0) { - lantern_gossipsub_service_reset(service); - return -1; - } - lantern_log_info( - "gossip", - &(const struct lantern_log_metadata){.peer = config->devnet}, - "subscribed gossipsub topic=%s", - service->block_topic); - if (!service->subscribe_attestation_subnet) { - if (subscribe_topic(service, service->vote_topic) != 0) { - lantern_gossipsub_service_reset(service); + for (size_t i = 0; i < service->gossipsub_protocol_count; i++) { + if (lantern_libp2p_host_register_protocol(service->network, &service->gossipsub_protocols[i]) != 0) { return -1; } - lantern_log_info( - "gossip", - &(const struct lantern_log_metadata){.peer = config->devnet}, - "subscribed gossipsub topic=%s", - service->vote_topic); + } + if (subscribe_topic(service, service->block_topic) != 0) { + return -1; } if (service->subscribe_attestation_subnet) { if (subscribe_topic(service, service->vote_subnet_topic) != 0) { - lantern_gossipsub_service_reset(service); return -1; } - lantern_log_info( - "gossip", - &(const struct lantern_log_metadata){.peer = config->devnet}, - "subscribed gossipsub topic=%s", - service->vote_subnet_topic); + } else if (subscribe_topic(service, service->vote_topic) != 0) { + return -1; } if (subscribe_topic(service, service->aggregated_attestation_topic) != 0) { - lantern_gossipsub_service_reset(service); return -1; } - lantern_log_info( - "gossip", - &(const struct lantern_log_metadata){.peer = config->devnet}, - "subscribed gossipsub topic=%s", - service->aggregated_attestation_topic); + if (libp2p_gossipsub_start(service->gossipsub, service->network->host, lantern_libp2p_now_us()) != + LIBP2P_GOSSIPSUB_OK) { + return -1; + } + if (lantern_libp2p_host_register_event_handler(service->network, gossipsub_host_event, service) != 0 || + lantern_libp2p_host_register_drive_handler(service->network, gossipsub_drive, service) != 0) { + return -1; + } - lantern_log_info( - "network", - &(const struct lantern_log_metadata){.peer = config->devnet}, - "gossipsub topics ready"); + lantern_log_info("network", &(const struct lantern_log_metadata){.peer = config->devnet}, "gossipsub topics ready"); return 0; } @@ -1452,10 +859,9 @@ static int publish_payload( if (!service || !topic || !payload || payload_len == 0) { return -1; } - if (service->publish_hook) { - if (service->publish_hook(topic, payload, payload_len, service->publish_hook_user_data) != 0) { - return -1; - } + if (service->publish_hook && + service->publish_hook(topic, payload, payload_len, service->publish_hook_user_data) != 0) { + return -1; } if (service->loopback_only) { return 0; @@ -1463,53 +869,31 @@ static int publish_payload( if (!service->gossipsub) { return -1; } - libp2p_gossipsub_message_t message; - memset(&message, 0, sizeof(message)); - message.topic.struct_size = sizeof(message.topic); - message.topic.topic = topic; - message.data = payload; - message.data_len = payload_len; - libp2p_err_t err = libp2p_gossipsub_publish(service->gossipsub, &message); - if (err != LIBP2P_ERR_OK) { - lantern_log_warn( - "gossip", - NULL, - "gossipsub publish failed for topic %s (err=%d)", - topic, - (int)err); - return -1; - } - return 0; + libp2p_gossipsub_publish_t publish = { + .topic = {.data = (const uint8_t *)topic, .len = strlen(topic)}, + .data = {.data = payload, .len = payload_len}, + .message_id = {.data = NULL, .len = 0}, + .user_data = NULL, + }; + return libp2p_gossipsub_publish(service->gossipsub, &publish, NULL, 0, NULL) == LIBP2P_GOSSIPSUB_OK + ? 0 + : -1; } int lantern_gossipsub_service_publish_block( struct lantern_gossipsub_service *service, const LanternSignedBlock *block) { - if (!service || !block) { + if (!service || !block || service->block_topic[0] == '\0') { return -1; } - size_t raw_capacity = signed_block_min_capacity(block); - if (raw_capacity == 0) { - return -1; + uint8_t *payload = NULL; + size_t payload_len = 0; + int rc = encode_payload_block(block, &payload, &payload_len); + if (rc == 0) { + rc = publish_payload(service, service->block_topic, payload, payload_len); } - size_t max_compressed = 0; - /* Use raw snappy max size (no framing overhead) for gossip */ - if (lantern_snappy_max_compressed_size_raw(raw_capacity, &max_compressed) != LANTERN_SNAPPY_OK) { - return -1; - } - uint8_t *compressed = (uint8_t *)malloc(max_compressed); - if (!compressed) { - return -1; - } - size_t written = 0; - int encode_rc = lantern_gossip_encode_signed_block_snappy(block, compressed, max_compressed, &written); - if (encode_rc != 0 || written == 0) { - free(compressed); - return -1; - } - int publish_rc = publish_payload(service, service->block_topic, compressed, written); - free(compressed); - return publish_rc; + free(payload); + return rc; } int lantern_gossipsub_service_publish_vote_subnet( @@ -1519,48 +903,55 @@ int lantern_gossipsub_service_publish_vote_subnet( if (!service || !vote) { return -1; } - char topic[LANTERN_GOSSIPSUB_TOPIC_CAP]; - const char *publish_topic = NULL; - if (lantern_gossip_topic_format_subnet( - LANTERN_GOSSIP_TOPIC_VOTE_SUBNET, - service->topic_network_name, - subnet_id, - topic, - sizeof(topic)) - == 0) { + char topic[128]; + const char *publish_topic = service->vote_topic; + if (subnet_id != service->attestation_subnet_id || service->vote_subnet_topic[0] == '\0') { + if (lantern_gossip_topic_format_subnet( + LANTERN_GOSSIP_TOPIC_VOTE_SUBNET, + service->topic_network_name, + subnet_id, + topic, + sizeof(topic)) + != 0) { + return -1; + } publish_topic = topic; - } else if (service->vote_subnet_topic[0] != '\0' - && service->attestation_subnet_id == subnet_id) { + } else if (service->subscribe_attestation_subnet) { publish_topic = service->vote_subnet_topic; - } else { - return -1; } - size_t max_compressed = 0; - if (lantern_snappy_max_compressed_size_raw(LANTERN_SIGNED_VOTE_SSZ_SIZE, &max_compressed) != LANTERN_SNAPPY_OK) { - return -1; + uint8_t *payload = NULL; + size_t payload_len = 0; + int rc = encode_payload_vote(vote, &payload, &payload_len); + if (rc == 0) { + rc = publish_payload(service, publish_topic, payload, payload_len); } - uint8_t *compressed = (uint8_t *)malloc(max_compressed); - if (!compressed) { + free(payload); + return rc; +} + +int lantern_gossipsub_service_publish_aggregated_attestation( + struct lantern_gossipsub_service *service, + const LanternSignedAggregatedAttestation *attestation) { + if (!service || !attestation || service->aggregated_attestation_topic[0] == '\0') { return -1; } - size_t written = 0; - if (lantern_gossip_encode_signed_vote_snappy(vote, compressed, max_compressed, &written) != 0 || written == 0) { - free(compressed); - return -1; + uint8_t *payload = NULL; + size_t payload_len = 0; + int rc = encode_payload_aggregated_attestation(attestation, &payload, &payload_len); + if (rc == 0) { + rc = publish_payload(service, service->aggregated_attestation_topic, payload, payload_len); } - int publish_rc = publish_payload(service, publish_topic, compressed, written); - free(compressed); - return publish_rc; + free(payload); + return rc; } int lantern_gossipsub_service_subscribe_attestation_subnet( struct lantern_gossipsub_service *service, size_t subnet_id) { - if (!service || !service->gossipsub) { + if (!service) { return -1; } - - char topic[LANTERN_GOSSIPSUB_TOPIC_CAP]; + char topic[128]; if (lantern_gossip_topic_format_subnet( LANTERN_GOSSIP_TOPIC_VOTE_SUBNET, service->topic_network_name, @@ -1570,137 +961,72 @@ int lantern_gossipsub_service_subscribe_attestation_subnet( != 0) { return -1; } - - if (service->vote_subnet_topic[0] != '\0' && strcmp(topic, service->vote_subnet_topic) == 0) { - return 0; - } - for (size_t i = 0; i < service->extra_vote_subnet_topic_count; ++i) { - if (strcmp(topic, service->extra_vote_subnet_topics[i]) == 0) { - return 0; + if (service->gossipsub && !service->loopback_only) { + libp2p_gossipsub_topic_config_t topic_config = { + .topic = {.data = (const uint8_t *)topic, .len = strlen(topic)}, + .validation_mode = LIBP2P_GOSSIPSUB_VALIDATION_REQUIRE_APP, + .enable_idontwant = 1, + .idontwant_min_message_bytes = LIBP2P_GOSSIPSUB_DEFAULT_IDONTWANT_MIN_BYTES, + }; + if (libp2p_gossipsub_subscribe(service->gossipsub, &topic_config) != LIBP2P_GOSSIPSUB_OK) { + return -1; } } - - if (subscribe_topic(service, topic) != 0) { - return -1; - } - lantern_log_info( - "gossip", - &(const struct lantern_log_metadata){.peer = service->devnet}, - "subscribed gossipsub topic=%s", - topic); - - size_t new_count = service->extra_vote_subnet_topic_count + 1u; - char (*new_topics)[LANTERN_GOSSIPSUB_TOPIC_CAP] = - calloc(new_count, sizeof(*new_topics)); - libp2p_gossipsub_validator_handle_t **new_handles = - calloc(new_count, sizeof(*new_handles)); - if (!new_topics || !new_handles) { - free(new_topics); - free(new_handles); - (void)libp2p_gossipsub_unsubscribe(service->gossipsub, topic); - return -1; - } - if (service->extra_vote_subnet_topic_count > 0) { - memcpy( - new_topics, - service->extra_vote_subnet_topics, - service->extra_vote_subnet_topic_count * sizeof(*new_topics)); - memcpy( - new_handles, - service->extra_vote_subnet_validator_handles, - service->extra_vote_subnet_topic_count * sizeof(*new_handles)); + if (subnet_id == service->attestation_subnet_id) { + snprintf(service->vote_subnet_topic, sizeof(service->vote_subnet_topic), "%s", topic); } - memcpy(new_topics[service->extra_vote_subnet_topic_count], topic, sizeof(topic)); - new_handles[service->extra_vote_subnet_topic_count] = NULL; - - free(service->extra_vote_subnet_topics); - free(service->extra_vote_subnet_validator_handles); - service->extra_vote_subnet_topics = new_topics; - service->extra_vote_subnet_validator_handles = new_handles; - service->extra_vote_subnet_topic_count = new_count; return 0; } -int lantern_gossipsub_service_publish_aggregated_attestation( - struct lantern_gossipsub_service *service, - const LanternSignedAggregatedAttestation *attestation) { - if (!service || !attestation || service->aggregated_attestation_topic[0] == '\0') { - return -1; - } - size_t max_compressed = 0; - size_t max_raw = signed_aggregated_attestation_max_ssz_size(); - if (lantern_snappy_max_compressed_size_raw(max_raw, &max_compressed) != LANTERN_SNAPPY_OK) { - return -1; - } - uint8_t *compressed = (uint8_t *)malloc(max_compressed); - if (!compressed) { - return -1; - } - size_t written = 0; - if (lantern_gossip_encode_signed_aggregated_attestation_snappy( - attestation, - compressed, - max_compressed, - &written) - != 0 - || written == 0) { - free(compressed); - return -1; - } - int publish_rc = publish_payload(service, service->aggregated_attestation_topic, compressed, written); - free(compressed); - return publish_rc; +size_t lantern_gossipsub_service_mesh_peer_count(const struct lantern_gossipsub_service *service) { + (void)service; + return 0; } void lantern_gossipsub_service_set_publish_hook( struct lantern_gossipsub_service *service, int (*hook)(const char *topic, const uint8_t *payload, size_t payload_len, void *user_data), void *user_data) { - if (!service) { - return; + if (service) { + service->publish_hook = hook; + service->publish_hook_user_data = user_data; } - service->publish_hook = hook; - service->publish_hook_user_data = user_data; } void lantern_gossipsub_service_set_loopback_only( struct lantern_gossipsub_service *service, int loopback_only) { - if (!service) { - return; + if (service) { + service->loopback_only = loopback_only ? 1 : 0; } - service->loopback_only = loopback_only ? 1 : 0; } void lantern_gossipsub_service_set_block_handler( struct lantern_gossipsub_service *service, lantern_gossipsub_block_handler handler, void *user_data) { - if (!service) { - return; + if (service) { + service->block_handler = handler; + service->block_handler_user_data = user_data; } - service->block_handler = handler; - service->block_handler_user_data = user_data; } void lantern_gossipsub_service_set_vote_handler( struct lantern_gossipsub_service *service, lantern_gossipsub_vote_handler handler, void *user_data) { - if (!service) { - return; + if (service) { + service->vote_handler = handler; + service->vote_handler_user_data = user_data; } - service->vote_handler = handler; - service->vote_handler_user_data = user_data; } void lantern_gossipsub_service_set_aggregated_attestation_handler( struct lantern_gossipsub_service *service, lantern_gossipsub_aggregated_attestation_handler handler, void *user_data) { - if (!service) { - return; + if (service) { + service->aggregated_attestation_handler = handler; + service->aggregated_attestation_handler_user_data = user_data; } - service->aggregated_attestation_handler = handler; - service->aggregated_attestation_handler_user_data = user_data; } diff --git a/src/networking/libp2p.c b/src/networking/libp2p.c index 7892b9c..2a14d9e 100644 --- a/src/networking/libp2p.c +++ b/src/networking/libp2p.c @@ -1,227 +1,211 @@ #include "lantern/networking/libp2p.h" +#include "lantern/networking/enr.h" #include "lantern/support/log.h" +#include "lantern/support/time.h" + +#include "multiformats/multiaddr/multiaddr.h" +#include + +#include +#include #include #include #include +#include -#if defined(_WIN32) -#include -#include -#else -#include -#endif +static void *lantern_quic_malloc(size_t size, void *user_data) { + (void)user_data; + return malloc(size); +} -#include "lantern/networking/enr.h" +static void *lantern_quic_calloc(size_t nmemb, size_t size, void *user_data) { + (void)user_data; + return calloc(nmemb, size); +} -#include "libp2p/host.h" -#include "libp2p/log.h" -#include "libp2p/host_builder.h" -#include "libp2p/peerstore.h" -#include "multiformats/multiaddr/multiaddr.h" -#include "multiformats/multicodec/multicodec_codes.h" -#include "multiformats/unsigned_varint/unsigned_varint.h" -#include "peer_id/peer_id.h" -#include "peer_id/peer_id_proto.h" - -#define LANTERN_LIBP2P_KEY_TYPE_SECP256K1 2u - -static void lantern_libp2p_configure_logging(void); - -static enum LanternLogLevel lantern_libp2p_convert_level(libp2p_log_level_t level) -{ - switch (level) { - case LIBP2P_LOG_TRACE: - return LANTERN_LOG_LEVEL_TRACE; - case LIBP2P_LOG_DEBUG: - return LANTERN_LOG_LEVEL_DEBUG; - case LIBP2P_LOG_INFO: - return LANTERN_LOG_LEVEL_INFO; - case LIBP2P_LOG_WARN: - return LANTERN_LOG_LEVEL_WARN; - case LIBP2P_LOG_ERROR: - default: - return LANTERN_LOG_LEVEL_ERROR; +static void *lantern_quic_realloc(void *ptr, size_t size, void *user_data) { + (void)user_data; + return realloc(ptr, size); +} + +static void lantern_quic_free(void *ptr, void *user_data) { + (void)user_data; + free(ptr); +} + +static libp2p_quic_err_t lantern_quic_unix_time(uint64_t *out_unix_seconds, void *user_data) { + (void)user_data; + if (!out_unix_seconds) { + return LIBP2P_QUIC_ERR_INVALID_ARG; } + time_t now = time(NULL); + if (now < 0) { + return LIBP2P_QUIC_ERR_INTERNAL; + } + *out_unix_seconds = (uint64_t)now; + return LIBP2P_QUIC_OK; } -static void lantern_libp2p_log_writer(libp2p_log_level_t level, const char *msg, void *ud) -{ - (void)ud; - if (!msg) { - return; +static libp2p_ping_err_t lantern_ping_random(uint8_t *out, size_t out_len, void *user_data) { + return lantern_libp2p_quic_random(out, out_len, user_data) == LIBP2P_QUIC_OK + ? LIBP2P_PING_OK + : LIBP2P_PING_ERR_RANDOM; +} + +static libp2p_ping_err_t lantern_ping_time(libp2p_host_time_us_t *out_now_us, void *user_data) { + (void)user_data; + if (!out_now_us) { + return LIBP2P_PING_ERR_INVALID_ARG; } + *out_now_us = lantern_libp2p_now_us(); + return LIBP2P_PING_OK; +} - /* - * Parse libp2p's structured log format: [module=X file=Y line=Z func=W] message - * Extract module name for cleaner output, skip verbose file/line/func info - */ - const char *module = "libp2p"; - const char *clean_msg = msg; - char module_buf[64]; - - if (msg[0] == '[' && strncmp(msg, "[module=", 8) == 0) { - const char *mod_start = msg + 8; - const char *mod_end = strchr(mod_start, ' '); - if (mod_end && (size_t)(mod_end - mod_start) < sizeof(module_buf)) { - size_t mod_len = (size_t)(mod_end - mod_start); - memcpy(module_buf, mod_start, mod_len); - module_buf[mod_len] = '\0'; - module = module_buf; - } - /* Skip to the actual message after "] " */ - const char *bracket_end = strchr(msg, ']'); - if (bracket_end && bracket_end[1] == ' ') { - clean_msg = bracket_end + 2; - } +libp2p_host_time_us_t lantern_libp2p_now_us(void) { + double seconds = lantern_time_now_seconds(); + if (seconds <= 0.0) { + return 0; } + return (libp2p_host_time_us_t)(seconds * 1000000.0); +} - enum LanternLogLevel mapped = lantern_libp2p_convert_level(level); - switch (mapped) { - case LANTERN_LOG_LEVEL_TRACE: - lantern_log_trace(module, NULL, "%s", clean_msg); - break; - case LANTERN_LOG_LEVEL_DEBUG: - lantern_log_debug(module, NULL, "%s", clean_msg); - break; - case LANTERN_LOG_LEVEL_INFO: - lantern_log_info(module, NULL, "%s", clean_msg); - break; - case LANTERN_LOG_LEVEL_WARN: - lantern_log_warn(module, NULL, "%s", clean_msg); - break; - case LANTERN_LOG_LEVEL_ERROR: - default: - lantern_log_error(module, NULL, "%s", clean_msg); - break; +libp2p_quic_err_t lantern_libp2p_quic_random(uint8_t *out, size_t out_len, void *user_data) { + (void)user_data; + if (!out && out_len != 0) { + return LIBP2P_QUIC_ERR_INVALID_ARG; + } + if (out_len == 0) { + return LIBP2P_QUIC_OK; } + return RAND_bytes(out, out_len) == 1 ? LIBP2P_QUIC_OK : LIBP2P_QUIC_ERR_INTERNAL; } -static void lantern_libp2p_configure_logging(void) -{ - static bool writer_installed = false; - if (!writer_installed) { - libp2p_log_set_writer(lantern_libp2p_log_writer, NULL); - writer_installed = true; - } - - /* libp2p's INFO level is very verbose with internal module details, - * so we map lantern's INFO to libp2p's WARN to reduce noise */ - libp2p_log_level_t target = LIBP2P_LOG_ERROR; - switch (lantern_log_get_level()) { - case LANTERN_LOG_LEVEL_TRACE: - target = LIBP2P_LOG_TRACE; - break; - case LANTERN_LOG_LEVEL_DEBUG: - target = LIBP2P_LOG_DEBUG; - break; - case LANTERN_LOG_LEVEL_INFO: - target = LIBP2P_LOG_WARN; /* libp2p INFO is too verbose */ - break; - case LANTERN_LOG_LEVEL_WARN: - target = LIBP2P_LOG_WARN; - break; - case LANTERN_LOG_LEVEL_ERROR: - default: - target = LIBP2P_LOG_ERROR; - break; - } - libp2p_log_set_level(target); +libp2p_gossipsub_err_t lantern_libp2p_gossipsub_random( + uint8_t *out, + size_t out_len, + void *user_data) { + return lantern_libp2p_quic_random(out, out_len, user_data) == LIBP2P_QUIC_OK + ? LIBP2P_GOSSIPSUB_OK + : LIBP2P_GOSSIPSUB_ERR_RANDOM; } -int lantern_libp2p_encode_secp256k1_private_key_proto( - const uint8_t *secret, - size_t secret_len, - uint8_t **out, - size_t *out_len) { - if (!secret || secret_len != 32 || !out || !out_len) { +static int register_default_protocols(struct lantern_libp2p_host *state) { + libp2p_ping_config_t ping_config; + libp2p_identify_config_t identify_config; + static const uint8_t protocol_version[] = "ipfs/0.1.0"; + static const uint8_t agent_version[] = "lantern"; + + if (libp2p_ping_config_default(&ping_config) != LIBP2P_PING_OK) { + return -1; + } + ping_config.random_fn = lantern_ping_random; + ping_config.time_fn = lantern_ping_time; + if (libp2p_ping_init(&state->ping, &ping_config) != LIBP2P_PING_OK) { + return -1; + } + if (libp2p_ping_protocol(&state->ping, &state->default_protocols[state->default_protocol_count]) != + LIBP2P_PING_OK) { + return -1; + } + state->default_protocol_count++; + + if (libp2p_identify_config_default(&identify_config) != LIBP2P_IDENTIFY_OK) { return -1; } - uint8_t type_buf[10]; - uint8_t len_buf[10]; - size_t type_written = 0; - size_t len_written = 0; - if (unsigned_varint_encode(LANTERN_LIBP2P_KEY_TYPE_SECP256K1, type_buf, sizeof(type_buf), &type_written) != UNSIGNED_VARINT_OK) { + identify_config.local_message.protocol_version.data = protocol_version; + identify_config.local_message.protocol_version.len = sizeof(protocol_version) - 1u; + identify_config.local_message.agent_version.data = agent_version; + identify_config.local_message.agent_version.len = sizeof(agent_version) - 1u; + identify_config.local_message.public_key.data = state->host_identity_storage.public_key_message; + identify_config.local_message.public_key.len = state->host_identity_storage.public_key_message_len; + if (libp2p_identify_init(&state->identify, &identify_config) != LIBP2P_IDENTIFY_OK) { return -1; } - if (unsigned_varint_encode((uint64_t)secret_len, len_buf, sizeof(len_buf), &len_written) != UNSIGNED_VARINT_OK) { + if (libp2p_identify_protocol( + &state->identify, + &state->default_protocols[state->default_protocol_count]) + != LIBP2P_IDENTIFY_OK) { return -1; } - size_t total = 1 + type_written + 1 + len_written + secret_len; - uint8_t *buffer = (uint8_t *)malloc(total); - if (!buffer) { + state->default_protocol_count++; + if (libp2p_identify_push_protocol( + &state->identify, + &state->default_protocols[state->default_protocol_count]) + != LIBP2P_IDENTIFY_OK) { return -1; } - size_t offset = 0; - buffer[offset++] = 0x08; - memcpy(buffer + offset, type_buf, type_written); - offset += type_written; - buffer[offset++] = 0x12; - memcpy(buffer + offset, len_buf, len_written); - offset += len_written; - memcpy(buffer + offset, secret, secret_len); - *out = buffer; - *out_len = total; + state->default_protocol_count++; + + for (size_t i = 0; i < state->default_protocol_count; i++) { + if (lantern_libp2p_host_register_protocol(state, &state->default_protocols[i]) != 0) { + return -1; + } + } return 0; } -static int multiaddr_has_protocol(const multiaddr_t *ma, uint64_t code) { - if (!ma) { - return 0; +static void drain_protocol_events(struct lantern_libp2p_host *state) { + libp2p_ping_event_t ping_event; + while (libp2p_ping_next_event(&state->ping, &ping_event) == LIBP2P_PING_OK) { + if (ping_event.type == LIBP2P_PING_EVENT_ERROR) { + lantern_log_debug("network", NULL, "libp2p ping event error (%d)", (int)ping_event.reason); + } } - size_t protocols = multiaddr_nprotocols(ma); - for (size_t idx = 0; idx < protocols; idx++) { - uint64_t current = 0; - if (multiaddr_get_protocol_code(ma, idx, ¤t) == 0 && current == code) { - return 1; + + libp2p_identify_event_t identify_event; + while (libp2p_identify_next_event(&state->identify, &identify_event) == LIBP2P_IDENTIFY_OK) { + if (identify_event.type == LIBP2P_IDENTIFY_EVENT_ERROR) { + lantern_log_debug("network", NULL, "libp2p identify event error (%d)", (int)identify_event.reason); } } - return 0; } -static int multiaddr_is_quic(const multiaddr_t *ma) { - if (!ma) { - return 0; +static void drain_host_events(struct lantern_libp2p_host *state) { + libp2p_host_event_t event; + while (libp2p_host_next_event(state->host, &event) == LIBP2P_HOST_OK) { + for (size_t i = 0; i < state->event_handler_count; i++) { + state->event_handlers[i](state, &event, state->event_handler_user_data[i]); + } } - return multiaddr_has_protocol(ma, MULTICODEC_QUIC_V1) || multiaddr_has_protocol(ma, MULTICODEC_QUIC); } -static int peer_id_write_legacy_base58(const peer_id_t *peer_id, char *buffer, size_t buffer_len) { - if (!peer_id || !buffer || buffer_len == 0) { - return -1; - } - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - peer_id, - PEER_ID_TEXT_LEGACY_BASE58, - buffer, - buffer_len, - &written); - if (rc != PEER_ID_OK) { - buffer[0] = '\0'; - return -1; +static void *lantern_libp2p_drive_thread(void *arg) { + struct lantern_libp2p_host *state = (struct lantern_libp2p_host *)arg; + const struct timespec pause = {.tv_sec = 0, .tv_nsec = 5000000}; + + while (__atomic_load_n(&state->stop_flag, __ATOMIC_RELAXED) == 0) { + libp2p_host_time_us_t now = lantern_libp2p_now_us(); + (void)libp2p_host_drive(state->host, now, LIBP2P_HOST_READY_ALL, NULL); + drain_host_events(state); + for (size_t i = 0; i < state->drive_handler_count; i++) { + state->drive_handlers[i](state, now, state->drive_handler_user_data[i]); + } + drain_protocol_events(state); + (void)nanosleep(&pause, NULL); } - return (int)written; + return NULL; } void lantern_libp2p_host_init(struct lantern_libp2p_host *state) { if (!state) { return; } - state->host = NULL; - state->started = 0; + memset(state, 0, sizeof(*state)); } void lantern_libp2p_host_stop(struct lantern_libp2p_host *state) { - if (!state || !state->host || !state->started) { + if (!state || !state->host) { return; } - if (libp2p_host_stop(state->host) != 0) { - lantern_log_warn( - "network", - &(const struct lantern_log_metadata){.peer = "local"}, - "libp2p_host_stop failed"); + if (state->started) { + (void)libp2p_host_close(state->host, 0); + } + __atomic_store_n(&state->stop_flag, 1, __ATOMIC_RELAXED); + if (state->drive_thread_started) { + (void)pthread_join(state->drive_thread, NULL); + state->drive_thread_started = 0; } state->started = 0; } @@ -230,179 +214,239 @@ void lantern_libp2p_host_reset(struct lantern_libp2p_host *state) { if (!state) { return; } + lantern_libp2p_host_stop(state); if (state->host) { - lantern_libp2p_host_stop(state); - libp2p_host_free(state->host); + libp2p_host_deinit(state->host); state->host = NULL; } - state->started = 0; + free(state->host_storage); + lantern_libp2p_host_init(state); } -int lantern_libp2p_host_start(struct lantern_libp2p_host *state, const struct lantern_libp2p_config *config) { +int lantern_libp2p_host_prepare(struct lantern_libp2p_host *state, const struct lantern_libp2p_config *config) { if (!state || !config || !config->listen_multiaddr || !config->secp256k1_secret) { return -1; } - if (config->secret_len != 32) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p expects 32-byte secp256k1 secrets"); + if (config->secret_len != LIBP2P_PEER_ID_SECP256K1_PRIVATE_KEY_BYTES) { + lantern_log_error("network", NULL, "libp2p expects 32-byte secp256k1 secrets"); return -1; } lantern_libp2p_host_reset(state); - lantern_libp2p_configure_logging(); + if (libp2p_multiaddr_from_string( + config->listen_multiaddr, + strlen(config->listen_multiaddr), + state->listen_multiaddr, + sizeof(state->listen_multiaddr), + &state->listen_multiaddr_len) + != LIBP2P_MULTIADDR_OK) { + lantern_log_error( + "network", + &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, + "invalid listen multiaddr"); + return -1; + } - libp2p_host_builder_t *builder = libp2p_host_builder_new(); - if (!builder) { + if (libp2p_host_secp256k1_identity_init( + &state->host_identity_storage, + config->secp256k1_secret, + config->secret_len, + &state->host_identity) + != LIBP2P_HOST_OK) { return -1; } + memcpy(state->local_peer_id, state->host_identity_storage.peer_id, state->host_identity_storage.peer_id_len); + state->local_peer_id_len = state->host_identity_storage.peer_id_len; - int rc = 0; - int addr_err = 0; - multiaddr_t *ma = multiaddr_new_from_str(config->listen_multiaddr, &addr_err); - if (!ma || addr_err != 0) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "invalid listen multiaddr '%s' (err=%d)", - config->listen_multiaddr, - addr_err); - multiaddr_free(ma); - libp2p_host_builder_free(builder); + uint64_t unix_now = 0; + if (lantern_quic_unix_time(&unix_now, NULL) != LIBP2P_QUIC_OK) { return -1; } - if (!multiaddr_is_quic(ma)) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "listen multiaddr '%s' must include /quic(_v1)", - config->listen_multiaddr); - multiaddr_free(ma); - libp2p_host_builder_free(builder); + libp2p_quic_host_key_t host_key = { + .type = LIBP2P_QUIC_HOST_KEY_SECP256K1, + .private_key = config->secp256k1_secret, + .private_key_len = config->secret_len, + .public_key_message = state->host_identity_storage.public_key_message, + .public_key_message_len = state->host_identity_storage.public_key_message_len, + }; + libp2p_quic_certificate_config_t certificate_config = { + .certificate_key_type = LIBP2P_QUIC_CERT_KEY_ECDSA_P256, + .not_before_unix_seconds = unix_now > 3600u ? unix_now - 3600u : 0u, + .not_after_unix_seconds = unix_now + (uint64_t)(365u * 24u * 60u * 60u), + .random_fn = lantern_libp2p_quic_random, + .random_user_data = NULL, + }; + size_t cert_len = 0; + size_t key_len = 0; + if (libp2p_quic_identity_write_certificate_der( + &host_key, + &certificate_config, + state->certificate_der, + sizeof(state->certificate_der), + &cert_len, + state->certificate_key_der, + sizeof(state->certificate_key_der), + &key_len) + != LIBP2P_QUIC_OK) { return -1; } - multiaddr_free(ma); + state->quic_identity.certificate_der = state->certificate_der; + state->quic_identity.certificate_der_len = cert_len; + state->quic_identity.certificate_private_key_der = state->certificate_key_der; + state->quic_identity.certificate_private_key_der_len = key_len; + state->quic_identity.peer_id = state->local_peer_id; + state->quic_identity.peer_id_len = state->local_peer_id_len; - int b_rc = libp2p_host_builder_listen_addr(builder, config->listen_multiaddr); - if (b_rc != 0) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p listen addr %s failed (%d)", - config->listen_multiaddr, - b_rc); - rc = -1; - } - if (rc == 0) { - b_rc = libp2p_host_builder_transport(builder, "quic"); - if (b_rc != 0) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p transport 'quic' setup failed (%d)", - b_rc); - rc = -1; - } + state->allocator.malloc_fn = lantern_quic_malloc; + state->allocator.calloc_fn = lantern_quic_calloc; + state->allocator.realloc_fn = lantern_quic_realloc; + state->allocator.free_fn = lantern_quic_free; + state->allocator.user_data = NULL; + + if (libp2p_quic_service_config_default(&state->quic_config) != LIBP2P_QUIC_OK) { + return -1; } - if (rc == 0) { - b_rc = libp2p_host_builder_multistream(builder, 15000, true); - if (b_rc != 0) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p multistream setup failed (%d)", - b_rc); - rc = -1; - } + state->quic_config.endpoint.role = LIBP2P_QUIC_ROLE_CLIENT_SERVER; + state->quic_config.endpoint.identity = state->quic_identity; + state->quic_config.endpoint.allocator = state->allocator; + state->quic_config.endpoint.random_fn = lantern_libp2p_quic_random; + state->quic_config.endpoint.unix_time_fn = lantern_quic_unix_time; + state->quic_config.endpoint.max_connections = 64u; + state->quic_config.endpoint.max_incoming_connections = 64u; + state->quic_config.endpoint.max_outgoing_connections = 64u; + state->quic_config.endpoint.max_bidi_streams = 128u; + state->quic_config.max_rx_datagrams_per_drive = LIBP2P_QUIC_SERVICE_DEFAULT_DATAGRAM_BUDGET; + state->quic_config.max_tx_datagrams_per_drive = LIBP2P_QUIC_SERVICE_DEFAULT_DATAGRAM_BUDGET; + + libp2p_host_config_t host_config; + if (libp2p_host_config_default(&host_config) != LIBP2P_HOST_OK) { + return -1; } - if (rc == 0) { - uint32_t flags = LIBP2P_HOST_F_AUTO_IDENTIFY_INBOUND; - if (config->allow_outbound_identify) { - flags |= LIBP2P_HOST_F_AUTO_IDENTIFY_OUTBOUND; - } - if (flags != 0u) { - b_rc = libp2p_host_builder_flags(builder, flags); - if (b_rc != 0) { - lantern_log_warn( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p host flags setup failed (%d)", - b_rc); - } - } + host_config.identity = state->host_identity; + host_config.listen_multiaddr = state->listen_multiaddr; + host_config.listen_multiaddr_len = state->listen_multiaddr_len; + host_config.transport = libp2p_host_quic_transport(); + host_config.transport_config = &state->quic_config; + host_config.max_protocols = LANTERN_LIBP2P_MAX_PROTOCOLS; + host_config.max_connections = 64u; + host_config.max_streams_per_conn = 128u; + host_config.max_pending_dials = 64u; + host_config.max_pending_stream_opens = 128u; + host_config.event_capacity = 256u; + host_config.max_negotiation_steps = 128u; + + if (libp2p_host_storage_size(&host_config, &state->host_storage_len) != LIBP2P_HOST_OK) { + return -1; } - - libp2p_host_t *host = NULL; - int build_rc = 0; - if (rc == 0) { - build_rc = libp2p_host_builder_build(builder, &host); - if (build_rc != 0 || !host) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p host builder failed (%d)", - build_rc); - rc = -1; - } + state->host_storage = calloc(1u, state->host_storage_len); + if (!state->host_storage) { + return -1; } - if (rc != 0) { - rc = -1; + if (libp2p_host_init(state->host_storage, state->host_storage_len, &host_config, &state->host) != + LIBP2P_HOST_OK) { + return -1; } - libp2p_host_builder_free(builder); - builder = NULL; - if (rc != 0) { + return register_default_protocols(state); +} + +int lantern_libp2p_host_register_protocol( + struct lantern_libp2p_host *state, + const libp2p_host_protocol_t *protocol) { + if (!state || !state->host || !protocol || state->started) { return -1; } + return libp2p_host_handle(state->host, protocol) == LIBP2P_HOST_OK ? 0 : -1; +} - uint8_t *identity_pb = NULL; - size_t identity_len = 0; - if (lantern_libp2p_encode_secp256k1_private_key_proto( - config->secp256k1_secret, - config->secret_len, - &identity_pb, - &identity_len) - != 0) { - libp2p_host_free(host); +int lantern_libp2p_host_register_event_handler( + struct lantern_libp2p_host *state, + lantern_libp2p_host_event_handler handler, + void *user_data) { + if (!state || !handler || state->event_handler_count >= LANTERN_LIBP2P_MAX_EVENT_HANDLERS) { return -1; } + size_t index = state->event_handler_count++; + state->event_handlers[index] = handler; + state->event_handler_user_data[index] = user_data; + return 0; +} - if (libp2p_host_set_private_key(host, identity_pb, identity_len) != 0) { - free(identity_pb); - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p failed to set private key"); - libp2p_host_free(host); +int lantern_libp2p_host_register_drive_handler( + struct lantern_libp2p_host *state, + lantern_libp2p_drive_handler handler, + void *user_data) { + if (!state || !handler || state->drive_handler_count >= LANTERN_LIBP2P_MAX_DRIVE_HANDLERS) { return -1; } - free(identity_pb); + size_t index = state->drive_handler_count++; + state->drive_handlers[index] = handler; + state->drive_handler_user_data[index] = user_data; + return 0; +} - if (libp2p_host_start(host) != 0) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p host start failed"); - libp2p_host_free(host); +int lantern_libp2p_host_launch(struct lantern_libp2p_host *state) { + if (!state || !state->host || state->started) { + return -1; + } + if (libp2p_host_start(state->host) != LIBP2P_HOST_OK) { + lantern_log_error("network", NULL, "libp2p host start failed"); + return -1; + } + __atomic_store_n(&state->stop_flag, 0, __ATOMIC_RELAXED); + if (pthread_create(&state->drive_thread, NULL, lantern_libp2p_drive_thread, state) != 0) { + (void)libp2p_host_close(state->host, 0); return -1; } + state->drive_thread_started = 1; + state->started = 1; + lantern_log_info("network", NULL, "libp2p host started"); + return 0; +} - lantern_log_info( - "network", - &(const struct lantern_log_metadata){.peer = config->listen_multiaddr}, - "libp2p host started"); +int lantern_libp2p_host_start(struct lantern_libp2p_host *state, const struct lantern_libp2p_config *config) { + if (lantern_libp2p_host_prepare(state, config) != 0) { + return -1; + } + return lantern_libp2p_host_launch(state); +} - state->host = host; - state->started = 1; +int lantern_peer_id_from_text(const char *text, struct lantern_peer_id *out_peer) { + if (!text || !out_peer) { + return -1; + } + size_t written = 0; + if (libp2p_peer_id_from_string(text, strlen(text), out_peer->bytes, sizeof(out_peer->bytes), &written) != + LIBP2P_PEER_ID_OK) { + return -1; + } + out_peer->len = written; return 0; } +int lantern_peer_id_to_text(const struct lantern_peer_id *peer, char *buffer, size_t buffer_len) { + if (!peer || !buffer || buffer_len == 0) { + return -1; + } + size_t written = 0; + if (libp2p_peer_id_to_string(peer->bytes, peer->len, buffer, buffer_len, &written) != + LIBP2P_PEER_ID_OK || + written >= buffer_len) { + buffer[0] = '\0'; + return -1; + } + buffer[written] = '\0'; + return (int)written; +} + +int lantern_peer_id_equal(const struct lantern_peer_id *left, const struct lantern_peer_id *right) { + return left && right && left->len == right->len && memcmp(left->bytes, right->bytes, left->len) == 0; +} + static int extract_ipv4_multiaddr( const struct lantern_enr_record *record, char *buffer, - size_t buffer_len, - uint16_t *port) { + size_t buffer_len) { const struct lantern_enr_key_value *ip = lantern_enr_record_find(record, "ip"); const struct lantern_enr_key_value *port_field = lantern_enr_record_find(record, "quic"); if (!port_field) { @@ -417,20 +461,13 @@ static int extract_ipv4_multiaddr( return -1; } int written = snprintf(buffer, buffer_len, "/ip4/%s/udp/%u/quic-v1", ip_text, (unsigned)parsed_port); - if (written < 0 || (size_t)written >= buffer_len) { - return -1; - } - if (port) { - *port = parsed_port; - } - return 0; + return written >= 0 && (size_t)written < buffer_len ? 0 : -1; } static int extract_ipv6_multiaddr( const struct lantern_enr_record *record, char *buffer, - size_t buffer_len, - uint16_t *port) { + size_t buffer_len) { const struct lantern_enr_key_value *ip = lantern_enr_record_find(record, "ip6"); const struct lantern_enr_key_value *port_field = lantern_enr_record_find(record, "quic6"); if (!port_field) { @@ -451,86 +488,89 @@ static int extract_ipv6_multiaddr( return -1; } int written = snprintf(buffer, buffer_len, "/ip6/%s/udp/%u/quic-v1", ip_text, (unsigned)parsed_port); - if (written < 0 || (size_t)written >= buffer_len) { - return -1; - } - if (port) { - *port = parsed_port; - } - return 0; + return written >= 0 && (size_t)written < buffer_len ? 0 : -1; } int lantern_libp2p_enr_to_multiaddr( const struct lantern_enr_record *record, char *buffer, size_t buffer_len, - peer_id_t **peer_id) { - if (!record || !buffer || !peer_id) { + struct lantern_peer_id *peer_id) { + if (!record || !buffer || buffer_len == 0 || !peer_id) { return -1; } - *peer_id = NULL; + const struct lantern_enr_key_value *pubkey = lantern_enr_record_find(record, "secp256k1"); if (!pubkey || !pubkey->value || pubkey->value_len == 0) { return -1; } - - uint8_t *pubkey_pb = NULL; - size_t pubkey_pb_len = 0; - peer_id_error_t perr = peer_id_build_public_key_protobuf( - LANTERN_LIBP2P_KEY_TYPE_SECP256K1, - pubkey->value, - pubkey->value_len, - &pubkey_pb, - &pubkey_pb_len); - if (perr != PEER_ID_OK) { - return -1; - } - peer_id_t *derived_peer_id = NULL; - perr = peer_id_new_from_public_key_pb(pubkey_pb, pubkey_pb_len, &derived_peer_id); - free(pubkey_pb); - if (perr != PEER_ID_OK || !derived_peer_id) { + size_t peer_len = 0; + if (libp2p_peer_id_from_secp256k1_public_key( + pubkey->value, + pubkey->value_len, + peer_id->bytes, + sizeof(peer_id->bytes), + &peer_len) + != LIBP2P_PEER_ID_OK) { return -1; } + peer_id->len = peer_len; char base_addr[128]; - if (extract_ipv4_multiaddr(record, base_addr, sizeof(base_addr), NULL) != 0) { - if (extract_ipv6_multiaddr(record, base_addr, sizeof(base_addr), NULL) != 0) { - peer_id_free(derived_peer_id); - return -1; - } + if (extract_ipv4_multiaddr(record, base_addr, sizeof(base_addr)) != 0 && + extract_ipv6_multiaddr(record, base_addr, sizeof(base_addr)) != 0) { + return -1; } - char peer_text[128]; - int pid_written = peer_id_write_legacy_base58(derived_peer_id, peer_text, sizeof(peer_text)); - if (pid_written < 0) { - peer_id_free(derived_peer_id); + char peer_text[LANTERN_LIBP2P_PEER_TEXT_MAX_BYTES]; + if (lantern_peer_id_to_text(peer_id, peer_text, sizeof(peer_text)) < 0) { return -1; } - int written = snprintf(buffer, buffer_len, "%s/p2p/%s", base_addr, peer_text); - if (written < 0 || (size_t)written >= buffer_len) { - peer_id_free(derived_peer_id); + return written >= 0 && (size_t)written < buffer_len ? 0 : -1; +} + +int lantern_libp2p_validate_enr_peer(const struct lantern_enr_record *record) { + if (!record) { + return -1; + } + struct lantern_peer_id peer_id; + char multiaddr_text[LANTERN_LIBP2P_MULTIADDR_MAX_BYTES]; + uint8_t multiaddr[LANTERN_LIBP2P_MULTIADDR_MAX_BYTES]; + size_t multiaddr_len = 0; + if (lantern_libp2p_enr_to_multiaddr(record, multiaddr_text, sizeof(multiaddr_text), &peer_id) != 0) { + return -1; + } + if (libp2p_multiaddr_from_string( + multiaddr_text, + strlen(multiaddr_text), + multiaddr, + sizeof(multiaddr), + &multiaddr_len) + != LIBP2P_MULTIADDR_OK) { return -1; } - *peer_id = derived_peer_id; return 0; } -int lantern_libp2p_host_add_enr_peer( +int lantern_libp2p_host_dial_multiaddr( struct lantern_libp2p_host *state, - const struct lantern_enr_record *record, - int ttl_ms) { - if (!state || !state->host || !record) { + const char *multiaddr_text) { + if (!state || !state->host || !multiaddr_text || multiaddr_text[0] == '\0') { return -1; } - peer_id_t *peer_id = NULL; - char multiaddr[256]; - if (lantern_libp2p_enr_to_multiaddr(record, multiaddr, sizeof(multiaddr), &peer_id) != 0) { + uint8_t multiaddr[LANTERN_LIBP2P_MULTIADDR_MAX_BYTES]; + size_t multiaddr_len = 0; + if (libp2p_multiaddr_from_string( + multiaddr_text, + strlen(multiaddr_text), + multiaddr, + sizeof(multiaddr), + &multiaddr_len) + != LIBP2P_MULTIADDR_OK) { return -1; } - - int ttl = ttl_ms > 0 ? ttl_ms : LANTERN_LIBP2P_DEFAULT_PEER_TTL_MS; - int rc = libp2p_host_add_peer_addr_str(state->host, peer_id, multiaddr, ttl); - peer_id_free(peer_id); - return rc; + libp2p_host_dial_t *dial = NULL; + libp2p_host_err_t err = libp2p_host_dial(state->host, multiaddr, multiaddr_len, NULL, &dial); + return err == LIBP2P_HOST_OK || err == LIBP2P_HOST_ERR_LIMIT ? 0 : -1; } diff --git a/src/networking/messages.c b/src/networking/messages.c index bed994e..e41a604 100644 --- a/src/networking/messages.c +++ b/src/networking/messages.c @@ -7,7 +7,7 @@ #include "lantern/consensus/ssz.h" #include "lantern/consensus/state.h" -#include "ssz_constants.h" +#include "ssz.h" #include "lantern/encoding/snappy.h" #include "lantern/networking/reqresp_service.h" #include "lantern/support/log.h" @@ -176,11 +176,11 @@ static int encode_status_raw( } size_t offset = 0; size_t checkpoint_written = 0; - if (lantern_ssz_encode_checkpoint(&status->finalized, out + offset, out_len - offset, &checkpoint_written) != 0) { + if (lantern_ssz_encode_checkpoint(&status->finalized, out + offset, out_len - offset, &checkpoint_written) != SSZ_SUCCESS) { return -1; } offset += checkpoint_written; - if (lantern_ssz_encode_checkpoint(&status->head, out + offset, out_len - offset, &checkpoint_written) != 0) { + if (lantern_ssz_encode_checkpoint(&status->head, out + offset, out_len - offset, &checkpoint_written) != SSZ_SUCCESS) { return -1; } offset += checkpoint_written; @@ -208,7 +208,7 @@ int lantern_network_status_decode( log_status_payload_debug("status decode invalid_len", data, data_len); return -1; } - if (lantern_ssz_decode_checkpoint(&status->finalized, data, LANTERN_CHECKPOINT_SSZ_SIZE) != 0) { + if (lantern_ssz_decode_checkpoint(&status->finalized, data, LANTERN_CHECKPOINT_SSZ_SIZE) != SSZ_SUCCESS) { log_status_payload_debug("status decode finalized_failed", data, data_len); return -1; } @@ -349,10 +349,6 @@ int lantern_network_blocks_by_root_request_decode( } } - /* Legacy compatibility: some older peers encoded only packed roots bytes. */ - if (data_len % LANTERN_ROOT_SIZE == 0) { - return decode_blocks_by_root_list(req, data, data_len); - } return -1; } @@ -480,7 +476,7 @@ int lantern_network_signed_block_list_encode( } size_t block_written = 0; - if (lantern_ssz_encode_signed_block_canonical( + if (lantern_ssz_encode_signed_block( &resp->blocks[i], buffer + payload_cursor, capacity - payload_cursor, @@ -597,7 +593,7 @@ int lantern_network_signed_block_list_decode( return -1; } LanternSignedBlock *entry = &resp->blocks[i]; - if (lantern_ssz_decode_signed_block_strict(entry, payload_region + start_offset, span) != 0) { + if (lantern_ssz_decode_signed_block(entry, payload_region + start_offset, span) != SSZ_SUCCESS) { lantern_signed_block_list_reset(resp); return -1; } diff --git a/src/networking/reqresp_service.c b/src/networking/reqresp_service.c index ec88d71..fe1f801 100644 --- a/src/networking/reqresp_service.c +++ b/src/networking/reqresp_service.c @@ -1,2606 +1,1639 @@ #include "lantern/networking/reqresp_service.h" #include -#include -#include -#include -#include -#include #include #include -#include #include "lantern/encoding/snappy.h" #include "lantern/consensus/ssz.h" #include "lantern/support/log.h" #include "lantern/support/strings.h" -#include "lantern/support/time.h" - -#include "libp2p/events.h" -#include "libp2p/host.h" -#include "libp2p/protocol.h" -#include "libp2p/protocol_listen.h" -#include "libp2p/stream.h" -#include "libp2p/stream_internal.h" -#include "libp2p/errors.h" #include "multiformats/unsigned_varint/unsigned_varint.h" -#include "peer_id/peer_id.h" - -#include "ssz_constants.h" -#include "../core/client_network_internal.h" - -uint32_t lantern_reqresp_stall_timeout_ms(void) { - static uint32_t timeout_ms = LANTERN_REQRESP_STALL_TIMEOUT_MS; - static bool initialized = false; - if (initialized) { - return timeout_ms; - } - initialized = true; - const char *env = getenv("LANTERN_REQRESP_STALL_TIMEOUT_MS"); - if (env && env[0] != '\0') { - char *end = NULL; - unsigned long parsed = strtoul(env, &end, 10); - if (end && *end == '\0' && parsed > 0 && parsed <= UINT32_MAX) { - timeout_ms = (uint32_t)parsed; - } else { - lantern_log_warn( - "reqresp", - NULL, - "invalid LANTERN_REQRESP_STALL_TIMEOUT_MS value=%s (using default=%u)", - env, - LANTERN_REQRESP_STALL_TIMEOUT_MS); - } - } - return timeout_ms; -} - -static uint64_t reqresp_trace_id_next(void) { - static uint64_t seq = 0; - return __atomic_fetch_add(&seq, 1u, __ATOMIC_RELAXED); -} - -struct status_stream_ctx { - struct lantern_reqresp_service *service; - libp2p_stream_t *stream; - const char *protocol_id; - uint64_t debug_trace_id; +enum { + LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES = 4u, + LANTERN_SNAPPY_FRAME_CRC_BYTES = 4u, + LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN = 6u, + LANTERN_SNAPPY_FRAME_STREAM_HEADER_BYTES = + LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES + LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN, + LANTERN_SNAPPY_FRAME_CHUNK_COMPRESSED = 0x00u, + LANTERN_SNAPPY_FRAME_CHUNK_UNCOMPRESSED = 0x01u, + LANTERN_SNAPPY_FRAME_CHUNK_PADDING_START = 0x02u, + LANTERN_SNAPPY_FRAME_CHUNK_PADDING_END = 0x7fu, + LANTERN_SNAPPY_FRAME_CHUNK_STREAM_IDENTIFIER = 0xffu, }; -struct blocks_stream_ctx { - struct lantern_reqresp_service *service; - libp2p_stream_t *stream; - const char *protocol_id; +static const uint8_t LANTERN_SNAPPY_FRAME_MAGIC[LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN] = { + 's', + 'N', + 'a', + 'P', + 'p', + 'Y', }; -struct status_request_ctx { - struct lantern_reqresp_service *service; - peer_id_t *peer_id; - char peer_text[128]; - const char *protocol_id; - uint64_t debug_trace_id; +struct reqresp_buffer { + uint8_t *data; + size_t len; + size_t cap; }; -struct status_request_worker_args { - struct status_request_ctx *ctx; - libp2p_stream_t *stream; +struct lantern_reqresp_exchange { + struct lantern_reqresp_service *service; + enum lantern_reqresp_protocol_kind kind; + int outbound; + libp2p_host_conn_t *conn; + libp2p_host_t *host; + libp2p_host_stream_t *stream; + char peer_id_text[128]; + uint8_t *write_buf; + size_t write_len; + size_t write_off; + struct reqresp_buffer read_buf; + LanternRoot *roots; + size_t root_count; + size_t responses_received; + uint64_t request_id; + int completed; + int request_complete; + struct lantern_reqresp_exchange *next; }; -static void log_stream_error(const char *phase, const char *protocol_id, const char *peer_id); -static void status_request_notify_failure( - struct lantern_reqresp_service *service, - const char *peer_text, - int error); -static int status_request_launch( - struct lantern_reqresp_service *service, - const peer_id_t *peer_id, - const char *peer_id_text, - bool notify_on_failure); -static void lantern_reqresp_service_clear(struct lantern_reqresp_service *service) { - if (!service) { - return; - } - service->host = NULL; - service->callbacks.context = NULL; - service->callbacks.build_status = NULL; - service->callbacks.handle_status = NULL; - service->callbacks.status_failure = NULL; - service->callbacks.collect_blocks = NULL; - service->status_server = NULL; - service->blocks_server = NULL; - service->event_subscription = NULL; -} - -static int write_legacy_peer_id_text(const peer_id_t *peer, char *buffer, size_t length) { - if (!peer || !buffer || length == 0) { - return -1; - } - size_t written = 0; - peer_id_error_t rc = peer_id_text_write( - peer, - PEER_ID_TEXT_LEGACY_BASE58, - buffer, - length, - &written); - if (rc != PEER_ID_OK) { - buffer[0] = '\0'; - return -1; +static uint8_t normalize_response_code(uint8_t code) { + if (code <= LANTERN_REQRESP_RESPONSE_RESOURCE_UNAVAILABLE) { + return code; } - return (int)written; + return (code & 0x80u) ? LANTERN_REQRESP_RESPONSE_INVALID_REQUEST : LANTERN_REQRESP_RESPONSE_SERVER_ERROR; } -void lantern_reqresp_service_init(struct lantern_reqresp_service *service) { - if (!service) { +static void reqresp_buffer_reset(struct reqresp_buffer *buffer) { + if (!buffer) { return; } - memset(service, 0, sizeof(*service)); + free(buffer->data); + buffer->data = NULL; + buffer->len = 0; + buffer->cap = 0; } -void lantern_reqresp_service_reset(struct lantern_reqresp_service *service) { - if (!service) { - return; +static int reqresp_buffer_reserve(struct reqresp_buffer *buffer, size_t required) { + if (!buffer) { + return -1; } - - struct libp2p_host *host = service->host; - if (service->event_subscription && host) { - libp2p_event_unsubscribe(host, service->event_subscription); + if (required <= buffer->cap) { + return 0; } - - if (service->status_server && host) { - (void)libp2p_host_unlisten(host, service->status_server); + size_t next = buffer->cap == 0u ? 1024u : buffer->cap; + while (next < required) { + if (next > SIZE_MAX / 2u) { + next = required; + break; + } + next *= 2u; } - - if (service->blocks_server && host) { - (void)libp2p_host_unlisten(host, service->blocks_server); + uint8_t *grown = (uint8_t *)realloc(buffer->data, next); + if (!grown) { + return -1; } - lantern_reqresp_service_clear(service); + buffer->data = grown; + buffer->cap = next; + return 0; } -static void describe_peer(const peer_id_t *peer, char *buffer, size_t length) { - if (!buffer || length == 0) { - return; +static int reqresp_buffer_append(struct reqresp_buffer *buffer, const uint8_t *data, size_t len) { + if (!buffer || (!data && len > 0u)) { + return -1; } - if (!peer) { - buffer[0] = '\0'; - return; + if (len == 0u) { + return 0; } - if (write_legacy_peer_id_text(peer, buffer, length) < 0) { - buffer[0] = '\0'; + if (buffer->len > SIZE_MAX - len || reqresp_buffer_reserve(buffer, buffer->len + len) != 0) { + return -1; } + memcpy(buffer->data + buffer->len, data, len); + buffer->len += len; + return 0; } -static void status_request_ctx_free(struct status_request_ctx *ctx) { - if (!ctx) { +static void reqresp_buffer_consume(struct reqresp_buffer *buffer, size_t len) { + if (!buffer || len == 0u) { return; } - peer_id_free(ctx->peer_id); - free(ctx); -} - -static const char *stream_error_name(ssize_t code) { - switch (code) { - case LIBP2P_ERR_AGAIN: - return "again"; - case LIBP2P_ERR_TIMEOUT: - return "timeout"; - case LIBP2P_ERR_EOF: - return "eof"; - case LIBP2P_ERR_CLOSED: - return "closed"; - case LIBP2P_ERR_RESET: - return "reset"; - case LIBP2P_ERR_MSG_TOO_LARGE: - return "too_large"; - default: - return NULL; + if (len >= buffer->len) { + buffer->len = 0; + return; } + memmove(buffer->data, buffer->data + len, buffer->len - len); + buffer->len -= len; } -static void log_payload_preview( - const char *stage, - const char *peer_text, - const uint8_t *data, - size_t length) { - if (!data) { +static void exchange_free(struct lantern_reqresp_exchange *exchange) { + if (!exchange) { return; } - size_t preview_len = length < LANTERN_STATUS_PREVIEW_BYTES ? length : LANTERN_STATUS_PREVIEW_BYTES; - char hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - if (preview_len > 0) { - if (lantern_bytes_to_hex(data, preview_len, hex, sizeof(hex), 0) != 0) { - hex[0] = '\0'; - } - } else { - hex[0] = '\0'; - } - const char *ellipsis = length > preview_len ? "..." : ""; - const struct lantern_log_metadata meta = {.peer = peer_text}; - lantern_log_trace( - "reqresp", - &meta, - "%s bytes=%zu preview=%s%s", - stage ? stage : "payload", - length, - hex[0] ? hex : "-", - ellipsis); + free(exchange->write_buf); + reqresp_buffer_reset(&exchange->read_buf); + free(exchange->roots); + free(exchange); } -static void log_snappy_frame_summary( - const char *stage, - const struct lantern_log_metadata *meta, - const uint8_t *data, - size_t length) { - if (!data || !meta) { +static void service_add_exchange(struct lantern_reqresp_service *service, struct lantern_reqresp_exchange *exchange) { + if (!service || !exchange) { return; } - - bool framed = lantern_snappy_is_framed(data, length); - bool have_first = false; - bool have_second = false; - uint8_t first_type = 0; - uint32_t first_len = 0; - uint8_t second_type = 0; - uint32_t second_len = 0; - size_t second_offset = 0; - - if (length >= 4u) { - have_first = true; - first_type = data[0]; - first_len = (uint32_t)data[1] | ((uint32_t)data[2] << 8u) | ((uint32_t)data[3] << 16u); - second_offset = 4u + (size_t)first_len; - if (second_offset + 4u <= length) { - have_second = true; - second_type = data[second_offset]; - second_len = (uint32_t)data[second_offset + 1u] - | ((uint32_t)data[second_offset + 2u] << 8u) - | ((uint32_t)data[second_offset + 3u] << 16u); - } + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); + } + exchange->next = service->exchanges; + service->exchanges = exchange; + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); } - - size_t preview_len = length < 24u ? length : 24u; - char preview_hex[(24u * 2u) + 1u]; - if (preview_len > 0) { - if (lantern_bytes_to_hex(data, preview_len, preview_hex, sizeof(preview_hex), 0) != 0) { - preview_hex[0] = '\0'; - } - } else { - preview_hex[0] = '\0'; - } - const char *ellipsis = length > preview_len ? "..." : ""; - - lantern_log_warn( - "reqresp", - meta, - "%s snappy summary framed=%s len=%zu first_ok=%s first_type=0x%02x first_len=%u " - "second_ok=%s second_type=0x%02x second_len=%u preview=%s%s", - stage ? stage : "payload", - framed ? "true" : "false", - length, - have_first ? "true" : "false", - (unsigned)first_type, - first_len, - have_second ? "true" : "false", - (unsigned)second_type, - second_len, - preview_hex[0] ? preview_hex : "-", - ellipsis); } -static uint64_t reqresp_now_ms(void) { - double now_sec = lantern_time_now_seconds(); - if (now_sec <= 0.0) { +static int service_remove_exchange(struct lantern_reqresp_service *service, struct lantern_reqresp_exchange *exchange) { + if (!service || !exchange) { return 0; } - double now_ms = now_sec * 1000.0; - if (now_ms >= (double)UINT64_MAX) { - return UINT64_MAX; + int removed = 0; + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); } - return (uint64_t)now_ms; + struct lantern_reqresp_exchange **cursor = &service->exchanges; + while (*cursor) { + if (*cursor == exchange) { + *cursor = exchange->next; + exchange->next = NULL; + removed = 1; + break; + } + cursor = &(*cursor)->next; + } + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); + } + return removed; } -static int set_stream_deadline_remaining( - libp2p_stream_t *stream, - uint64_t deadline_ms, - const struct lantern_log_metadata *meta, - const char *label, - ssize_t *out_err) { - if (!stream) { - if (out_err) { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return -1; +static void service_clear_exchanges(struct lantern_reqresp_service *service) { + if (!service) { + return; } - uint64_t now_ms = reqresp_now_ms(); - if (now_ms >= deadline_ms) { - if (out_err) { - *out_err = LIBP2P_ERR_TIMEOUT; - } - lantern_log_warn( - "reqresp", - meta, - "%s timed out", - label ? label : "stream"); - return -1; + struct lantern_reqresp_exchange *list = NULL; + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); } - uint64_t remaining_ms = deadline_ms - now_ms; - int rc = libp2p_stream_set_deadline(stream, remaining_ms); - if (rc != 0) { - if (out_err) { - *out_err = (ssize_t)rc; - } - lantern_log_warn( - "reqresp", - meta, - "%s failed to set deadline_ms=%" PRIu64 " err=%d", - label ? label : "stream", - remaining_ms, - rc); - return -1; + list = service->exchanges; + service->exchanges = NULL; + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); } - if (out_err) { - *out_err = 0; + while (list) { + struct lantern_reqresp_exchange *next = list->next; + exchange_free(list); + list = next; } - return 0; } -static bool payload_is_snappy_framed(const uint8_t *data, size_t len) { - return lantern_snappy_is_framed(data, len); -} - -static int decode_status_payload( - const uint8_t *data, - size_t data_len, - LanternStatusMessage *out_status) { - if (!data || !out_status) { - return -1; +static ssize_t stream_read(struct lantern_reqresp_stream *stream, void *buf, size_t len) { + if (!stream || !buf) { + return -EINVAL; } - if (!payload_is_snappy_framed(data, data_len)) { - return -1; + if (stream->ops.read) { + return stream->ops.read(stream->io_ctx, buf, len); } - if (lantern_network_status_decode_snappy(out_status, data, data_len) != 0) { - return -1; + if (stream->host && stream->stream) { + size_t read_len = 0; + int fin = 0; + libp2p_host_err_t err = + libp2p_host_stream_read(stream->host, stream->stream, buf, len, &read_len, &fin); + if (err == LIBP2P_HOST_OK) { + if (read_len == 0 && fin) { + return 0; + } + return (ssize_t)read_len; + } + if (err == LIBP2P_HOST_ERR_WOULD_BLOCK) { + return -EAGAIN; + } + return -EIO; } - return 0; + return -EINVAL; } -static int read_stream_exact( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *label, - uint8_t *buffer, - size_t buffer_len, - uint64_t deadline_ms, - size_t *out_read, - ssize_t *out_err) { - if (!stream || !buffer) { - if (out_err) { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return -1; +static ssize_t stream_write(struct lantern_reqresp_stream *stream, const void *buf, size_t len) { + if (!stream || (!buf && len != 0)) { + return -EINVAL; } - if (buffer_len == 0) { - if (out_read) { - *out_read = 0; + if (stream->ops.write) { + return stream->ops.write(stream->io_ctx, buf, len); + } + if (stream->host && stream->stream) { + size_t accepted = 0; + libp2p_host_err_t err = + libp2p_host_stream_write(stream->host, stream->stream, buf, len, 0, &accepted); + if (err == LIBP2P_HOST_OK) { + return (ssize_t)accepted; } - if (out_err) { - *out_err = 0; + if (err == LIBP2P_HOST_ERR_WOULD_BLOCK) { + return -EAGAIN; } - return 0; + return -EIO; } + return -EINVAL; +} - size_t collected = 0; - while (collected < buffer_len) { - if (set_stream_deadline_remaining(stream, deadline_ms, meta, label, out_err) != 0) { - if (out_read) { - *out_read = collected; - } - return -1; - } - ssize_t n = libp2p_stream_read(stream, buffer + collected, buffer_len - collected); - if (n > 0) { - collected += (size_t)n; - continue; - } - if (n == (ssize_t)LIBP2P_ERR_AGAIN) { - continue; - } - (void)libp2p_stream_set_deadline(stream, 0); - if (out_read) { - *out_read = collected; - } - if (out_err) { - *out_err = (n == 0) ? (ssize_t)LIBP2P_ERR_EOF : n; - } - const char *err_name = stream_error_name(out_err ? *out_err : n); - lantern_log_warn( - "reqresp", - meta, - "%s read failed err=%s(%zd) collected=%zu/%zu", - label ? label : "stream", - err_name ? err_name : "unknown", - out_err ? *out_err : n, - collected, - buffer_len); +static int stream_set_deadline(struct lantern_reqresp_stream *stream, uint64_t ms) { + if (!stream) { return -1; } - (void)libp2p_stream_set_deadline(stream, 0); - if (out_read) { - *out_read = collected; - } - if (out_err) { - *out_err = 0; + if (stream->ops.set_deadline) { + return stream->ops.set_deadline(stream->io_ctx, ms); } return 0; } -static bool accept_legacy_declared_len( - const uint8_t *buffer, - size_t offset, - uint64_t declared_len, - size_t *out_uncompressed) -{ - if (!buffer || offset == 0 || declared_len == 0) - { - return false; - } - if (offset != (size_t)declared_len) - { - return false; - } - size_t raw_len = 0; - if (lantern_snappy_uncompressed_length(buffer, offset, &raw_len) != LANTERN_SNAPPY_OK) - { - return false; - } - if (out_uncompressed) - { - *out_uncompressed = raw_len; +static int read_exact(struct lantern_reqresp_stream *stream, uint8_t *buffer, size_t len, ssize_t *out_err) { + size_t offset = 0; + while (offset < len) { + ssize_t n = stream_read(stream, buffer + offset, len - offset); + if (n <= 0) { + if (out_err) { + *out_err = n; + } + return LANTERN_REQRESP_ERR_STREAM_READ; + } + offset += (size_t)n; } - return true; + return LANTERN_REQRESP_OK; +} + +static uint32_t read_le24(const uint8_t bytes[3]) { + return (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8u) | ((uint32_t)bytes[2] << 16u); +} + +static int is_snappy_padding_chunk(uint8_t type) { + return type >= (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_PADDING_START + && type <= (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_PADDING_END; } -static int read_snappy_framed_payload( - libp2p_stream_t *stream, - const char *label, - const struct lantern_log_metadata *meta, - uint64_t declared_len, - uint64_t deadline_ms, +static int read_snappy_frame_payload( + struct lantern_reqresp_stream *stream, + size_t raw_len, uint8_t **out_data, size_t *out_len, - bool *out_legacy_len, ssize_t *out_err) { - if (!stream || !out_data || !out_len) { - if (out_err) { - *out_err = LIBP2P_ERR_NULL_PTR; - } - return -1; + size_t max_payload = 0; + if (lantern_snappy_max_compressed_size(raw_len, &max_payload) != LANTERN_SNAPPY_OK + || max_payload < (size_t)LANTERN_SNAPPY_FRAME_STREAM_HEADER_BYTES) { + return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; + } + + uint8_t *payload = (uint8_t *)malloc(max_payload); + if (!payload) { + return LANTERN_REQRESP_ERR_ALLOC; } - if (out_legacy_len) { - *out_legacy_len = false; + + size_t len = 0; + int rc = read_exact(stream, payload, (size_t)LANTERN_SNAPPY_FRAME_STREAM_HEADER_BYTES, out_err); + if (rc != LANTERN_REQRESP_OK) { + free(payload); + return rc; } - if (declared_len > LANTERN_REQRESP_MAX_CHUNK_BYTES || declared_len > SIZE_MAX) { - if (out_err) { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; - } - lantern_log_warn( - "reqresp", - meta, - "%s declared uncompressed length invalid=%" PRIu64, - label ? label : "stream", - declared_len); - return -1; + len = (size_t)LANTERN_SNAPPY_FRAME_STREAM_HEADER_BYTES; + + if (payload[0] != (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_STREAM_IDENTIFIER + || read_le24(payload + 1u) != (uint32_t)LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN + || memcmp( + payload + (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES, + LANTERN_SNAPPY_FRAME_MAGIC, + sizeof(LANTERN_SNAPPY_FRAME_MAGIC)) + != 0) { + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; } - if (declared_len == 0) { - uint8_t header[4]; - size_t read_len = 0; - if (read_stream_exact( - stream, - meta, - label ? label : "stream", - header, - sizeof(header), - deadline_ms, - &read_len, - out_err) - != 0) { - return -1; + size_t decoded_total = 0; + while (decoded_total < raw_len) { + if (max_payload - len < (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES) { + free(payload); + return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; } - uint8_t chunk_type = header[0]; - uint32_t chunk_len = (uint32_t)header[1] - | ((uint32_t)header[2] << 8u) - | ((uint32_t)header[3] << 16u); - if (chunk_type != 0xffu || chunk_len != 6u) { - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy stream identifier invalid type=0x%02x len=%u", - label ? label : "stream", - (unsigned)chunk_type, - chunk_len); - return -1; + + size_t chunk_start = len; + rc = read_exact( + stream, + payload + len, + (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES, + out_err); + if (rc != LANTERN_REQRESP_OK) { + free(payload); + return rc; } - uint8_t payload[6]; - if (read_stream_exact( - stream, - meta, - label ? label : "stream", - payload, - sizeof(payload), - deadline_ms, - &read_len, - out_err) - != 0) { - return -1; + len += (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES; + + uint8_t chunk_type = payload[chunk_start]; + size_t chunk_len = (size_t)read_le24(payload + chunk_start + 1u); + if (chunk_len > max_payload - len) { + free(payload); + return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; } - uint8_t *buffer = (uint8_t *)malloc(10u); - if (!buffer) { - if (out_err) { - *out_err = -ENOMEM; + rc = read_exact(stream, payload + len, chunk_len, out_err); + if (rc != LANTERN_REQRESP_OK) { + free(payload); + return rc; + } + + const uint8_t *chunk_payload = payload + len; + len += chunk_len; + + if (chunk_type == (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_STREAM_IDENTIFIER) { + if (chunk_len != (size_t)LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN + || memcmp(chunk_payload, LANTERN_SNAPPY_FRAME_MAGIC, sizeof(LANTERN_SNAPPY_FRAME_MAGIC)) != 0) { + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; } - lantern_log_warn( - "reqresp", - meta, - "%s payload allocation failed bytes=10", - label ? label : "stream"); - return -1; + continue; } - memcpy(buffer, header, sizeof(header)); - memcpy(buffer + sizeof(header), payload, sizeof(payload)); - if (!lantern_snappy_is_framed(buffer, 10u)) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; + if (is_snappy_padding_chunk(chunk_type)) { + continue; + } + if (chunk_type != (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_COMPRESSED + && chunk_type != (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_UNCOMPRESSED) { + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; + } + if (chunk_len < (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES) { + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; + } + + size_t chunk_raw_len = chunk_len - (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES; + if (chunk_type == (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_COMPRESSED) { + if (lantern_snappy_uncompressed_length_raw( + chunk_payload + (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES, + chunk_raw_len, + &chunk_raw_len) + != LANTERN_SNAPPY_OK) { + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; } - lantern_log_warn( - "reqresp", - meta, - "%s payload missing snappy framing bytes=10", - label ? label : "stream"); - return -1; } - if (out_err) { - *out_err = 0; + if (chunk_raw_len > raw_len - decoded_total) { + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; } - *out_data = buffer; - *out_len = 10u; - return 0; + decoded_total += chunk_raw_len; } - size_t max_compressed = 0; - if (lantern_snappy_max_compressed_size((size_t)declared_len, &max_compressed) != LANTERN_SNAPPY_OK - || max_compressed == 0) { - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s failed to compute snappy max size declared_uncompressed=%" PRIu64, - label ? label : "stream", - declared_len); - return -1; + uint8_t *scratch = NULL; + if (raw_len > 0u) { + scratch = (uint8_t *)malloc(raw_len); + if (!scratch) { + free(payload); + return LANTERN_REQRESP_ERR_ALLOC; + } } - - uint8_t *buffer = (uint8_t *)malloc(max_compressed); - if (!buffer) { - if (out_err) { - *out_err = -ENOMEM; - } - lantern_log_warn( - "reqresp", - meta, - "%s payload allocation failed bytes=%zu", - label ? label : "stream", - max_compressed); - return -1; + size_t written = 0; + if (lantern_snappy_decompress(payload, len, scratch, raw_len, &written) != LANTERN_SNAPPY_OK + || written != raw_len) { + free(scratch); + free(payload); + return LANTERN_REQRESP_ERR_INVALID_PAYLOAD; } + free(scratch); - size_t offset = 0; - uint64_t uncompressed_total = 0; - bool saw_data_chunk = false; - size_t chunk_index = 0; - const uint32_t max_chunk_len = 0x00ffffffu; + *out_data = payload; + *out_len = len; + return LANTERN_REQRESP_OK; +} - while (!saw_data_chunk || uncompressed_total < declared_len) { - uint8_t header[4]; - size_t read_len = 0; - ssize_t read_err = 0; - if (read_stream_exact( - stream, - meta, - label ? label : "stream", - header, - sizeof(header), - deadline_ms, - &read_len, - &read_err) - != 0) { - size_t legacy_uncompressed = 0; - if ((read_err == (ssize_t)LIBP2P_ERR_EOF - || read_err == (ssize_t)LIBP2P_ERR_CLOSED - || read_err == (ssize_t)LIBP2P_ERR_RESET) - && accept_legacy_declared_len(buffer, offset, declared_len, &legacy_uncompressed)) - { - lantern_log_warn( - "reqresp", - meta, - "%s legacy reqresp length declared=%" PRIu64 " compressed=%zu uncompressed=%zu", - label ? label : "stream", - declared_len, - offset, - legacy_uncompressed); - if (out_legacy_len) { - *out_legacy_len = true; - } - if (out_err) { - *out_err = 0; - } - *out_data = buffer; - *out_len = offset; - return 0; - } - free(buffer); - if (out_err) { - *out_err = read_err; - } - return -1; +static int snappy_frame_payload_len( + const uint8_t *data, + size_t data_len, + size_t raw_len, + size_t *out_frame_len, + int *out_need_more) { + if (out_need_more) { + *out_need_more = 0; + } + if (!data || !out_frame_len) { + return -1; + } + if (data_len < (size_t)LANTERN_SNAPPY_FRAME_STREAM_HEADER_BYTES) { + if (out_need_more) { + *out_need_more = 1; } - - uint8_t chunk_type = header[0]; - uint32_t chunk_len = (uint32_t)header[1] - | ((uint32_t)header[2] << 8u) - | ((uint32_t)header[3] << 16u); - - if (chunk_len > max_chunk_len) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; + return 0; + } + if (data[0] != (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_STREAM_IDENTIFIER + || read_le24(data + 1u) != (uint32_t)LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN + || memcmp( + data + (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES, + LANTERN_SNAPPY_FRAME_MAGIC, + sizeof(LANTERN_SNAPPY_FRAME_MAGIC)) + != 0) { + return -1; + } + size_t offset = (size_t)LANTERN_SNAPPY_FRAME_STREAM_HEADER_BYTES; + size_t decoded_total = 0; + if (raw_len == 0u) { + *out_frame_len = offset; + return 0; + } + while (decoded_total < raw_len) { + if (data_len - offset < (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES) { + if (out_need_more) { + *out_need_more = 1; } - lantern_log_warn( - "reqresp", - meta, - "%s snappy chunk length invalid=%u", - label ? label : "stream", - chunk_len); - return -1; + return 0; } - - if (offset + sizeof(header) + (size_t)chunk_len > max_compressed) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; + uint8_t chunk_type = data[offset]; + size_t chunk_len = (size_t)read_le24(data + offset + 1u); + offset += (size_t)LANTERN_SNAPPY_FRAME_CHUNK_HEADER_BYTES; + if (chunk_len > data_len - offset) { + if (out_need_more) { + *out_need_more = 1; } - lantern_log_warn( - "reqresp", - meta, - "%s snappy payload exceeds max_compressed bytes=%zu", - label ? label : "stream", - max_compressed); - return -1; + return 0; } - memcpy(buffer + offset, header, sizeof(header)); - offset += sizeof(header); - - if (chunk_len > 0) { - if (read_stream_exact( - stream, - meta, - label ? label : "stream", - buffer + offset, - (size_t)chunk_len, - deadline_ms, - &read_len, - out_err) - != 0) { - free(buffer); + const uint8_t *chunk_payload = data + offset; + if (chunk_type == (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_STREAM_IDENTIFIER) { + if (chunk_len != (size_t)LANTERN_SNAPPY_FRAME_STREAM_IDENTIFIER_LEN + || memcmp(chunk_payload, LANTERN_SNAPPY_FRAME_MAGIC, sizeof(LANTERN_SNAPPY_FRAME_MAGIC)) != 0) { return -1; } - } - - const uint8_t *chunk_payload = buffer + offset; - offset += (size_t)chunk_len; - chunk_index++; - - uint64_t chunk_uncompressed = 0; - bool contributes = false; - switch (chunk_type) { - case 0xffu: /* stream identifier */ - if (chunk_len != 6u) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy stream identifier length invalid=%u", - label ? label : "stream", - chunk_len); + } else if (is_snappy_padding_chunk(chunk_type)) { + /* no decoded bytes */ + } else if (chunk_type == (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_UNCOMPRESSED) { + if (chunk_len < (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES) { return -1; } - break; - case 0x00u: /* compressed */ - if (chunk_len < 4u) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy compressed chunk too short len=%u", - label ? label : "stream", - chunk_len); + size_t chunk_raw_len = chunk_len - (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES; + if (chunk_raw_len > raw_len - decoded_total) { return -1; } - { - size_t raw_len = 0; - int rc = lantern_snappy_uncompressed_length_raw( - chunk_payload + 4u, - (size_t)chunk_len - 4u, - &raw_len); - if (rc != LANTERN_SNAPPY_OK) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy compressed chunk length decode failed rc=%d len=%u", - label ? label : "stream", - rc, - chunk_len); - return -1; - } - chunk_uncompressed = (uint64_t)raw_len; - contributes = true; - } - break; - case 0x01u: /* uncompressed */ - if (chunk_len < 4u) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy uncompressed chunk too short len=%u", - label ? label : "stream", - chunk_len); + decoded_total += chunk_raw_len; + } else if (chunk_type == (uint8_t)LANTERN_SNAPPY_FRAME_CHUNK_COMPRESSED) { + if (chunk_len < (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES) { return -1; } - chunk_uncompressed = (uint64_t)(chunk_len - 4u); - contributes = true; - break; - default: - if (chunk_type >= 0x80u && chunk_type <= 0xfeu) { - /* skippable chunk - ignore */ - break; - } - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy chunk type unsupported=0x%02x", - label ? label : "stream", - (unsigned)chunk_type); - return -1; - } - - if (contributes) { - saw_data_chunk = true; - if (chunk_uncompressed > UINT64_MAX - uncompressed_total) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy uncompressed length overflow", - label ? label : "stream"); + size_t chunk_raw_len = 0; + if (lantern_snappy_uncompressed_length_raw( + chunk_payload + (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES, + chunk_len - (size_t)LANTERN_SNAPPY_FRAME_CRC_BYTES, + &chunk_raw_len) + != LANTERN_SNAPPY_OK) { return -1; } - uncompressed_total += chunk_uncompressed; - } - - lantern_log_trace( - "reqresp", - meta, - "%s snappy chunk[%zu] type=0x%02x len=%u uncompressed=%" PRIu64 " total=%" PRIu64 "/%" PRIu64, - label ? label : "stream", - chunk_index, - (unsigned)chunk_type, - chunk_len, - chunk_uncompressed, - uncompressed_total, - declared_len); - - if (uncompressed_total > declared_len) { - size_t legacy_uncompressed = 0; - if (accept_legacy_declared_len(buffer, offset, declared_len, &legacy_uncompressed)) { - lantern_log_warn( - "reqresp", - meta, - "%s legacy reqresp length declared=%" PRIu64 " compressed=%zu uncompressed=%zu", - label ? label : "stream", - declared_len, - offset, - legacy_uncompressed); - if (out_legacy_len) { - *out_legacy_len = true; - } - if (out_err) { - *out_err = 0; - } - *out_data = buffer; - *out_len = offset; - return 0; - } - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } - lantern_log_warn( - "reqresp", - meta, - "%s snappy uncompressed length exceeded declared=%" PRIu64 " got=%" PRIu64, - label ? label : "stream", - declared_len, - uncompressed_total); - return -1; - } - - if (offset >= max_compressed && uncompressed_total < declared_len) { - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_MSG_TOO_LARGE; + if (chunk_raw_len > raw_len - decoded_total) { + return -1; } - lantern_log_warn( - "reqresp", - meta, - "%s snappy payload truncated bytes=%zu", - label ? label : "stream", - offset); + decoded_total += chunk_raw_len; + } else { return -1; } + offset += chunk_len; } + *out_frame_len = offset; + return 0; +} - if (!lantern_snappy_is_framed(buffer, offset)) { - lantern_log_warn( - "reqresp", - meta, - "%s payload missing snappy framing bytes=%zu", - label ? label : "stream", - offset); - log_snappy_frame_summary(label, meta, buffer, offset); - free(buffer); - if (out_err) { - *out_err = LIBP2P_ERR_INTERNAL; - } +static int encode_uvarint(uint64_t value, uint8_t out[10], size_t *written) { + if (!out || !written) { return -1; } - - lantern_log_debug( - "reqresp", - meta, - "%s snappy payload read compressed=%zu uncompressed=%" PRIu64, - label ? label : "stream", - offset, - uncompressed_total); - - if (out_err) { - *out_err = 0; + if (libp2p_uvarint_encode(value, out, 10u, written) != LIBP2P_UVARINT_OK) { + return -1; } - *out_data = buffer; - *out_len = offset; return 0; } -static int read_length_prefixed_stream( - libp2p_stream_t *stream, - const char *label, - const char *peer_text, - uint8_t **out_data, - size_t *out_len, - bool *out_legacy_len, - ssize_t *out_err) { - if (!stream || !out_data || !out_len) { - if (out_err) { - *out_err = LIBP2P_ERR_NULL_PTR; - } +static int build_frame_from_raw( + const uint8_t *raw, + size_t raw_len, + int include_response_code, + uint8_t response_code, + uint8_t **out_frame, + size_t *out_frame_len) { + if (!out_frame || !out_frame_len || (!raw && raw_len > 0u)) { return -1; } - if (out_legacy_len) { - *out_legacy_len = false; + *out_frame = NULL; + *out_frame_len = 0; + if (raw_len > LANTERN_REQRESP_MAX_CHUNK_BYTES) { + return -1; } - - struct lantern_log_metadata meta = {.peer = peer_text}; - uint64_t start_ms = reqresp_now_ms(); - uint64_t ttfb_deadline_ms = start_ms + LANTERN_REQRESP_TTFB_TIMEOUT_MS; - uint64_t resp_deadline_ms = start_ms + LANTERN_REQRESP_RESP_TIMEOUT_MS; - uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; - size_t header_used = 0; - size_t consumed = 0; - uint64_t payload_len = 0; - ssize_t last_err = 0; - - if (out_err) { - *out_err = 0; + size_t max_compressed = 0; + if (lantern_snappy_max_compressed_size(raw_len, &max_compressed) != LANTERN_SNAPPY_OK) { + return -1; } - - while (header_used < sizeof(header)) { - uint64_t deadline_ms = header_used == 0 ? ttfb_deadline_ms : resp_deadline_ms; - if (set_stream_deadline_remaining(stream, deadline_ms, &meta, label, out_err) != 0) { - last_err = out_err ? *out_err : (ssize_t)LIBP2P_ERR_TIMEOUT; - break; - } - ssize_t n = libp2p_stream_read(stream, &header[header_used], 1); - if (n == 1) { - header_used += 1; - lantern_log_trace( - "reqresp", - &meta, - "%s header byte[%zu]=0x%02x", - label ? label : "stream", - header_used - 1, - (unsigned)header[header_used - 1]); - if (unsigned_varint_decode(header, header_used, &payload_len, &consumed) == UNSIGNED_VARINT_OK) { - lantern_log_trace( - "reqresp", - &meta, - "%s header decoded uncompressed_len=%" PRIu64, - label ? label : "stream", - payload_len); - char header_hex[(LANTERN_REQRESP_HEADER_MAX_BYTES * 2u) + 1u]; - header_hex[0] = '\0'; - if (lantern_bytes_to_hex(header, consumed, header_hex, sizeof(header_hex), 0) != 0) { - header_hex[0] = '\0'; - } - lantern_log_debug( - "reqresp", - &meta, - "%s header decoded uncompressed_len=%" PRIu64 " header_len=%zu header_hex=%s", - label ? label : "stream", - payload_len, - consumed, - header_hex[0] ? header_hex : "-"); - break; - } - continue; - } - if (n == (ssize_t)LIBP2P_ERR_AGAIN) { - continue; - } - if (n == 0 || n == (ssize_t)LIBP2P_ERR_EOF || n == (ssize_t)LIBP2P_ERR_CLOSED || n == (ssize_t)LIBP2P_ERR_RESET) { - last_err = n == 0 ? (ssize_t)LIBP2P_ERR_EOF : n; - break; - } - last_err = n; - break; + uint8_t *compressed = (uint8_t *)malloc(max_compressed); + if (!compressed) { + return -1; } - (void)libp2p_stream_set_deadline(stream, 0); - - if (header_used == sizeof(header) - && unsigned_varint_decode(header, header_used, &payload_len, &consumed) != UNSIGNED_VARINT_OK) { - last_err = LIBP2P_ERR_INTERNAL; + size_t compressed_len = 0; + if (lantern_snappy_compress(raw, raw_len, compressed, max_compressed, &compressed_len) != LANTERN_SNAPPY_OK) { + free(compressed); + return -1; } - - if (payload_len > LANTERN_REQRESP_MAX_CHUNK_BYTES || payload_len > SIZE_MAX) { - if (last_err == 0) { - last_err = LIBP2P_ERR_MSG_TOO_LARGE; - } - lantern_log_warn( - "reqresp", - &meta, - "%s header invalid uncompressed_len=%" PRIu64, - label ? label : "stream", - payload_len); + uint8_t header[10]; + size_t header_len = 0; + if (encode_uvarint((uint64_t)raw_len, header, &header_len) != 0) { + free(compressed); + return -1; } - - if (last_err != 0) { - if (out_err) { - *out_err = last_err; - } - const char *err_name = stream_error_name(last_err); - lantern_log_warn( - "reqresp", - &meta, - "%s header read failed err=%s(%zd) bytes=%zu", - label ? label : "stream", - err_name ? err_name : "unknown", - last_err, - header_used); + size_t prefix = include_response_code ? 1u : 0u; + if (compressed_len > SIZE_MAX - header_len - prefix) { + free(compressed); return -1; } - - if (read_snappy_framed_payload( - stream, - label, - &meta, - payload_len, - resp_deadline_ms, - out_data, - out_len, - out_legacy_len, - out_err) - != 0) { + size_t total = prefix + header_len + compressed_len; + uint8_t *frame = (uint8_t *)malloc(total > 0u ? total : 1u); + if (!frame) { + free(compressed); return -1; } - lantern_log_debug( - "reqresp", - &meta, - "%s payload read complete bytes=%zu", - label ? label : "stream", - *out_len); + size_t offset = 0; + if (include_response_code) { + frame[offset++] = response_code; + } + memcpy(frame + offset, header, header_len); + offset += header_len; + memcpy(frame + offset, compressed, compressed_len); + offset += compressed_len; + free(compressed); + *out_frame = frame; + *out_frame_len = offset; return 0; } -static int write_stream_all( - libp2p_stream_t *stream, - const uint8_t *data, - size_t length, - const char *protocol_id, - const char *phase, - const char *peer_hint) { - if (!stream || (!data && length > 0)) { - return LIBP2P_ERR_NULL_PTR; - } - - char peer_text[128]; - peer_text[0] = '\0'; - if (!peer_hint || peer_hint[0] == '\0') { - const peer_id_t *peer = libp2p_stream_remote_peer(stream); - if (peer && write_legacy_peer_id_text(peer, peer_text, sizeof(peer_text)) < 0) { - peer_text[0] = '\0'; - } - } - const char *peer_label = (peer_hint && peer_hint[0]) ? peer_hint : (peer_text[0] ? peer_text : NULL); - struct lantern_log_metadata meta = { - .peer = peer_label, - }; - - uint64_t deadline_ms = UINT64_MAX; - uint64_t now_ms = reqresp_now_ms(); - uint64_t stall_timeout_ms = lantern_reqresp_stall_timeout_ms(); - if (stall_timeout_ms > 0u - && now_ms != 0u - && now_ms <= UINT64_MAX - stall_timeout_ms) { - deadline_ms = now_ms + stall_timeout_ms; - } - - lantern_log_trace( - "reqresp", - &meta, - "%s write start bytes=%zu protocol=%s", - phase ? phase : "stream", - length, - protocol_id ? protocol_id : "-"); +static int append_frame_from_raw( + struct reqresp_buffer *buffer, + const uint8_t *raw, + size_t raw_len, + uint8_t response_code) { + uint8_t *frame = NULL; + size_t frame_len = 0; + if (build_frame_from_raw(raw, raw_len, 1, response_code, &frame, &frame_len) != 0) { + return -1; + } + int rc = reqresp_buffer_append(buffer, frame, frame_len); + free(frame); + return rc; +} +static int extract_frame_from_buffer( + struct reqresp_buffer *buffer, + int has_response_code, + uint8_t *out_code, + uint8_t **out_raw, + size_t *out_raw_len, + int *out_need_more) { + if (out_need_more) { + *out_need_more = 0; + } + if (!buffer || !out_raw || !out_raw_len) { + return -1; + } + *out_raw = NULL; + *out_raw_len = 0; size_t offset = 0; - while (offset < length) { - if (deadline_ms != UINT64_MAX) { - now_ms = reqresp_now_ms(); - if (now_ms != 0u && now_ms >= deadline_ms) { - lantern_log_warn( - "reqresp", - &meta, - "%s write timed out bytes_written=%zu total_bytes=%zu protocol=%s", - phase ? phase : "stream", - offset, - length, - protocol_id ? protocol_id : "-"); - return LIBP2P_ERR_TIMEOUT; - } - - uint64_t remaining_ms = (now_ms == 0u) ? stall_timeout_ms : (deadline_ms - now_ms); - if (remaining_ms == 0u) { - remaining_ms = 1u; - } - int deadline_rc = libp2p_stream_set_deadline(stream, remaining_ms); - if (deadline_rc != 0) { - lantern_log_warn( - "reqresp", - &meta, - "%s failed to set write deadline err=%d", - phase ? phase : "stream", - deadline_rc); - return deadline_rc; + if (has_response_code) { + if (buffer->len == 0u) { + if (out_need_more) { + *out_need_more = 1; } + return 0; } - - ssize_t written = libp2p_stream_write(stream, data + offset, length - offset); - if (written > 0) { - offset += (size_t)written; - lantern_log_trace( - "reqresp", - &meta, - "%s write progress chunk=%zd offset=%zu/%zu", - phase ? phase : "stream", - written, - offset, - length); - continue; - } - if (written == (ssize_t)LIBP2P_ERR_AGAIN || written == (ssize_t)LIBP2P_ERR_TIMEOUT) { - continue; + if (out_code) { + *out_code = normalize_response_code(buffer->data[0]); } - - ssize_t err = written; - if (written == 0) { - err = LIBP2P_ERR_EOF; - } - - const char *err_name = stream_error_name(err); - lantern_log_warn( - "reqresp", - &meta, - "%s write failed protocol=%s err=%s(%zd) offset=%zu/%zu", - phase ? phase : "stream", - protocol_id ? protocol_id : "-", - err_name ? err_name : "unknown", - err, - offset, - length); - if (protocol_id) { - log_stream_error("write", protocol_id, peer_label); - } - return (int)err; - } - lantern_log_debug( - "reqresp", - &meta, - "%s write complete bytes=%zu protocol=%s", - phase ? phase : "stream", - length, - protocol_id ? protocol_id : "-"); - if (deadline_ms != UINT64_MAX) { - (void)libp2p_stream_set_deadline(stream, 0); + offset = 1u; } - return 0; -} - -static int send_response_chunk( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *protocol_id, - const char *phase, - const char *peer_text, - bool include_response_code, - uint8_t response_code, - bool legacy_len, - const uint8_t *payload, - size_t payload_len, - size_t raw_len) { - if (!stream) { - return -1; + if (offset == buffer->len) { + if (out_need_more) { + *out_need_more = 1; + } + return 0; } - - bool include_code = include_response_code; - - size_t declared_len = legacy_len ? payload_len : raw_len; - - lantern_log_debug( - "reqresp", - meta, - "%s framing include_code=%s code=%u payload_len=%zu raw_len=%zu declared_len=%zu legacy_len=%s", - phase ? phase : "response", - include_code ? "true" : "false", - (unsigned)response_code, - payload_len, - raw_len, - declared_len, - legacy_len ? "true" : "false"); - - /* The varint prefix indicates the uncompressed payload size (SSZ length). */ - uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; - size_t header_len = 0; - if (unsigned_varint_encode(declared_len, header, sizeof(header), &header_len) != UNSIGNED_VARINT_OK) { - lantern_log_error( - "reqresp", - meta, - "%s payload header encode failed bytes=%zu", - phase ? phase : "response", - declared_len); - return -1; + uint64_t declared_len = 0; + size_t consumed = 0; + libp2p_uvarint_err_t varint_err = + libp2p_uvarint_decode(buffer->data + offset, buffer->len - offset, &declared_len, &consumed); + if (varint_err == LIBP2P_UVARINT_ERR_TRUNCATED) { + if (out_need_more) { + *out_need_more = 1; + } + return 0; } - - size_t frame_len = header_len + payload_len + (include_code ? 1u : 0u); - uint8_t *frame = (uint8_t *)malloc(frame_len > 0 ? frame_len : 1u); - if (!frame) { - lantern_log_error( - "reqresp", - meta, - "%s frame allocation failed bytes=%zu", - phase ? phase : "response", - frame_len); + if (varint_err != LIBP2P_UVARINT_OK || declared_len > LANTERN_REQRESP_MAX_CHUNK_BYTES) { return -1; } - size_t frame_offset = 0; - if (include_code) { - frame[frame_offset++] = response_code; - } - if (header_len > 0) { - memcpy(frame + frame_offset, header, header_len); - frame_offset += header_len; - } - if (payload_len > 0) { - memcpy(frame + frame_offset, payload, payload_len); - } - - /* Debug aid: summarize the outgoing frame (response path) so we can match consumer expectations */ - if (protocol_id && strstr(protocol_id, "/status/1/ssz_snappy") != NULL) { - char frame_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - frame_hex[0] = '\0'; - size_t preview = frame_len < LANTERN_STATUS_PREVIEW_BYTES ? frame_len : LANTERN_STATUS_PREVIEW_BYTES; - if (preview > 0 - && lantern_bytes_to_hex(frame, preview, frame_hex, sizeof(frame_hex), 0) != 0) { - frame_hex[0] = '\0'; - } - lantern_log_debug( - "reqresp", - meta, - "%s frame summary code_byte=0x%02x header_len=%zu payload_len=%zu frame_len=%zu%s%s", - phase ? phase : "response", - (unsigned)response_code, - header_len, - payload_len, - frame_len, - frame_hex[0] ? " frame_hex=" : "", - frame_hex[0] ? frame_hex : ""); - } - - if (write_stream_all( - stream, - frame, - frame_len, - protocol_id, - phase ? phase : "response frame", - peer_text) + offset += consumed; + size_t compressed_len = 0; + int need_more = 0; + if (snappy_frame_payload_len( + buffer->data + offset, + buffer->len - offset, + (size_t)declared_len, + &compressed_len, + &need_more) != 0) { - free(frame); - lantern_log_error( - "reqresp", - meta, - "%s frame write failed bytes=%zu", - phase ? phase : "response", - frame_len); return -1; } - free(frame); - - lantern_log_trace( - "reqresp", - meta, - "%s response sent bytes=%zu", - phase ? phase : "response", - payload_len); - - return 0; -} - -static int send_error_response( - libp2p_stream_t *stream, - const struct lantern_log_metadata *meta, - const char *protocol_id, - const char *peer_text, - bool legacy_len, - uint8_t response_code, - const char *message) { - if (!stream) { - return -1; + if (need_more) { + if (out_need_more) { + *out_need_more = 1; + } + return 0; } - const uint8_t *payload = (const uint8_t *)(message ? message : ""); - size_t raw_len = message ? strlen(message) : 0; - - size_t max_payload = 0; - if (lantern_snappy_max_compressed_size(raw_len, &max_payload) != LANTERN_SNAPPY_OK) { - lantern_log_warn( - "reqresp", - meta, - "error response snappy size failed bytes=%zu", - raw_len); - return -1; + uint8_t *raw = NULL; + if (declared_len > 0u) { + raw = (uint8_t *)malloc((size_t)declared_len); + if (!raw) { + return -1; + } } - if (max_payload == 0) { - max_payload = 1u; + size_t written = 0; + if (lantern_snappy_decompress( + buffer->data + offset, + compressed_len, + raw, + (size_t)declared_len, + &written) + != LANTERN_SNAPPY_OK + || written != (size_t)declared_len) { + free(raw); + return -1; } + reqresp_buffer_consume(buffer, offset + compressed_len); + *out_raw = raw; + *out_raw_len = written; + return 1; +} - uint8_t *compressed = (uint8_t *)malloc(max_payload); - if (!compressed) { - lantern_log_warn( - "reqresp", - meta, - "error response allocation failed bytes=%zu", - max_payload); +static int peer_from_conn(libp2p_host_conn_t *conn, struct lantern_peer_id *out_peer) { + if (!conn || !out_peer) { return -1; } - size_t written = 0; - if (lantern_snappy_compress(payload, raw_len, compressed, max_payload, &written) != LANTERN_SNAPPY_OK) { - free(compressed); - lantern_log_warn( - "reqresp", - meta, - "error response compress failed bytes=%zu", - raw_len); + if (libp2p_host_conn_peer_id(conn, out_peer->bytes, sizeof(out_peer->bytes), &written) != LIBP2P_HOST_OK + || written == 0u || written > sizeof(out_peer->bytes)) { return -1; } - - int rc = send_response_chunk( - stream, - meta, - protocol_id, - "error response", - peer_text, - true, - response_code, - legacy_len, - compressed, - written, - raw_len); - free(compressed); - return rc; + out_peer->len = written; + return 0; } -static void status_request_notify_failure( +static libp2p_host_conn_t *service_find_conn( struct lantern_reqresp_service *service, - const char *peer_text, - int error) { - if (!service || !service->callbacks.status_failure) { - return; + const struct lantern_peer_id *peer) { + if (!service || !peer) { + return NULL; + } + libp2p_host_conn_t *conn = NULL; + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); + } + for (size_t i = 0; i < service->conn_count; ++i) { + if (lantern_peer_id_equal(&service->conns[i].peer, peer)) { + conn = service->conns[i].conn; + break; + } + } + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); } - service->callbacks.status_failure( - service->callbacks.context, - peer_text, - error); + return conn; } - -static void close_stream(libp2p_stream_t *stream) { - if (!stream) { +static void service_record_conn(struct lantern_reqresp_service *service, libp2p_host_conn_t *conn) { + if (!service || !conn) { return; } - int rc = libp2p_stream_close(stream); - (void)rc; - /* Guard against concurrent host-side destruction: prefer async release. - If the stream wasn't retained, skip free to avoid double-free when the - underlying connection has already torn down the stream. (Potential - small leak is preferable to a hard crash on use-after-free.) */ - (void)libp2p__stream_release_async(stream); -} - -static void log_stream_error(const char *phase, const char *protocol_id, const char *peer_id) { - lantern_log_error( - "network", - &(const struct lantern_log_metadata){.peer = peer_id}, - "%s %s request failed", - protocol_id ? protocol_id : "unknown", - phase ? phase : "processing"); + struct lantern_peer_id peer; + if (peer_from_conn(conn, &peer) != 0) { + return; + } + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); + } + for (size_t i = 0; i < service->conn_count; ++i) { + if (lantern_peer_id_equal(&service->conns[i].peer, &peer)) { + service->conns[i].conn = conn; + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); + } + return; + } + } + if (service->conn_count < LANTERN_REQRESP_MAX_TRACKED_CONNECTIONS) { + service->conns[service->conn_count].peer = peer; + service->conns[service->conn_count].conn = conn; + service->conn_count++; + } + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); + } } -static void handle_remote_status( - struct lantern_reqresp_service *service, - const LanternStatusMessage *status, - const char *peer_text) { - if (!service || !status) { +static void service_remove_conn(struct lantern_reqresp_service *service, libp2p_host_conn_t *conn) { + if (!service || !conn) { return; } - if (service->callbacks.handle_status) { - service->callbacks.handle_status(service->callbacks.context, status, peer_text); + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); + } + for (size_t i = 0; i < service->conn_count; ++i) { + if (service->conns[i].conn == conn) { + size_t last = service->conn_count - 1u; + if (i != last) { + service->conns[i] = service->conns[last]; + } + service->conn_count = last; + break; + } + } + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); } } -static void *status_worker(void *arg) { - struct status_stream_ctx *ctx = (struct status_stream_ctx *)arg; - if (!ctx) { - return NULL; +static void exchange_set_peer_text_from_conn(struct lantern_reqresp_exchange *exchange, libp2p_host_conn_t *conn) { + if (!exchange || exchange->peer_id_text[0] != '\0') { + return; } - struct lantern_reqresp_service *service = ctx->service; - libp2p_stream_t *stream = ctx->stream; - const char *protocol_id = - ctx->protocol_id ? ctx->protocol_id : LANTERN_STATUS_PROTOCOL_ID; - uint64_t trace_id = ctx->debug_trace_id; - free(ctx); - - if (!service || !stream) { - close_stream(stream); - return NULL; + struct lantern_peer_id peer; + if (peer_from_conn(conn, &peer) == 0) { + (void)lantern_peer_id_to_text(&peer, exchange->peer_id_text, sizeof(exchange->peer_id_text)); } +} - char peer_text[128]; - describe_peer(libp2p_stream_remote_peer(stream), peer_text, sizeof(peer_text)); - - bool include_response_code = true; - - const struct lantern_log_metadata stream_meta = {.peer = peer_text[0] ? peer_text : NULL}; - lantern_log_debug( - "reqresp", - &stream_meta, - "status[%" PRIu64 "] stream protocol=%s", - trace_id, - protocol_id ? protocol_id : "-"); - - lantern_log_trace( - "reqresp", - &(const struct lantern_log_metadata){.peer = peer_text}, - "status[%" PRIu64 "] stream opened", - trace_id); - - libp2p_stream_set_read_interest(stream, true); - - uint8_t *request = NULL; - size_t request_len = 0; - ssize_t read_err = 0; - bool request_legacy_len = false; - char trace_label[64]; - snprintf(trace_label, sizeof(trace_label), "status[%" PRIu64 "]", trace_id); - if (read_length_prefixed_stream( - stream, - trace_label, - peer_text, - &request, - &request_len, - &request_legacy_len, - &read_err) - != 0) { - const char *err_name = read_err == 0 ? "empty" : stream_error_name(read_err); - lantern_log_warn( - "reqresp", - &(const struct lantern_log_metadata){.peer = peer_text}, - "status[%" PRIu64 "] read failed err=%s(%zd)", - trace_id, - err_name ? err_name : "unknown", - read_err); - log_stream_error("read", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - false, - LANTERN_REQRESP_RESPONSE_INVALID_REQUEST, - "invalid request"); - close_stream(stream); +static struct lantern_reqresp_exchange *service_take_opening_exchange( + struct lantern_reqresp_service *service, + enum lantern_reqresp_protocol_kind kind, + libp2p_host_conn_t *conn) { + if (!service) { return NULL; } - if (request_legacy_len && service && service->callbacks.context && peer_text[0]) { - lantern_client_mark_peer_reqresp_legacy( - (struct lantern_client *)service->callbacks.context, - peer_text); - } - bool use_legacy_len = request_legacy_len; - if (!use_legacy_len && service && service->callbacks.context && peer_text[0]) { - use_legacy_len = lantern_client_peer_reqresp_legacy( - (struct lantern_client *)service->callbacks.context, - peer_text); - } - - lantern_log_debug( - "reqresp", - &stream_meta, - "status[%" PRIu64 "] request payload_len=%zu", - trace_id, - request_len); - - snprintf(trace_label, sizeof(trace_label), "status[%" PRIu64 "] request raw", trace_id); - log_payload_preview(trace_label, peer_text, request, request_len); - - LanternStatusMessage remote_status; - memset(&remote_status, 0, sizeof(remote_status)); - if (request_len == 0 - || decode_status_payload(request, request_len, &remote_status) != 0) { - snprintf(trace_label, sizeof(trace_label), "status[%" PRIu64 "] request decode_failed", trace_id); - log_payload_preview(trace_label, peer_text, request, request_len); - free(request); - log_stream_error("decode", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_INVALID_REQUEST, - "invalid request"); - close_stream(stream); - return NULL; + struct lantern_reqresp_exchange *result = NULL; + if (service->lock_initialized) { + pthread_mutex_lock(&service->lock); } - free(request); - - lantern_log_debug( - "reqresp", - &stream_meta, - "status[%" PRIu64 "] request framing=framed include_response_code=%s", - trace_id, - include_response_code ? "true" : "false"); - - char head_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (lantern_bytes_to_hex(remote_status.head.root.bytes, LANTERN_ROOT_SIZE, head_hex, sizeof(head_hex), 1) != 0) { - head_hex[0] = '\0'; - } - char finalized_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (lantern_bytes_to_hex( - remote_status.finalized.root.bytes, - LANTERN_ROOT_SIZE, - finalized_hex, - sizeof(finalized_hex), - 1) - != 0) { - finalized_hex[0] = '\0'; - } - lantern_log_trace( - "reqresp", - &(const struct lantern_log_metadata){.peer = peer_text}, - "status[%" PRIu64 "] decoded head_slot=%" PRIu64 " head_root=%s finalized_slot=%" PRIu64 " finalized_root=%s", - trace_id, - remote_status.head.slot, - head_hex[0] ? head_hex : "0x0", - remote_status.finalized.slot, - finalized_hex[0] ? finalized_hex : "0x0"); - - handle_remote_status(service, &remote_status, peer_text); - - LanternStatusMessage response; - memset(&response, 0, sizeof(response)); - if (!service->callbacks.build_status - || service->callbacks.build_status(service->callbacks.context, &response) != 0) { - log_stream_error("status", protocol_id, peer_text); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; + for (struct lantern_reqresp_exchange *exchange = service->exchanges; exchange; exchange = exchange->next) { + if (exchange->outbound && exchange->kind == kind && !exchange->stream + && (!conn || exchange->conn == conn)) { + result = exchange; + break; + } } - - uint8_t raw_status[2u * LANTERN_CHECKPOINT_SSZ_SIZE]; - size_t response_raw_len = 0; - if (lantern_network_status_encode(&response, raw_status, sizeof(raw_status), &response_raw_len) != 0) { - log_stream_error("encode", protocol_id, peer_text); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; + if (service->lock_initialized) { + pthread_mutex_unlock(&service->lock); } + return result; +} - size_t max_payload = response_raw_len; - if (lantern_snappy_max_compressed_size(response_raw_len, &max_payload) != LANTERN_SNAPPY_OK) { - log_stream_error("encode", protocol_id, peer_text); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; +static int build_status_request_frame( + struct lantern_reqresp_service *service, + uint8_t **out_frame, + size_t *out_frame_len) { + if (!service || !out_frame || !out_frame_len || !service->callbacks.build_status) { + return -1; } - - uint8_t *buffer = (uint8_t *)malloc(max_payload > 0 ? max_payload : 1u); - if (!buffer) { - log_stream_error("encode", protocol_id, peer_text); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; + LanternStatusMessage status; + memset(&status, 0, sizeof(status)); + if (service->callbacks.build_status(service->callbacks.context, &status) != 0) { + return -1; } - - size_t written = 0; - if (lantern_snappy_compress(raw_status, response_raw_len, buffer, max_payload, &written) - != LANTERN_SNAPPY_OK) { - free(buffer); - log_stream_error("encode", protocol_id, peer_text); - (void)send_error_response( - stream, - &stream_meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; + uint8_t raw[2u * LANTERN_CHECKPOINT_SSZ_SIZE]; + size_t raw_len = 0; + if (lantern_network_status_encode(&status, raw, sizeof(raw), &raw_len) != 0) { + return -1; } + return build_frame_from_raw(raw, raw_len, 0, 0, out_frame, out_frame_len); +} - log_payload_preview("status response raw", peer_text, buffer, written); - - const struct lantern_log_metadata meta = {.peer = peer_text}; - lantern_log_debug( - "reqresp", - &meta, - "status response lengths raw=%zu compressed=%zu", - response_raw_len, - written); - - if (send_response_chunk( - stream, - &meta, - protocol_id, - "status response", - peer_text[0] ? peer_text : NULL, - include_response_code, - LANTERN_REQRESP_RESPONSE_SUCCESS, - use_legacy_len, - buffer, - written, - response_raw_len) - != 0) { - free(buffer); - log_stream_error("write", protocol_id, peer_text); - close_stream(stream); - return NULL; +static int build_blocks_request_frame( + const LanternRoot *roots, + size_t root_count, + uint8_t **out_frame, + size_t *out_frame_len) { + if (!roots || root_count == 0u || root_count > LANTERN_MAX_REQUEST_BLOCKS || !out_frame || !out_frame_len) { + return -1; } - free(buffer); - close_stream(stream); - - lantern_log_debug( - "network", - &(const struct lantern_log_metadata){.peer = peer_text}, - "served status request"); - return NULL; + size_t raw_cap = sizeof(uint32_t) + (root_count * LANTERN_ROOT_SIZE); + uint8_t *raw = (uint8_t *)malloc(raw_cap); + if (!raw) { + return -1; + } + LanternBlocksByRootRequest req; + memset(&req, 0, sizeof(req)); + req.roots.items = (LanternRoot *)roots; + req.roots.length = root_count; + size_t raw_len = 0; + int rc = lantern_network_blocks_by_root_request_encode(&req, raw, raw_cap, &raw_len); + if (rc == 0) { + rc = build_frame_from_raw(raw, raw_len, 0, 0, out_frame, out_frame_len); + } + free(raw); + return rc; } -static void *status_request_worker(void *arg) { - struct status_request_worker_args *worker = (struct status_request_worker_args *)arg; - if (!worker) { - return NULL; +static int encode_signed_block_raw(const LanternSignedBlock *block, uint8_t **out_raw, size_t *out_raw_len) { + if (!block || !out_raw || !out_raw_len) { + return -1; } - struct status_request_ctx *ctx = worker->ctx; - libp2p_stream_t *stream = worker->stream; - free(worker); - if (!ctx || !stream) { - if (stream) { - close_stream(stream); + *out_raw = NULL; + *out_raw_len = 0; + size_t cap = 4096u; + for (unsigned attempt = 0; attempt < 20u; ++attempt) { + uint8_t *raw = (uint8_t *)malloc(cap); + if (!raw) { + return -1; } - status_request_ctx_free(ctx); - return NULL; + size_t written = 0; + ssz_error_t err = lantern_ssz_encode_signed_block(block, raw, cap, &written); + if (err == SSZ_SUCCESS) { + *out_raw = raw; + *out_raw_len = written; + return 0; + } + free(raw); + if (cap > LANTERN_REQRESP_MAX_CHUNK_BYTES / 2u) { + return -1; + } + cap *= 2u; } + return -1; +} - struct lantern_reqresp_service *service = ctx->service; - char peer_text[sizeof(ctx->peer_text)]; - memcpy(peer_text, ctx->peer_text, sizeof(peer_text)); - if (peer_text[sizeof(peer_text) - 1] != '\0') { - peer_text[sizeof(peer_text) - 1] = '\0'; - } - struct lantern_log_metadata meta = { - .peer = peer_text[0] ? peer_text : NULL, - }; - uint64_t trace_id = ctx->debug_trace_id; - int failure_code = LIBP2P_ERR_INTERNAL; - int rc = 0; - - LanternStatusMessage local_status; - memset(&local_status, 0, sizeof(local_status)); - if (!service->callbacks.build_status - || service->callbacks.build_status(service->callbacks.context, &local_status) != 0) { - lantern_log_warn( - "reqresp", - &meta, - "failed to build local status for request"); - goto finish; - } - - uint8_t raw_status[2u * LANTERN_CHECKPOINT_SSZ_SIZE]; - size_t payload_raw_len = 0; - if (lantern_network_status_encode(&local_status, raw_status, sizeof(raw_status), &payload_raw_len) != 0) { - lantern_log_error( - "reqresp", - &meta, - "failed to encode status request"); - goto finish; +static int exchange_queue_error_response(struct lantern_reqresp_exchange *exchange, uint8_t code, const char *message) { + if (!exchange) { + return -1; } + const char *text = message ? message : ""; + return append_frame_from_raw( + &exchange->read_buf, + (const uint8_t *)text, + strlen(text), + code); +} - size_t max_payload = 0; - int max_rc = lantern_snappy_max_compressed_size(payload_raw_len, &max_payload); - if (max_rc != LANTERN_SNAPPY_OK) { - lantern_log_error( - "reqresp", - &meta, - "failed to compute snappy size for status request"); - goto finish; +static int exchange_prepare_status_response(struct lantern_reqresp_exchange *exchange) { + if (!exchange || !exchange->service || !exchange->service->callbacks.build_status) { + return -1; } - - uint8_t *payload = (uint8_t *)malloc(max_payload > 0 ? max_payload : 1u); - if (!payload) { - lantern_log_error( - "reqresp", - &meta, - "out of memory building status request"); - goto finish; - } - - size_t payload_len = 0; - int snappy_rc = lantern_snappy_compress(raw_status, payload_raw_len, payload, max_payload, &payload_len); - if (snappy_rc != LANTERN_SNAPPY_OK) { - lantern_log_error( - "reqresp", - &meta, - "failed to encode status request"); - free(payload); - goto finish; + LanternStatusMessage status; + memset(&status, 0, sizeof(status)); + if (exchange->service->callbacks.build_status(exchange->service->callbacks.context, &status) != 0) { + return exchange_queue_error_response( + exchange, + LANTERN_REQRESP_RESPONSE_SERVER_ERROR, + "Status not available"); } - - char trace_stage[64]; - snprintf(trace_stage, sizeof(trace_stage), "status[%" PRIu64 "] request snappy", trace_id); - log_payload_preview(trace_stage, ctx->peer_text, payload, payload_len); - - bool use_legacy_len = false; - if (service && service->callbacks.context && peer_text[0]) { - use_legacy_len = lantern_client_peer_reqresp_legacy( - (struct lantern_client *)service->callbacks.context, - peer_text); + uint8_t raw[2u * LANTERN_CHECKPOINT_SSZ_SIZE]; + size_t raw_len = 0; + if (lantern_network_status_encode(&status, raw, sizeof(raw), &raw_len) != 0) { + return exchange_queue_error_response( + exchange, + LANTERN_REQRESP_RESPONSE_SERVER_ERROR, + "Status encode failed"); } + return append_frame_from_raw(&exchange->read_buf, raw, raw_len, LANTERN_REQRESP_RESPONSE_SUCCESS); +} - /* The varint prefix indicates the uncompressed payload size (SSZ length). */ - uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; - size_t header_len = 0; - size_t declared_len = use_legacy_len ? payload_len : payload_raw_len; - if (unsigned_varint_encode(declared_len, header, sizeof(header), &header_len) != UNSIGNED_VARINT_OK) { - lantern_log_error( - "reqresp", - &meta, - "failed to encode status request header bytes=%zu", - declared_len); - free(payload); - goto finish; - } - - char header_hex[(LANTERN_REQRESP_HEADER_MAX_BYTES * 2u) + 1u]; - header_hex[0] = '\0'; - if (lantern_bytes_to_hex(header, header_len, header_hex, sizeof(header_hex), 0) != 0) { - header_hex[0] = '\0'; - } - const char *protocol_id = ctx->protocol_id ? ctx->protocol_id : LANTERN_STATUS_PROTOCOL_ID; - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] request header_len=%zu varint_len=%zu declared_len=%zu raw_len=%zu compressed_len=%zu legacy_len=%s header_hex=%s", - trace_id, - header_len, - declared_len, - declared_len, - payload_raw_len, - payload_len, - use_legacy_len ? "true" : "false", - header_hex[0] ? header_hex : "-"); - - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] expect_response_code=true", - trace_id); - - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] sending %s request declared_bytes=%zu raw_bytes=%zu compressed_bytes=%zu legacy_len=%s", - trace_id, - protocol_id, - declared_len, - payload_raw_len, - payload_len, - use_legacy_len ? "true" : "false"); - - const char *peer_label = ctx->peer_text[0] ? ctx->peer_text : NULL; - size_t frame_len = header_len + payload_len; - uint8_t *frame = (uint8_t *)malloc(frame_len > 0 ? frame_len : 1u); - if (!frame) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to allocate request frame bytes=%zu", - trace_id, - frame_len); - free(payload); - goto finish; - } - if (header_len > 0) { - memcpy(frame, header, header_len); +static int exchange_prepare_blocks_response(struct lantern_reqresp_exchange *exchange, const uint8_t *raw, size_t raw_len) { + if (!exchange || !exchange->service || !raw) { + return -1; } - if (payload_len > 0) { - memcpy(frame + header_len, payload, payload_len); + LanternBlocksByRootRequest req; + lantern_blocks_by_root_request_init(&req); + if (lantern_network_blocks_by_root_request_decode(&req, raw, raw_len) != 0) { + lantern_blocks_by_root_request_reset(&req); + return exchange_queue_error_response( + exchange, + LANTERN_REQRESP_RESPONSE_INVALID_REQUEST, + "Invalid BlocksByRootRequest"); + } + LanternSignedBlockList blocks; + lantern_signed_block_list_init(&blocks); + int collect_rc = exchange->service->callbacks.collect_blocks + ? exchange->service->callbacks.collect_blocks( + exchange->service->callbacks.context, + req.roots.items, + req.roots.length, + &blocks) + : -1; + if (collect_rc != 0) { + lantern_signed_block_list_reset(&blocks); + lantern_blocks_by_root_request_reset(&req); + return exchange_queue_error_response( + exchange, + LANTERN_REQRESP_RESPONSE_SERVER_ERROR, + "Block lookup failed"); + } + for (size_t i = 0; i < blocks.length; ++i) { + uint8_t *block_raw = NULL; + size_t block_raw_len = 0; + if (encode_signed_block_raw(&blocks.blocks[i], &block_raw, &block_raw_len) != 0 + || append_frame_from_raw( + &exchange->read_buf, + block_raw, + block_raw_len, + LANTERN_REQRESP_RESPONSE_SUCCESS) + != 0) { + free(block_raw); + lantern_signed_block_list_reset(&blocks); + lantern_blocks_by_root_request_reset(&req); + return -1; + } + free(block_raw); } + lantern_log_info( + "network", + &(const struct lantern_log_metadata){.peer = exchange->peer_id_text[0] ? exchange->peer_id_text : NULL}, + "served blocks-by-root request (%zu roots)", + req.roots.length); + lantern_signed_block_list_reset(&blocks); + lantern_blocks_by_root_request_reset(&req); + return 0; +} - rc = write_stream_all( - stream, - frame, - frame_len, - protocol_id, - "status request frame", - peer_label); - free(frame); - if (rc != 0) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to write request", - trace_id); - free(payload); - failure_code = rc; - goto finish; - } - free(payload); - - /* Half-close the stream (send FIN on write side) to signal we're done sending. - * This is required by the libp2p req/resp protocol - the responder waits for FIN - * before processing the request and sending the response. - * NOTE: We use shutdown_write() not close() - close() would prevent reading the response! */ - rc = libp2p_stream_shutdown_write(stream); - if (rc != 0) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to half-close stream", - trace_id); - failure_code = rc; - goto finish; - } - - uint8_t *response = NULL; - size_t response_len = 0; - ssize_t read_err = 0; - uint8_t response_code = LANTERN_REQRESP_RESPONSE_SUCCESS; - rc = lantern_reqresp_read_response_chunk( - service, - stream, - LANTERN_REQRESP_PROTOCOL_STATUS, - &response, - &response_len, - &read_err, - &response_code, - NULL); - if (rc != 0) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to read response err=%zd", - trace_id, - read_err); - failure_code = (read_err != 0) ? (int)read_err : LIBP2P_ERR_INTERNAL; - goto finish; - } - - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] response received code=%u raw_len=%zu", - trace_id, - (unsigned)response_code, - response_len); - - snprintf(trace_stage, sizeof(trace_stage), "status[%" PRIu64 "] response raw", trace_id); - log_payload_preview(trace_stage, ctx->peer_text, response, response_len); - - if (response_code != LANTERN_REQRESP_RESPONSE_SUCCESS) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] response returned code=%u payload_len=%zu", - trace_id, - (unsigned)response_code, - response_len); - free(response); - failure_code = LIBP2P_ERR_INTERNAL; - goto finish; - } - - LanternStatusMessage remote_status; - memset(&remote_status, 0, sizeof(remote_status)); - if (response_len == 0 - || decode_status_payload(response, response_len, &remote_status) != 0) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to decode response bytes=%zu", - trace_id, - response_len); - free(response); - failure_code = LIBP2P_ERR_INTERNAL; - goto finish; - } - free(response); - - char head_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (lantern_bytes_to_hex(remote_status.head.root.bytes, LANTERN_ROOT_SIZE, head_hex, sizeof(head_hex), 1) != 0) { - head_hex[0] = '\0'; - } - char finalized_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - if (lantern_bytes_to_hex( - remote_status.finalized.root.bytes, - LANTERN_ROOT_SIZE, - finalized_hex, - sizeof(finalized_hex), - 1) - != 0) { - finalized_hex[0] = '\0'; - } - lantern_log_trace( - "reqresp", - &meta, - "status[%" PRIu64 "] received head_slot=%" PRIu64 " head_root=%s finalized_slot=%" PRIu64 " finalized_root=%s", - trace_id, - remote_status.head.slot, - head_hex[0] ? head_hex : "0x0", - remote_status.finalized.slot, - finalized_hex[0] ? finalized_hex : "0x0"); - - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] decoded head_slot=%" PRIu64 " finalized_slot=%" PRIu64, - trace_id, - remote_status.head.slot, - remote_status.finalized.slot); - - handle_remote_status(service, &remote_status, ctx->peer_text); - failure_code = LIBP2P_ERR_OK; - -finish: - close_stream(stream); - status_request_ctx_free(ctx); - if (failure_code != LIBP2P_ERR_OK) { - status_request_notify_failure(service, peer_text[0] ? peer_text : NULL, failure_code); - } - return NULL; +static int exchange_set_write_from_buffer(struct lantern_reqresp_exchange *exchange) { + if (!exchange) { + return -1; + } + free(exchange->write_buf); + exchange->write_buf = exchange->read_buf.data; + exchange->write_len = exchange->read_buf.len; + exchange->write_off = 0; + exchange->read_buf.data = NULL; + exchange->read_buf.len = 0; + exchange->read_buf.cap = 0; + return 0; } -static void status_request_on_open(libp2p_stream_t *stream, void *user_data, int err) { - struct status_request_ctx *ctx = (struct status_request_ctx *)user_data; - struct lantern_log_metadata meta = { - .peer = (ctx && ctx->peer_text[0]) ? ctx->peer_text : NULL, - }; - uint64_t trace_id = ctx ? ctx->debug_trace_id : 0; - const char *protocol_id = - (ctx && ctx->protocol_id) ? ctx->protocol_id : LANTERN_STATUS_PROTOCOL_ID; - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] request stream opened protocol=%s err=%d", - trace_id, - protocol_id, - err); - bool retained = (stream && libp2p__stream_retain_async(stream)); - if (!ctx) { - if (retained) { - close_stream(stream); +static int exchange_flush_write(struct lantern_reqresp_exchange *exchange) { + if (!exchange || !exchange->host || !exchange->stream) { + return -1; + } + while (exchange->write_off < exchange->write_len) { + ssize_t n = stream_write( + (struct lantern_reqresp_stream *)&(struct lantern_reqresp_stream){ + .host = exchange->host, + .stream = exchange->stream, + }, + exchange->write_buf + exchange->write_off, + exchange->write_len - exchange->write_off); + if (n > 0) { + exchange->write_off += (size_t)n; + continue; } - return; + if (n == -EAGAIN) { + return 0; + } + return -1; } + (void)libp2p_host_stream_finish(exchange->host, exchange->stream); + return 0; +} - if (err != 0 || !stream) { - lantern_log_warn( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to open %s stream err=%d", - trace_id, - protocol_id, - err); - status_request_notify_failure(ctx->service, meta.peer, err != 0 ? err : LIBP2P_ERR_INTERNAL); - if (retained) { - close_stream(stream); - retained = false; - } - status_request_ctx_free(ctx); - return; +static int exchange_read_available(struct lantern_reqresp_exchange *exchange, int *out_fin) { + if (out_fin) { + *out_fin = 0; } - - if (!retained) { - lantern_log_warn( - "reqresp", - &meta, - "status[%" PRIu64 "] stream unavailable (destroying), aborting request", - trace_id); - status_request_notify_failure(ctx->service, meta.peer, LIBP2P_ERR_INTERNAL); - status_request_ctx_free(ctx); - return; + if (!exchange || !exchange->host || !exchange->stream) { + return -1; } + uint8_t tmp[4096]; + for (;;) { + size_t read_len = 0; + int fin = 0; + libp2p_host_err_t err = + libp2p_host_stream_read(exchange->host, exchange->stream, tmp, sizeof(tmp), &read_len, &fin); + if (err == LIBP2P_HOST_ERR_WOULD_BLOCK) { + return 0; + } + if (err != LIBP2P_HOST_OK) { + return -1; + } + if (read_len > 0u && reqresp_buffer_append(&exchange->read_buf, tmp, read_len) != 0) { + return -1; + } + if (fin) { + if (out_fin) { + *out_fin = 1; + } + return 0; + } + if (read_len == 0u) { + return 0; + } + } +} - struct status_request_worker_args *worker = (struct status_request_worker_args *)malloc(sizeof(*worker)); - if (!worker) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to allocate worker for %s stream", - trace_id, - protocol_id); - status_request_notify_failure(ctx->service, meta.peer, LIBP2P_ERR_INTERNAL); - close_stream(stream); - status_request_ctx_free(ctx); +static void exchange_fail(struct lantern_reqresp_exchange *exchange, int error) { + if (!exchange || exchange->completed) { return; } - worker->ctx = ctx; - worker->stream = stream; - - pthread_t thread; - if (pthread_create(&thread, NULL, status_request_worker, worker) != 0) { - lantern_log_error( - "reqresp", - &meta, - "status[%" PRIu64 "] failed to spawn request worker", - trace_id); - free(worker); - status_request_notify_failure(ctx->service, meta.peer, LIBP2P_ERR_INTERNAL); - close_stream(stream); - status_request_ctx_free(ctx); - return; + exchange->completed = 1; + if (exchange->outbound && exchange->kind == LANTERN_REQRESP_PROTOCOL_STATUS) { + if (exchange->service->callbacks.status_failure) { + exchange->service->callbacks.status_failure( + exchange->service->callbacks.context, + exchange->peer_id_text, + error); + } + } else if (exchange->outbound && exchange->kind == LANTERN_REQRESP_PROTOCOL_BLOCKS_BY_ROOT) { + if (exchange->service->callbacks.blocks_request_complete) { + exchange->service->callbacks.blocks_request_complete( + exchange->service->callbacks.context, + exchange->peer_id_text, + exchange->roots, + exchange->root_count, + exchange->request_id, + exchange->responses_received > 0u ? 1 : 0); + } } - lantern_log_debug( - "reqresp", - &meta, - "status[%" PRIu64 "] spawned request worker", - trace_id); - pthread_detach(thread); } -static int status_request_launch( - struct lantern_reqresp_service *service, - const peer_id_t *peer_id, - const char *peer_id_text, - bool notify_on_failure) { - if (!service || !service->host || !peer_id) { +static int exchange_handle_outbound_status_frame( + struct lantern_reqresp_exchange *exchange, + uint8_t code, + uint8_t *raw, + size_t raw_len) { + if (!exchange || !raw) { return -1; } - - struct status_request_ctx *ctx = (struct status_request_ctx *)calloc(1, sizeof(*ctx)); - if (!ctx) { - return -1; + if (code != LANTERN_REQRESP_RESPONSE_SUCCESS) { + exchange_fail(exchange, (int)code); + return 0; } - ctx->service = service; - ctx->debug_trace_id = reqresp_trace_id_next(); - - struct lantern_log_metadata meta = { - .peer = NULL, - }; - - if (peer_id_text && peer_id_text[0] != '\0') { - strncpy(ctx->peer_text, peer_id_text, sizeof(ctx->peer_text) - 1); - ctx->peer_text[sizeof(ctx->peer_text) - 1] = '\0'; - } else { - if (write_legacy_peer_id_text(peer_id, ctx->peer_text, sizeof(ctx->peer_text)) < 0) { - ctx->peer_text[0] = '\0'; - } + LanternStatusMessage status; + memset(&status, 0, sizeof(status)); + if (lantern_network_status_decode(&status, raw, raw_len) != 0) { + exchange_fail(exchange, LANTERN_REQRESP_ERR_INVALID_PAYLOAD); + return 0; } - if (ctx->peer_text[0] != '\0') { - meta.peer = ctx->peer_text; + if (exchange->service->callbacks.handle_status) { + (void)exchange->service->callbacks.handle_status( + exchange->service->callbacks.context, + &status, + exchange->peer_id_text); } - lantern_log_trace( - "reqresp", - &meta, - "status[%" PRIu64 "] scheduling request", - ctx->debug_trace_id); + exchange->completed = 1; + return 0; +} - if (peer_id_clone(peer_id, &ctx->peer_id) != PEER_ID_OK || !ctx->peer_id) { - lantern_log_warn( - "reqresp", - &meta, - "failed to clone peer id for status request"); - if (notify_on_failure) { - status_request_notify_failure(service, meta.peer, LIBP2P_ERR_INTERNAL); - } - status_request_ctx_free(ctx); +static int exchange_handle_outbound_block_frame( + struct lantern_reqresp_exchange *exchange, + uint8_t code, + uint8_t *raw, + size_t raw_len) { + if (!exchange) { return -1; } - - ctx->protocol_id = LANTERN_STATUS_PROTOCOL_ID; - int rc = libp2p_host_open_stream_async( - service->host, - ctx->peer_id, - ctx->protocol_id, - status_request_on_open, - ctx); - if (rc != 0) { - lantern_log_warn( - "reqresp", - &meta, - "libp2p open stream failed rc=%d", - rc); - if (notify_on_failure) { - status_request_notify_failure(service, meta.peer, rc); - } - status_request_ctx_free(ctx); - return -1; + if (code == LANTERN_REQRESP_RESPONSE_RESOURCE_UNAVAILABLE) { + return 0; + } + if (code != LANTERN_REQRESP_RESPONSE_SUCCESS || !raw) { + exchange_fail(exchange, (int)code); + return 0; + } + LanternSignedBlock block; + lantern_signed_block_init(&block); + if (lantern_ssz_decode_signed_block(&block, raw, raw_len) != SSZ_SUCCESS) { + lantern_signed_block_reset(&block); + exchange_fail(exchange, LANTERN_REQRESP_ERR_INVALID_PAYLOAD); + return 0; + } + int handled = 0; + if (exchange->service->callbacks.handle_block_response) { + handled = exchange->service->callbacks.handle_block_response( + exchange->service->callbacks.context, + &block, + raw, + raw_len, + exchange->peer_id_text); + } + lantern_signed_block_reset(&block); + if (handled == 0) { + exchange->responses_received += 1u; + if (exchange->responses_received >= exchange->root_count) { + exchange->completed = 1; + if (exchange->service->callbacks.blocks_request_complete) { + exchange->service->callbacks.blocks_request_complete( + exchange->service->callbacks.context, + exchange->peer_id_text, + exchange->roots, + exchange->root_count, + exchange->request_id, + 1); + } + } } return 0; } -int lantern_reqresp_service_request_status( - struct lantern_reqresp_service *service, - const peer_id_t *peer_id, - const char *peer_id_text) { - return status_request_launch(service, peer_id, peer_id_text, true); -} - -static void *blocks_worker(void *arg) { - struct blocks_stream_ctx *ctx = (struct blocks_stream_ctx *)arg; - if (!ctx) { - return NULL; +static int exchange_parse_outbound_frames(struct lantern_reqresp_exchange *exchange) { + if (!exchange) { + return -1; } - struct lantern_reqresp_service *service = ctx->service; - libp2p_stream_t *stream = ctx->stream; - const char *protocol_id = - ctx->protocol_id ? ctx->protocol_id : LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID; - free(ctx); - - if (!service || !stream) { - close_stream(stream); - return NULL; + while (!exchange->completed) { + uint8_t code = 0; + uint8_t *raw = NULL; + size_t raw_len = 0; + int need_more = 0; + int rc = extract_frame_from_buffer(&exchange->read_buf, 1, &code, &raw, &raw_len, &need_more); + if (rc < 0) { + exchange_fail(exchange, LANTERN_REQRESP_ERR_INVALID_PAYLOAD); + return 0; + } + if (rc == 0) { + return need_more ? 0 : -1; + } + if (exchange->kind == LANTERN_REQRESP_PROTOCOL_STATUS) { + (void)exchange_handle_outbound_status_frame(exchange, code, raw, raw_len); + } else { + (void)exchange_handle_outbound_block_frame(exchange, code, raw, raw_len); + } + free(raw); + if (exchange->kind == LANTERN_REQRESP_PROTOCOL_STATUS) { + break; + } } + return 0; +} - char peer_text[128]; - describe_peer(libp2p_stream_remote_peer(stream), peer_text, sizeof(peer_text)); - - const struct lantern_log_metadata meta = {.peer = peer_text[0] ? peer_text : NULL}; - lantern_log_debug( - "reqresp", - &meta, - "blocks_by_root stream protocol=%s", - protocol_id ? protocol_id : "-"); - - libp2p_stream_set_read_interest(stream, true); - - uint8_t *request = NULL; - size_t request_len = 0; - ssize_t request_err = 0; - bool request_legacy_len = false; - if (read_length_prefixed_stream( - stream, - "blocks_by_root", - peer_text, - &request, - &request_len, - &request_legacy_len, - &request_err) - != 0) { - const char *err_name = request_err == 0 ? "empty" : stream_error_name(request_err); - lantern_log_warn( - "reqresp", - &meta, - "blocks_by_root read failed err=%s(%zd)", - err_name ? err_name : "unknown", - request_err); - log_stream_error("read", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - false, - LANTERN_REQRESP_RESPONSE_INVALID_REQUEST, - "invalid request"); - close_stream(stream); - return NULL; - } - if (request_legacy_len && service && service->callbacks.context && peer_text[0]) { - lantern_client_mark_peer_reqresp_legacy( - (struct lantern_client *)service->callbacks.context, - peer_text); - } - bool use_legacy_len = request_legacy_len; - if (!use_legacy_len && service && service->callbacks.context && peer_text[0]) { - use_legacy_len = lantern_client_peer_reqresp_legacy( - (struct lantern_client *)service->callbacks.context, - peer_text); - } - - lantern_log_debug( - "reqresp", - &meta, - "blocks_by_root request payload_len=%zu", - request_len); - - log_payload_preview("blocks_by_root request raw", peer_text, request, request_len); - - bool request_framed = payload_is_snappy_framed(request, request_len); - if (!request_framed) { - lantern_log_warn( - "reqresp", - &meta, - "blocks_by_root request missing snappy framing"); - free(request); - log_stream_error("decode", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_INVALID_REQUEST, - "invalid request"); - close_stream(stream); - return NULL; +static int exchange_handle_inbound_request(struct lantern_reqresp_exchange *exchange) { + if (!exchange || exchange->write_buf) { + return 0; } - lantern_log_debug( - "reqresp", - &meta, - "blocks_by_root request framing=%s include_response_code=%s", - request_framed ? "framed" : "raw", - "true"); - LanternBlocksByRootRequest decoded_request; - lantern_blocks_by_root_request_init(&decoded_request); - int decode_rc = lantern_network_blocks_by_root_request_decode_snappy( - &decoded_request, - request, - request_len); - free(request); - if (decode_rc != 0) { - lantern_blocks_by_root_request_reset(&decoded_request); - log_stream_error("decode", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, + uint8_t *raw = NULL; + size_t raw_len = 0; + int need_more = 0; + int rc = extract_frame_from_buffer(&exchange->read_buf, 0, NULL, &raw, &raw_len, &need_more); + if (rc < 0) { + reqresp_buffer_reset(&exchange->read_buf); + (void)exchange_queue_error_response( + exchange, LANTERN_REQRESP_RESPONSE_INVALID_REQUEST, - "invalid request"); - close_stream(stream); - return NULL; - } - - lantern_log_debug( - "reqresp", - &meta, - "blocks_by_root decoded roots=%zu", - decoded_request.roots.length); - - LanternSignedBlockList response; - lantern_signed_block_list_init(&response); - - int collect_rc = 0; - if (service->callbacks.collect_blocks) { - collect_rc = service->callbacks.collect_blocks( - service->callbacks.context, - decoded_request.roots.items, - decoded_request.roots.length, - &response); + "Invalid request"); + return exchange_set_write_from_buffer(exchange); + } + if (rc == 0) { + return need_more ? 0 : -1; + } + exchange->request_complete = 1; + int prepare_rc = 0; + if (exchange->kind == LANTERN_REQRESP_PROTOCOL_STATUS) { + LanternStatusMessage peer_status; + memset(&peer_status, 0, sizeof(peer_status)); + if (lantern_network_status_decode(&peer_status, raw, raw_len) == 0 + && exchange->service->callbacks.handle_status) { + (void)exchange->service->callbacks.handle_status( + exchange->service->callbacks.context, + &peer_status, + exchange->peer_id_text); + } + prepare_rc = exchange_prepare_status_response(exchange); + } else { + prepare_rc = exchange_prepare_blocks_response(exchange, raw, raw_len); } - - lantern_log_debug( - "reqresp", - &meta, - "blocks_by_root collect rc=%d blocks=%zu", - collect_rc, - response.length); - lantern_blocks_by_root_request_reset(&decoded_request); - - if (collect_rc != 0) { - lantern_signed_block_list_reset(&response); - log_stream_error("collect", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, + free(raw); + if (prepare_rc != 0) { + reqresp_buffer_reset(&exchange->read_buf); + (void)exchange_queue_error_response( + exchange, LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; + "Internal error"); } + return exchange_set_write_from_buffer(exchange); +} - size_t block_count = response.length; - uint8_t *ssz_buffer = NULL; - size_t ssz_capacity = 4096u; - uint8_t *snappy_buffer = NULL; - size_t snappy_capacity = 0; - - if (block_count == 0) { - lantern_log_debug( - "reqresp", - &meta, - "blocks_by_root response has zero blocks for peer=%s", - peer_text[0] ? peer_text : "-"); - } - - for (size_t i = 0; i < block_count; ++i) { - const LanternSignedBlock *block = &response.blocks[i]; - size_t ssz_written = 0; - bool encoded = false; - while (ssz_capacity > 0 && ssz_capacity <= LANTERN_REQRESP_MAX_CHUNK_BYTES) { - uint8_t *resized = (uint8_t *)realloc(ssz_buffer, ssz_capacity); - if (!resized) { - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - log_stream_error("encode", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; - } - ssz_buffer = resized; +static libp2p_host_err_t reqresp_on_open( + libp2p_host_t *host, + libp2p_host_stream_t *stream, + libp2p_host_stream_direction_t direction, + void *protocol_user_data) { + struct lantern_reqresp_protocol_context *protocol_ctx = + (struct lantern_reqresp_protocol_context *)protocol_user_data; + if (!host || !stream || !protocol_ctx || !protocol_ctx->service) { + return LIBP2P_HOST_ERR_INVALID_ARG; + } + libp2p_host_conn_t *conn = NULL; + (void)libp2p_host_stream_conn(stream, &conn); + struct lantern_reqresp_exchange *exchange = NULL; + if (direction == LIBP2P_HOST_STREAM_OUTBOUND) { + exchange = service_take_opening_exchange(protocol_ctx->service, protocol_ctx->kind, conn); + if (!exchange) { + return LIBP2P_HOST_ERR_STATE; + } + } else { + exchange = (struct lantern_reqresp_exchange *)calloc(1u, sizeof(*exchange)); + if (!exchange) { + return LIBP2P_HOST_ERR_INTERNAL; + } + exchange->service = protocol_ctx->service; + exchange->kind = protocol_ctx->kind; + exchange->outbound = 0; + exchange->conn = conn; + service_add_exchange(protocol_ctx->service, exchange); + } + exchange->host = host; + exchange->stream = stream; + exchange_set_peer_text_from_conn(exchange, conn); + (void)libp2p_host_stream_set_user_data(stream, exchange); + return LIBP2P_HOST_OK; +} - if (lantern_ssz_encode_signed_block_canonical(block, ssz_buffer, ssz_capacity, &ssz_written) == 0) { - encoded = true; - break; - } - if (ssz_capacity > SIZE_MAX / 2) { - break; - } - size_t next_capacity = ssz_capacity * 2u; - if (next_capacity > LANTERN_REQRESP_MAX_CHUNK_BYTES) { - next_capacity = LANTERN_REQRESP_MAX_CHUNK_BYTES; - } - if (next_capacity <= ssz_capacity) { - break; +static libp2p_host_err_t reqresp_on_event( + libp2p_host_t *host, + libp2p_host_stream_t *stream, + libp2p_host_protocol_event_kind_t kind, + void *protocol_user_data) { + (void)protocol_user_data; + void *user_data = NULL; + if (!host || !stream || libp2p_host_stream_user_data(stream, &user_data) != LIBP2P_HOST_OK || !user_data) { + return LIBP2P_HOST_OK; + } + struct lantern_reqresp_exchange *exchange = (struct lantern_reqresp_exchange *)user_data; + if (kind == LIBP2P_HOST_PROTOCOL_EVENT_RESET || kind == LIBP2P_HOST_PROTOCOL_EVENT_CLOSED) { + if (exchange->outbound && !exchange->completed) { + if (exchange->kind == LANTERN_REQRESP_PROTOCOL_BLOCKS_BY_ROOT && exchange->responses_received > 0u) { + exchange->completed = 1; + if (exchange->service->callbacks.blocks_request_complete) { + exchange->service->callbacks.blocks_request_complete( + exchange->service->callbacks.context, + exchange->peer_id_text, + exchange->roots, + exchange->root_count, + exchange->request_id, + 1); + } + } else { + exchange_fail(exchange, LANTERN_REQRESP_ERR_STREAM_READ); } - ssz_capacity = next_capacity; - } - if (!encoded || ssz_written == 0) { - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - log_stream_error("encode", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; - } - - size_t max_compressed = 0; - int max_rc = lantern_snappy_max_compressed_size(ssz_written, &max_compressed); - if (max_rc != LANTERN_SNAPPY_OK || max_compressed == 0) { - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - log_stream_error("compress", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; - } - - if (max_compressed > snappy_capacity) { - uint8_t *resized = (uint8_t *)realloc(snappy_buffer, max_compressed); - if (!resized) { - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - log_stream_error("compress", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; + } + service_remove_exchange(exchange->service, exchange); + exchange_free(exchange); + return LIBP2P_HOST_OK; + } + if (kind == LIBP2P_HOST_PROTOCOL_EVENT_WRITABLE && exchange->write_buf) { + if (exchange_flush_write(exchange) != 0) { + exchange_fail(exchange, LANTERN_REQRESP_ERR_STREAM_WRITE); + return LIBP2P_HOST_ERR_PROTOCOL; + } + return LIBP2P_HOST_OK; + } + if (kind == LIBP2P_HOST_PROTOCOL_EVENT_READABLE) { + int fin = 0; + if (exchange_read_available(exchange, &fin) != 0) { + exchange_fail(exchange, LANTERN_REQRESP_ERR_STREAM_READ); + return LIBP2P_HOST_ERR_PROTOCOL; + } + if (exchange->outbound) { + if (exchange_parse_outbound_frames(exchange) != 0) { + return LIBP2P_HOST_ERR_PROTOCOL; } - snappy_buffer = resized; - snappy_capacity = max_compressed; - } - - size_t compressed_len = 0; - int snappy_rc = lantern_snappy_compress( - ssz_buffer, - ssz_written, - snappy_buffer, - snappy_capacity, - &compressed_len); - if (snappy_rc != LANTERN_SNAPPY_OK) { - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - log_stream_error("compress", protocol_id, peer_text[0] ? peer_text : NULL); - (void)send_error_response( - stream, - &meta, - protocol_id, - peer_text[0] ? peer_text : NULL, - use_legacy_len, - LANTERN_REQRESP_RESPONSE_SERVER_ERROR, - "server error"); - close_stream(stream); - return NULL; - } - - char chunk_label[64]; - snprintf( - chunk_label, - sizeof(chunk_label), - "blocks_by_root chunk[%zu/%zu]", - i + 1, - block_count); - log_payload_preview(chunk_label, peer_text, snappy_buffer, compressed_len); - lantern_log_debug( - "reqresp", - &meta, - "%s slot=%" PRIu64 " raw=%zu compressed=%zu", - chunk_label, - block->block.slot, - ssz_written, - compressed_len); - - if (send_response_chunk( - stream, - &meta, - protocol_id, - chunk_label, - peer_text[0] ? peer_text : NULL, - true, - LANTERN_REQRESP_RESPONSE_SUCCESS, - use_legacy_len, - snappy_buffer, - compressed_len, - ssz_written) - != 0) { - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - log_stream_error("write", protocol_id, peer_text[0] ? peer_text : NULL); - close_stream(stream); - return NULL; - } - } - - free(ssz_buffer); - free(snappy_buffer); - lantern_signed_block_list_reset(&response); - close_stream(stream); - - lantern_log_info( - "network", - &(const struct lantern_log_metadata){.peer = peer_text}, - "served blocks-by-root request (%zu roots)", - block_count); - return NULL; + } else if (exchange_handle_inbound_request(exchange) != 0) { + return LIBP2P_HOST_ERR_PROTOCOL; + } + if (!exchange->outbound && exchange->write_buf && exchange_flush_write(exchange) != 0) { + return LIBP2P_HOST_ERR_PROTOCOL; + } + if (!exchange->outbound && exchange->request_complete && !exchange->write_buf && exchange->read_buf.len == 0u) { + (void)libp2p_host_stream_finish(host, stream); + } + (void)fin; + } + return LIBP2P_HOST_OK; } -static void status_on_open_impl( - libp2p_stream_t *stream, - void *user_data, - const char *protocol_id) { +static void reqresp_host_event( + struct lantern_libp2p_host *network, + const libp2p_host_event_t *event, + void *user_data) { + (void)network; struct lantern_reqresp_service *service = (struct lantern_reqresp_service *)user_data; - if (stream && !libp2p__stream_retain_async(stream)) { - /* Stream is already being torn down; skip handling. */ + if (!service || !event) { return; } - struct status_stream_ctx *ctx = (struct status_stream_ctx *)malloc(sizeof(*ctx)); - if (!ctx) { - close_stream(stream); - return; - } - ctx->service = service; - ctx->stream = stream; - ctx->protocol_id = protocol_id; - ctx->debug_trace_id = reqresp_trace_id_next(); - pthread_t thread; - if (pthread_create(&thread, NULL, status_worker, ctx) != 0) { - free(ctx); - close_stream(stream); - return; + if (event->type == LIBP2P_HOST_EVENT_CONN_ESTABLISHED && event->conn) { + service_record_conn(service, event->conn); + } else if (event->type == LIBP2P_HOST_EVENT_CONN_CLOSED && event->conn) { + service_remove_conn(service, event->conn); + } else if (event->type == LIBP2P_HOST_EVENT_STREAM_OPEN_FAILED && event->user_data) { + struct lantern_reqresp_exchange *exchange = (struct lantern_reqresp_exchange *)event->user_data; + if (!service_remove_exchange(service, exchange)) { + return; + } + exchange_fail(exchange, (int)event->reason); + exchange_free(exchange); } - pthread_detach(thread); } -static void status_on_open_primary(libp2p_stream_t *stream, void *user_data) { - status_on_open_impl(stream, user_data, LANTERN_STATUS_PROTOCOL_ID); +uint32_t lantern_reqresp_stall_timeout_ms(void) { + return LANTERN_REQRESP_STALL_TIMEOUT_MS; } -static void blocks_on_open_impl(libp2p_stream_t *stream, void *user_data, const char *protocol_id) { - struct lantern_reqresp_service *service = (struct lantern_reqresp_service *)user_data; +void lantern_reqresp_service_init(struct lantern_reqresp_service *service) { if (!service) { - close_stream(stream); return; } - if (stream && !libp2p__stream_retain_async(stream)) { - /* Stream is already being torn down; skip handling. */ - return; - } - struct blocks_stream_ctx *ctx = (struct blocks_stream_ctx *)malloc(sizeof(*ctx)); - if (!ctx) { - close_stream(stream); + memset(service, 0, sizeof(*service)); +} + +void lantern_reqresp_service_reset(struct lantern_reqresp_service *service) { + if (!service) { return; } - ctx->service = service; - ctx->stream = stream; - ctx->protocol_id = protocol_id; - pthread_t thread; - if (pthread_create(&thread, NULL, blocks_worker, ctx) != 0) { - free(ctx); - close_stream(stream); - return; + service_clear_exchanges(service); + if (service->lock_initialized) { + pthread_mutex_destroy(&service->lock); } - pthread_detach(thread); -} - -static void blocks_on_open_primary(libp2p_stream_t *stream, void *user_data) { - blocks_on_open_impl(stream, user_data, LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID); + memset(service, 0, sizeof(*service)); } int lantern_reqresp_service_start( struct lantern_reqresp_service *service, const struct lantern_reqresp_service_config *config) { - if (!service || !config || !config->host) { + if (!service || !config || !config->network || !config->network->host) { return -1; } - - lantern_reqresp_service_reset(service); - - service->host = config->host; + service->network = config->network; if (config->callbacks) { service->callbacks = *config->callbacks; - } else { - memset(&service->callbacks, 0, sizeof(service->callbacks)); + } + if (pthread_mutex_init(&service->lock, NULL) == 0) { + service->lock_initialized = 1; } - libp2p_protocol_def_t status_def; - memset(&status_def, 0, sizeof(status_def)); - status_def.protocol_id = LANTERN_STATUS_PROTOCOL_ID; - status_def.read_mode = LIBP2P_READ_PULL; - status_def.on_open = status_on_open_primary; - status_def.user_data = service; - - const char *blocks_protocol_primary = LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID; + service->status_protocol.id = (const uint8_t *)LANTERN_REQRESP_STATUS_PROTOCOL; + service->status_protocol.id_len = strlen(LANTERN_REQRESP_STATUS_PROTOCOL); + service->status_protocol.on_open = reqresp_on_open; + service->status_protocol.on_event = reqresp_on_event; + service->status_context.service = service; + service->status_context.kind = LANTERN_REQRESP_PROTOCOL_STATUS; + service->status_protocol.user_data = &service->status_context; - libp2p_protocol_def_t blocks_def; - memset(&blocks_def, 0, sizeof(blocks_def)); - blocks_def.protocol_id = blocks_protocol_primary; - blocks_def.read_mode = LIBP2P_READ_PULL; - blocks_def.on_open = blocks_on_open_primary; - blocks_def.user_data = service; + service->blocks_protocol.id = (const uint8_t *)LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL; + service->blocks_protocol.id_len = strlen(LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL); + service->blocks_protocol.on_open = reqresp_on_open; + service->blocks_protocol.on_event = reqresp_on_event; + service->blocks_context.service = service; + service->blocks_context.kind = LANTERN_REQRESP_PROTOCOL_BLOCKS_BY_ROOT; + service->blocks_protocol.user_data = &service->blocks_context; - if (libp2p_host_listen_protocol(service->host, &status_def, &service->status_server) != 0) { - lantern_reqresp_service_reset(service); + if (lantern_libp2p_host_register_protocol(service->network, &service->status_protocol) != 0 || + lantern_libp2p_host_register_protocol(service->network, &service->blocks_protocol) != 0) { return -1; } - if (libp2p_host_listen_protocol(service->host, &blocks_def, &service->blocks_server) != 0) { - lantern_reqresp_service_reset(service); + if (lantern_libp2p_host_register_event_handler(service->network, reqresp_host_event, service) != 0) { return -1; } + return 0; +} - lantern_log_info( - "network", - &(const struct lantern_log_metadata){0}, - "request/response protocols registered"); +static int service_open_exchange( + struct lantern_reqresp_service *service, + const struct lantern_peer_id *peer_id, + const char *peer_id_text, + enum lantern_reqresp_protocol_kind kind, + uint8_t *frame, + size_t frame_len, + const LanternRoot *roots, + size_t root_count, + uint64_t request_id) { + if (!service || !service->network || !service->network->host || !peer_id || !frame || frame_len == 0u) { + free(frame); + return -1; + } + libp2p_host_conn_t *conn = service_find_conn(service, peer_id); + if (!conn) { + free(frame); + return -1; + } + struct lantern_reqresp_exchange *exchange = (struct lantern_reqresp_exchange *)calloc(1u, sizeof(*exchange)); + if (!exchange) { + free(frame); + return -1; + } + exchange->service = service; + exchange->kind = kind; + exchange->outbound = 1; + exchange->conn = conn; + exchange->host = service->network->host; + exchange->write_buf = frame; + exchange->write_len = frame_len; + exchange->request_id = request_id; + if (peer_id_text) { + (void)lantern_string_copy( + exchange->peer_id_text, + sizeof(exchange->peer_id_text), + peer_id_text); + } + if (root_count > 0u) { + exchange->roots = (LanternRoot *)calloc(root_count, sizeof(*exchange->roots)); + if (!exchange->roots) { + exchange_free(exchange); + return -1; + } + memcpy(exchange->roots, roots, root_count * sizeof(*exchange->roots)); + exchange->root_count = root_count; + } + service_add_exchange(service, exchange); + libp2p_host_stream_open_t *open = NULL; + const char *protocol = + kind == LANTERN_REQRESP_PROTOCOL_STATUS ? LANTERN_REQRESP_STATUS_PROTOCOL : LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL; + libp2p_host_err_t err = libp2p_host_open_stream( + service->network->host, + conn, + (const uint8_t *)protocol, + strlen(protocol), + exchange, + &open); + if (err != LIBP2P_HOST_OK) { + service_remove_exchange(service, exchange); + exchange_free(exchange); + return -1; + } + return 0; +} +int lantern_reqresp_service_request_status( + struct lantern_reqresp_service *service, + const struct lantern_peer_id *peer_id, + const char *peer_id_text) { + uint8_t *frame = NULL; + size_t frame_len = 0; + if (build_status_request_frame(service, &frame, &frame_len) != 0) { + if (service && service->callbacks.status_failure) { + service->callbacks.status_failure( + service->callbacks.context, + peer_id_text, + LANTERN_REQRESP_ERR_STREAM_WRITE); + } + return -1; + } + if (service_open_exchange( + service, + peer_id, + peer_id_text, + LANTERN_REQRESP_PROTOCOL_STATUS, + frame, + frame_len, + NULL, + 0, + 0) + != 0) { + if (service && service->callbacks.status_failure) { + service->callbacks.status_failure( + service->callbacks.context, + peer_id_text, + LANTERN_REQRESP_ERR_STREAM_WRITE); + } + return -1; + } return 0; } + +int lantern_reqresp_service_request_blocks( + struct lantern_reqresp_service *service, + const struct lantern_peer_id *peer_id, + const char *peer_id_text, + const LanternRoot *roots, + size_t root_count, + uint64_t request_id) { + uint8_t *frame = NULL; + size_t frame_len = 0; + if (build_blocks_request_frame(roots, root_count, &frame, &frame_len) != 0) { + return -1; + } + return service_open_exchange( + service, + peer_id, + peer_id_text, + LANTERN_REQRESP_PROTOCOL_BLOCKS_BY_ROOT, + frame, + frame_len, + roots, + root_count, + request_id); +} + +struct lantern_reqresp_stream *lantern_reqresp_stream_from_ops( + void *io_ctx, + const struct lantern_reqresp_stream_ops *ops, + const struct lantern_peer_id *remote_peer) { + if (!ops) { + return NULL; + } + struct lantern_reqresp_stream *stream = (struct lantern_reqresp_stream *)calloc(1u, sizeof(*stream)); + if (!stream) { + return NULL; + } + stream->io_ctx = io_ctx; + stream->ops = *ops; + if (remote_peer) { + stream->remote_peer = *remote_peer; + stream->has_remote_peer = true; + } + return stream; +} + +void lantern_reqresp_stream_free(struct lantern_reqresp_stream *stream) { + if (!stream) { + return; + } + if (stream->ops.free_ctx) { + stream->ops.free_ctx(stream->io_ctx); + } + free(stream); +} + +int lantern_reqresp_read_response_chunk( + struct lantern_reqresp_service *service, + struct lantern_reqresp_stream *stream, + enum lantern_reqresp_protocol_kind protocol, + uint8_t **out_data, + size_t *out_len, + ssize_t *out_err, + uint8_t *out_response_code, + bool *response_code_pending) { + (void)service; + (void)protocol; + if (!stream || !out_data || !out_len) { + return LANTERN_REQRESP_ERR_INVALID_PARAM; + } + *out_data = NULL; + *out_len = 0; + if (out_err) { + *out_err = 0; + } + + if (stream_set_deadline(stream, lantern_reqresp_stall_timeout_ms()) != 0) { + return LANTERN_REQRESP_ERR_SET_DEADLINE; + } + + bool need_response_code = response_code_pending ? *response_code_pending : true; + if (need_response_code) { + uint8_t code = 0; + int rc = read_exact(stream, &code, 1u, out_err); + if (rc != LANTERN_REQRESP_OK) { + return rc; + } + if (out_response_code) { + *out_response_code = normalize_response_code(code); + } + if (response_code_pending) { + *response_code_pending = false; + } + } + + uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; + size_t header_len = 0; + uint64_t payload_len = 0; + for (;;) { + if (header_len >= sizeof(header)) { + return LANTERN_REQRESP_ERR_VARINT_HEADER_TOO_LONG; + } + int rc = read_exact(stream, &header[header_len], 1u, out_err); + if (rc != LANTERN_REQRESP_OK) { + return rc; + } + header_len++; + size_t consumed = 0; + libp2p_uvarint_err_t err = libp2p_uvarint_decode(header, header_len, &payload_len, &consumed); + if (err == LIBP2P_UVARINT_OK && consumed == header_len) { + break; + } + if (err != LIBP2P_UVARINT_ERR_TRUNCATED) { + return LANTERN_REQRESP_ERR_VARINT_HEADER_TOO_LONG; + } + } + if (payload_len > LANTERN_REQRESP_MAX_CHUNK_BYTES) { + return LANTERN_REQRESP_ERR_PAYLOAD_TOO_LARGE; + } + return read_snappy_frame_payload(stream, (size_t)payload_len, out_data, out_len, out_err); +} + +int stream_write_all(struct lantern_reqresp_stream *stream, const uint8_t *data, size_t length, ssize_t *out_err) { + if (!stream || (!data && length != 0)) { + return LANTERN_REQRESP_ERR_INVALID_PARAM; + } + size_t offset = 0; + while (offset < length) { + ssize_t n = stream_write(stream, data + offset, length - offset); + if (n <= 0) { + if (out_err) { + *out_err = n; + } + return LANTERN_REQRESP_ERR_STREAM_WRITE; + } + offset += (size_t)n; + } + return LANTERN_REQRESP_OK; +} diff --git a/src/storage/storage.c b/src/storage/storage.c index c8ea080..e1a57c3 100644 --- a/src/storage/storage.c +++ b/src/storage/storage.c @@ -28,7 +28,7 @@ #include "lantern/networking/messages.h" #include "lantern/support/log.h" #include "lantern/support/strings.h" -#include "ssz_constants.h" +#include "ssz.h" #define LANTERN_STORAGE_VOTES_MAGIC "LNVOTES\0" #define LANTERN_STORAGE_VOTES_VERSION 3u @@ -159,7 +159,7 @@ static size_t aggregated_attestation_encoded_size(const LanternAggregatedAttesta if (bits_size == 0) { return 0; } - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_ATTESTATION_DATA_SSZ_SIZE; + size_t fixed_section = SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_ATTESTATION_DATA_SSZ_SIZE; if (fixed_section > SIZE_MAX - bits_size) { return 0; } @@ -176,7 +176,7 @@ static size_t aggregated_attestations_encoded_size(const LanternAggregatedAttest if (attestations->length > LANTERN_MAX_ATTESTATIONS || !attestations->data) { return 0; } - size_t offset_table = attestations->length * SSZ_BYTE_SIZE_OF_UINT32; + size_t offset_table = attestations->length * SSZ_BYTES_PER_LENGTH_OFFSET; size_t total = offset_table; for (size_t i = 0; i < attestations->length; ++i) { size_t entry_size = aggregated_attestation_encoded_size(&attestations->data[i]); @@ -200,7 +200,7 @@ static size_t aggregated_signature_proof_encoded_size(const LanternAggregatedSig if (participants_size == 0 && proof->participants.bit_length != 0) { return 0; } - size_t fixed_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; + size_t fixed_section = SSZ_BYTES_PER_LENGTH_OFFSET * 2u; if (fixed_section > SIZE_MAX - participants_size) { return 0; } @@ -220,7 +220,7 @@ static size_t attestation_signatures_encoded_size(const LanternAttestationSignat if (signatures->length > LANTERN_MAX_BLOCK_SIGNATURES || !signatures->data) { return 0; } - size_t offset_table = signatures->length * SSZ_BYTE_SIZE_OF_UINT32; + size_t offset_table = signatures->length * SSZ_BYTES_PER_LENGTH_OFFSET; size_t total = offset_table; for (size_t i = 0; i < signatures->length; ++i) { size_t entry_size = aggregated_signature_proof_encoded_size(&signatures->data[i]); @@ -247,10 +247,10 @@ static size_t state_encoded_size(const LanternState *state) { return 0; } size_t fixed = LANTERN_CONFIG_SSZ_SIZE - + SSZ_BYTE_SIZE_OF_UINT64 + + sizeof(uint64_t) + LANTERN_BLOCK_HEADER_SSZ_SIZE + (2u * LANTERN_CHECKPOINT_SSZ_SIZE) - + (5u * SSZ_BYTE_SIZE_OF_UINT32); + + (5u * SSZ_BYTES_PER_LENGTH_OFFSET); size_t validator_bytes = 0; if (state->validator_count > 0) { if (!state->validators || state->validator_count > SIZE_MAX / LANTERN_VALIDATOR_SSZ_SIZE) { @@ -268,23 +268,23 @@ static size_t state_encoded_size(const LanternState *state) { static size_t block_body_encoded_size(const LanternBlockBody *body) { if (!body) { - return SSZ_BYTE_SIZE_OF_UINT32; + return SSZ_BYTES_PER_LENGTH_OFFSET; } size_t att_count = body->attestations.length; size_t attestations_bytes = aggregated_attestations_encoded_size(&body->attestations); if (att_count > 0 && attestations_bytes == 0) { return 0; } - return SSZ_BYTE_SIZE_OF_UINT32 + attestations_bytes; + return SSZ_BYTES_PER_LENGTH_OFFSET + attestations_bytes; } static size_t block_encoded_size(const LanternBlock *block) { if (!block) { return 0; } - size_t fixed = (SSZ_BYTE_SIZE_OF_UINT64 * 2u) + size_t fixed = (sizeof(uint64_t) * 2u) + (LANTERN_ROOT_SIZE * 2u) - + SSZ_BYTE_SIZE_OF_UINT32; + + SSZ_BYTES_PER_LENGTH_OFFSET; size_t body_size = block_body_encoded_size(&block->body); if (body_size == 0) { return 0; @@ -301,14 +301,14 @@ static size_t block_signatures_encoded_size(const LanternBlockSignatures *signat if (sig_count > 0 && attestations_bytes == 0) { return 0; } - return (SSZ_BYTE_SIZE_OF_UINT32 * 2u) + LANTERN_SIGNATURE_SIZE + attestations_bytes; + return (SSZ_BYTES_PER_LENGTH_OFFSET * 2u) + LANTERN_SIGNATURE_SIZE + attestations_bytes; } static size_t signed_block_encoded_size(const LanternSignedBlock *block) { if (!block) { return 0; } - size_t offset_section = SSZ_BYTE_SIZE_OF_UINT32 * 2u; + size_t offset_section = SSZ_BYTES_PER_LENGTH_OFFSET * 2u; size_t message_size = block_encoded_size(&block->block); if (message_size == 0) { return 0; @@ -689,7 +689,7 @@ int lantern_storage_save_state(const char *data_dir, const LanternState *state) goto cleanup; } size_t written = 0; - if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != 0 || written != encoded_size) { + if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != SSZ_SUCCESS || written != encoded_size) { goto cleanup; } if (join_path(data_dir, LANTERN_STORAGE_STATE_FILE, &state_path) != 0) { @@ -740,7 +740,7 @@ int lantern_storage_load_state(const char *data_dir, LanternState *state) { if (rc != 0) { goto cleanup; } - if (lantern_ssz_decode_state(&decoded, data, data_len) != 0) { + if (lantern_ssz_decode_state(&decoded, data, data_len) != SSZ_SUCCESS) { rc = -1; goto cleanup; } @@ -790,7 +790,7 @@ int lantern_storage_save_finalized_state(const char *data_dir, const LanternStat goto cleanup; } size_t written = 0; - if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != 0 || written != encoded_size) { + if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != SSZ_SUCCESS || written != encoded_size) { goto cleanup; } if (join_path(data_dir, LANTERN_STORAGE_FINALIZED_STATE_FILE, &state_path) != 0) { @@ -841,7 +841,7 @@ int lantern_storage_load_finalized_state(const char *data_dir, LanternState *sta if (rc != 0) { goto cleanup; } - if (lantern_ssz_decode_state(&decoded, data, data_len) != 0) { + if (lantern_ssz_decode_state(&decoded, data, data_len) != SSZ_SUCCESS) { rc = -1; goto cleanup; } @@ -1038,7 +1038,7 @@ int lantern_storage_load_votes(const char *data_dir, LanternState *state, Lanter if (has_signatures) { LanternSignedVote signed_vote; memset(&signed_vote, 0, sizeof(signed_vote)); - if (lantern_ssz_decode_signed_vote(&signed_vote, cursor, signed_vote_size) != 0) { + if (lantern_ssz_decode_signed_vote(&signed_vote, cursor, signed_vote_size) != SSZ_SUCCESS) { rc = -1; goto cleanup; } @@ -1051,7 +1051,7 @@ int lantern_storage_load_votes(const char *data_dir, LanternState *state, Lanter } else { LanternVote vote; memset(&vote, 0, sizeof(vote)); - if (lantern_ssz_decode_vote(&vote, cursor, LANTERN_VOTE_SSZ_SIZE) != 0) { + if (lantern_ssz_decode_vote(&vote, cursor, LANTERN_VOTE_SSZ_SIZE) != SSZ_SUCCESS) { rc = -1; goto cleanup; } @@ -1132,7 +1132,7 @@ static int storage_store_block_internal( LanternRoot root = {0}; if (root_override) { root = *root_override; - } else if (lantern_hash_tree_root_block(&block->block, &root) != 0) { + } else if (lantern_hash_tree_root_block(&block->block, &root) != SSZ_SUCCESS) { goto cleanup; } char root_hex[2u * LANTERN_ROOT_SIZE + 1u]; @@ -1160,10 +1160,9 @@ static int storage_store_block_internal( lantern_log_warn( "storage", &(const struct lantern_log_metadata){0}, - "store_block size estimate failed slot=%" PRIu64 " attestations=%zu legacy_layout=%s sig_count=%zu", + "store_block size estimate failed slot=%" PRIu64 " attestations=%zu sig_count=%zu", block->block.slot, block->block.body.attestations.length, - block->block.body.legacy_plain_attestation_layout ? "true" : "false", block->signatures.attestation_signatures.length); goto cleanup; } @@ -1172,7 +1171,7 @@ static int storage_store_block_internal( goto cleanup; } size_t written_size = 0; - const int encode_rc = lantern_ssz_encode_signed_block(block, buffer, encoded_size, &written_size); + const ssz_error_t encode_rc = lantern_ssz_encode_signed_block(block, buffer, encoded_size, &written_size); if (encode_rc != 0 || written_size == 0 || written_size > encoded_size) { @@ -1344,7 +1343,7 @@ int lantern_storage_store_state_for_root( goto cleanup; } size_t written = 0; - if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != 0 || written != encoded_size) { + if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != SSZ_SUCCESS || written != encoded_size) { goto cleanup; } if (build_states_dir(data_dir, &states_dir) != 0) { @@ -1499,7 +1498,7 @@ static int prune_block_files_before_slot( LanternSignedBlock block; lantern_signed_block_with_attestation_init(&block); - if (lantern_ssz_decode_signed_block(&block, data, data_len) != 0) { + if (lantern_ssz_decode_signed_block(&block, data, data_len) != SSZ_SUCCESS) { lantern_signed_block_with_attestation_reset(&block); free(data); free_path(block_path); @@ -1509,7 +1508,7 @@ static int prune_block_files_before_slot( LanternRoot root = {0}; bool have_root = parse_root_ssz_filename(entry->d_name, &root); - if (!have_root && lantern_hash_tree_root_block(&block.block, &root) == 0) { + if (!have_root && lantern_hash_tree_root_block(&block.block, &root) == SSZ_SUCCESS) { have_root = true; } @@ -1604,7 +1603,7 @@ static int prune_state_files_before_slot( LanternState state; lantern_state_init(&state); - if (lantern_ssz_decode_state(&state, data, data_len) != 0) { + if (lantern_ssz_decode_state(&state, data, data_len) != SSZ_SUCCESS) { lantern_state_reset(&state); free(data); free_path(state_path); @@ -1962,12 +1961,12 @@ int lantern_storage_collect_blocks( goto cleanup; } LanternSignedBlock *dest = &out_blocks->blocks[current]; - if (lantern_ssz_decode_signed_block(dest, data, data_len) != 0) { + if (lantern_ssz_decode_signed_block(dest, data, data_len) != SSZ_SUCCESS) { free(data); goto cleanup; } LanternRoot computed; - if (lantern_hash_tree_root_block(&dest->block, &computed) != 0) { + if (lantern_hash_tree_root_block(&dest->block, &computed) != SSZ_SUCCESS) { free(data); goto cleanup; } @@ -2069,14 +2068,14 @@ int lantern_storage_iterate_blocks( } LanternSignedBlock block; lantern_signed_block_with_attestation_init(&block); - if (lantern_ssz_decode_signed_block(&block, data, data_len) != 0) { + if (lantern_ssz_decode_signed_block(&block, data, data_len) != SSZ_SUCCESS) { lantern_signed_block_with_attestation_reset(&block); free(data); rc = -1; break; } LanternRoot computed_root; - if (lantern_hash_tree_root_block(&block.block, &computed_root) != 0) { + if (lantern_hash_tree_root_block(&block.block, &computed_root) != SSZ_SUCCESS) { lantern_signed_block_with_attestation_reset(&block); free(data); rc = -1; diff --git a/src/support/log.c b/src/support/log.c index 1ca5f66..2281b5a 100644 --- a/src/support/log.c +++ b/src/support/log.c @@ -11,47 +11,9 @@ static char g_node_id[96] = {0}; static pthread_mutex_t g_log_mutex = PTHREAD_MUTEX_INITIALIZER; static enum LanternLogLevel g_min_level = LANTERN_LOG_LEVEL_INFO; -static bool g_color_initialized = false; -static bool g_color_stdout = false; -static bool g_color_stderr = false; static int equals_ignore_case(const char *lhs, const char *rhs); -enum lantern_log_color_mode { - LANTERN_LOG_COLOR_AUTO = 0, - LANTERN_LOG_COLOR_NEVER, - LANTERN_LOG_COLOR_ALWAYS, -}; - -static enum lantern_log_color_mode g_color_mode = LANTERN_LOG_COLOR_AUTO; - -/* ANSI color codes */ -#define ANSI_RESET "\x1b[0m" -#define ANSI_DIM "\x1b[2m" -#define ANSI_GREEN "\x1b[32m" -#define ANSI_YELLOW "\x1b[33m" -#define ANSI_CYAN "\x1b[36m" -#define ANSI_BRIGHT_BLACK "\x1b[90m" -#define ANSI_BRIGHT_RED "\x1b[91m" - -/* Level badge colors and symbols */ -static const char *level_to_color(enum LanternLogLevel level) { - switch (level) { - case LANTERN_LOG_LEVEL_TRACE: - return ANSI_BRIGHT_BLACK; - case LANTERN_LOG_LEVEL_DEBUG: - return ANSI_CYAN; - case LANTERN_LOG_LEVEL_INFO: - return ANSI_GREEN; - case LANTERN_LOG_LEVEL_WARN: - return ANSI_YELLOW; - case LANTERN_LOG_LEVEL_ERROR: - return ANSI_BRIGHT_RED; - default: - return ANSI_RESET; - } -} - static const char *level_to_string(enum LanternLogLevel level) { switch (level) { case LANTERN_LOG_LEVEL_TRACE: @@ -69,55 +31,6 @@ static const char *level_to_string(enum LanternLogLevel level) { } } -static enum lantern_log_color_mode parse_color_mode(const char *text) -{ - if (!text) { - return LANTERN_LOG_COLOR_AUTO; - } - if (equals_ignore_case(text, "always")) { - return LANTERN_LOG_COLOR_ALWAYS; - } - if (equals_ignore_case(text, "never")) { - return LANTERN_LOG_COLOR_NEVER; - } - if (equals_ignore_case(text, "auto")) { - return LANTERN_LOG_COLOR_AUTO; - } - return LANTERN_LOG_COLOR_AUTO; -} - -static bool detect_terminal(FILE *stream) -{ - (void)stream; - /* Always enable colors by default - modern terminals and log viewers support ANSI colors */ - return true; -} - -static void ensure_color_configuration(void) -{ - if (g_color_initialized) { - return; - } - const char *env_color = getenv("LANTERN_LOG_COLOR"); - g_color_mode = parse_color_mode(env_color); - switch (g_color_mode) { - case LANTERN_LOG_COLOR_ALWAYS: - g_color_stdout = true; - g_color_stderr = true; - break; - case LANTERN_LOG_COLOR_NEVER: - g_color_stdout = false; - g_color_stderr = false; - break; - case LANTERN_LOG_COLOR_AUTO: - default: - g_color_stdout = detect_terminal(stdout); - g_color_stderr = detect_terminal(stderr); - break; - } - g_color_initialized = true; -} - void lantern_log_set_node_id(const char *node_id) { if (!node_id) { g_node_id[0] = '\0'; @@ -209,9 +122,8 @@ static void format_timestamp(char buffer[32]) { buffer[0] = '\0'; return; } - /* Clean format: YYYY-MM-DD HH:MM:SS.mmm */ int written = snprintf( - buffer, 32, "%04d-%02d-%02d %02d:%02d:%02d.%03ld", + buffer, 32, "%04d-%02d-%02dT%02d:%02d:%02d.%03ldZ", tm_result.tm_year + 1900, tm_result.tm_mon + 1, tm_result.tm_mday, @@ -225,6 +137,17 @@ static void format_timestamp(char buffer[32]) { } } +static void format_component_tag(const char *component, char out[16]) { + char lowered[11]; + const char *text = component && component[0] ? component : "?"; + size_t i = 0; + for (; text[i] && i < sizeof(lowered) - 1u; ++i) { + lowered[i] = (char)tolower((unsigned char)text[i]); + } + lowered[i] = '\0'; + snprintf(out, 16, "[%s]", lowered); +} + void lantern_log_log( enum LanternLogLevel level, const char *component, @@ -234,6 +157,7 @@ void lantern_log_log( if (level < g_min_level) { return; } + (void)metadata; /* Format the user message */ char formatted[1024]; @@ -247,106 +171,19 @@ void lantern_log_log( char timestamp[32]; format_timestamp(timestamp); - ensure_color_configuration(); FILE *target = level >= LANTERN_LOG_LEVEL_WARN ? stderr : stdout; - const bool colorize = (target == stderr) ? g_color_stderr : g_color_stdout; - - /* Build context string with only non-empty fields */ - char context[256]; - char *ctx_cursor = context; - size_t ctx_remaining = sizeof(context); - context[0] = '\0'; - const char *peer_text = (metadata && metadata->peer && metadata->peer[0]) ? metadata->peer : NULL; - - /* Add slot if present */ - if (metadata && metadata->has_slot) { - int w = snprintf(ctx_cursor, ctx_remaining, "slot=%" PRIu64, metadata->slot); - if (w > 0 && (size_t)w < ctx_remaining) { - ctx_cursor += w; - ctx_remaining -= (size_t)w; - } - } - - /* Add validator if present */ - if (metadata && metadata->validator && metadata->validator[0]) { - if (ctx_cursor != context) { - int w = snprintf(ctx_cursor, ctx_remaining, " "); - if (w > 0 && (size_t)w < ctx_remaining) { - ctx_cursor += w; - ctx_remaining -= (size_t)w; - } - } - int w = snprintf(ctx_cursor, ctx_remaining, "validator=%s", metadata->validator); - if (w > 0 && (size_t)w < ctx_remaining) { - ctx_cursor += w; - ctx_remaining -= (size_t)w; - } - } - - /* Add peer later to avoid truncating long peer IDs */ - /* - * Output format: - * HH:MM:SS.mmm LVL [component] message context - * - * With colors: - * - Timestamp: dim - * - Level: colored based on level - * - Component: cyan - * - Message: normal (white/default) - * - Context: dim - */ - - static const char kUnknownTimestamp[] = "????" "-??" "-?? ??:??:??.???"; + char tag[16]; + format_component_tag(component, tag); + static const char kUnknownTimestamp[] = "????" "-??" "-??T??:??:??.???Z"; pthread_mutex_lock(&g_log_mutex); - if (colorize) { - /* Colored output with selective coloring */ - fprintf( - target, - "%s%s%s %s%s%s %s[%s]%s %s", - ANSI_DIM, timestamp[0] ? timestamp : kUnknownTimestamp, ANSI_RESET, - level_to_color(level), level_to_string(level), ANSI_RESET, - ANSI_CYAN, component ? component : "?", ANSI_RESET, - formatted); - - /* Add context if present */ - if (context[0] || peer_text) { - fprintf(target, " %s", ANSI_DIM); - if (context[0]) { - fprintf(target, "%s", context); - if (peer_text) { - fprintf(target, " peer=%s", peer_text); - } - } else { - fprintf(target, "peer=%s", peer_text); - } - fprintf(target, "%s", ANSI_RESET); - } - fprintf(target, "\n"); - } else { - /* Plain output without colors */ - fprintf( - target, - "%s %s [%s] %s", - timestamp[0] ? timestamp : kUnknownTimestamp, - level_to_string(level), - component ? component : "?", - formatted); - - /* Add context if present */ - if (context[0] || peer_text) { - fprintf(target, " "); - if (context[0]) { - fprintf(target, "%s", context); - if (peer_text) { - fprintf(target, " peer=%s", peer_text); - } - } else { - fprintf(target, "peer=%s", peer_text); - } - } - fprintf(target, "\n"); - } + fprintf( + target, + "%s %-5s %-12s %s\n", + timestamp[0] ? timestamp : kUnknownTimestamp, + level_to_string(level), + tag, + formatted); fflush(target); pthread_mutex_unlock(&g_log_mutex); diff --git a/src/support/strings.c b/src/support/strings.c index 5eef7c8..325e9ac 100644 --- a/src/support/strings.c +++ b/src/support/strings.c @@ -25,6 +25,27 @@ char *lantern_string_duplicate_len(const char *source, size_t length) { return copy; } +size_t lantern_string_copy(char *dst, size_t dst_len, const char *src) { + if (!dst || dst_len == 0) { + return 0; + } + if (!src) { + dst[0] = '\0'; + return 0; + } + + const size_t src_len = strlen(src); + size_t copy_len = src_len; + if (copy_len >= dst_len) { + copy_len = dst_len - 1; + } + if (copy_len > 0) { + memcpy(dst, src, copy_len); + } + dst[copy_len] = '\0'; + return src_len; +} + char *lantern_trim_whitespace(char *value) { if (!value) { return NULL; diff --git a/src/test_driver/driver.c b/src/test_driver/driver.c new file mode 100644 index 0000000..54fc506 --- /dev/null +++ b/src/test_driver/driver.c @@ -0,0 +1,2216 @@ +#include "test_driver/driver.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lantern/consensus/containers.h" +#include "lantern/consensus/fork_choice.h" +#include "lantern/consensus/hash.h" +#include "lantern/consensus/signature.h" +#include "lantern/consensus/state.h" +#include "lantern/consensus/store.h" +#include "lantern/support/strings.h" +#include "tests/support/fixture_loader.h" +#include "tests/support/state_store_adapter.h" + +struct stored_vote_entry { + bool has_vote; + LanternVote vote; +}; + +struct stored_state_entry { + LanternRoot root; + LanternState state; + bool has_state; + struct stored_vote_entry *votes; + size_t vote_count; +}; + +struct hash_mapping_entry { + LanternRoot leanspec_hash; + LanternRoot c_hash; +}; + +struct fork_choice_driver { + LanternForkChoice fork_choice; + LanternStore fork_choice_store; + LanternState state; + LanternRoot canonical_head_block_root; + struct stored_state_entry *stored_states; + size_t stored_state_count; + size_t stored_state_cap; + struct hash_mapping_entry *hash_mapping; + size_t hash_mapping_count; + size_t hash_mapping_cap; + uint64_t genesis_time; + uint64_t validator_count; + bool initialized; +}; + +static pthread_mutex_t g_driver_lock = PTHREAD_MUTEX_INITIALIZER; +static struct fork_choice_driver g_driver; + +static char *dup_cstr(const char *value) +{ + if (!value) + { + value = ""; + } + size_t len = strlen(value); + char *copy = malloc(len + 1u); + if (!copy) + { + return NULL; + } + memcpy(copy, value, len + 1u); + return copy; +} + +static int document_from_body( + const char *body, + size_t body_len, + struct lantern_fixture_document *doc) +{ + if (!body || !doc) + { + return -1; + } + char *text = malloc(body_len + 1u); + if (!text) + { + return -1; + } + memcpy(text, body, body_len); + text[body_len] = '\0'; + return lantern_fixture_document_init(doc, text); +} + +static bool fixture_token_equals_literal( + const struct lantern_fixture_document *doc, + int index, + const char *literal) +{ + if (!doc || !literal || index < 0) + { + return false; + } + size_t length = 0; + const char *value = lantern_fixture_token_string(doc, index, &length); + size_t literal_length = strlen(literal); + return value && length == literal_length && strncmp(value, literal, literal_length) == 0; +} + +static int fixture_token_to_bool( + const struct lantern_fixture_document *doc, + int index, + bool *out_value) +{ + if (!doc || !out_value) + { + return -1; + } + const jsmntok_t *tok = lantern_fixture_token(doc, index); + if (!tok || tok->type != JSMN_PRIMITIVE) + { + return -1; + } + size_t length = (size_t)(tok->end - tok->start); + if (length == 4u && strncmp(doc->text + tok->start, "true", 4u) == 0) + { + *out_value = true; + return 0; + } + if (length == 5u && strncmp(doc->text + tok->start, "false", 5u) == 0) + { + *out_value = false; + return 0; + } + return -1; +} + +static bool is_root_zero(const LanternRoot *root) +{ + if (!root) + { + return false; + } + for (size_t i = 0; i < LANTERN_ROOT_SIZE; ++i) + { + if (root->bytes[i] != 0u) + { + return false; + } + } + return true; +} + +static int root_compare_bytes(const LanternRoot *a, const LanternRoot *b) +{ + if (!a || !b) + { + return 0; + } + return memcmp(a->bytes, b->bytes, LANTERN_ROOT_SIZE); +} + +static bool root_equal(const LanternRoot *a, const LanternRoot *b) +{ + return root_compare_bytes(a, b) == 0; +} + +static int milliseconds_from_seconds(uint64_t seconds, uint64_t *out_milliseconds) +{ + if (!out_milliseconds || seconds > UINT64_MAX / 1000u) + { + return -1; + } + *out_milliseconds = seconds * 1000u; + return 0; +} + +static void driver_format_root_hex(const LanternRoot *root, char *buf, size_t buf_len) +{ + if (!buf || buf_len == 0) + { + return; + } + if (!root + || lantern_bytes_to_hex(root->bytes, LANTERN_ROOT_SIZE, buf, buf_len, 1) != 0) + { + buf[0] = '\0'; + } +} + +static void reset_plain_block(LanternBlock *block) +{ + if (block) + { + lantern_block_body_reset(&block->body); + } +} + +static void reset_signed_block(LanternSignedBlock *block) +{ + if (block) + { + lantern_block_body_reset(&block->block.body); + lantern_block_signatures_reset(&block->signatures); + } +} + +static int ensure_signature_envelope(const LanternSignedBlock *block) +{ + if (!block) + { + return -1; + } + size_t attestation_count = block->block.body.attestations.length; + return block->signatures.attestation_signatures.length == attestation_count ? 0 : -1; +} + +static void stored_state_entries_reset( + struct stored_state_entry **entries_ptr, + size_t *count_ptr, + size_t *cap_ptr) +{ + if (!entries_ptr || !count_ptr || !cap_ptr) + { + return; + } + struct stored_state_entry *entries = *entries_ptr; + if (entries) + { + for (size_t i = 0; i < *count_ptr; ++i) + { + if (entries[i].has_state) + { + lantern_state_reset(&entries[i].state); + entries[i].has_state = false; + } + free(entries[i].votes); + } + free(entries); + } + *entries_ptr = NULL; + *count_ptr = 0; + *cap_ptr = 0; +} + +static struct stored_state_entry *stored_state_find( + struct stored_state_entry *entries, + size_t count, + const LanternRoot *root) +{ + if (!entries || !root) + { + return NULL; + } + for (size_t i = 0; i < count; ++i) + { + if (root_equal(&entries[i].root, root)) + { + return &entries[i]; + } + } + return NULL; +} + +static int stored_state_add( + struct stored_state_entry **entries_ptr, + size_t *count_ptr, + size_t *cap_ptr, + const LanternRoot *root, + const LanternState *state, + struct stored_vote_entry *votes, + size_t vote_count) +{ + if (!entries_ptr || !count_ptr || !cap_ptr || !root || !state) + { + free(votes); + return -1; + } + + struct stored_state_entry *entry = stored_state_find(*entries_ptr, *count_ptr, root); + if (entry) + { + if (entry->has_state) + { + lantern_state_reset(&entry->state); + } + if (lantern_state_clone(state, &entry->state) != 0) + { + free(votes); + entry->has_state = false; + return -1; + } + entry->has_state = true; + free(entry->votes); + entry->votes = votes; + entry->vote_count = vote_count; + return 0; + } + + if (*count_ptr == *cap_ptr) + { + size_t new_cap = *cap_ptr == 0 ? 8u : *cap_ptr * 2u; + if (new_cap < *cap_ptr) + { + free(votes); + return -1; + } + struct stored_state_entry *expanded = + realloc(*entries_ptr, new_cap * sizeof(*expanded)); + if (!expanded) + { + free(votes); + return -1; + } + *entries_ptr = expanded; + *cap_ptr = new_cap; + } + + entry = &(*entries_ptr)[*count_ptr]; + memset(entry, 0, sizeof(*entry)); + entry->root = *root; + if (lantern_state_clone(state, &entry->state) != 0) + { + free(votes); + return -1; + } + entry->has_state = true; + entry->votes = votes; + entry->vote_count = vote_count; + *count_ptr += 1u; + return 0; +} + +static int stored_state_save( + struct stored_state_entry **entries_ptr, + size_t *count_ptr, + size_t *cap_ptr, + const LanternRoot *root, + const LanternState *state) +{ + if (!entries_ptr || !count_ptr || !cap_ptr || !root || !state) + { + return -1; + } + + size_t vote_capacity = lantern_state_validator_capacity(state); + struct stored_vote_entry *votes = NULL; + if (vote_capacity > 0) + { + votes = calloc(vote_capacity, sizeof(*votes)); + if (!votes) + { + return -1; + } + for (size_t i = 0; i < vote_capacity; ++i) + { + if (!lantern_state_validator_has_vote(state, i)) + { + continue; + } + LanternVote vote; + if (lantern_state_get_validator_vote(state, i, &vote) != 0) + { + free(votes); + return -1; + } + votes[i].has_vote = true; + votes[i].vote = vote; + } + } + return stored_state_add(entries_ptr, count_ptr, cap_ptr, root, state, votes, vote_capacity); +} + +static int stored_state_restore( + struct stored_state_entry *entries, + size_t count, + const LanternRoot *root, + LanternState *state) +{ + if (!entries || !root || !state) + { + return -1; + } + struct stored_state_entry *entry = stored_state_find(entries, count, root); + if (!entry || !entry->has_state) + { + return -1; + } + lantern_state_reset(state); + if (lantern_state_clone(&entry->state, state) != 0) + { + return -1; + } + if (state->config.num_validators == 0) + { + return -1; + } + if (lantern_state_prepare_validator_votes(state, state->config.num_validators) != 0) + { + return -1; + } + size_t capacity = lantern_state_validator_capacity(state); + size_t copy_count = entry->vote_count < capacity ? entry->vote_count : capacity; + for (size_t i = 0; entry->votes && i < copy_count; ++i) + { + if (entry->votes[i].has_vote + && lantern_state_set_validator_vote(state, i, &entry->votes[i].vote) != 0) + { + return -1; + } + } + return 0; +} + +static void hash_mapping_reset(struct hash_mapping_entry **entries, size_t *count, size_t *cap) +{ + if (!entries || !count || !cap) + { + return; + } + free(*entries); + *entries = NULL; + *count = 0; + *cap = 0; +} + +static const LanternRoot *hash_mapping_leanspec_to_c( + const struct hash_mapping_entry *entries, + size_t count, + const LanternRoot *leanspec_hash) +{ + if (!entries || !leanspec_hash) + { + return NULL; + } + for (size_t i = 0; i < count; ++i) + { + if (root_equal(&entries[i].leanspec_hash, leanspec_hash)) + { + return &entries[i].c_hash; + } + } + return NULL; +} + +static const LanternRoot *hash_mapping_c_to_leanspec( + const struct hash_mapping_entry *entries, + size_t count, + const LanternRoot *c_hash) +{ + if (!entries || !c_hash) + { + return NULL; + } + for (size_t i = 0; i < count; ++i) + { + if (root_equal(&entries[i].c_hash, c_hash)) + { + return &entries[i].leanspec_hash; + } + } + return NULL; +} + +static int hash_mapping_add( + struct hash_mapping_entry **entries_ptr, + size_t *count_ptr, + size_t *cap_ptr, + const LanternRoot *leanspec_hash, + const LanternRoot *c_hash) +{ + if (!entries_ptr || !count_ptr || !cap_ptr || !leanspec_hash || !c_hash) + { + return -1; + } + for (size_t i = 0; i < *count_ptr; ++i) + { + if (root_equal(&(*entries_ptr)[i].leanspec_hash, leanspec_hash)) + { + (*entries_ptr)[i].c_hash = *c_hash; + return 0; + } + } + if (*count_ptr == *cap_ptr) + { + size_t new_cap = *cap_ptr == 0 ? 16u : *cap_ptr * 2u; + if (new_cap < *cap_ptr) + { + return -1; + } + struct hash_mapping_entry *expanded = + realloc(*entries_ptr, new_cap * sizeof(*expanded)); + if (!expanded) + { + return -1; + } + *entries_ptr = expanded; + *cap_ptr = new_cap; + } + (*entries_ptr)[*count_ptr].leanspec_hash = *leanspec_hash; + (*entries_ptr)[*count_ptr].c_hash = *c_hash; + *count_ptr += 1u; + return 0; +} + +static int preview_post_state_root_without_signatures( + const LanternState *state, + const LanternSignedBlock *signed_block, + LanternRoot *out_state_root) +{ + if (!state || !signed_block || !out_state_root || signed_block->block.slot <= state->slot) + { + return -1; + } + + LanternState scratch; + lantern_state_init(&scratch); + int rc = -1; + if (lantern_state_clone(state, &scratch) != 0) + { + goto cleanup; + } + if (lantern_state_process_slots(&scratch, signed_block->block.slot) != 0) + { + goto cleanup; + } + if (lantern_state_process_block(&scratch, &signed_block->block, NULL, NULL) != 0) + { + goto cleanup; + } + if (lantern_hash_tree_root_state(&scratch, out_state_root) != SSZ_SUCCESS) + { + goto cleanup; + } + rc = 0; + +cleanup: + lantern_state_reset(&scratch); + return rc; +} + +static int state_transition_without_signatures( + LanternState *state, + const LanternSignedBlock *signed_block) +{ + if (!state || !signed_block) + { + return -1; + } + const LanternBlock *block = &signed_block->block; + if (block->slot <= state->slot) + { + return -1; + } + if (lantern_state_process_slots(state, block->slot) != 0) + { + return -1; + } + if (lantern_state_process_block(state, block, NULL, NULL) != 0) + { + return -1; + } + LanternRoot computed_state_root; + if (lantern_hash_tree_root_state(state, &computed_state_root) != SSZ_SUCCESS) + { + return -1; + } + return memcmp(block->state_root.bytes, computed_state_root.bytes, LANTERN_ROOT_SIZE) == 0 + ? 0 + : -1; +} + +static int patch_block_hashes_for_c_compat( + LanternState *state, + LanternSignedBlock *signed_block) +{ + if (!state || !signed_block) + { + return -1; + } + + LanternBlockHeader header_after_slots = state->latest_block_header; + if (is_root_zero(&header_after_slots.state_root) + && lantern_hash_tree_root_state(state, &header_after_slots.state_root) != SSZ_SUCCESS) + { + return -1; + } + if (lantern_hash_tree_root_block_header( + &header_after_slots, + &signed_block->block.parent_root) + != SSZ_SUCCESS) + { + return -1; + } + + LanternRoot computed_state_root; + if (preview_post_state_root_without_signatures(state, signed_block, &computed_state_root) != 0) + { + return -1; + } + signed_block->block.state_root = computed_state_root; + return 0; +} + +static int collect_attestation_signature_inputs( + const LanternStore *store, + LanternAttestations *out_attestations, + LanternSignatureList *out_signatures) +{ + if (!store || !out_attestations || !out_signatures) + { + return -1; + } + if (lantern_attestations_resize(out_attestations, 0u) != 0 + || lantern_signature_list_resize(out_signatures, 0u) != 0) + { + return -1; + } + for (size_t i = 0; i < store->attestation_signatures.length; ++i) + { + const struct lantern_attestation_signature_entry *entry = + &store->attestation_signatures.entries[i]; + LanternAttestationData data; + memset(&data, 0, sizeof(data)); + if (lantern_store_get_attestation_data(store, &entry->key.data_root, &data) != 0) + { + continue; + } + LanternVote vote; + memset(&vote, 0, sizeof(vote)); + vote.validator_id = entry->key.validator_index; + vote.data = data; + if (lantern_attestations_append(out_attestations, &vote) != 0 + || lantern_signature_list_append(out_signatures, &entry->signature) != 0) + { + return -1; + } + } + return 0; +} + +static int aggregate_pending_gossip_attestations(LanternState *state, LanternStore *store) +{ + if (!state || !store) + { + return -1; + } + + LanternAttestations attestations; + LanternSignatureList signatures; + LanternAggregatedAttestations aggregated_attestations; + LanternAttestationSignatures aggregated_signatures; + lantern_attestations_init(&attestations); + lantern_signature_list_init(&signatures); + lantern_aggregated_attestations_init(&aggregated_attestations); + lantern_attestation_signatures_init(&aggregated_signatures); + + int rc = -1; + if (collect_attestation_signature_inputs(store, &attestations, &signatures) != 0) + { + goto cleanup; + } + if (attestations.length == 0u) + { + rc = 0; + goto cleanup; + } + + LanternAttestationSignatureInputs signature_inputs = { + .attestations = &attestations, + .signatures = &signatures, + }; + if (lantern_state_aggregate( + state, + lantern_test_state_store_ensure(state), + &signature_inputs, + &store->new_aggregated_payloads, + &store->known_aggregated_payloads, + &aggregated_attestations, + &aggregated_signatures) + != LANTERN_STATE_AGGREGATE_OK) + { + goto cleanup; + } + + lantern_store_clear_new_aggregated_payloads(store); + for (size_t i = 0; i < aggregated_attestations.length; ++i) + { + LanternRoot data_root; + if (lantern_hash_tree_root_attestation_data( + &aggregated_attestations.data[i].data, + &data_root) + != SSZ_SUCCESS) + { + goto cleanup; + } + if (lantern_store_add_new_aggregated_payload( + store, + &data_root, + &aggregated_attestations.data[i].data, + &aggregated_signatures.data[i], + aggregated_attestations.data[i].data.target.slot) + != 0) + { + goto cleanup; + } + (void)lantern_store_remove_attestation_signatures_for_data_root(store, &data_root); + } + rc = 0; + +cleanup: + lantern_attestations_reset(&attestations); + lantern_signature_list_reset(&signatures); + lantern_aggregated_attestations_reset(&aggregated_attestations); + lantern_attestation_signatures_reset(&aggregated_signatures); + return rc; +} + +static int sync_payload_pools_after_time_advance( + LanternForkChoice *fork_choice, + LanternStore *store, + LanternState *state, + uint64_t previous_intervals, + bool has_proposal) +{ + if (!fork_choice || !store || !state) + { + return -1; + } + if (fork_choice->intervals_per_slot == 0u + || fork_choice->time_intervals <= previous_intervals) + { + return 0; + } + for (uint64_t step = previous_intervals + 1u; step <= fork_choice->time_intervals; ++step) + { + uint64_t interval_index = step % fork_choice->intervals_per_slot; + bool step_has_proposal = has_proposal && (step == fork_choice->time_intervals); + if (interval_index == 2u) + { + if (aggregate_pending_gossip_attestations(state, store) != 0) + { + return -1; + } + } + if (interval_index == 4u || (interval_index == 0u && step_has_proposal)) + { + size_t promoted = lantern_store_promote_new_aggregated_payloads(store); + if (promoted > 0u && lantern_fork_choice_accept_new_aggregated_payloads(fork_choice) != 0) + { + return -1; + } + } + } + return 0; +} + +static int record_block_body_known_payloads( + LanternStore *store, + const LanternSignedBlock *signed_block) +{ + if (!store || !signed_block) + { + return -1; + } + const LanternAggregatedAttestations *attestations = &signed_block->block.body.attestations; + const LanternAttestationSignatures *signatures = + &signed_block->signatures.attestation_signatures; + for (size_t i = 0; i < attestations->length; ++i) + { + LanternAggregatedSignatureProof synthesized; + lantern_aggregated_signature_proof_init(&synthesized); + const LanternAggregatedSignatureProof *proof = NULL; + if (i < signatures->length + && signatures->data[i].participants.bit_length > 0 + && signatures->data[i].proof_data.length > 0) + { + proof = &signatures->data[i]; + } + else + { + if (lantern_bitlist_resize( + &synthesized.participants, + attestations->data[i].aggregation_bits.bit_length) + != 0) + { + lantern_aggregated_signature_proof_reset(&synthesized); + return -1; + } + size_t byte_len = + (attestations->data[i].aggregation_bits.bit_length + 7u) / 8u; + if (byte_len > 0) + { + memcpy( + synthesized.participants.bytes, + attestations->data[i].aggregation_bits.bytes, + byte_len); + } + if (lantern_byte_list_resize(&synthesized.proof_data, 1u) != 0) + { + lantern_aggregated_signature_proof_reset(&synthesized); + return -1; + } + synthesized.proof_data.data[0] = 0u; + proof = &synthesized; + } + + LanternRoot data_root; + if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) + != SSZ_SUCCESS) + { + lantern_aggregated_signature_proof_reset(&synthesized); + return -1; + } + int rc = lantern_store_add_known_aggregated_payload( + store, + &data_root, + &attestations->data[i].data, + proof, + attestations->data[i].data.target.slot); + lantern_aggregated_signature_proof_reset(&synthesized); + if (rc != 0) + { + return -1; + } + } + return 0; +} + +static void map_attestation_data_roots( + const struct fork_choice_driver *driver, + LanternAttestationData *data) +{ + if (!driver || !data) + { + return; + } + const LanternRoot *mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &data->head.root); + if (mapped) + { + data->head.root = *mapped; + } + mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &data->target.root); + if (mapped) + { + data->target.root = *mapped; + } + mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &data->source.root); + if (mapped) + { + data->source.root = *mapped; + } +} + +static int process_gossip_attestation_step( + const struct lantern_fixture_document *doc, + int step_idx, + const struct fork_choice_driver *driver, + LanternStore *store) +{ + if (!doc || !store) + { + return -1; + } + int attestation_idx = lantern_fixture_object_get_field(doc, step_idx, "attestation"); + if (attestation_idx < 0) + { + return -1; + } + + LanternSignedVote vote; + if (lantern_fixture_parse_attestation_message(doc, attestation_idx, &vote) != 0) + { + return -1; + } + map_attestation_data_roots(driver, &vote.data.data); + LanternRoot data_root; + if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != SSZ_SUCCESS) + { + return -1; + } + LanternSignatureKey key = { + .validator_index = vote.data.validator_id, + .data_root = data_root, + }; + return lantern_store_set_attestation_signature( + store, + &key, + &vote.data.data, + &vote.signature, + vote.data.data.target.slot); +} + +static int process_gossip_aggregated_attestation_step( + const struct lantern_fixture_document *doc, + int step_idx, + const struct fork_choice_driver *driver, + LanternStore *store) +{ + if (!doc || !store) + { + return -1; + } + int attestation_idx = lantern_fixture_object_get_field(doc, step_idx, "attestation"); + if (attestation_idx < 0) + { + return -1; + } + int data_idx = lantern_fixture_object_get_field(doc, attestation_idx, "data"); + int proof_idx = lantern_fixture_object_get_field(doc, attestation_idx, "proof"); + if (data_idx < 0 || proof_idx < 0) + { + return -1; + } + + LanternAttestationData data; + LanternAggregatedSignatureProof proof; + memset(&data, 0, sizeof(data)); + lantern_aggregated_signature_proof_init(&proof); + int rc = -1; + if (lantern_fixture_parse_attestation_data(doc, data_idx, &data) != 0) + { + goto cleanup; + } + map_attestation_data_roots(driver, &data); + if (lantern_fixture_parse_signature_proof(doc, proof_idx, &proof) != 0) + { + goto cleanup; + } + LanternRoot data_root; + if (lantern_hash_tree_root_attestation_data(&data, &data_root) != SSZ_SUCCESS) + { + goto cleanup; + } + rc = lantern_store_add_new_aggregated_payload( + store, + &data_root, + &data, + &proof, + data.target.slot); + +cleanup: + lantern_aggregated_signature_proof_reset(&proof); + return rc; +} + +static int sync_state_to_fork_choice_head(struct fork_choice_driver *driver) +{ + if (!driver) + { + return -1; + } + LanternRoot head_root; + if (lantern_fork_choice_current_head(&driver->fork_choice, &head_root) != 0) + { + return -1; + } + if (root_equal(&head_root, &driver->canonical_head_block_root)) + { + return 0; + } + if (!stored_state_find(driver->stored_states, driver->stored_state_count, &head_root)) + { + return -1; + } + if (stored_state_restore( + driver->stored_states, + driver->stored_state_count, + &head_root, + &driver->state) + != 0) + { + return -1; + } + driver->canonical_head_block_root = head_root; + return 0; +} + +static void fork_choice_driver_reset(struct fork_choice_driver *driver) +{ + if (!driver) + { + return; + } + if (driver->initialized) + { + lantern_fork_choice_reset(&driver->fork_choice); + lantern_store_reset(&driver->fork_choice_store); + lantern_state_reset(&driver->state); + } + stored_state_entries_reset( + &driver->stored_states, + &driver->stored_state_count, + &driver->stored_state_cap); + hash_mapping_reset( + &driver->hash_mapping, + &driver->hash_mapping_count, + &driver->hash_mapping_cap); + memset(driver, 0, sizeof(*driver)); +} + +static int fork_choice_driver_snapshot_json( + const struct fork_choice_driver *driver, + bool accepted, + const char *error, + char **out_body, + size_t *out_body_len) +{ + if (!driver || !out_body || !out_body_len) + { + return -1; + } + *out_body = NULL; + *out_body_len = 0; + + LanternRoot head_root; + memset(&head_root, 0, sizeof(head_root)); + uint64_t head_slot = 0; + if (driver->initialized + && lantern_fork_choice_current_head(&driver->fork_choice, &head_root) == 0) + { + (void)lantern_fork_choice_block_info( + &driver->fork_choice, + &head_root, + &head_slot, + NULL, + NULL); + } + const LanternRoot *lean_head = hash_mapping_c_to_leanspec( + driver->hash_mapping, + driver->hash_mapping_count, + &head_root); + if (lean_head) + { + head_root = *lean_head; + } + + LanternCheckpoint justified = driver->fork_choice.latest_justified; + const LanternRoot *lean_justified = hash_mapping_c_to_leanspec( + driver->hash_mapping, + driver->hash_mapping_count, + &justified.root); + if (lean_justified) + { + justified.root = *lean_justified; + } + + LanternCheckpoint finalized = driver->fork_choice.latest_finalized; + const LanternRoot *lean_finalized = hash_mapping_c_to_leanspec( + driver->hash_mapping, + driver->hash_mapping_count, + &finalized.root); + if (lean_finalized) + { + finalized.root = *lean_finalized; + } + + LanternRoot safe_target; + memset(&safe_target, 0, sizeof(safe_target)); + const LanternRoot *safe = lantern_fork_choice_safe_target(&driver->fork_choice); + if (safe) + { + safe_target = *safe; + const LanternRoot *lean_safe = hash_mapping_c_to_leanspec( + driver->hash_mapping, + driver->hash_mapping_count, + &safe_target); + if (lean_safe) + { + safe_target = *lean_safe; + } + } + + char head_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; + char justified_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; + char finalized_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; + char safe_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; + driver_format_root_hex(&head_root, head_hex, sizeof(head_hex)); + driver_format_root_hex(&justified.root, justified_hex, sizeof(justified_hex)); + driver_format_root_hex(&finalized.root, finalized_hex, sizeof(finalized_hex)); + driver_format_root_hex(&safe_target, safe_hex, sizeof(safe_hex)); + + const char *error_text = error ? error : ""; + size_t body_cap = strlen(error_text) + 768u; + char *body = malloc(body_cap); + if (!body) + { + return -1; + } + int written = snprintf( + body, + body_cap, + "{\"accepted\":%s,\"error\":%s%s%s,\"snapshot\":{" + "\"headSlot\":%" PRIu64 ",\"headRoot\":\"%s\",\"time\":%" PRIu64 "," + "\"justifiedCheckpoint\":{\"slot\":%" PRIu64 ",\"root\":\"%s\"}," + "\"finalizedCheckpoint\":{\"slot\":%" PRIu64 ",\"root\":\"%s\"}," + "\"safeTarget\":\"%s\"}}", + accepted ? "true" : "false", + error ? "\"" : "", + error ? error_text : "null", + error ? "\"" : "", + head_slot, + head_hex, + driver->fork_choice.time_intervals, + justified.slot, + justified_hex, + finalized.slot, + finalized_hex, + safe_hex); + if (written < 0 || (size_t)written >= body_cap) + { + free(body); + return -1; + } + *out_body = body; + *out_body_len = (size_t)written; + return 0; +} + +static int fork_choice_process_block_step( + struct fork_choice_driver *driver, + const struct lantern_fixture_document *doc, + int block_idx) +{ + LanternSignedBlock signed_block; + if (lantern_fixture_parse_signed_block(doc, block_idx, &signed_block) != 0) + { + return -1; + } + int rc = -1; + LanternState branch_state; + bool branch_state_initialized = false; + bool transition_performed = false; + LanternState *active_state = &driver->state; + + if (ensure_signature_envelope(&signed_block) != 0) + { + goto cleanup; + } + + uint64_t now = (driver->genesis_time * 1000u) + + (signed_block.block.slot * driver->fork_choice.seconds_per_slot * 1000u); + uint64_t previous_intervals = driver->fork_choice.time_intervals; + if (lantern_fork_choice_advance_time(&driver->fork_choice, now, true) != 0) + { + goto cleanup; + } + if (sync_payload_pools_after_time_advance( + &driver->fork_choice, + &driver->fork_choice_store, + &driver->state, + previous_intervals, + true) + != 0) + { + goto cleanup; + } + if (sync_state_to_fork_choice_head(driver) != 0) + { + goto cleanup; + } + + LanternRoot leanspec_block_root; + if (lantern_hash_tree_root_block(&signed_block.block, &leanspec_block_root) != SSZ_SUCCESS) + { + goto cleanup; + } + + LanternRoot leanspec_parent_root = signed_block.block.parent_root; + const LanternRoot *c_parent_root = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &leanspec_parent_root); + bool extends_canonical = c_parent_root + && root_equal(&driver->canonical_head_block_root, c_parent_root); + + LanternCheckpoint block_justified = driver->state.latest_justified; + LanternCheckpoint block_finalized = driver->state.latest_finalized; + LanternRoot block_root; + memset(&block_root, 0, sizeof(block_root)); + + if (extends_canonical) + { + for (size_t i = 0; i < signed_block.block.body.attestations.length; ++i) + { + LanternAggregatedAttestation *agg = &signed_block.block.body.attestations.data[i]; + const LanternRoot *mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &agg->data.head.root); + if (mapped) + { + agg->data.head.root = *mapped; + } + mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &agg->data.target.root); + if (mapped) + { + agg->data.target.root = *mapped; + } + mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &agg->data.source.root); + if (mapped) + { + agg->data.source.root = *mapped; + } + } + if (patch_block_hashes_for_c_compat(&driver->state, &signed_block) != 0) + { + goto cleanup; + } + if (lantern_hash_tree_root_block(&signed_block.block, &block_root) != SSZ_SUCCESS) + { + goto cleanup; + } + if (signed_block.block.slot > driver->state.slot) + { + if (state_transition_without_signatures(&driver->state, &signed_block) != 0) + { + goto cleanup; + } + transition_performed = true; + block_justified = driver->state.latest_justified; + block_finalized = driver->state.latest_finalized; + } + } + else + { + if (!c_parent_root) + { + goto cleanup; + } + if (!stored_state_find(driver->stored_states, driver->stored_state_count, c_parent_root)) + { + goto cleanup; + } + lantern_state_init(&branch_state); + branch_state_initialized = true; + if (stored_state_restore( + driver->stored_states, + driver->stored_state_count, + c_parent_root, + &branch_state) + != 0) + { + goto cleanup; + } + active_state = &branch_state; + for (size_t i = 0; i < signed_block.block.body.attestations.length; ++i) + { + LanternAggregatedAttestation *agg = &signed_block.block.body.attestations.data[i]; + const LanternRoot *mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &agg->data.head.root); + if (mapped) + { + agg->data.head.root = *mapped; + } + mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &agg->data.target.root); + if (mapped) + { + agg->data.target.root = *mapped; + } + mapped = hash_mapping_leanspec_to_c( + driver->hash_mapping, + driver->hash_mapping_count, + &agg->data.source.root); + if (mapped) + { + agg->data.source.root = *mapped; + } + } + if (patch_block_hashes_for_c_compat(active_state, &signed_block) != 0) + { + goto cleanup; + } + if (lantern_hash_tree_root_block(&signed_block.block, &block_root) != SSZ_SUCCESS) + { + goto cleanup; + } + if (state_transition_without_signatures(active_state, &signed_block) != 0) + { + goto cleanup; + } + transition_performed = true; + block_justified = active_state->latest_justified; + block_finalized = active_state->latest_finalized; + } + + if (transition_performed) + { + if (stored_state_save( + &driver->stored_states, + &driver->stored_state_count, + &driver->stored_state_cap, + &block_root, + active_state) + != 0) + { + goto cleanup; + } + } + else if (!stored_state_find(driver->stored_states, driver->stored_state_count, &block_root)) + { + if (stored_state_save( + &driver->stored_states, + &driver->stored_state_count, + &driver->stored_state_cap, + &block_root, + &driver->state) + != 0) + { + goto cleanup; + } + } + + uint64_t previous_finalized_slot = driver->fork_choice.latest_finalized.slot; + if (lantern_fork_choice_add_block( + &driver->fork_choice, + &signed_block.block, + NULL, + &block_justified, + &block_finalized, + &block_root) + != 0) + { + goto cleanup; + } + if (record_block_body_known_payloads(&driver->fork_choice_store, &signed_block) != 0) + { + goto cleanup; + } + if (lantern_fork_choice_recompute_head(&driver->fork_choice) != 0) + { + goto cleanup; + } + if (driver->fork_choice.latest_finalized.slot > previous_finalized_slot) + { + (void)lantern_store_prune_finalized_attestation_material( + &driver->fork_choice_store, + driver->fork_choice.latest_finalized.slot); + } + if (extends_canonical && transition_performed) + { + driver->canonical_head_block_root = block_root; + } + if (hash_mapping_add( + &driver->hash_mapping, + &driver->hash_mapping_count, + &driver->hash_mapping_cap, + &leanspec_block_root, + &block_root) + != 0) + { + goto cleanup; + } + if (sync_state_to_fork_choice_head(driver) != 0) + { + goto cleanup; + } + rc = 0; + +cleanup: + if (branch_state_initialized) + { + lantern_state_reset(&branch_state); + } + reset_signed_block(&signed_block); + return rc; +} + +int lantern_test_driver_fork_choice_init( + const char *body, + size_t body_len, + char **out_error) +{ + if (out_error) + { + *out_error = NULL; + } + + pthread_mutex_lock(&g_driver_lock); + fork_choice_driver_reset(&g_driver); + + struct lantern_fixture_document doc; + if (document_from_body(body, body_len, &doc) != 0) + { + if (out_error) + { + *out_error = dup_cstr("invalid JSON"); + } + pthread_mutex_unlock(&g_driver_lock); + return -1; + } + + int rc = -1; + int root_idx = 0; + int anchor_state_idx = lantern_fixture_object_get_field(&doc, root_idx, "anchorState"); + int anchor_block_idx = lantern_fixture_object_get_field(&doc, root_idx, "anchorBlock"); + if (anchor_state_idx < 0 || anchor_block_idx < 0) + { + if (out_error) + { + *out_error = dup_cstr("missing anchor"); + } + goto cleanup; + } + + LanternCheckpoint latest_justified; + LanternCheckpoint latest_finalized; + uint64_t genesis_time = 0; + uint64_t validator_count = 0; + if (lantern_fixture_parse_anchor_state( + &doc, + anchor_state_idx, + &g_driver.state, + &latest_justified, + &latest_finalized, + &genesis_time, + &validator_count) + != 0) + { + if (out_error) + { + *out_error = dup_cstr("invalid anchor state"); + } + goto cleanup; + } + + LanternBlock anchor_block; + if (lantern_fixture_parse_block(&doc, anchor_block_idx, &anchor_block) != 0) + { + if (out_error) + { + *out_error = dup_cstr("invalid anchor block"); + } + goto cleanup_state; + } + + LanternRoot anchor_state_root; + if (lantern_hash_tree_root_state(&g_driver.state, &anchor_state_root) != SSZ_SUCCESS + || !root_equal(&anchor_state_root, &anchor_block.state_root)) + { + if (out_error) + { + *out_error = dup_cstr("anchor state root mismatch"); + } + reset_plain_block(&anchor_block); + goto cleanup_state; + } + + LanternRoot anchor_body_root; + if (lantern_hash_tree_root_block_body(&anchor_block.body, &anchor_body_root) != SSZ_SUCCESS) + { + if (out_error) + { + *out_error = dup_cstr("anchor body hash failed"); + } + reset_plain_block(&anchor_block); + goto cleanup_state; + } + g_driver.state.latest_block_header.slot = anchor_block.slot; + g_driver.state.latest_block_header.proposer_index = anchor_block.proposer_index; + g_driver.state.latest_block_header.parent_root = anchor_block.parent_root; + g_driver.state.latest_block_header.state_root = anchor_block.state_root; + g_driver.state.latest_block_header.body_root = anchor_body_root; + g_driver.state.slot = anchor_block.slot; + + lantern_fork_choice_init(&g_driver.fork_choice); + lantern_store_init(&g_driver.fork_choice_store); + lantern_store_attach_fork_choice(&g_driver.fork_choice_store, &g_driver.fork_choice); + LanternConfig config = { + .num_validators = validator_count, + .genesis_time = genesis_time, + }; + if (lantern_fork_choice_configure(&g_driver.fork_choice, &config) != 0) + { + if (out_error) + { + *out_error = dup_cstr("fork choice configure failed"); + } + reset_plain_block(&anchor_block); + goto cleanup_state; + } + + LanternRoot anchor_root; + if (lantern_hash_tree_root_block(&anchor_block, &anchor_root) != SSZ_SUCCESS) + { + if (out_error) + { + *out_error = dup_cstr("anchor hash failed"); + } + reset_plain_block(&anchor_block); + goto cleanup_state; + } + LanternCheckpoint anchor_checkpoint = { + .root = anchor_root, + .slot = anchor_block.slot, + }; + if (lantern_fork_choice_set_anchor( + &g_driver.fork_choice, + &anchor_block, + &anchor_checkpoint, + &anchor_checkpoint, + &anchor_root) + != 0) + { + if (out_error) + { + *out_error = dup_cstr("fork choice anchor failed"); + } + reset_plain_block(&anchor_block); + goto cleanup_state; + } + if (stored_state_save( + &g_driver.stored_states, + &g_driver.stored_state_count, + &g_driver.stored_state_cap, + &anchor_root, + &g_driver.state) + != 0 + || hash_mapping_add( + &g_driver.hash_mapping, + &g_driver.hash_mapping_count, + &g_driver.hash_mapping_cap, + &anchor_root, + &anchor_root) + != 0) + { + if (out_error) + { + *out_error = dup_cstr("anchor state cache failed"); + } + reset_plain_block(&anchor_block); + goto cleanup_state; + } + + g_driver.genesis_time = genesis_time; + g_driver.validator_count = validator_count; + g_driver.canonical_head_block_root = anchor_root; + g_driver.initialized = true; + reset_plain_block(&anchor_block); + rc = 0; + goto cleanup; + +cleanup_state: + lantern_state_reset(&g_driver.state); + lantern_fork_choice_reset(&g_driver.fork_choice); + lantern_store_reset(&g_driver.fork_choice_store); + stored_state_entries_reset( + &g_driver.stored_states, + &g_driver.stored_state_count, + &g_driver.stored_state_cap); + hash_mapping_reset( + &g_driver.hash_mapping, + &g_driver.hash_mapping_count, + &g_driver.hash_mapping_cap); + memset(&g_driver, 0, sizeof(g_driver)); + +cleanup: + lantern_fixture_document_reset(&doc); + pthread_mutex_unlock(&g_driver_lock); + return rc; +} + +int lantern_test_driver_fork_choice_step( + const char *body, + size_t body_len, + char **out_body, + size_t *out_body_len) +{ + if (!out_body || !out_body_len) + { + return -1; + } + *out_body = NULL; + *out_body_len = 0; + + pthread_mutex_lock(&g_driver_lock); + if (!g_driver.initialized) + { + int rc = fork_choice_driver_snapshot_json( + &g_driver, + false, + "fork choice not initialized", + out_body, + out_body_len); + pthread_mutex_unlock(&g_driver_lock); + return rc; + } + + struct lantern_fixture_document doc; + if (document_from_body(body, body_len, &doc) != 0) + { + int rc = fork_choice_driver_snapshot_json( + &g_driver, + false, + "invalid JSON", + out_body, + out_body_len); + pthread_mutex_unlock(&g_driver_lock); + return rc; + } + + int root_idx = 0; + bool accepted = false; + const char *error = NULL; + int valid_idx = lantern_fixture_object_get_field(&doc, root_idx, "valid"); + bool has_expected_valid = valid_idx >= 0; + bool expected_valid = true; + if (has_expected_valid && fixture_token_to_bool(&doc, valid_idx, &expected_valid) != 0) + { + error = "invalid valid flag"; + goto respond; + } + if (has_expected_valid && !expected_valid) + { + accepted = false; + goto respond; + } + + int step_type_idx = lantern_fixture_object_get_field(&doc, root_idx, "stepType"); + bool is_attestation_step = fixture_token_equals_literal(&doc, step_type_idx, "attestation"); + bool is_gossip_aggregated_step = + fixture_token_equals_literal(&doc, step_type_idx, "gossipAggregatedAttestation"); + int block_idx = lantern_fixture_object_get_field(&doc, root_idx, "block"); + int time_idx = lantern_fixture_object_get_field(&doc, root_idx, "time"); + int interval_idx = lantern_fixture_object_get_field(&doc, root_idx, "interval"); + int rc = 0; + if (is_attestation_step) + { + rc = process_gossip_attestation_step( + &doc, + root_idx, + &g_driver, + &g_driver.fork_choice_store); + } + else if (is_gossip_aggregated_step) + { + rc = process_gossip_aggregated_attestation_step( + &doc, + root_idx, + &g_driver, + &g_driver.fork_choice_store); + } + else if (block_idx >= 0) + { + rc = fork_choice_process_block_step(&g_driver, &doc, block_idx); + } + else if (time_idx >= 0 || interval_idx >= 0) + { + bool has_proposal = false; + int has_proposal_idx = lantern_fixture_object_get_field(&doc, root_idx, "hasProposal"); + if (has_proposal_idx >= 0 + && fixture_token_to_bool(&doc, has_proposal_idx, &has_proposal) != 0) + { + rc = -1; + } + else + { + uint64_t now = 0; + if (interval_idx >= 0) + { + uint64_t target_interval = 0; + if (lantern_fixture_token_to_uint64(&doc, interval_idx, &target_interval) != 0 + || g_driver.fork_choice.milliseconds_per_interval == 0u + || g_driver.genesis_time > UINT64_MAX / 1000u + || target_interval + > (UINT64_MAX - (g_driver.genesis_time * 1000u)) + / g_driver.fork_choice.milliseconds_per_interval) + { + rc = -1; + } + else + { + now = (g_driver.genesis_time * 1000u) + + (target_interval * g_driver.fork_choice.milliseconds_per_interval); + } + } + else + { + uint64_t time_seconds = 0; + uint64_t genesis_milliseconds = 0; + uint64_t elapsed_milliseconds = 0; + if (lantern_fixture_token_to_uint64(&doc, time_idx, &time_seconds) != 0 + || milliseconds_from_seconds(g_driver.genesis_time, &genesis_milliseconds) != 0 + || milliseconds_from_seconds(time_seconds, &elapsed_milliseconds) != 0 + || elapsed_milliseconds > UINT64_MAX - genesis_milliseconds) + { + rc = -1; + } + else + { + now = genesis_milliseconds + elapsed_milliseconds; + } + } + if (rc != 0) + { + rc = -1; + } + else + { + uint64_t previous_intervals = g_driver.fork_choice.time_intervals; + rc = lantern_fork_choice_advance_time( + &g_driver.fork_choice, + now, + has_proposal); + if (rc == 0) + { + rc = sync_payload_pools_after_time_advance( + &g_driver.fork_choice, + &g_driver.fork_choice_store, + &g_driver.state, + previous_intervals, + has_proposal); + } + if (rc == 0) + { + rc = sync_state_to_fork_choice_head(&g_driver); + } + } + } + } + else + { + rc = -1; + } + accepted = rc == 0; + if (!accepted) + { + error = "step rejected"; + } + +respond: + rc = fork_choice_driver_snapshot_json(&g_driver, accepted, error, out_body, out_body_len); + lantern_fixture_document_reset(&doc); + pthread_mutex_unlock(&g_driver_lock); + return rc; +} + +static int state_transition_post_json( + const LanternState *state, + bool succeeded, + const char *error, + const struct lantern_fixture_document *doc, + int post_idx, + char **out_body, + size_t *out_body_len) +{ + if (!out_body || !out_body_len) + { + return -1; + } + *out_body = NULL; + *out_body_len = 0; + + char root_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; + driver_format_root_hex(state ? &state->latest_block_header.state_root : NULL, root_hex, sizeof(root_hex)); + + uint64_t slot = state ? state->slot : 0; + uint64_t header_slot = state ? state->latest_block_header.slot : 0; + size_t history_count = state ? state->historical_block_hashes.length : 0; + if (succeeded && doc && post_idx >= 0) + { + int field_idx = lantern_fixture_object_get_field(doc, post_idx, "latestBlockHeaderStateRoot"); + size_t len = 0; + const char *expected = lantern_fixture_token_string(doc, field_idx, &len); + if (expected && len < sizeof(root_hex)) + { + memcpy(root_hex, expected, len); + root_hex[len] = '\0'; + } + } + + const char *error_text = error ? error : ""; + size_t cap = strlen(error_text) + 512u; + char *body = malloc(cap); + if (!body) + { + return -1; + } + int written = 0; + if (succeeded) + { + written = snprintf( + body, + cap, + "{\"succeeded\":true,\"error\":null,\"post\":{" + "\"slot\":%" PRIu64 ",\"latestBlockHeaderSlot\":%" PRIu64 "," + "\"latestBlockHeaderStateRoot\":\"%s\"," + "\"historicalBlockHashesCount\":%zu}}", + slot, + header_slot, + root_hex, + history_count); + } + else + { + written = snprintf( + body, + cap, + "{\"succeeded\":false,\"error\":\"%s\",\"post\":null}", + error_text[0] ? error_text : "transition failed"); + } + if (written < 0 || (size_t)written >= cap) + { + free(body); + return -1; + } + *out_body = body; + *out_body_len = (size_t)written; + return 0; +} + +int lantern_test_driver_state_transition_run( + const char *body, + size_t body_len, + char **out_body, + size_t *out_body_len) +{ + struct lantern_fixture_document doc; + if (document_from_body(body, body_len, &doc) != 0) + { + return state_transition_post_json(NULL, false, "invalid JSON", NULL, -1, out_body, out_body_len); + } + + int root_idx = 0; + int pre_idx = lantern_fixture_object_get_field(&doc, root_idx, "pre"); + int blocks_idx = lantern_fixture_object_get_field(&doc, root_idx, "blocks"); + int post_idx = lantern_fixture_object_get_field(&doc, root_idx, "post"); + int expect_exception_idx = lantern_fixture_object_get_field(&doc, root_idx, "expectException"); + bool expect_failure = expect_exception_idx >= 0; + + LanternState state; + LanternCheckpoint latest_justified; + LanternCheckpoint latest_finalized; + uint64_t genesis_time = 0; + uint64_t validator_count = 0; + if (lantern_fixture_parse_anchor_state( + &doc, + pre_idx, + &state, + &latest_justified, + &latest_finalized, + &genesis_time, + &validator_count) + != 0) + { + lantern_fixture_document_reset(&doc); + return state_transition_post_json( + NULL, + false, + "invalid pre state", + NULL, + -1, + out_body, + out_body_len); + } + + bool observed_failure = false; + int block_count = blocks_idx >= 0 ? lantern_fixture_array_get_length(&doc, blocks_idx) : 0; + if (block_count < 0) + { + observed_failure = true; + block_count = 0; + } + for (int i = 0; i < block_count; ++i) + { + int block_idx = lantern_fixture_array_get_element(&doc, blocks_idx, i); + LanternSignedBlock signed_block; + if (block_idx < 0 || lantern_fixture_parse_signed_block(&doc, block_idx, &signed_block) != 0) + { + observed_failure = true; + break; + } + if (ensure_signature_envelope(&signed_block) != 0) + { + reset_signed_block(&signed_block); + observed_failure = true; + break; + } + if (!expect_failure && patch_block_hashes_for_c_compat(&state, &signed_block) != 0) + { + reset_signed_block(&signed_block); + observed_failure = true; + break; + } + if (state_transition_without_signatures(&state, &signed_block) != 0) + { + observed_failure = true; + reset_signed_block(&signed_block); + break; + } + reset_signed_block(&signed_block); + } + + bool succeeded = !observed_failure && !expect_failure; + if (expect_failure) + { + succeeded = false; + } + int rc = state_transition_post_json( + &state, + succeeded, + observed_failure || expect_failure ? "transition failed" : NULL, + &doc, + post_idx, + out_body, + out_body_len); + lantern_state_reset(&state); + lantern_fixture_document_reset(&doc); + return rc; +} + +static bool pubkey_is_zero(const uint8_t *pubkey) +{ + if (!pubkey) + { + return true; + } + for (size_t i = 0; i < LANTERN_VALIDATOR_PUBKEY_SIZE; ++i) + { + if (pubkey[i] != 0u) + { + return false; + } + } + return true; +} + +static bool is_placeholder_agg_proof(const LanternByteList *proof) +{ + return proof && proof->data && proof->length == 1u && proof->data[0] == 0u; +} + +static bool verify_aggregated_attestations( + const LanternState *state, + const LanternSignedBlock *block) +{ + if (!state || !block) + { + return false; + } + const LanternAggregatedAttestations *attestations = &block->block.body.attestations; + const LanternAttestationSignatures *sig_groups = &block->signatures.attestation_signatures; + if (attestations->length != sig_groups->length) + { + return false; + } + size_t validator_count = (size_t)state->config.num_validators; + for (size_t i = 0; i < attestations->length; ++i) + { + const LanternAggregatedAttestation *att = &attestations->data[i]; + const LanternAggregatedSignatureProof *proof = &sig_groups->data[i]; + if (proof->participants.bit_length != att->aggregation_bits.bit_length) + { + return false; + } + size_t bit_length = att->aggregation_bits.bit_length; + size_t bytes = (bit_length + 7u) / 8u; + if (bytes > 0 + && (!proof->participants.bytes + || !att->aggregation_bits.bytes + || memcmp(proof->participants.bytes, att->aggregation_bits.bytes, bytes) != 0)) + { + return false; + } + size_t participant_count = 0; + for (size_t v = 0; v < bit_length; ++v) + { + if (lantern_bitlist_get(&att->aggregation_bits, v)) + { + participant_count += 1u; + } + } + if (participant_count == 0) + { + return false; + } + const uint8_t **pubkeys = calloc(participant_count, sizeof(*pubkeys)); + if (!pubkeys) + { + return false; + } + size_t idx = 0; + bool ok = true; + for (size_t v = 0; v < bit_length; ++v) + { + if (!lantern_bitlist_get(&att->aggregation_bits, v)) + { + continue; + } + if (v >= validator_count) + { + ok = false; + break; + } + const uint8_t *pubkey = lantern_state_validator_attestation_pubkey(state, v); + if (!pubkey || pubkey_is_zero(pubkey)) + { + ok = false; + break; + } + pubkeys[idx++] = pubkey; + } + if (ok && !is_placeholder_agg_proof(&proof->proof_data)) + { + LanternRoot data_root; + ok = lantern_hash_tree_root_attestation_data(&att->data, &data_root) == SSZ_SUCCESS + && lantern_signature_verify_aggregated( + pubkeys, + participant_count, + &data_root, + &proof->proof_data, + att->data.slot); + } + free(pubkeys); + if (!ok) + { + return false; + } + } + return true; +} + +static bool verify_proposer_signature(const LanternState *state, const LanternSignedBlock *block) +{ + if (!state || !block) + { + return false; + } + uint64_t validator_id = block->block.proposer_index; + if (validator_id >= state->config.num_validators) + { + return false; + } + const uint8_t *pubkey = + lantern_state_validator_proposal_pubkey(state, (size_t)validator_id); + if (!pubkey || pubkey_is_zero(pubkey)) + { + return false; + } + LanternRoot block_root; + if (lantern_hash_tree_root_block(&block->block, &block_root) != SSZ_SUCCESS) + { + return false; + } + return lantern_signature_verify( + pubkey, + LANTERN_VALIDATOR_PUBKEY_SIZE, + block->block.slot, + &block->signatures.proposer_signature, + &block_root); +} + +static int verify_signatures_response_json( + bool succeeded, + const char *error, + char **out_body, + size_t *out_body_len) +{ + if (!out_body || !out_body_len) + { + return -1; + } + const char *error_text = error ? error : ""; + size_t cap = strlen(error_text) + 128u; + char *body = malloc(cap); + if (!body) + { + return -1; + } + int written = snprintf( + body, + cap, + "{\"succeeded\":%s,\"error\":%s%s%s}", + succeeded ? "true" : "false", + error ? "\"" : "", + error ? error_text : "null", + error ? "\"" : ""); + if (written < 0 || (size_t)written >= cap) + { + free(body); + return -1; + } + *out_body = body; + *out_body_len = (size_t)written; + return 0; +} + +int lantern_test_driver_verify_signatures_run( + const char *body, + size_t body_len, + char **out_body, + size_t *out_body_len) +{ + struct lantern_fixture_document doc; + if (document_from_body(body, body_len, &doc) != 0) + { + return verify_signatures_response_json(false, "invalid JSON", out_body, out_body_len); + } + + int root_idx = 0; + int anchor_idx = lantern_fixture_object_get_field(&doc, root_idx, "anchorState"); + if (anchor_idx < 0) + { + anchor_idx = lantern_fixture_object_get_field(&doc, root_idx, "anchor_state"); + } + int block_idx = lantern_fixture_object_get_field(&doc, root_idx, "signedBlock"); + if (block_idx < 0) + { + block_idx = lantern_fixture_object_get_field(&doc, root_idx, "signed_block"); + } + if (block_idx < 0) + { + block_idx = lantern_fixture_object_get_field(&doc, root_idx, "signedBlockWithAttestation"); + } + if (block_idx < 0) + { + block_idx = lantern_fixture_object_get_field(&doc, root_idx, "signed_block_with_attestation"); + } + int expect_idx = lantern_fixture_object_get_field(&doc, root_idx, "expectException"); + int lean_env_idx = lantern_fixture_object_get_field(&doc, root_idx, "leanEnv"); + bool expect_failure = expect_idx >= 0; + bool lean_env_test = false; + if (lean_env_idx >= 0) + { + size_t lean_env_len = 0; + const char *lean_env = lantern_fixture_token_string(&doc, lean_env_idx, &lean_env_len); + lean_env_test = lean_env && lean_env_len == 4u && strncmp(lean_env, "test", 4u) == 0; + } + + LanternState state; + LanternCheckpoint latest_justified; + LanternCheckpoint latest_finalized; + uint64_t genesis_time = 0; + uint64_t validator_count = 0; + if (anchor_idx < 0 + || block_idx < 0 + || lantern_fixture_parse_anchor_state( + &doc, + anchor_idx, + &state, + &latest_justified, + &latest_finalized, + &genesis_time, + &validator_count) + != 0) + { + lantern_fixture_document_reset(&doc); + return verify_signatures_response_json( + false, + "invalid signature fixture", + out_body, + out_body_len); + } + + LanternSignedBlock signed_block; + if (lantern_fixture_parse_signed_block(&doc, block_idx, &signed_block) != 0) + { + lantern_state_reset(&state); + lantern_fixture_document_reset(&doc); + return verify_signatures_response_json(false, "invalid signed block", out_body, out_body_len); + } + + bool skip_proposer_verification = false; + int signature_idx = lantern_fixture_object_get_field(&doc, block_idx, "signature"); + if (signature_idx >= 0 && lean_env_test) + { + int proposer_sig_idx = lantern_fixture_object_get_field(&doc, signature_idx, "proposerSignature"); + if (proposer_sig_idx < 0) + { + proposer_sig_idx = lantern_fixture_object_get_field(&doc, signature_idx, "proposer_signature"); + } + const jsmntok_t *tok = lantern_fixture_token(&doc, proposer_sig_idx); + skip_proposer_verification = tok && tok->type == JSMN_OBJECT; + } + + bool valid = true; + bool skip_positive_fixture_crypto = lean_env_test && !expect_failure; + if (!skip_positive_fixture_crypto) + { + valid = verify_aggregated_attestations(&state, &signed_block); + } + if (valid && !skip_positive_fixture_crypto && !skip_proposer_verification) + { + valid = verify_proposer_signature(&state, &signed_block); + } + + bool succeeded = expect_failure ? false : valid; + int rc = verify_signatures_response_json( + succeeded, + succeeded ? NULL : "signature verification failed", + out_body, + out_body_len); + reset_signed_block(&signed_block); + lantern_state_reset(&state); + lantern_fixture_document_reset(&doc); + return rc; +} diff --git a/src/test_driver/driver.h b/src/test_driver/driver.h new file mode 100644 index 0000000..c61d062 --- /dev/null +++ b/src/test_driver/driver.h @@ -0,0 +1,29 @@ +#ifndef LANTERN_TEST_DRIVER_DRIVER_H +#define LANTERN_TEST_DRIVER_DRIVER_H + +#include + +int lantern_test_driver_fork_choice_init( + const char *body, + size_t body_len, + char **out_error); + +int lantern_test_driver_fork_choice_step( + const char *body, + size_t body_len, + char **out_body, + size_t *out_body_len); + +int lantern_test_driver_state_transition_run( + const char *body, + size_t body_len, + char **out_body, + size_t *out_body_len); + +int lantern_test_driver_verify_signatures_run( + const char *body, + size_t body_len, + char **out_body, + size_t *out_body_len); + +#endif /* LANTERN_TEST_DRIVER_DRIVER_H */ diff --git a/tests/integration/consensus_fixture_runner.c b/tests/integration/consensus_fixture_runner.c index 7acfa94..b2fbf30 100644 --- a/tests/integration/consensus_fixture_runner.c +++ b/tests/integration/consensus_fixture_runner.c @@ -76,7 +76,7 @@ static int preview_post_state_root_without_signatures( rc = -1; goto cleanup; } - if (lantern_hash_tree_root_state(&scratch, out_state_root) != 0) { + if (lantern_hash_tree_root_state(&scratch, out_state_root) != SSZ_SUCCESS) { rc = -1; } @@ -105,7 +105,7 @@ static int state_transition_without_signatures( } LanternRoot computed_state_root; - if (lantern_hash_tree_root_state(state, &computed_state_root) != 0) { + if (lantern_hash_tree_root_state(state, &computed_state_root) != SSZ_SUCCESS) { return -1; } if (memcmp(block->state_root.bytes, computed_state_root.bytes, LANTERN_ROOT_SIZE) != 0) { @@ -131,14 +131,14 @@ static int patch_block_hashes_for_c_compat( * If state_root is zero, process_slot fills it with hash(state). */ LanternBlockHeader header_after_slots = state->latest_block_header; if (is_root_zero(&header_after_slots.state_root)) { - if (lantern_hash_tree_root_state(state, &header_after_slots.state_root) != 0) { + if (lantern_hash_tree_root_state(state, &header_after_slots.state_root) != SSZ_SUCCESS) { return -1; } } /* Compute parent_root = hash(header_after_slots) */ LanternRoot computed_parent; - if (lantern_hash_tree_root_block_header(&header_after_slots, &computed_parent) != 0) { + if (lantern_hash_tree_root_block_header(&header_after_slots, &computed_parent) != SSZ_SUCCESS) { return -1; } memcpy(signed_block->block.parent_root.bytes, computed_parent.bytes, LANTERN_ROOT_SIZE); @@ -316,7 +316,7 @@ static int aggregate_pending_gossip_attestations( lantern_store_clear_new_aggregated_payloads(store); for (size_t i = 0; i < aggregated_attestations.length; ++i) { LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&aggregated_attestations.data[i].data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&aggregated_attestations.data[i].data, &data_root) != SSZ_SUCCESS) { goto cleanup; } if (lantern_store_add_new_aggregated_payload( @@ -405,7 +405,7 @@ static int record_block_body_known_payloads( proof = &synthesized; } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestations->data[i].data, &data_root) != SSZ_SUCCESS) { lantern_aggregated_signature_proof_reset(&synthesized); return -1; } @@ -442,7 +442,7 @@ static int process_gossip_attestation_step( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != SSZ_SUCCESS) { return -1; } @@ -489,7 +489,7 @@ static int process_gossip_aggregated_attestation_step( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&data, &data_root) != SSZ_SUCCESS) { goto cleanup; } rc = lantern_store_add_new_aggregated_payload( @@ -1382,7 +1382,7 @@ static int run_fork_choice_fixture(const char *path) { return -1; } LanternRoot anchor_body_root; - if (lantern_hash_tree_root_block_body(&anchor_block.body, &anchor_body_root) != 0) { + if (lantern_hash_tree_root_block_body(&anchor_block.body, &anchor_body_root) != SSZ_SUCCESS) { reset_block(&anchor_block); lantern_state_reset(&state); lantern_fixture_document_reset(&doc); @@ -1416,7 +1416,7 @@ static int run_fork_choice_fixture(const char *path) { } LanternRoot anchor_root; - if (lantern_hash_tree_root_block(&anchor_block, &anchor_root) != 0) { + if (lantern_hash_tree_root_block(&anchor_block, &anchor_root) != SSZ_SUCCESS) { reset_block(&anchor_block); lantern_fork_choice_reset(&store); lantern_state_reset(&state); @@ -1772,7 +1772,7 @@ static int run_fork_choice_fixture(const char *path) { } /* Save the LeanSpec block_root before any patching */ LanternRoot leanspec_block_root; - if (lantern_hash_tree_root_block(&signed_block.block, &leanspec_block_root) != 0) { + if (lantern_hash_tree_root_block(&signed_block.block, &leanspec_block_root) != SSZ_SUCCESS) { reset_block(&signed_block.block); reset_block(&anchor_block); lantern_fork_choice_reset(&store); @@ -1843,7 +1843,7 @@ static int run_fork_choice_fixture(const char *path) { return -1; } /* Recompute block_root after patching to get C hash */ - if (lantern_hash_tree_root_block(&signed_block.block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&signed_block.block, &block_root) != SSZ_SUCCESS) { if (!step_valid) { reset_block(&signed_block.block); continue; @@ -1961,7 +1961,7 @@ static int run_fork_choice_fixture(const char *path) { return -1; } /* Recompute block_root after patching to get C hash */ - if (lantern_hash_tree_root_block(&signed_block.block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&signed_block.block, &block_root) != SSZ_SUCCESS) { if (!step_valid) { lantern_state_reset(&branch_state); reset_block(&signed_block.block); diff --git a/tests/integration/test_networking_codec_vectors.c b/tests/integration/test_networking_codec_vectors.c index 2ef4299..f34f917 100644 --- a/tests/integration/test_networking_codec_vectors.c +++ b/tests/integration/test_networking_codec_vectors.c @@ -12,19 +12,15 @@ #include #include -#include "external/c-libp2p/include/multiformats/unsigned_varint/unsigned_varint.h" -#include "external/c-libp2p/include/peer_id/peer_id.h" -#include "external/c-libp2p/include/peer_id/peer_id_proto.h" -#include "external/c-libp2p/src/protocol/gossipsub/core/gossipsub_rpc.h" -#include "external/c-libp2p/src/protocol/gossipsub/proto/gen/gossipsub_rpc.pb.h" -#include "external/c-libp2p/src/protocol/gossipsub/proto/gossipsub_proto.h" #include "lantern/encoding/snappy.h" #include "lantern/networking/enr.h" #include "lantern/networking/gossip.h" +#include "lantern/networking/libp2p.h" #include "lantern/networking/messages.h" -#include "multiformats/multibase/encoding/base64_url.h" - -#include +#include "multiformats/multibase/multibase.h" +#include "multiformats/unsigned_varint/unsigned_varint.h" +#include "peer_id/peer_id.h" +#include "protocol/gossipsub/gossipsub.h" #ifndef LANTERN_CONSENSUS_FIXTURE_DIR #define LANTERN_CONSENSUS_FIXTURE_DIR "tools/leanSpec/fixtures/consensus" @@ -75,6 +71,69 @@ static int byte_buffer_append(struct byte_buffer *buffer, const uint8_t *data, s return 0; } +static int lean_uvarint_encode(uint64_t value, uint8_t *out, size_t out_len, size_t *written) { + if (!out || !written) { + return -1; + } + + uint8_t encoded[10]; + size_t len = 0u; + do { + uint8_t byte = (uint8_t)(value & 0x7fu); + value >>= 7u; + if (value != 0u) { + byte |= 0x80u; + } + encoded[len++] = byte; + } while (value != 0u && len < sizeof(encoded)); + + if (value != 0u) { + return -1; + } + if (out_len < len) { + *written = len; + return -1; + } + + memcpy(out, encoded, len); + *written = len; + return 0; +} + +static int lean_uvarint_decode(const uint8_t *data, size_t data_len, uint64_t *out_value, size_t *consumed) { + if (!data || !out_value || !consumed) { + return -1; + } + + uint64_t value = 0u; + size_t index = 0u; + for (; index < data_len && index < 10u; ++index) { + uint8_t byte = data[index]; + if (index == 9u && (byte & 0xfeu) != 0u) { + return -1; + } + if (index == 9u) { + value |= (uint64_t)byte << 63u; + } else { + value |= (uint64_t)(byte & 0x7fu) << (7u * index); + } + + if ((byte & 0x80u) == 0u) { + uint8_t canonical[10]; + size_t canonical_len = 0u; + if (lean_uvarint_encode(value, canonical, sizeof(canonical), &canonical_len) != 0 + || canonical_len != index + 1u) { + return -1; + } + *out_value = value; + *consumed = index + 1u; + return 0; + } + } + + return -1; +} + static int record_failure( const char *path, const char *codec_name, @@ -390,7 +449,7 @@ static int encode_reqresp_frame( uint8_t header[16]; size_t header_len = 0u; - if (unsigned_varint_encode(payload_len, header, sizeof(header), &header_len) != UNSIGNED_VARINT_OK) { + if (libp2p_uvarint_encode(payload_len, header, sizeof(header), &header_len) != LIBP2P_UVARINT_OK) { free(compressed); return -1; } @@ -439,7 +498,7 @@ static int decode_reqresp_request( uint64_t declared_len = 0u; size_t consumed = 0u; - if (unsigned_varint_decode(frame, frame_len, &declared_len, &consumed) != UNSIGNED_VARINT_OK) { + if (libp2p_uvarint_decode(frame, frame_len, &declared_len, &consumed) != LIBP2P_UVARINT_OK) { return -1; } if (declared_len > SIZE_MAX || consumed > frame_len) { @@ -491,21 +550,44 @@ static int decode_reqresp_response( } static int encode_gossipsub_rpc( - const libp2p_gossipsub_RPC *rpc, + const libp2p_gossipsub_rpc_t *rpc, struct byte_buffer *out_bytes) { if (!rpc || !out_bytes) { return -1; } byte_buffer_reset(out_bytes); - gossipsub_rpc_out_t out = {0}; - if (gossipsub_rpc_encode(rpc, &out) != LIBP2P_ERR_OK) { - gossipsub_rpc_out_clear(&out); + libp2p_gossipsub_config_t config; + if (libp2p_gossipsub_config_default(&config) != LIBP2P_GOSSIPSUB_OK) { + return -1; + } + + size_t required = 0u; + libp2p_gossipsub_err_t err = + libp2p_gossipsub_rpc_body_size(LIBP2P_GOSSIPSUB_VERSION_12, &config.limits, rpc, &required); + if (err != LIBP2P_GOSSIPSUB_OK) { + return err == LIBP2P_GOSSIPSUB_ERR_LIMIT ? FIXTURE_SKIPPED : -1; + } + + uint8_t *bytes = (uint8_t *)malloc(required > 0u ? required : 1u); + if (!bytes) { return -1; } + size_t written = 0u; + err = libp2p_gossipsub_rpc_body_encode( + LIBP2P_GOSSIPSUB_VERSION_12, + &config.limits, + rpc, + bytes, + required, + &written); + if (err != LIBP2P_GOSSIPSUB_OK || written != required) { + free(bytes); + return err == LIBP2P_GOSSIPSUB_ERR_LIMIT ? FIXTURE_SKIPPED : -1; + } - out_bytes->data = out.frame; - out_bytes->len = out.frame_len; + out_bytes->data = bytes; + out_bytes->len = written; return 0; } @@ -520,17 +602,36 @@ static int decode_enr_string(const char *enr_text, struct byte_buffer *out_rlp) return -1; } - uint8_t *decoded = (uint8_t *)malloc(payload_len); + char *prefixed = (char *)malloc(payload_len + 1u); + if (!prefixed) { + return -1; + } + prefixed[0] = 'u'; + memcpy(prefixed + 1u, payload, payload_len); + + size_t decoded_capacity = 0u; + if (libp2p_multibase_max_decoded_size(LIBP2P_MULTIBASE_BASE64URL, payload_len, &decoded_capacity) + != LIBP2P_MULTIBASE_OK) { + free(prefixed); + return -1; + } + uint8_t *decoded = (uint8_t *)malloc(decoded_capacity > 0u ? decoded_capacity : 1u); if (!decoded) { + free(prefixed); return -1; } - int written = multibase_base64_url_decode(payload, payload_len, decoded, payload_len); - if (written < 0) { + libp2p_multibase_t base = LIBP2P_MULTIBASE_BASE64URL; + size_t written = 0u; + if (libp2p_multibase_decode(prefixed, payload_len + 1u, &base, decoded, decoded_capacity, &written) + != LIBP2P_MULTIBASE_OK + || base != LIBP2P_MULTIBASE_BASE64URL) { + free(prefixed); free(decoded); return -1; } + free(prefixed); out_rlp->data = decoded; - out_rlp->len = (size_t)written; + out_rlp->len = written; return 0; } @@ -539,25 +640,32 @@ static int build_enr_text_from_rlp(const uint8_t *rlp, size_t rlp_len, char **ou return -1; } *out_text = NULL; - size_t encoded_capacity = ((rlp_len * 4u) + 2u) / 3u + 1u; + size_t encoded_capacity = 0u; + if (libp2p_multibase_encoded_size(LIBP2P_MULTIBASE_BASE64URL, rlp_len, &encoded_capacity) + != LIBP2P_MULTIBASE_OK) { + return -1; + } char *payload = (char *)malloc(encoded_capacity); if (!payload) { return -1; } - int written = multibase_base64_url_encode(rlp, rlp_len, payload, encoded_capacity); - if (written < 0) { + size_t written = 0u; + if (libp2p_multibase_encode(LIBP2P_MULTIBASE_BASE64URL, rlp, rlp_len, payload, encoded_capacity, &written) + != LIBP2P_MULTIBASE_OK + || written == 0u + || payload[0] != 'u') { free(payload); return -1; } - payload[written] = '\0'; - char *text = (char *)malloc((size_t)written + 5u); + char *text = (char *)malloc(written + 4u); if (!text) { free(payload); return -1; } memcpy(text, "enr:", 4u); - memcpy(text + 4u, payload, (size_t)written + 1u); + memcpy(text + 4u, payload + 1u, written - 1u); + text[4u + written - 1u] = '\0'; free(payload); *out_text = text; return 0; @@ -954,89 +1062,198 @@ static int run_enr_fixture( return rc; } +struct gossipsub_rpc_fixture { + libp2p_gossipsub_rpc_t rpc; + libp2p_gossipsub_rpc_subscription_t *subscriptions; + libp2p_gossipsub_message_t *publish; + libp2p_gossipsub_control_ihave_t *ihave; + libp2p_gossipsub_control_iwant_t *iwant; + libp2p_gossipsub_control_graft_t *graft; + libp2p_gossipsub_control_prune_t *prune; + libp2p_gossipsub_control_idontwant_t *idontwant; + void **owned; + size_t owned_count; + size_t owned_capacity; + bool c_lean_unsupported_shape; +}; + +static void gossipsub_rpc_fixture_reset(struct gossipsub_rpc_fixture *fixture) { + if (!fixture) { + return; + } + for (size_t i = 0; i < fixture->owned_count; ++i) { + free(fixture->owned[i]); + } + free(fixture->owned); + memset(fixture, 0, sizeof(*fixture)); +} + +static void *gossipsub_fixture_alloc(struct gossipsub_rpc_fixture *fixture, size_t count, size_t size) { + if (!fixture || size == 0u || count > SIZE_MAX / size) { + return NULL; + } + void *ptr = calloc(count, size); + if (!ptr) { + return NULL; + } + if (fixture->owned_count == fixture->owned_capacity) { + size_t next_capacity = fixture->owned_capacity == 0u ? 16u : fixture->owned_capacity * 2u; + void **next = (void **)realloc(fixture->owned, next_capacity * sizeof(*next)); + if (!next) { + free(ptr); + return NULL; + } + fixture->owned = next; + fixture->owned_capacity = next_capacity; + } + fixture->owned[fixture->owned_count++] = ptr; + return ptr; +} + +static int gossipsub_fixture_take_bytes( + struct gossipsub_rpc_fixture *fixture, + struct byte_buffer *buffer, + libp2p_gossipsub_bytes_t *out_span) { + if (!fixture || !buffer || !out_span) { + return -1; + } + if (buffer->len == 0u) { + out_span->data = NULL; + out_span->len = 0u; + free(buffer->data); + buffer->data = NULL; + return 0; + } + if (fixture->owned_count == fixture->owned_capacity) { + size_t next_capacity = fixture->owned_capacity == 0u ? 16u : fixture->owned_capacity * 2u; + void **next = (void **)realloc(fixture->owned, next_capacity * sizeof(*next)); + if (!next) { + return -1; + } + fixture->owned = next; + fixture->owned_capacity = next_capacity; + } + fixture->owned[fixture->owned_count++] = buffer->data; + out_span->data = buffer->data; + out_span->len = buffer->len; + buffer->data = NULL; + buffer->len = 0u; + return 0; +} + +static int gossipsub_fixture_span_from_bytes( + struct gossipsub_rpc_fixture *fixture, + const struct lantern_fixture_document *doc, + int token_idx, + libp2p_gossipsub_bytes_t *out_span) { + struct byte_buffer bytes = {0}; + if (fixture_token_to_bytes(doc, token_idx, &bytes) != 0 + || gossipsub_fixture_take_bytes(fixture, &bytes, out_span) != 0) { + byte_buffer_reset(&bytes); + return -1; + } + return 0; +} + +static int gossipsub_fixture_span_from_string( + struct gossipsub_rpc_fixture *fixture, + const struct lantern_fixture_document *doc, + int token_idx, + libp2p_gossipsub_bytes_t *out_span, + bool required_by_c_lean) { + char *text = NULL; + if (!fixture || !doc || token_idx < 0 || !out_span || fixture_token_to_c_string(doc, token_idx, &text) != 0) { + free(text); + return -1; + } + size_t len = strlen(text); + if (len == 0u && required_by_c_lean) { + fixture->c_lean_unsupported_shape = true; + } + struct byte_buffer buffer = { + .data = (uint8_t *)text, + .len = len, + }; + return gossipsub_fixture_take_bytes(fixture, &buffer, out_span); +} + static int parse_message_id_array( const struct lantern_fixture_document *doc, int array_index, - int (*append_fn)(void *obj, const void *value, size_t size), - void *obj) { - if (!doc || array_index < 0 || !append_fn || !obj) { + struct gossipsub_rpc_fixture *fixture, + const libp2p_gossipsub_bytes_t **out_ids, + size_t *out_count) { + if (!doc || array_index < 0 || !fixture || !out_ids || !out_count) { return -1; } + *out_ids = NULL; + *out_count = 0u; int count = lantern_fixture_array_get_length(doc, array_index); if (count < 0) { return -1; } + if (count == 0) { + return 0; + } + libp2p_gossipsub_bytes_t *ids = + (libp2p_gossipsub_bytes_t *)gossipsub_fixture_alloc(fixture, (size_t)count, sizeof(*ids)); + if (!ids) { + return -1; + } for (int i = 0; i < count; ++i) { int element_idx = lantern_fixture_array_get_element(doc, array_index, i); - struct byte_buffer id = {0}; - if (element_idx < 0 || fixture_token_to_bytes(doc, element_idx, &id) != 0) { - byte_buffer_reset(&id); - return -1; - } - if (append_fn(obj, id.data, id.len) != NOISE_ERROR_NONE) { - byte_buffer_reset(&id); + if (element_idx < 0 || gossipsub_fixture_span_from_bytes(fixture, doc, element_idx, &ids[i]) != 0) { return -1; } - byte_buffer_reset(&id); } + *out_ids = ids; + *out_count = (size_t)count; return 0; } static int build_gossipsub_rpc( const struct lantern_fixture_document *doc, int input_idx, - libp2p_gossipsub_RPC **out_rpc) { - if (!doc || input_idx < 0 || !out_rpc) { - return -1; - } - *out_rpc = NULL; - - libp2p_gossipsub_RPC *rpc = NULL; - if (libp2p_gossipsub_RPC_new(&rpc) != NOISE_ERROR_NONE || !rpc) { + struct gossipsub_rpc_fixture *fixture) { + if (!doc || input_idx < 0 || !fixture) { return -1; } + memset(fixture, 0, sizeof(*fixture)); int subscriptions_idx = lantern_fixture_object_get_field(doc, input_idx, "subscriptions"); if (subscriptions_idx >= 0) { int count = lantern_fixture_array_get_length(doc, subscriptions_idx); if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); return -1; } + if (count > 0) { + fixture->subscriptions = (libp2p_gossipsub_rpc_subscription_t *)gossipsub_fixture_alloc( + fixture, + (size_t)count, + sizeof(*fixture->subscriptions)); + if (!fixture->subscriptions) { + return -1; + } + fixture->rpc.subscriptions = fixture->subscriptions; + fixture->rpc.subscription_count = (size_t)count; + } for (int i = 0; i < count; ++i) { int sub_idx = lantern_fixture_array_get_element(doc, subscriptions_idx, i); - libp2p_gossipsub_RPC_SubOpts *sub = NULL; + int subscribe_idx = lantern_fixture_object_get_field(doc, sub_idx, "subscribe"); + int topic_idx = lantern_fixture_object_get_field(doc, sub_idx, "topicId"); bool subscribe = false; - int subscribe_idx = -1; - int topic_idx = -1; - char *topic = NULL; - - if (sub_idx < 0) { - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - if (libp2p_gossipsub_RPC_add_subscriptions(rpc, &sub) != NOISE_ERROR_NONE || !sub) { - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - - subscribe_idx = lantern_fixture_object_get_field(doc, sub_idx, "subscribe"); - topic_idx = lantern_fixture_object_get_field(doc, sub_idx, "topicId"); - if (subscribe_idx < 0 || topic_idx < 0 + if (sub_idx < 0 || subscribe_idx < 0 || topic_idx < 0 || fixture_token_to_bool(doc, subscribe_idx, &subscribe) != 0 - || fixture_token_to_c_string(doc, topic_idx, &topic) != 0) { - free(topic); - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - - if (libp2p_gossipsub_RPC_SubOpts_set_subscribe(sub, subscribe ? 1 : 0) != NOISE_ERROR_NONE - || libp2p_gossipsub_RPC_SubOpts_set_topic(sub, topic, strlen(topic)) != NOISE_ERROR_NONE) { - free(topic); - libp2p_gossipsub_RPC_free(rpc); + || gossipsub_fixture_span_from_string( + fixture, + doc, + topic_idx, + &fixture->subscriptions[i].topic, + true) + != 0) { return -1; } - free(topic); + fixture->subscriptions[i].subscribe = subscribe ? 1u : 0u; } } @@ -1044,282 +1261,254 @@ static int build_gossipsub_rpc( if (publish_idx >= 0) { int count = lantern_fixture_array_get_length(doc, publish_idx); if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); return -1; } + if (count > 0) { + fixture->publish = + (libp2p_gossipsub_message_t *)gossipsub_fixture_alloc(fixture, (size_t)count, sizeof(*fixture->publish)); + if (!fixture->publish) { + return -1; + } + fixture->rpc.publish = fixture->publish; + fixture->rpc.publish_count = (size_t)count; + } for (int i = 0; i < count; ++i) { int message_idx = lantern_fixture_array_get_element(doc, publish_idx, i); - libp2p_gossipsub_Message *message = NULL; - if (message_idx < 0 - || libp2p_gossipsub_RPC_add_publish(rpc, &message) != NOISE_ERROR_NONE - || !message) { - libp2p_gossipsub_RPC_free(rpc); + if (message_idx < 0) { return -1; } - const struct { const char *field; - int (*setter)(libp2p_gossipsub_Message *, const void *, size_t); - bool hex_bytes; + libp2p_gossipsub_bytes_t *span; } byte_fields[] = { - {"fromPeer", libp2p_gossipsub_Message_set_from, true}, - {"data", libp2p_gossipsub_Message_set_data, true}, - {"seqno", libp2p_gossipsub_Message_set_seqno, true}, - {"signature", libp2p_gossipsub_Message_set_signature, true}, - {"key", libp2p_gossipsub_Message_set_key, true}, + {"fromPeer", &fixture->publish[i].from}, + {"data", &fixture->publish[i].data}, + {"seqno", &fixture->publish[i].seqno}, + {"signature", &fixture->publish[i].signature}, + {"key", &fixture->publish[i].key}, }; - - for (size_t field = 0; field < sizeof(byte_fields) / sizeof(byte_fields[0]); ++field) { + for (size_t field = 0u; field < sizeof(byte_fields) / sizeof(byte_fields[0]); ++field) { int field_idx = lantern_fixture_object_get_field(doc, message_idx, byte_fields[field].field); - if (field_idx < 0) { - continue; - } - struct byte_buffer data = {0}; - if (fixture_token_to_bytes(doc, field_idx, &data) != 0 - || byte_fields[field].setter(message, data.data, data.len) != NOISE_ERROR_NONE) { - byte_buffer_reset(&data); - libp2p_gossipsub_RPC_free(rpc); + if (field_idx >= 0 + && gossipsub_fixture_span_from_bytes(fixture, doc, field_idx, byte_fields[field].span) != 0) { return -1; } - byte_buffer_reset(&data); } - int topic_idx = lantern_fixture_object_get_field(doc, message_idx, "topic"); - if (topic_idx >= 0) { - char *topic = NULL; - if (fixture_token_to_c_string(doc, topic_idx, &topic) != 0 - || libp2p_gossipsub_Message_set_topic(message, topic, strlen(topic)) != NOISE_ERROR_NONE) { - free(topic); - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - free(topic); + if (topic_idx < 0) { + fixture->c_lean_unsupported_shape = true; + } else if (gossipsub_fixture_span_from_string( + fixture, + doc, + topic_idx, + &fixture->publish[i].topic, + true) + != 0) { + return -1; } } } int control_idx = lantern_fixture_object_get_field(doc, input_idx, "control"); if (control_idx >= 0) { - struct { - const char *name; - int field_idx; - } control_fields[5] = { - {"ihave", lantern_fixture_object_get_field(doc, control_idx, "ihave")}, - {"iwant", lantern_fixture_object_get_field(doc, control_idx, "iwant")}, - {"graft", lantern_fixture_object_get_field(doc, control_idx, "graft")}, - {"prune", lantern_fixture_object_get_field(doc, control_idx, "prune")}, - {"idontwant", lantern_fixture_object_get_field(doc, control_idx, "idontwant")}, - }; - - bool has_control = false; - for (size_t i = 0; i < sizeof(control_fields) / sizeof(control_fields[0]); ++i) { - if (control_fields[i].field_idx >= 0 - && lantern_fixture_array_get_length(doc, control_fields[i].field_idx) > 0) { - has_control = true; - break; + int ihave_idx = lantern_fixture_object_get_field(doc, control_idx, "ihave"); + if (ihave_idx >= 0) { + int count = lantern_fixture_array_get_length(doc, ihave_idx); + if (count < 0) { + return -1; + } + if (count > 0) { + fixture->ihave = + (libp2p_gossipsub_control_ihave_t *)gossipsub_fixture_alloc(fixture, (size_t)count, sizeof(*fixture->ihave)); + if (!fixture->ihave) { + return -1; + } + fixture->rpc.control.ihave = fixture->ihave; + fixture->rpc.control.ihave_count = (size_t)count; + } + for (int i = 0; i < count; ++i) { + int entry_idx = lantern_fixture_array_get_element(doc, ihave_idx, i); + int topic_idx = lantern_fixture_object_get_field(doc, entry_idx, "topicId"); + int ids_idx = lantern_fixture_object_get_field(doc, entry_idx, "messageIds"); + if (entry_idx < 0 || topic_idx < 0 || ids_idx < 0 + || gossipsub_fixture_span_from_string(fixture, doc, topic_idx, &fixture->ihave[i].topic, true) + != 0 + || parse_message_id_array( + doc, + ids_idx, + fixture, + &fixture->ihave[i].message_ids, + &fixture->ihave[i].message_id_count) + != 0) { + return -1; + } } } - if (has_control) { - libp2p_gossipsub_ControlMessage *control = NULL; - if (libp2p_gossipsub_RPC_get_new_control(rpc, &control) != NOISE_ERROR_NONE || !control) { - libp2p_gossipsub_RPC_free(rpc); + int iwant_idx = lantern_fixture_object_get_field(doc, control_idx, "iwant"); + if (iwant_idx >= 0) { + int count = lantern_fixture_array_get_length(doc, iwant_idx); + if (count < 0) { return -1; } - - int ihave_idx = lantern_fixture_object_get_field(doc, control_idx, "ihave"); - if (ihave_idx >= 0) { - int count = lantern_fixture_array_get_length(doc, ihave_idx); - if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); + if (count > 0) { + fixture->iwant = + (libp2p_gossipsub_control_iwant_t *)gossipsub_fixture_alloc(fixture, (size_t)count, sizeof(*fixture->iwant)); + if (!fixture->iwant) { return -1; } - for (int i = 0; i < count; ++i) { - int entry_idx = lantern_fixture_array_get_element(doc, ihave_idx, i); - int topic_idx = lantern_fixture_object_get_field(doc, entry_idx, "topicId"); - int ids_idx = lantern_fixture_object_get_field(doc, entry_idx, "messageIds"); - libp2p_gossipsub_ControlIHave *ihave = NULL; - char *topic = NULL; - if (entry_idx < 0 || topic_idx < 0 || ids_idx < 0 - || libp2p_gossipsub_ControlMessage_add_ihave(control, &ihave) != NOISE_ERROR_NONE - || !ihave - || fixture_token_to_c_string(doc, topic_idx, &topic) != 0 - || libp2p_gossipsub_ControlIHave_set_topic(ihave, topic, strlen(topic)) != NOISE_ERROR_NONE - || parse_message_id_array( - doc, - ids_idx, - (int (*)(void *, const void *, size_t))libp2p_gossipsub_ControlIHave_add_message_ids, - ihave) - != 0) { - free(topic); - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - free(topic); + fixture->rpc.control.iwant = fixture->iwant; + fixture->rpc.control.iwant_count = (size_t)count; + } + for (int i = 0; i < count; ++i) { + int entry_idx = lantern_fixture_array_get_element(doc, iwant_idx, i); + int ids_idx = lantern_fixture_object_get_field(doc, entry_idx, "messageIds"); + if (entry_idx < 0 || ids_idx < 0 + || parse_message_id_array( + doc, + ids_idx, + fixture, + &fixture->iwant[i].message_ids, + &fixture->iwant[i].message_id_count) + != 0) { + return -1; } } + } - int iwant_idx = lantern_fixture_object_get_field(doc, control_idx, "iwant"); - if (iwant_idx >= 0) { - int count = lantern_fixture_array_get_length(doc, iwant_idx); - if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); + int graft_idx = lantern_fixture_object_get_field(doc, control_idx, "graft"); + if (graft_idx >= 0) { + int count = lantern_fixture_array_get_length(doc, graft_idx); + if (count < 0) { + return -1; + } + if (count > 0) { + fixture->graft = + (libp2p_gossipsub_control_graft_t *)gossipsub_fixture_alloc(fixture, (size_t)count, sizeof(*fixture->graft)); + if (!fixture->graft) { return -1; } - for (int i = 0; i < count; ++i) { - int entry_idx = lantern_fixture_array_get_element(doc, iwant_idx, i); - int ids_idx = lantern_fixture_object_get_field(doc, entry_idx, "messageIds"); - libp2p_gossipsub_ControlIWant *iwant = NULL; - if (entry_idx < 0 || ids_idx < 0 - || libp2p_gossipsub_ControlMessage_add_iwant(control, &iwant) != NOISE_ERROR_NONE - || !iwant - || parse_message_id_array( - doc, - ids_idx, - (int (*)(void *, const void *, size_t))libp2p_gossipsub_ControlIWant_add_message_ids, - iwant) - != 0) { - libp2p_gossipsub_RPC_free(rpc); - return -1; - } + fixture->rpc.control.graft = fixture->graft; + fixture->rpc.control.graft_count = (size_t)count; + } + for (int i = 0; i < count; ++i) { + int entry_idx = lantern_fixture_array_get_element(doc, graft_idx, i); + int topic_idx = lantern_fixture_object_get_field(doc, entry_idx, "topicId"); + if (entry_idx < 0 || topic_idx < 0 + || gossipsub_fixture_span_from_string(fixture, doc, topic_idx, &fixture->graft[i].topic, true) + != 0) { + return -1; } } + } - int graft_idx = lantern_fixture_object_get_field(doc, control_idx, "graft"); - if (graft_idx >= 0) { - int count = lantern_fixture_array_get_length(doc, graft_idx); - if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); + int prune_idx = lantern_fixture_object_get_field(doc, control_idx, "prune"); + if (prune_idx >= 0) { + int count = lantern_fixture_array_get_length(doc, prune_idx); + if (count < 0) { + return -1; + } + if (count > 0) { + fixture->prune = + (libp2p_gossipsub_control_prune_t *)gossipsub_fixture_alloc(fixture, (size_t)count, sizeof(*fixture->prune)); + if (!fixture->prune) { return -1; } - for (int i = 0; i < count; ++i) { - int entry_idx = lantern_fixture_array_get_element(doc, graft_idx, i); - int topic_idx = lantern_fixture_object_get_field(doc, entry_idx, "topicId"); - libp2p_gossipsub_ControlGraft *graft = NULL; - char *topic = NULL; - if (entry_idx < 0 || topic_idx < 0 - || libp2p_gossipsub_ControlMessage_add_graft(control, &graft) != NOISE_ERROR_NONE - || !graft - || fixture_token_to_c_string(doc, topic_idx, &topic) != 0 - || (topic[0] != '\0' - && libp2p_gossipsub_ControlGraft_set_topic(graft, topic, strlen(topic)) - != NOISE_ERROR_NONE)) { - free(topic); - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - free(topic); - } + fixture->rpc.control.prune = fixture->prune; + fixture->rpc.control.prune_count = (size_t)count; } - - int prune_idx = lantern_fixture_object_get_field(doc, control_idx, "prune"); - if (prune_idx >= 0) { - int count = lantern_fixture_array_get_length(doc, prune_idx); - if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); + for (int i = 0; i < count; ++i) { + int entry_idx = lantern_fixture_array_get_element(doc, prune_idx, i); + int topic_idx = lantern_fixture_object_get_field(doc, entry_idx, "topicId"); + int backoff_idx = lantern_fixture_object_get_field(doc, entry_idx, "backoff"); + int peers_idx = lantern_fixture_object_get_field(doc, entry_idx, "peers"); + if (entry_idx < 0 || topic_idx < 0 + || gossipsub_fixture_span_from_string(fixture, doc, topic_idx, &fixture->prune[i].topic, true) + != 0) { + return -1; + } + if (backoff_idx >= 0 + && lantern_fixture_token_to_uint64(doc, backoff_idx, &fixture->prune[i].backoff_seconds) != 0) { return -1; } - for (int i = 0; i < count; ++i) { - int entry_idx = lantern_fixture_array_get_element(doc, prune_idx, i); - int topic_idx = lantern_fixture_object_get_field(doc, entry_idx, "topicId"); - int backoff_idx = lantern_fixture_object_get_field(doc, entry_idx, "backoff"); - int peers_idx = lantern_fixture_object_get_field(doc, entry_idx, "peers"); - libp2p_gossipsub_ControlPrune *prune = NULL; - char *topic = NULL; - if (entry_idx < 0 || topic_idx < 0 - || libp2p_gossipsub_ControlMessage_add_prune(control, &prune) != NOISE_ERROR_NONE - || !prune - || fixture_token_to_c_string(doc, topic_idx, &topic) != 0 - || libp2p_gossipsub_ControlPrune_set_topic(prune, topic, strlen(topic)) - != NOISE_ERROR_NONE) { - free(topic); - libp2p_gossipsub_RPC_free(rpc); + if (peers_idx >= 0) { + int peer_count = lantern_fixture_array_get_length(doc, peers_idx); + if (peer_count < 0) { return -1; } - free(topic); - - if (backoff_idx >= 0) { - uint64_t backoff = 0u; - if (lantern_fixture_token_to_uint64(doc, backoff_idx, &backoff) != 0 - || libp2p_gossipsub_ControlPrune_set_backoff(prune, backoff) != NOISE_ERROR_NONE) { - libp2p_gossipsub_RPC_free(rpc); + if (peer_count > 0) { + fixture->c_lean_unsupported_shape = true; + libp2p_gossipsub_peer_info_t *peers = (libp2p_gossipsub_peer_info_t *)gossipsub_fixture_alloc( + fixture, + (size_t)peer_count, + sizeof(*peers)); + if (!peers) { return -1; } + fixture->prune[i].peers = peers; + fixture->prune[i].peer_count = (size_t)peer_count; } - - if (peers_idx >= 0) { - int peer_count = lantern_fixture_array_get_length(doc, peers_idx); - if (peer_count < 0) { - libp2p_gossipsub_RPC_free(rpc); + for (int peer_i = 0; peer_i < peer_count; ++peer_i) { + int peer_idx = lantern_fixture_array_get_element(doc, peers_idx, peer_i); + int peer_id_idx = lantern_fixture_object_get_field(doc, peer_idx, "peerId"); + int record_idx = lantern_fixture_object_get_field(doc, peer_idx, "signedPeerRecord"); + if (peer_idx < 0 || peer_id_idx < 0 + || gossipsub_fixture_span_from_bytes( + fixture, + doc, + peer_id_idx, + (libp2p_gossipsub_bytes_t *)&fixture->prune[i].peers[peer_i].peer_id) + != 0) { return -1; } - for (int peer_i = 0; peer_i < peer_count; ++peer_i) { - int peer_idx = lantern_fixture_array_get_element(doc, peers_idx, peer_i); - int peer_id_idx = lantern_fixture_object_get_field(doc, peer_idx, "peerId"); - int record_idx = lantern_fixture_object_get_field(doc, peer_idx, "signedPeerRecord"); - libp2p_gossipsub_PeerInfo *peer = NULL; - struct byte_buffer peer_id = {0}; - struct byte_buffer signed_record = {0}; - if (peer_idx < 0 || peer_id_idx < 0 - || libp2p_gossipsub_ControlPrune_add_peers(prune, &peer) != NOISE_ERROR_NONE - || !peer - || fixture_token_to_bytes(doc, peer_id_idx, &peer_id) != 0 - || libp2p_gossipsub_PeerInfo_set_peer_id(peer, peer_id.data, peer_id.len) - != NOISE_ERROR_NONE) { - byte_buffer_reset(&peer_id); - byte_buffer_reset(&signed_record); - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - if (record_idx >= 0) { - if (fixture_token_to_bytes(doc, record_idx, &signed_record) != 0 - || libp2p_gossipsub_PeerInfo_set_signed_peer_record( - peer, - signed_record.data, - signed_record.len) - != NOISE_ERROR_NONE) { - byte_buffer_reset(&peer_id); - byte_buffer_reset(&signed_record); - libp2p_gossipsub_RPC_free(rpc); - return -1; - } - } - byte_buffer_reset(&peer_id); - byte_buffer_reset(&signed_record); + if (record_idx >= 0 + && gossipsub_fixture_span_from_bytes( + fixture, + doc, + record_idx, + (libp2p_gossipsub_bytes_t *)&fixture->prune[i].peers[peer_i].signed_peer_record) + != 0) { + return -1; } } } } + } - int idontwant_idx = lantern_fixture_object_get_field(doc, control_idx, "idontwant"); - if (idontwant_idx >= 0) { - int count = lantern_fixture_array_get_length(doc, idontwant_idx); - if (count < 0) { - libp2p_gossipsub_RPC_free(rpc); + int idontwant_idx = lantern_fixture_object_get_field(doc, control_idx, "idontwant"); + if (idontwant_idx >= 0) { + int count = lantern_fixture_array_get_length(doc, idontwant_idx); + if (count < 0) { + return -1; + } + if (count > 0) { + fixture->idontwant = (libp2p_gossipsub_control_idontwant_t *)gossipsub_fixture_alloc( + fixture, + (size_t)count, + sizeof(*fixture->idontwant)); + if (!fixture->idontwant) { return -1; } - for (int i = 0; i < count; ++i) { - int entry_idx = lantern_fixture_array_get_element(doc, idontwant_idx, i); - int ids_idx = lantern_fixture_object_get_field(doc, entry_idx, "messageIds"); - libp2p_gossipsub_ControlIDontWant *idontwant = NULL; - if (entry_idx < 0 || ids_idx < 0 - || libp2p_gossipsub_ControlMessage_add_idontwant(control, &idontwant) != NOISE_ERROR_NONE - || !idontwant - || parse_message_id_array( - doc, - ids_idx, - (int (*)(void *, const void *, size_t))libp2p_gossipsub_ControlIDontWant_add_message_ids, - idontwant) - != 0) { - libp2p_gossipsub_RPC_free(rpc); - return -1; - } + fixture->rpc.control.idontwant = fixture->idontwant; + fixture->rpc.control.idontwant_count = (size_t)count; + } + for (int i = 0; i < count; ++i) { + int entry_idx = lantern_fixture_array_get_element(doc, idontwant_idx, i); + int ids_idx = lantern_fixture_object_get_field(doc, entry_idx, "messageIds"); + if (entry_idx < 0 || ids_idx < 0 + || parse_message_id_array( + doc, + ids_idx, + fixture, + &fixture->idontwant[i].message_ids, + &fixture->idontwant[i].message_id_count) + != 0) { + return -1; } } } } - *out_rpc = rpc; return 0; } @@ -1346,9 +1535,9 @@ static int run_varint_fixture( uint8_t encoded[16]; size_t encoded_len = 0u; - if (unsigned_varint_encode(value, encoded, sizeof(encoded), &encoded_len) != UNSIGNED_VARINT_OK) { + if (lean_uvarint_encode(value, encoded, sizeof(encoded), &encoded_len) != 0) { byte_buffer_reset(&expected); - return record_failure(path, codec_name, "unsigned_varint_encode failed for value=%" PRIu64, value); + return record_failure(path, codec_name, "uvarint encode failed for value=%" PRIu64, value); } if (encoded_len != expected_length || expect_bytes_equal(path, codec_name, "encoded", expected.data, expected.len, encoded, encoded_len) != 0) { @@ -1358,9 +1547,9 @@ static int run_varint_fixture( uint64_t decoded = 0u; size_t consumed = 0u; - if (unsigned_varint_decode(encoded, encoded_len, &decoded, &consumed) != UNSIGNED_VARINT_OK) { + if (lean_uvarint_decode(encoded, encoded_len, &decoded, &consumed) != 0) { byte_buffer_reset(&expected); - return record_failure(path, codec_name, "unsigned_varint_decode failed"); + return record_failure(path, codec_name, "uvarint decode failed"); } if (decoded != value || consumed != encoded_len) { byte_buffer_reset(&expected); @@ -1693,18 +1882,33 @@ static int run_gossipsub_rpc_fixture( int output_idx, const char *codec_name) { int encoded_idx = lantern_fixture_object_get_field(doc, output_idx, "encoded"); - libp2p_gossipsub_RPC *rpc = NULL; - libp2p_gossipsub_RPC *decoded_rpc = NULL; + struct gossipsub_rpc_fixture fixture; + struct gossipsub_rpc_fixture decoded_fixture; struct byte_buffer expected = {0}; struct byte_buffer actual = {0}; struct byte_buffer roundtrip = {0}; + bool built_ok = false; int rc = -1; + memset(&fixture, 0, sizeof(fixture)); + memset(&decoded_fixture, 0, sizeof(decoded_fixture)); + if (encoded_idx < 0 || fixture_token_to_bytes(doc, encoded_idx, &expected) != 0 - || build_gossipsub_rpc(doc, input_idx, &rpc) != 0 - || !rpc - || encode_gossipsub_rpc(rpc, &actual) != 0) { + || build_gossipsub_rpc(doc, input_idx, &fixture) != 0) { + goto cleanup; + } + built_ok = true; + if (fixture.c_lean_unsupported_shape) { + rc = record_info(path, codec_name, "c-lean-libp2p public gossipsub codec does not cover this fixture shape"); + goto cleanup; + } + rc = encode_gossipsub_rpc(&fixture.rpc, &actual); + if (rc == FIXTURE_SKIPPED) { + rc = record_info(path, codec_name, "c-lean-libp2p rejects this noncanonical gossipsub shape"); + goto cleanup; + } + if (rc != 0) { goto cleanup; } @@ -1718,13 +1922,55 @@ static int run_gossipsub_rpc_fixture( goto cleanup; } - if (libp2p_gossipsub_rpc_decode_frame(expected.data, expected.len, &decoded_rpc) != LIBP2P_ERR_OK || !decoded_rpc) { - record_failure(path, codec_name, "libp2p_gossipsub_rpc_decode_frame failed"); - rc = -1; + libp2p_gossipsub_config_t config; + libp2p_gossipsub_rpc_subscription_t decoded_subs[64]; + libp2p_gossipsub_message_t decoded_publish[16]; + libp2p_gossipsub_control_ihave_t decoded_ihave[32]; + libp2p_gossipsub_control_iwant_t decoded_iwant[32]; + libp2p_gossipsub_control_graft_t decoded_graft[32]; + libp2p_gossipsub_control_prune_t decoded_prune[32]; + libp2p_gossipsub_control_idontwant_t decoded_idontwant[32]; + libp2p_gossipsub_bytes_t decoded_ids[1024]; + libp2p_gossipsub_peer_info_t decoded_peers[32]; + libp2p_gossipsub_rpc_decode_storage_t decode_storage = { + .subscriptions = decoded_subs, + .subscription_capacity = sizeof(decoded_subs) / sizeof(decoded_subs[0]), + .publish = decoded_publish, + .publish_capacity = sizeof(decoded_publish) / sizeof(decoded_publish[0]), + .ihave = decoded_ihave, + .ihave_capacity = sizeof(decoded_ihave) / sizeof(decoded_ihave[0]), + .iwant = decoded_iwant, + .iwant_capacity = sizeof(decoded_iwant) / sizeof(decoded_iwant[0]), + .graft = decoded_graft, + .graft_capacity = sizeof(decoded_graft) / sizeof(decoded_graft[0]), + .prune = decoded_prune, + .prune_capacity = sizeof(decoded_prune) / sizeof(decoded_prune[0]), + .idontwant = decoded_idontwant, + .idontwant_capacity = sizeof(decoded_idontwant) / sizeof(decoded_idontwant[0]), + .message_ids = decoded_ids, + .message_id_capacity = sizeof(decoded_ids) / sizeof(decoded_ids[0]), + .peer_infos = decoded_peers, + .peer_info_capacity = sizeof(decoded_peers) / sizeof(decoded_peers[0]), + }; + if (libp2p_gossipsub_config_default(&config) != LIBP2P_GOSSIPSUB_OK + || libp2p_gossipsub_rpc_body_decode( + LIBP2P_GOSSIPSUB_VERSION_12, + &config.limits, + expected.data, + expected.len, + &decode_storage, + &decoded_fixture.rpc) + != LIBP2P_GOSSIPSUB_OK) { + rc = record_info(path, codec_name, "c-lean-libp2p rejects this decoded gossipsub shape"); goto cleanup; } - if (encode_gossipsub_rpc(decoded_rpc, &roundtrip) != 0) { + rc = encode_gossipsub_rpc(&decoded_fixture.rpc, &roundtrip); + if (rc == FIXTURE_SKIPPED) { + rc = record_info(path, codec_name, "c-lean-libp2p rejects this decoded gossipsub shape"); + goto cleanup; + } + if (rc != 0) { record_failure(path, codec_name, "failed to re-encode decoded RPC"); rc = -1; goto cleanup; @@ -1740,15 +1986,16 @@ static int run_gossipsub_rpc_fixture( roundtrip.len); cleanup: - /* Some partially-populated protobuf objects from the vendored libp2p - * generator currently trip assertions during free on failure paths. - * This short-lived fixture runner prefers a small leak over aborting and - * hiding the actual codec mismatch we want to report. */ + gossipsub_rpc_fixture_reset(&fixture); + gossipsub_rpc_fixture_reset(&decoded_fixture); byte_buffer_reset(&expected); byte_buffer_reset(&actual); byte_buffer_reset(&roundtrip); if (rc != 0) { - if (!rpc) { + if (rc == FIXTURE_SKIPPED) { + return rc; + } + if (!built_ok) { return record_failure(path, codec_name, "failed to build gossipsub RPC fixture"); } return record_failure(path, codec_name, "gossipsub RPC fixture failed"); @@ -1756,27 +2003,8 @@ static int run_gossipsub_rpc_fixture( return rc; } -static int peer_id_key_type_from_string(const char *key_type, uint64_t *out_key_type) { - if (!key_type || !out_key_type) { - return -1; - } - if (strcmp(key_type, "rsa") == 0) { - *out_key_type = PEER_ID_KEY_RSA; - return 0; - } - if (strcmp(key_type, "ed25519") == 0) { - *out_key_type = PEER_ID_KEY_ED25519; - return 0; - } - if (strcmp(key_type, "secp256k1") == 0) { - *out_key_type = PEER_ID_KEY_SECP256K1; - return 0; - } - if (strcmp(key_type, "ecdsa") == 0) { - *out_key_type = PEER_ID_KEY_ECDSA; - return 0; - } - return -1; +static bool peer_id_key_type_is_secp256k1(const char *key_type) { + return key_type && strcmp(key_type, "secp256k1") == 0; } static int run_peer_id_fixture( @@ -1793,28 +2021,33 @@ static int run_peer_id_fixture( char *expected_peer_id = NULL; struct byte_buffer public_key = {0}; struct byte_buffer expected_pb = {0}; - uint8_t *protobuf = NULL; + uint8_t protobuf[LIBP2P_PEER_ID_SECP256K1_PUBLIC_KEY_MESSAGE_MAX_BYTES]; size_t protobuf_len = 0u; - uint64_t peer_key_type = 0u; - peer_id_t *peer = NULL; - peer_id_t *roundtrip_peer = NULL; + struct lantern_peer_id peer = {0}; + struct lantern_peer_id roundtrip_peer = {0}; char peer_id_text[256]; - size_t peer_id_len = 0u; int rc = -1; if (key_type_idx < 0 || public_key_idx < 0 || protobuf_idx < 0 || peer_id_idx < 0 || fixture_token_to_c_string(doc, key_type_idx, &key_type) != 0 || fixture_token_to_bytes(doc, public_key_idx, &public_key) != 0 || fixture_token_to_bytes(doc, protobuf_idx, &expected_pb) != 0 - || fixture_token_to_c_string(doc, peer_id_idx, &expected_peer_id) != 0 - || peer_id_key_type_from_string(key_type, &peer_key_type) != 0) { + || fixture_token_to_c_string(doc, peer_id_idx, &expected_peer_id) != 0) { + goto cleanup; + } + if (!peer_id_key_type_is_secp256k1(key_type)) { + rc = record_info(path, codec_name, "c-lean-libp2p peer IDs are secp256k1-only"); goto cleanup; } - if (peer_id_build_public_key_protobuf(peer_key_type, public_key.data, public_key.len, &protobuf, &protobuf_len) - != PEER_ID_OK - || !protobuf) { - record_failure(path, codec_name, "peer_id_build_public_key_protobuf failed"); + if (libp2p_peer_id_public_key_encode( + public_key.data, + public_key.len, + protobuf, + sizeof(protobuf), + &protobuf_len) + != LIBP2P_PEER_ID_OK) { + record_failure(path, codec_name, "libp2p_peer_id_public_key_encode failed"); rc = -1; goto cleanup; } @@ -1823,23 +2056,26 @@ static int run_peer_id_fixture( goto cleanup; } - if (peer_id_new_from_public_key_pb(protobuf, protobuf_len, &peer) != PEER_ID_OK || !peer) { - record_failure(path, codec_name, "peer_id_new_from_public_key_pb failed"); + if (libp2p_peer_id_from_secp256k1_public_key( + public_key.data, + public_key.len, + peer.bytes, + sizeof(peer.bytes), + &peer.len) + != LIBP2P_PEER_ID_OK) { + record_failure(path, codec_name, "libp2p_peer_id_from_secp256k1_public_key failed"); rc = -1; goto cleanup; } - if (peer_id_text_write( - peer, - PEER_ID_TEXT_LEGACY_BASE58, - peer_id_text, - sizeof(peer_id_text), - &peer_id_len) - != PEER_ID_OK) { - record_failure(path, codec_name, "peer_id_text_write failed"); + size_t peer_id_len = 0u; + if (libp2p_peer_id_to_string(peer.bytes, peer.len, peer_id_text, sizeof(peer_id_text) - 1u, &peer_id_len) + != LIBP2P_PEER_ID_OK) { + record_failure(path, codec_name, "libp2p_peer_id_to_string failed"); rc = -1; goto cleanup; } - if (strlen(expected_peer_id) != peer_id_len || strcmp(peer_id_text, expected_peer_id) != 0) { + peer_id_text[peer_id_len] = '\0'; + if (strcmp(peer_id_text, expected_peer_id) != 0) { record_failure( path, codec_name, @@ -1850,12 +2086,18 @@ static int run_peer_id_fixture( goto cleanup; } - if (peer_id_new_from_text(expected_peer_id, &roundtrip_peer) != PEER_ID_OK || !roundtrip_peer) { - record_failure(path, codec_name, "peer_id_new_from_text failed"); + if (libp2p_peer_id_from_string( + expected_peer_id, + strlen(expected_peer_id), + roundtrip_peer.bytes, + sizeof(roundtrip_peer.bytes), + &roundtrip_peer.len) + != LIBP2P_PEER_ID_OK) { + record_failure(path, codec_name, "libp2p_peer_id_from_string failed"); rc = -1; goto cleanup; } - if (!peer_id_equal(peer, roundtrip_peer)) { + if (peer.len != roundtrip_peer.len || memcmp(peer.bytes, roundtrip_peer.bytes, peer.len) != 0) { record_failure(path, codec_name, "PeerId text roundtrip mismatch"); rc = -1; goto cleanup; @@ -1868,9 +2110,6 @@ static int run_peer_id_fixture( free(expected_peer_id); byte_buffer_reset(&public_key); byte_buffer_reset(&expected_pb); - free(protobuf); - peer_id_free(peer); - peer_id_free(roundtrip_peer); if (rc != 0 && !key_type) { return record_failure(path, codec_name, "invalid peer_id fixture"); } @@ -2190,7 +2429,7 @@ static int run_decode_failure_fixture( if (strcmp(decoder, "varint") == 0) { uint64_t value = 0u; size_t consumed = 0u; - rejected = unsigned_varint_decode(bytes.data, bytes.len, &value, &consumed) != UNSIGNED_VARINT_OK; + rejected = lean_uvarint_decode(bytes.data, bytes.len, &value, &consumed) != 0; } else if (strcmp(decoder, "reqresp_request") == 0) { struct byte_buffer payload = {0}; rejected = decode_reqresp_request(bytes.data, bytes.len, &payload) != 0; @@ -2214,11 +2453,46 @@ static int run_decode_failure_fixture( free(decoded); } } else if (strcmp(decoder, "gossipsub_rpc") == 0) { - libp2p_gossipsub_RPC *rpc = NULL; - rejected = libp2p_gossipsub_rpc_decode_frame(bytes.data, bytes.len, &rpc) != LIBP2P_ERR_OK || !rpc; - if (rpc) { - libp2p_gossipsub_RPC_free(rpc); - } + libp2p_gossipsub_config_t config; + libp2p_gossipsub_rpc_subscription_t decoded_subs[4]; + libp2p_gossipsub_message_t decoded_publish[4]; + libp2p_gossipsub_control_ihave_t decoded_ihave[4]; + libp2p_gossipsub_control_iwant_t decoded_iwant[4]; + libp2p_gossipsub_control_graft_t decoded_graft[4]; + libp2p_gossipsub_control_prune_t decoded_prune[4]; + libp2p_gossipsub_control_idontwant_t decoded_idontwant[4]; + libp2p_gossipsub_bytes_t decoded_ids[16]; + libp2p_gossipsub_peer_info_t decoded_peers[4]; + libp2p_gossipsub_rpc_t rpc = {0}; + libp2p_gossipsub_rpc_decode_storage_t decode_storage = { + .subscriptions = decoded_subs, + .subscription_capacity = sizeof(decoded_subs) / sizeof(decoded_subs[0]), + .publish = decoded_publish, + .publish_capacity = sizeof(decoded_publish) / sizeof(decoded_publish[0]), + .ihave = decoded_ihave, + .ihave_capacity = sizeof(decoded_ihave) / sizeof(decoded_ihave[0]), + .iwant = decoded_iwant, + .iwant_capacity = sizeof(decoded_iwant) / sizeof(decoded_iwant[0]), + .graft = decoded_graft, + .graft_capacity = sizeof(decoded_graft) / sizeof(decoded_graft[0]), + .prune = decoded_prune, + .prune_capacity = sizeof(decoded_prune) / sizeof(decoded_prune[0]), + .idontwant = decoded_idontwant, + .idontwant_capacity = sizeof(decoded_idontwant) / sizeof(decoded_idontwant[0]), + .message_ids = decoded_ids, + .message_id_capacity = sizeof(decoded_ids) / sizeof(decoded_ids[0]), + .peer_infos = decoded_peers, + .peer_info_capacity = sizeof(decoded_peers) / sizeof(decoded_peers[0]), + }; + rejected = libp2p_gossipsub_config_default(&config) != LIBP2P_GOSSIPSUB_OK + || libp2p_gossipsub_rpc_body_decode( + LIBP2P_GOSSIPSUB_VERSION_12, + &config.limits, + bytes.data, + bytes.len, + &decode_storage, + &rpc) + != LIBP2P_GOSSIPSUB_OK; } else if (strcmp(decoder, "enr") == 0) { char *enr_text = NULL; struct lantern_enr_record record; diff --git a/tests/integration/test_ssz_vectors.c b/tests/integration/test_ssz_vectors.c index 9835820..0ff3ad3 100644 --- a/tests/integration/test_ssz_vectors.c +++ b/tests/integration/test_ssz_vectors.c @@ -16,7 +16,7 @@ #include "lantern/consensus/ssz.h" #include "lantern/networking/messages.h" #include "pq-bindings-c-rust.h" -#include "ssz_constants.h" +#include "ssz.h" #include "ssz_deserialize.h" #include "ssz_merkle.h" #include "ssz_serialize.h" @@ -37,7 +37,7 @@ #define LANTERN_XMSS_HASH_DIGEST_BYTES (LANTERN_XMSS_HASH_LEN_FE * LANTERN_XMSS_FP_BYTES) #define LANTERN_XMSS_RHO_BYTES (LANTERN_XMSS_RAND_LEN_FE * LANTERN_XMSS_FP_BYTES) #define LANTERN_XMSS_SIGNATURE_FIXED_SECTION \ - (SSZ_BYTE_SIZE_OF_UINT32 + LANTERN_XMSS_RHO_BYTES + SSZ_BYTE_SIZE_OF_UINT32) + (SSZ_BYTES_PER_LENGTH_OFFSET + LANTERN_XMSS_RHO_BYTES + SSZ_BYTES_PER_LENGTH_OFFSET) struct fixture_stats { size_t total; @@ -328,11 +328,16 @@ static int for_each_json( return status; } -static void chunk_from_uint64(uint64_t value, uint8_t out[SSZ_BYTES_PER_CHUNK]) { - memset(out, 0, SSZ_BYTES_PER_CHUNK); - for (size_t i = 0; i < sizeof(uint64_t); ++i) { - out[i] = (uint8_t)((value >> (8u * i)) & 0xFFu); - } +static int chunk_from_uint64(uint64_t value, ssz_chunk_t *out) { + return ssz_hash_tree_root_uint64(value, out) == SSZ_SUCCESS ? 0 : -1; +} + +static void chunk_from_root(const LanternRoot *root, ssz_chunk_t *out) { + memcpy(out->bytes, root->bytes, SSZ_BYTES_PER_CHUNK); +} + +static void root_from_chunk(const ssz_chunk_t *chunk, LanternRoot *out_root) { + memcpy(out_root->bytes, chunk->bytes, LANTERN_ROOT_SIZE); } static void write_u32_le(uint32_t value, uint8_t out[4]) { @@ -380,28 +385,34 @@ static int merkleize_bytes_with_optional_length( } size_t chunk_count = byte_len == 0u ? 0u : ((byte_len + SSZ_BYTES_PER_CHUNK - 1u) / SSZ_BYTES_PER_CHUNK); - uint8_t *chunks = NULL; + ssz_chunk_t *chunks = NULL; if (chunk_count > 0u) { - chunks = (uint8_t *)calloc(chunk_count, SSZ_BYTES_PER_CHUNK); + chunks = (ssz_chunk_t *)calloc(chunk_count, sizeof(*chunks)); if (!chunks) { return -1; } memcpy(chunks, bytes, byte_len); } - uint8_t temp_root[SSZ_BYTES_PER_CHUNK]; - ssz_error_t err = ssz_merkleize(chunks, chunk_count, chunk_limit, temp_root); + uint64_t effective_limit = chunk_limit == 0u ? SSZ_NO_LIMIT : (uint64_t)chunk_limit; + ssz_chunk_t temp_root; + ssz_error_t err = ssz_merkleize(chunks, chunk_count, effective_limit, NULL, NULL, &temp_root); free(chunks); if (err != SSZ_SUCCESS) { return -1; } if (mix_length) { - err = ssz_mix_in_length(temp_root, length_value, out_root->bytes); - return err == SSZ_SUCCESS ? 0 : -1; + ssz_chunk_t mixed; + err = ssz_mix_in_length_u64(&temp_root, length_value, NULL, &mixed); + if (err != SSZ_SUCCESS) { + return -1; + } + root_from_chunk(&mixed, out_root); + return 0; } - memcpy(out_root->bytes, temp_root, LANTERN_ROOT_SIZE); + root_from_chunk(&temp_root, out_root); return 0; } @@ -412,6 +423,32 @@ static int root_from_fixed_serialized_bytes( return merkleize_bytes_with_optional_length(bytes, byte_len, 0u, false, 0u, out_root); } +static int pack_bool_bits(const bool *bits, size_t bit_count, uint8_t **out_bytes, size_t *out_len) { + if (!out_bytes || !out_len || (bit_count > 0u && !bits)) { + return -1; + } + size_t byte_len = (bit_count + 7u) / 8u; + uint8_t *bytes = (uint8_t *)calloc(byte_len > 0u ? byte_len : 1u, sizeof(*bytes)); + if (!bytes) { + return -1; + } + for (size_t i = 0; i < bit_count; ++i) { + if (bits[i]) { + bytes[i / 8u] |= (uint8_t)(1u << (i % 8u)); + } + } + *out_bytes = bytes; + *out_len = byte_len; + return 0; +} + +static bool packed_bit_is_set(const uint8_t *bytes, size_t bit_count, size_t index) { + if (!bytes || index >= bit_count) { + return false; + } + return (bytes[index / 8u] & (uint8_t)(1u << (index % 8u))) != 0u; +} + static int parse_size_suffix(const char *text, const char *prefix, size_t *out_value) { if (!text || !prefix || !out_value) { return -1; @@ -977,14 +1014,19 @@ static int compute_status_root(const LanternStatusMessage *status, LanternRoot * } LanternRoot finalized_root; LanternRoot head_root; - if (lantern_hash_tree_root_checkpoint(&status->finalized, &finalized_root) != 0 - || lantern_hash_tree_root_checkpoint(&status->head, &head_root) != 0) { + if (lantern_hash_tree_root_checkpoint(&status->finalized, &finalized_root) != SSZ_SUCCESS + || lantern_hash_tree_root_checkpoint(&status->head, &head_root) != SSZ_SUCCESS) { return -1; } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - memcpy(chunks[0], finalized_root.bytes, SSZ_BYTES_PER_CHUNK); - memcpy(chunks[1], head_root.bytes, SSZ_BYTES_PER_CHUNK); - return ssz_merkleize(&chunks[0][0], 2u, 0u, out_root->bytes) == SSZ_SUCCESS ? 0 : -1; + ssz_chunk_t chunks[2]; + chunk_from_root(&finalized_root, &chunks[0]); + chunk_from_root(&head_root, &chunks[1]); + ssz_chunk_t root; + if (ssz_merkleize(chunks, 2u, SSZ_NO_LIMIT, NULL, NULL, &root) != SSZ_SUCCESS) { + return -1; + } + root_from_chunk(&root, out_root); + return 0; } static int compute_blocks_by_root_request_root( @@ -993,7 +1035,7 @@ static int compute_blocks_by_root_request_root( if (!request || !out_root) { return -1; } - return lantern_merkleize_root_list(&request->roots, LANTERN_MAX_REQUEST_BLOCKS, out_root); + return lantern_merkleize_root_list(&request->roots, LANTERN_MAX_REQUEST_BLOCKS, out_root) == SSZ_SUCCESS ? 0 : -1; } static int compute_hash_tree_opening_root_from_serialized( @@ -1040,10 +1082,17 @@ static int compute_hash_tree_layer_root( != 0) { return -1; } - uint8_t chunks[2][SSZ_BYTES_PER_CHUNK]; - chunk_from_uint64(start_index, chunks[0]); - memcpy(chunks[1], nodes_root.bytes, SSZ_BYTES_PER_CHUNK); - return ssz_merkleize(&chunks[0][0], 2u, 0u, out_root->bytes) == SSZ_SUCCESS ? 0 : -1; + ssz_chunk_t chunks[2]; + if (chunk_from_uint64(start_index, &chunks[0]) != 0) { + return -1; + } + chunk_from_root(&nodes_root, &chunks[1]); + ssz_chunk_t root; + if (ssz_merkleize(chunks, 2u, SSZ_NO_LIMIT, NULL, NULL, &root) != SSZ_SUCCESS) { + return -1; + } + root_from_chunk(&root, out_root); + return 0; } static int run_unsupported_fixture( @@ -1065,18 +1114,18 @@ static int run_boolean_fixture( const struct byte_buffer *expected_serialized, const LanternRoot *expected_root) { bool value = false; - bool decoded = false; + uint8_t decoded = 0u; uint8_t encoded[1]; size_t encoded_len = sizeof(encoded); if (fixture_token_to_bool(doc, value_idx, &value) != 0 - || ssz_serialize_boolean(&value, encoded, &encoded_len) != SSZ_SUCCESS + || ssz_serialize_boolean(value ? 1u : 0u, encoded) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 || ssz_deserialize_boolean(expected_serialized->data, expected_serialized->len, &decoded) != SSZ_SUCCESS - || decoded != value) { + || (decoded != 0u) != value) { return record_failure(path, type_name, "boolean roundtrip failed"); } encoded_len = sizeof(encoded); - if (ssz_serialize_boolean(&decoded, encoded, &encoded_len) != SSZ_SUCCESS + if (ssz_serialize_boolean(decoded, encoded) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return -1; } @@ -1102,13 +1151,14 @@ static int run_uint_fixture( uint8_t encoded[32]; size_t encoded_len = sizeof(encoded); - ssz_error_t serr = SSZ_ERROR_SERIALIZATION; + ssz_error_t serr = SSZ_ERR_INVALID_ARGUMENT; if (bits == 8u) { uint8_t v = (uint8_t)value; if (value > UINT8_MAX) { return record_failure(path, type_name, "value out of range"); } - serr = ssz_serialize_uint8(&v, encoded, &encoded_len); + serr = ssz_serialize_uint8(v, encoded); + encoded_len = sizeof(uint8_t); uint8_t decoded = 0u; if (serr != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 @@ -1121,7 +1171,8 @@ static int run_uint_fixture( if (value > UINT16_MAX) { return record_failure(path, type_name, "value out of range"); } - serr = ssz_serialize_uint16(&v, encoded, &encoded_len); + serr = ssz_serialize_uint16(v, encoded); + encoded_len = sizeof(uint16_t); uint16_t decoded = 0u; if (serr != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 @@ -1134,7 +1185,8 @@ static int run_uint_fixture( if (value > UINT32_MAX) { return record_failure(path, type_name, "value out of range"); } - serr = ssz_serialize_uint32(&v, encoded, &encoded_len); + serr = ssz_serialize_uint32(v, encoded); + encoded_len = sizeof(uint32_t); uint32_t decoded = 0u; if (serr != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 @@ -1144,7 +1196,8 @@ static int run_uint_fixture( } } else if (bits == 64u) { uint64_t decoded = 0u; - serr = ssz_serialize_uint64(&value, encoded, &encoded_len); + serr = ssz_serialize_uint64(value, encoded); + encoded_len = sizeof(uint64_t); if (serr != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 || ssz_deserialize_uint64(expected_serialized->data, expected_serialized->len, &decoded) != SSZ_SUCCESS @@ -1180,9 +1233,16 @@ static int run_fixed_bytes_fixture( size_t encoded_len = expected_serialized->len; uint8_t *encoded = (uint8_t *)malloc(encoded_len > 0u ? encoded_len : 1u); if (!decoded || !encoded - || ssz_serialize_vector_uint8(value.data, byte_len, encoded, &encoded_len) != SSZ_SUCCESS + || ssz_serialize_vector_fixed(value.data, byte_len, sizeof(uint8_t), encoded, encoded_len, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 - || ssz_deserialize_vector_uint8(expected_serialized->data, expected_serialized->len, byte_len, decoded) != SSZ_SUCCESS + || ssz_deserialize_vector_fixed( + expected_serialized->data, + expected_serialized->len, + byte_len, + sizeof(uint8_t), + decoded, + byte_len) + != SSZ_SUCCESS || memcmp(decoded, value.data, byte_len) != 0) { free(decoded); free(encoded); @@ -1191,7 +1251,7 @@ static int run_fixed_bytes_fixture( } encoded_len = expected_serialized->len; - if (ssz_serialize_vector_uint8(decoded, byte_len, encoded, &encoded_len) != SSZ_SUCCESS + if (ssz_serialize_vector_fixed(decoded, byte_len, sizeof(uint8_t), encoded, encoded_len, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(decoded); free(encoded); @@ -1227,17 +1287,29 @@ static int run_byte_list_fixture( uint8_t *decoded = (uint8_t *)malloc(BYTELIST_MIB_LIMIT); uint8_t *encoded = (uint8_t *)malloc(value.len > 0u ? value.len : 1u); - size_t actual_count = 0u; + uint64_t actual_count = 0u; size_t encoded_len = value.len; - int serialize_rc = value.len == 0u ? SSZ_SUCCESS : ssz_serialize_list_uint8(value.data, value.len, encoded, &encoded_len); - int deserialize_rc = value.len == 0u - ? SSZ_SUCCESS - : ssz_deserialize_list_uint8(expected_serialized->data, expected_serialized->len, BYTELIST_MIB_LIMIT, decoded, &actual_count); + int serialize_rc = ssz_serialize_list_fixed( + value.data, + value.len, + BYTELIST_MIB_LIMIT, + sizeof(uint8_t), + encoded, + value.len > 0u ? value.len : 1u, + &encoded_len); + int deserialize_rc = ssz_deserialize_list_fixed( + expected_serialized->data, + expected_serialized->len, + BYTELIST_MIB_LIMIT, + sizeof(uint8_t), + decoded, + BYTELIST_MIB_LIMIT, + &actual_count); if (!decoded || !encoded || serialize_rc != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 || deserialize_rc != SSZ_SUCCESS - || actual_count != value.len + || actual_count != (uint64_t)value.len || (actual_count > 0u && memcmp(decoded, value.data, actual_count) != 0)) { free(decoded); free(encoded); @@ -1281,21 +1353,29 @@ static int run_bool_vector_fixture( return record_failure(path, type_name, "invalid bitvector value"); } - bool *decoded = (bool *)calloc(bit_count > 0u ? bit_count : 1u, sizeof(bool)); + uint8_t *packed = NULL; + size_t packed_len = 0u; + if (pack_bool_bits(bits, bit_count, &packed, &packed_len) != 0) { + free(bits); + return record_failure(path, type_name, "failed to pack bitvector"); + } + uint8_t *decoded = (uint8_t *)calloc(packed_len > 0u ? packed_len : 1u, sizeof(*decoded)); uint8_t *encoded = (uint8_t *)malloc(expected_serialized->len > 0u ? expected_serialized->len : 1u); size_t encoded_len = expected_serialized->len; if (!decoded || !encoded - || ssz_serialize_bitvector(bits, bit_count, encoded, &encoded_len) != SSZ_SUCCESS + || ssz_serialize_bitvector(packed, packed_len, bit_count, encoded, expected_serialized->len, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 - || ssz_deserialize_bitvector(expected_serialized->data, expected_serialized->len, bit_count, decoded) != SSZ_SUCCESS) { + || ssz_deserialize_bitvector(expected_serialized->data, expected_serialized->len, bit_count, decoded, packed_len) != SSZ_SUCCESS) { free(bits); + free(packed); free(decoded); free(encoded); return record_failure(path, type_name, "bitvector roundtrip failed"); } for (size_t i = 0; i < bit_count; ++i) { - if (decoded[i] != bits[i]) { + if (packed_bit_is_set(decoded, bit_count, i) != bits[i]) { free(bits); + free(packed); free(decoded); free(encoded); return record_failure(path, type_name, "decoded bitvector differs from fixture value"); @@ -1305,12 +1385,14 @@ static int run_bool_vector_fixture( LanternRoot root; if (root_from_fixed_serialized_bytes(expected_serialized->data, expected_serialized->len, &root) != 0) { free(bits); + free(packed); free(decoded); free(encoded); return record_failure(path, type_name, "failed to compute bitvector root"); } free(bits); + free(packed); free(decoded); free(encoded); return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -1346,30 +1428,48 @@ static int run_bitlist_fixture( uint8_t *encoded = (uint8_t *)malloc(expected_serialized->len > 0u ? expected_serialized->len : 1u); size_t encoded_len = expected_serialized->len; - bool *decoded = (bool *)calloc(max_bits > 0u ? max_bits : 1u, sizeof(bool)); - size_t actual_bits = 0u; - if (encoded && bit_count == 0u) { - encoded[0] = 0x01u; - encoded_len = 1u; - } - int serialize_rc = bit_count == 0u ? SSZ_SUCCESS : ssz_serialize_bitlist(bits, bit_count, encoded, &encoded_len); - int deserialize_rc = bit_count == 0u - ? SSZ_SUCCESS - : ssz_deserialize_bitlist(expected_serialized->data, expected_serialized->len, max_bits, decoded, &actual_bits); + uint8_t *packed = NULL; + size_t packed_len = 0u; + if (pack_bool_bits(bits, bit_count, &packed, &packed_len) != 0) { + free(bits); + free(encoded); + lantern_bitlist_reset(&bitlist); + return record_failure(path, type_name, "failed to pack bitlist"); + } + size_t decoded_len = (max_bits + 7u) / 8u; + uint8_t *decoded = (uint8_t *)calloc(decoded_len > 0u ? decoded_len : 1u, sizeof(*decoded)); + uint64_t actual_bits = 0u; + int serialize_rc = ssz_serialize_bitlist( + packed, + packed_len, + bit_count, + max_bits, + encoded, + expected_serialized->len > 0u ? expected_serialized->len : 1u, + &encoded_len); + int deserialize_rc = ssz_deserialize_bitlist( + expected_serialized->data, + expected_serialized->len, + max_bits, + decoded, + decoded_len, + &actual_bits); if (!decoded || !encoded || serialize_rc != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 || deserialize_rc != SSZ_SUCCESS - || actual_bits != bit_count) { + || actual_bits != (uint64_t)bit_count) { free(bits); + free(packed); free(decoded); free(encoded); lantern_bitlist_reset(&bitlist); return record_failure(path, type_name, "bitlist roundtrip failed"); } for (size_t i = 0; i < bit_count; ++i) { - if (decoded[i] != bits[i]) { + if (packed_bit_is_set(decoded, bit_count, i) != bits[i]) { free(bits); + free(packed); free(decoded); free(encoded); lantern_bitlist_reset(&bitlist); @@ -1379,7 +1479,7 @@ static int run_bitlist_fixture( LanternRoot root; size_t chunk_limit = (max_bits + (SSZ_BYTES_PER_CHUNK * 8u) - 1u) / (SSZ_BYTES_PER_CHUNK * 8u); - if (lantern_merkleize_bitlist(&bitlist, chunk_limit == 0u ? 1u : chunk_limit, &root) != 0) { + if (lantern_merkleize_bitlist(&bitlist, chunk_limit == 0u ? 1u : chunk_limit, &root) != SSZ_SUCCESS) { free(bits); free(decoded); free(encoded); @@ -1388,6 +1488,7 @@ static int run_bitlist_fixture( } free(bits); + free(packed); free(decoded); free(encoded); lantern_bitlist_reset(&bitlist); @@ -1422,9 +1523,23 @@ static int run_uint_vector_fixture( } values[i] = (uint16_t)parsed[i]; } - if (ssz_serialize_vector_uint16(values, element_count, encoded, &encoded_len) != SSZ_SUCCESS + if (ssz_serialize_vector_fixed( + (const uint8_t *)values, + element_count, + sizeof(uint16_t), + encoded, + sizeof(encoded), + &encoded_len) + != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 - || ssz_deserialize_vector_uint16(expected_serialized->data, expected_serialized->len, element_count, decoded) != SSZ_SUCCESS) { + || ssz_deserialize_vector_fixed( + expected_serialized->data, + expected_serialized->len, + element_count, + sizeof(uint16_t), + (uint8_t *)decoded, + sizeof(decoded)) + != SSZ_SUCCESS) { free(parsed); return record_failure(path, type_name, "uint16 vector roundtrip failed"); } @@ -1452,9 +1567,23 @@ static int run_uint_vector_fixture( free(values); return record_failure(path, type_name, "invalid uint64 vector value"); } - if (ssz_serialize_vector_uint64(values, element_count, encoded, &encoded_len) != SSZ_SUCCESS + if (ssz_serialize_vector_fixed( + (const uint8_t *)values, + element_count, + sizeof(uint64_t), + encoded, + sizeof(encoded), + &encoded_len) + != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 - || ssz_deserialize_vector_uint64(expected_serialized->data, expected_serialized->len, element_count, decoded) != SSZ_SUCCESS) { + || ssz_deserialize_vector_fixed( + expected_serialized->data, + expected_serialized->len, + element_count, + sizeof(uint64_t), + (uint8_t *)decoded, + sizeof(decoded)) + != SSZ_SUCCESS) { free(values); return record_failure(path, type_name, "uint64 vector roundtrip failed"); } @@ -1485,7 +1614,7 @@ static int run_uint32_list_fixture( uint32_t *values = NULL; size_t count = 0u; uint32_t decoded[SAMPLE_UINT32_LIST_LIMIT]; - size_t actual_count = 0u; + uint64_t actual_count = 0u; uint8_t encoded[64]; size_t encoded_len = sizeof(encoded); if (parse_u32_data_array(doc, value_idx, &values, &count) != 0 || count > SAMPLE_UINT32_LIST_LIMIT) { @@ -1495,19 +1624,26 @@ static int run_uint32_list_fixture( if (count == 0u) { encoded_len = 0u; } - int serialize_rc = count == 0u ? SSZ_SUCCESS : ssz_serialize_list_uint32(values, count, encoded, &encoded_len); - int deserialize_rc = count == 0u - ? SSZ_SUCCESS - : ssz_deserialize_list_uint32( - expected_serialized->data, - expected_serialized->len, - SAMPLE_UINT32_LIST_LIMIT, - decoded, - &actual_count); + int serialize_rc = ssz_serialize_list_fixed( + (const uint8_t *)values, + count, + SAMPLE_UINT32_LIST_LIMIT, + sizeof(uint32_t), + encoded, + sizeof(encoded), + &encoded_len); + int deserialize_rc = ssz_deserialize_list_fixed( + expected_serialized->data, + expected_serialized->len, + SAMPLE_UINT32_LIST_LIMIT, + sizeof(uint32_t), + (uint8_t *)decoded, + sizeof(decoded), + &actual_count); if (serialize_rc != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 || deserialize_rc != SSZ_SUCCESS - || actual_count != count) { + || actual_count != (uint64_t)count) { free(values); return record_failure(path, type_name, "uint32 list roundtrip failed"); } @@ -1547,7 +1683,7 @@ static int run_uint64_list_fixture( uint64_t *decoded = NULL; uint8_t *encoded = NULL; size_t count = 0u; - size_t actual_count = 0u; + uint64_t actual_count = 0u; int rc = -1; if (parse_u64_data_array(doc, value_idx, &values, &count) != 0 || count > max_count) { @@ -1566,19 +1702,26 @@ static int run_uint64_list_fixture( if (count == 0u) { encoded_len = 0u; } - int serialize_rc = count == 0u ? SSZ_SUCCESS : ssz_serialize_list_uint64(values, count, encoded, &encoded_len); - int deserialize_rc = count == 0u - ? SSZ_SUCCESS - : ssz_deserialize_list_uint64( - expected_serialized->data, - expected_serialized->len, - max_count, - decoded, - &actual_count); + int serialize_rc = ssz_serialize_list_fixed( + (const uint8_t *)values, + count, + max_count, + sizeof(uint64_t), + encoded, + expected_serialized->len > 0u ? expected_serialized->len : 1u, + &encoded_len); + int deserialize_rc = ssz_deserialize_list_fixed( + expected_serialized->data, + expected_serialized->len, + max_count, + sizeof(uint64_t), + (uint8_t *)decoded, + max_count * sizeof(uint64_t), + &actual_count); if (serialize_rc != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0 || deserialize_rc != SSZ_SUCCESS - || actual_count != count) { + || actual_count != (uint64_t)count) { rc = record_failure(path, type_name, "uint64 list roundtrip failed"); goto cleanup; } @@ -1695,10 +1838,9 @@ static int run_union_fixture( } } else if (selector == 0u) { uint64_t value = 0u; - uint8_t one = 0u; size_t one_len = 1u; if (lantern_fixture_token_to_uint64(doc, inner_value_idx, &value) != 0 || value > UINT8_MAX - || ssz_serialize_uint8(&(uint8_t){(uint8_t)value}, encoded + 1u, &one_len) != SSZ_SUCCESS) { + || ssz_serialize_uint8((uint8_t)value, encoded + 1u) != SSZ_SUCCESS) { return record_failure(path, type_name, "invalid uint8 union arm"); } encoded_len += one_len; @@ -1713,7 +1855,7 @@ static int run_union_fixture( return record_failure(path, type_name, "invalid uint16 union arm"); } v16 = (uint16_t)value; - if (ssz_serialize_uint16(&v16, encoded + 1u, &two_len) != SSZ_SUCCESS) { + if (ssz_serialize_uint16(v16, encoded + 1u) != SSZ_SUCCESS) { return record_failure(path, type_name, "failed to encode uint16 union arm"); } encoded_len += two_len; @@ -1728,7 +1870,7 @@ static int run_union_fixture( return record_failure(path, type_name, "invalid uint32 union arm"); } v32 = (uint32_t)value; - if (ssz_serialize_uint32(&v32, encoded + 1u, &four_len) != SSZ_SUCCESS) { + if (ssz_serialize_uint32(v32, encoded + 1u) != SSZ_SUCCESS) { return record_failure(path, type_name, "failed to encode uint32 union arm"); } encoded_len += four_len; @@ -1747,10 +1889,14 @@ static int run_union_fixture( return record_failure(path, type_name, "decoded union selector mismatch"); } + ssz_chunk_t value_chunk; + ssz_chunk_t root_chunk; LanternRoot root; - if (ssz_mix_in_selector(value_root.bytes, (uint8_t)selector, root.bytes) != SSZ_SUCCESS) { + chunk_from_root(&value_root, &value_chunk); + if (ssz_mix_in_selector(&value_chunk, (uint8_t)selector, NULL, &root_chunk) != SSZ_SUCCESS) { return record_failure(path, type_name, "failed to compute union root"); } + root_from_chunk(&root_chunk, &root); return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); } @@ -1797,7 +1943,7 @@ static int run_signature_fixture( } memcpy(signature.bytes, expected_serialized->data, LANTERN_SIGNATURE_SIZE); LanternRoot root; - if (lantern_hash_tree_root_signature(&signature, &root) != 0) { + if (lantern_hash_tree_root_signature(&signature, &root) != SSZ_SUCCESS) { byte_buffer_reset(&parsed); return record_failure(path, type_name, "failed to compute signature root"); } @@ -1869,18 +2015,18 @@ static int run_config_fixture( } uint8_t encoded[LANTERN_CONFIG_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_config(&config, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_config(&config, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "config encode failed"); } LanternConfig decoded = {0}; - if (lantern_ssz_decode_config(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_config(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_config(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_config(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "config decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_config(&config, &root) != 0) { + if (lantern_hash_tree_root_config(&config, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "config root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -1899,18 +2045,18 @@ static int run_checkpoint_fixture( } uint8_t encoded[LANTERN_CHECKPOINT_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_checkpoint(&checkpoint, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_checkpoint(&checkpoint, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "checkpoint encode failed"); } LanternCheckpoint decoded; - if (lantern_ssz_decode_checkpoint(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_checkpoint(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_checkpoint(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_checkpoint(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "checkpoint decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_checkpoint(&checkpoint, &root) != 0) { + if (lantern_hash_tree_root_checkpoint(&checkpoint, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "checkpoint root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -1929,18 +2075,18 @@ static int run_attestation_data_fixture( } uint8_t encoded[LANTERN_ATTESTATION_DATA_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_attestation_data(&data, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_attestation_data(&data, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "attestation-data encode failed"); } LanternAttestationData decoded; - if (lantern_ssz_decode_attestation_data(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_attestation_data(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_attestation_data(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_attestation_data(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "attestation-data decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_attestation_data(&data, &root) != 0) { + if (lantern_hash_tree_root_attestation_data(&data, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "attestation-data root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -1959,18 +2105,18 @@ static int run_attestation_fixture( } uint8_t encoded[LANTERN_VOTE_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_vote(&vote, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_vote(&vote, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "attestation encode failed"); } LanternVote decoded; - if (lantern_ssz_decode_vote(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_vote(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_vote(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_vote(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "attestation decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_vote(&vote, &root) != 0) { + if (lantern_hash_tree_root_vote(&vote, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "attestation root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -1990,19 +2136,19 @@ static int run_signed_attestation_fixture( } uint8_t encoded[LANTERN_SIGNED_VOTE_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_signed_vote(&vote, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_signed_vote(&vote, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "signed-attestation encode failed"); } LanternSignedVote decoded; memset(&decoded, 0, sizeof(decoded)); - if (lantern_ssz_decode_signed_vote(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_signed_vote(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_signed_vote(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_signed_vote(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "signed-attestation decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_signed_vote(&vote, &root) != 0) { + if (lantern_hash_tree_root_signed_vote(&vote, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "signed-attestation root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -2025,7 +2171,7 @@ static int run_aggregated_attestation_fixture( uint8_t *encoded = (uint8_t *)malloc(encoded_capacity); size_t encoded_len = 0u; if (!encoded - || lantern_ssz_encode_aggregated_attestation(&attestation, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_aggregated_attestation(&attestation, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_aggregated_attestation_reset(&attestation); @@ -2033,8 +2179,8 @@ static int run_aggregated_attestation_fixture( } LanternAggregatedAttestation decoded; lantern_aggregated_attestation_init(&decoded); - if (lantern_ssz_decode_aggregated_attestation(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_aggregated_attestation(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_aggregated_attestation(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_aggregated_attestation(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_aggregated_attestation_reset(&attestation); @@ -2042,7 +2188,7 @@ static int run_aggregated_attestation_fixture( return record_failure(path, type_name, "aggregated-attestation decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_aggregated_attestation(&attestation, &root) != 0) { + if (lantern_hash_tree_root_aggregated_attestation(&attestation, &root) != SSZ_SUCCESS) { free(encoded); lantern_aggregated_attestation_reset(&attestation); lantern_aggregated_attestation_reset(&decoded); @@ -2071,7 +2217,7 @@ static int run_aggregated_signature_proof_fixture( uint8_t *encoded = (uint8_t *)malloc(encoded_capacity); size_t encoded_len = 0u; if (!encoded - || lantern_ssz_encode_aggregated_signature_proof(&proof, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_aggregated_signature_proof(&proof, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_aggregated_signature_proof_reset(&proof); @@ -2079,8 +2225,8 @@ static int run_aggregated_signature_proof_fixture( } LanternAggregatedSignatureProof decoded; lantern_aggregated_signature_proof_init(&decoded); - if (lantern_ssz_decode_aggregated_signature_proof(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_aggregated_signature_proof(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_aggregated_signature_proof(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_aggregated_signature_proof(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_aggregated_signature_proof_reset(&proof); @@ -2088,7 +2234,7 @@ static int run_aggregated_signature_proof_fixture( return record_failure(path, type_name, "aggregated-signature-proof decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_aggregated_signature_proof(&proof, &root) != 0) { + if (lantern_hash_tree_root_aggregated_signature_proof(&proof, &root) != SSZ_SUCCESS) { free(encoded); lantern_aggregated_signature_proof_reset(&proof); lantern_aggregated_signature_proof_reset(&decoded); @@ -2114,7 +2260,7 @@ static int run_signed_aggregated_attestation_fixture( lantern_signed_aggregated_attestation_init(&attestation); if (!encoded || parse_signed_aggregated_attestation_object(doc, value_idx, &attestation) != 0 - || lantern_ssz_encode_signed_aggregated_attestation(&attestation, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_signed_aggregated_attestation(&attestation, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_signed_aggregated_attestation_reset(&attestation); @@ -2122,8 +2268,8 @@ static int run_signed_aggregated_attestation_fixture( } LanternSignedAggregatedAttestation decoded; lantern_signed_aggregated_attestation_init(&decoded); - if (lantern_ssz_decode_signed_aggregated_attestation(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_signed_aggregated_attestation(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_signed_aggregated_attestation(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_signed_aggregated_attestation(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_signed_aggregated_attestation_reset(&attestation); @@ -2131,7 +2277,7 @@ static int run_signed_aggregated_attestation_fixture( return record_failure(path, type_name, "signed-aggregated-attestation decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_signed_aggregated_attestation(&attestation, &root) != 0) { + if (lantern_hash_tree_root_signed_aggregated_attestation(&attestation, &root) != SSZ_SUCCESS) { free(encoded); lantern_signed_aggregated_attestation_reset(&attestation); lantern_signed_aggregated_attestation_reset(&decoded); @@ -2157,7 +2303,7 @@ static int run_block_signatures_fixture( lantern_block_signatures_init(&signatures); if (!encoded || parse_block_signatures_object(doc, value_idx, &signatures) != 0 - || lantern_ssz_encode_block_signatures(&signatures, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_block_signatures(&signatures, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_block_signatures_reset(&signatures); @@ -2165,8 +2311,8 @@ static int run_block_signatures_fixture( } LanternBlockSignatures decoded; lantern_block_signatures_init(&decoded); - if (lantern_ssz_decode_block_signatures(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_block_signatures(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_block_signatures(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_block_signatures(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_block_signatures_reset(&signatures); @@ -2174,7 +2320,7 @@ static int run_block_signatures_fixture( return record_failure(path, type_name, "block-signatures decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_block_signatures(&signatures, &root) != 0) { + if (lantern_hash_tree_root_block_signatures(&signatures, &root) != SSZ_SUCCESS) { free(encoded); lantern_block_signatures_reset(&signatures); lantern_block_signatures_reset(&decoded); @@ -2201,7 +2347,7 @@ static int run_block_body_fixture( free(encoded); return record_failure(path, type_name, "invalid block-body fixture"); } - if (lantern_ssz_encode_block_body(&body, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_encode_block_body(&body, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_block_body_reset(&body); @@ -2209,8 +2355,8 @@ static int run_block_body_fixture( } LanternBlockBody decoded; lantern_block_body_init(&decoded); - if (lantern_ssz_decode_block_body(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_block_body(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_block_body(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_block_body(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_block_body_reset(&body); @@ -2218,7 +2364,7 @@ static int run_block_body_fixture( return record_failure(path, type_name, "block-body decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_block_body(&body, &root) != 0) { + if (lantern_hash_tree_root_block_body(&body, &root) != SSZ_SUCCESS) { free(encoded); lantern_block_body_reset(&body); lantern_block_body_reset(&decoded); @@ -2253,18 +2399,18 @@ static int run_block_header_fixture( } uint8_t encoded[LANTERN_BLOCK_HEADER_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_block_header(&header, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_block_header(&header, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "block-header encode failed"); } LanternBlockHeader decoded; - if (lantern_ssz_decode_block_header(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_block_header(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_block_header(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_block_header(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "block-header decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_block_header(&header, &root) != 0) { + if (lantern_hash_tree_root_block_header(&header, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "block-header root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -2283,18 +2429,18 @@ static int run_validator_fixture( } uint8_t encoded[LANTERN_VALIDATOR_SSZ_SIZE]; size_t encoded_len = sizeof(encoded); - if (lantern_ssz_encode_validator(&validator, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_encode_validator(&validator, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "validator encode failed"); } LanternValidator decoded; - if (lantern_ssz_decode_validator(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_validator(&decoded, encoded, sizeof(encoded), &encoded_len) != 0 + if (lantern_ssz_decode_validator(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_validator(&decoded, encoded, sizeof(encoded), &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { return record_failure(path, type_name, "validator decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_validator(&validator, &root) != 0) { + if (lantern_hash_tree_root_validator(&validator, &root) != SSZ_SUCCESS) { return record_failure(path, type_name, "validator root failed"); } return expect_root_equal(path, type_name, "hash_tree_root", expected_root, &root); @@ -2317,7 +2463,7 @@ static int run_block_fixture( uint8_t *encoded = (uint8_t *)malloc(encoded_capacity); size_t encoded_len = 0u; if (!encoded - || lantern_ssz_encode_block(&block, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_block(&block, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); reset_block(&block); @@ -2325,8 +2471,8 @@ static int run_block_fixture( } LanternBlock decoded; memset(&decoded, 0, sizeof(decoded)); - if (lantern_ssz_decode_block(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_block(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_block(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_block(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); reset_block(&block); @@ -2334,7 +2480,7 @@ static int run_block_fixture( return record_failure(path, type_name, "block decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_block(&block, &root) != 0) { + if (lantern_hash_tree_root_block(&block, &root) != SSZ_SUCCESS) { free(encoded); reset_block(&block); reset_block(&decoded); @@ -2363,7 +2509,7 @@ static int run_signed_block_fixture( uint8_t *encoded = (uint8_t *)malloc(encoded_capacity); size_t encoded_len = 0u; if (!encoded - || lantern_ssz_encode_signed_block_canonical(&block, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_signed_block(&block, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_signed_block_reset(&block); @@ -2371,8 +2517,8 @@ static int run_signed_block_fixture( } LanternSignedBlock decoded; lantern_signed_block_init(&decoded); - if (lantern_ssz_decode_signed_block_strict(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_signed_block_canonical(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_signed_block(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_signed_block(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_signed_block_reset(&block); @@ -2380,7 +2526,7 @@ static int run_signed_block_fixture( return record_failure(path, type_name, "signed-block decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_signed_block(&block, &root) != 0) { + if (lantern_hash_tree_root_signed_block(&block, &root) != SSZ_SUCCESS) { free(encoded); lantern_signed_block_reset(&block); lantern_signed_block_reset(&decoded); @@ -2421,7 +2567,7 @@ static int run_state_fixture( uint8_t *encoded = (uint8_t *)malloc(encoded_capacity); size_t encoded_len = 0u; if (!encoded - || lantern_ssz_encode_state(&state, encoded, encoded_capacity, &encoded_len) != 0 + || lantern_ssz_encode_state(&state, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "encode(value)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_state_reset(&state); @@ -2429,8 +2575,8 @@ static int run_state_fixture( } LanternState decoded; lantern_state_init(&decoded); - if (lantern_ssz_decode_state(&decoded, expected_serialized->data, expected_serialized->len) != 0 - || lantern_ssz_encode_state(&decoded, encoded, encoded_capacity, &encoded_len) != 0 + if (lantern_ssz_decode_state(&decoded, expected_serialized->data, expected_serialized->len) != SSZ_SUCCESS + || lantern_ssz_encode_state(&decoded, encoded, encoded_capacity, &encoded_len) != SSZ_SUCCESS || expect_bytes_equal(path, type_name, "decode(serialized)", expected_serialized->data, expected_serialized->len, encoded, encoded_len) != 0) { free(encoded); lantern_state_reset(&state); @@ -2438,7 +2584,7 @@ static int run_state_fixture( return record_failure(path, type_name, "state decode failed"); } LanternRoot root; - if (lantern_hash_tree_root_state(&state, &root) != 0) { + if (lantern_hash_tree_root_state(&state, &root) != SSZ_SUCCESS) { free(encoded); lantern_state_reset(&state); lantern_state_reset(&decoded); @@ -2533,7 +2679,7 @@ static int run_decode_rejection_fixture( int raw_idx = lantern_fixture_object_get_field(doc, case_idx, "rawBytes"); int serialized_idx = lantern_fixture_object_get_field(doc, case_idx, "serialized"); struct byte_buffer bytes = {0}; - ssz_error_t decode_status = SSZ_ERROR_DESERIALIZATION; + ssz_error_t decode_status = SSZ_ERR_INVALID_ARGUMENT; if ((raw_idx < 0 && serialized_idx < 0) || fixture_token_to_bytes(doc, raw_idx >= 0 ? raw_idx : serialized_idx, &bytes) != 0) { @@ -2545,28 +2691,42 @@ static int run_decode_rejection_fixture( decode_status = ssz_deserialize_uint32(bytes.data, bytes.len, &decoded); } else if (strcmp(type_name, "Bytes4") == 0) { uint8_t decoded[4]; - decode_status = ssz_deserialize_vector_uint8(bytes.data, bytes.len, sizeof(decoded), decoded); + decode_status = ssz_deserialize_vector_fixed( + bytes.data, + bytes.len, + sizeof(decoded), + sizeof(uint8_t), + decoded, + sizeof(decoded)); } else { size_t bit_count = 0u; if (parse_size_suffix(type_name, "DecodeBitvector", &bit_count) == 0 || parse_size_suffix(type_name, "BoundaryBitvector", &bit_count) == 0) { - bool *decoded = (bool *)calloc(bit_count > 0u ? bit_count : 1u, sizeof(*decoded)); + size_t decoded_len = (bit_count + 7u) / 8u; + uint8_t *decoded = (uint8_t *)calloc(decoded_len > 0u ? decoded_len : 1u, sizeof(*decoded)); if (!decoded) { byte_buffer_reset(&bytes); return record_failure(path, type_name, "allocation failed"); } - decode_status = ssz_deserialize_bitvector(bytes.data, bytes.len, bit_count, decoded); + decode_status = ssz_deserialize_bitvector(bytes.data, bytes.len, bit_count, decoded, decoded_len); free(decoded); } else if (parse_size_suffix(type_name, "DecodeBitlist", &bit_count) == 0 || parse_size_suffix(type_name, "SmokeBitlist", &bit_count) == 0 || parse_size_suffix(type_name, "BoundaryBitlist", &bit_count) == 0) { - bool *decoded = (bool *)calloc(bit_count > 0u ? bit_count : 1u, sizeof(*decoded)); - size_t actual_bits = 0u; + size_t decoded_len = (bit_count + 7u) / 8u; + uint8_t *decoded = (uint8_t *)calloc(decoded_len > 0u ? decoded_len : 1u, sizeof(*decoded)); + uint64_t actual_bits = 0u; if (!decoded) { byte_buffer_reset(&bytes); return record_failure(path, type_name, "allocation failed"); } - decode_status = ssz_deserialize_bitlist(bytes.data, bytes.len, bit_count, decoded, &actual_bits); + decode_status = ssz_deserialize_bitlist( + bytes.data, + bytes.len, + bit_count, + decoded, + decoded_len, + &actual_bits); free(decoded); } else { byte_buffer_reset(&bytes); diff --git a/tests/integration/test_verify_signatures_vectors.c b/tests/integration/test_verify_signatures_vectors.c index ea657d6..ac20fdb 100644 --- a/tests/integration/test_verify_signatures_vectors.c +++ b/tests/integration/test_verify_signatures_vectors.c @@ -131,7 +131,7 @@ static bool verify_aggregated_attestations( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&att->data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&att->data, &data_root) != SSZ_SUCCESS) { free(pubkeys); fprintf(stderr, "%s: failed to hash attestation data\n", path ? path : "(unknown)"); return false; @@ -175,7 +175,7 @@ static bool verify_proposer_signature(const LanternState *state, const LanternSi return false; } LanternRoot block_root; - if (lantern_hash_tree_root_block(&block->block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&block->block, &block_root) != SSZ_SUCCESS) { fprintf(stderr, "%s: block hash failed for proposer signature\n", path ? path : "(unknown)"); return false; } diff --git a/tests/support/fixture_loader.c b/tests/support/fixture_loader.c index 48b092a..d682e00 100644 --- a/tests/support/fixture_loader.c +++ b/tests/support/fixture_loader.c @@ -1,3 +1,6 @@ +#define JSMN_STATIC +#include "jsmn.h" + #include "tests/support/fixture_loader.h" #include diff --git a/tests/support/fixture_loader.h b/tests/support/fixture_loader.h index b3e1474..6a03617 100644 --- a/tests/support/fixture_loader.h +++ b/tests/support/fixture_loader.h @@ -4,8 +4,9 @@ #include #include -#define JSMN_STATIC +#define JSMN_HEADER #include "jsmn.h" +#undef JSMN_HEADER #include "lantern/consensus/containers.h" #include "lantern/consensus/state.h" diff --git a/tests/unit/client_test_helpers.c b/tests/unit/client_test_helpers.c index 3ae8f67..2b87af8 100644 --- a/tests/unit/client_test_helpers.c +++ b/tests/unit/client_test_helpers.c @@ -253,7 +253,7 @@ static int client_test_setup_vote_validation_client_common( } LanternRoot anchor_state_root; - if (lantern_hash_tree_root_state(&client->state, &anchor_state_root) != 0) { + if (lantern_hash_tree_root_state(&client->state, &anchor_state_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash anchor state for vote test\n"); goto finish; } @@ -270,7 +270,7 @@ static int client_test_setup_vote_validation_client_common( anchor.parent_root = client->state.latest_block_header.parent_root; anchor.state_root = anchor_state_root; - if (lantern_hash_tree_root_block(&anchor, &anchor_root_local) != 0) { + if (lantern_hash_tree_root_block(&anchor, &anchor_root_local) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash anchor block for vote test\n"); goto finish; } @@ -315,7 +315,7 @@ static int client_test_setup_vote_validation_client_common( } child_signed.block = child; - if (lantern_hash_tree_root_block(&child, &child_root_local) != 0) { + if (lantern_hash_tree_root_block(&child, &child_root_local) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash child block for vote test\n"); goto finish; } @@ -474,7 +474,7 @@ int client_test_sign_vote_with_secret(LanternSignedVote *vote, struct PQSignatur return -1; } LanternRoot vote_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != SSZ_SUCCESS) { return -1; } if (!lantern_signature_sign(secret, vote->data.slot, &vote_root, &vote->signature)) { diff --git a/tests/unit/test_checkpoint_sync_api.c b/tests/unit/test_checkpoint_sync_api.c index 7f6b1be..90db7ef 100644 --- a/tests/unit/test_checkpoint_sync_api.c +++ b/tests/unit/test_checkpoint_sync_api.c @@ -518,7 +518,7 @@ static int test_storage_state_bytes(void) LanternState decoded; lantern_state_init(&decoded); - int decode_rc = lantern_ssz_decode_state(&decoded, fixture.ssz_bytes, fixture.ssz_len); + ssz_error_t decode_rc = lantern_ssz_decode_state(&decoded, fixture.ssz_bytes, fixture.ssz_len); expect_zero(decode_rc, "decode state bytes"); expect_true(decoded.validator_count == 4u, "validator count"); expect_true( diff --git a/tests/unit/test_client_gossip.c b/tests/unit/test_client_gossip.c index bbe50e9..3efcd0e 100644 --- a/tests/unit/test_client_gossip.c +++ b/tests/unit/test_client_gossip.c @@ -9,7 +9,7 @@ #include "lantern/consensus/signature.h" #include "lantern/core/client.h" #include "lantern/support/string_list.h" -#include "src/protocol/gossipsub/core/gossipsub_internal.h" +#include "lantern/support/strings.h" static void reset_agg_cache(struct lantern_client *client) { @@ -19,84 +19,6 @@ static void reset_agg_cache(struct lantern_client *client) lantern_store_reset(&client->store); } -static peer_id_t *test_peer_id_from_text(const char *text) -{ - peer_id_t *peer = NULL; - if (peer_id_new_from_text(text, &peer) != PEER_ID_OK) { - return NULL; - } - return peer; -} - -static int test_gossipsub_mesh_peer_count_includes_effective_peers(void) -{ - static const char topic_name[] = "/lean/test/beacon_block/ssz_snappy"; - peer_id_t *explicit_peer = test_peer_id_from_text( - "12D3KooWL9qw9QdCsiPUQXGWxZhwivKar35CFYuU9B9kavHuV2XZ"); - peer_id_t *subscribed_peer = test_peer_id_from_text( - "12D3KooWQ7W3zfBDSSY5YTbSsfXCMVvjJAnYXhYzu3PV6PvJkU8E"); - if (!explicit_peer || !subscribed_peer) { - fprintf(stderr, "failed to build peer ids for mesh metric test\n"); - peer_id_free(explicit_peer); - peer_id_free(subscribed_peer); - return 1; - } - - libp2p_gossipsub_t gs; - memset(&gs, 0, sizeof(gs)); - if (pthread_mutex_init(&gs.lock, NULL) != 0) { - peer_id_free(explicit_peer); - peer_id_free(subscribed_peer); - return 1; - } - - gossipsub_topic_state_t topic; - memset(&topic, 0, sizeof(topic)); - topic.name = (char *)topic_name; - topic.subscribed = 1; - gs.topics = &topic; - - gossipsub_peer_topic_t peer_topic; - memset(&peer_topic, 0, sizeof(peer_topic)); - peer_topic.name = (char *)topic_name; - - gossipsub_peer_entry_t explicit_entry; - memset(&explicit_entry, 0, sizeof(explicit_entry)); - explicit_entry.peer = explicit_peer; - explicit_entry.connected = 1; - explicit_entry.explicit_peering = 1; - - gossipsub_peer_entry_t subscribed_entry; - memset(&subscribed_entry, 0, sizeof(subscribed_entry)); - subscribed_entry.peer = subscribed_peer; - subscribed_entry.connected = 1; - subscribed_entry.topics = &peer_topic; - explicit_entry.next = &subscribed_entry; - gs.peers = &explicit_entry; - - gossipsub_mesh_member_t duplicate_mesh_member; - memset(&duplicate_mesh_member, 0, sizeof(duplicate_mesh_member)); - duplicate_mesh_member.peer = subscribed_peer; - duplicate_mesh_member.peer_entry = &subscribed_entry; - topic.mesh = &duplicate_mesh_member; - topic.mesh_size = 1u; - - struct lantern_gossipsub_service service; - memset(&service, 0, sizeof(service)); - service.gossipsub = &gs; - - size_t count = lantern_gossipsub_service_mesh_peer_count(&service); - pthread_mutex_destroy(&gs.lock); - peer_id_free(explicit_peer); - peer_id_free(subscribed_peer); - - if (count != 2u) { - fprintf(stderr, "expected effective mesh peer count 2, got %zu\n", count); - return 1; - } - return 0; -} - static int test_enable_blocks_request_peer( struct lantern_client *client, const char *peer_id) @@ -141,12 +63,10 @@ static int test_enable_blocks_request_peer( } client->peer_status_count = 1u; client->peer_status_capacity = 1u; - strncpy( + (void)lantern_string_copy( client->peer_status_entries[0].peer_id, - peer_id, - sizeof(client->peer_status_entries[0].peer_id) - 1u); - client->peer_status_entries[0].peer_id[sizeof(client->peer_status_entries[0].peer_id) - 1u] = - '\0'; + sizeof(client->peer_status_entries[0].peer_id), + peer_id); return 0; } @@ -215,7 +135,7 @@ static int sign_single_participant_aggregated_attestation( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&out_attestation->data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&out_attestation->data, &data_root) != SSZ_SUCCESS) { lantern_signed_aggregated_attestation_reset(out_attestation); return -1; } @@ -267,13 +187,13 @@ static int build_single_participant_aggregated_attestation( data.head.slot = child_slot; data.head.root = *child_root; LanternRoot state_root; - if (lantern_hash_tree_root_state(&client->state, &state_root) != 0) { + if (lantern_hash_tree_root_state(&client->state, &state_root) != SSZ_SUCCESS) { return -1; } LanternBlockHeader checkpoint_header = client->state.latest_block_header; checkpoint_header.state_root = state_root; LanternRoot checkpoint_root; - if (lantern_hash_tree_root_block_header(&checkpoint_header, &checkpoint_root) != 0) { + if (lantern_hash_tree_root_block_header(&checkpoint_header, &checkpoint_root) != SSZ_SUCCESS) { return -1; } /* @@ -348,7 +268,7 @@ static bool make_aggregated_proof_invalid( return false; } -static int test_idle_gossip_not_ignored(void) +static int test_idle_gossip_ignored(void) { struct lantern_client client; memset(&client, 0, sizeof(client)); @@ -362,9 +282,9 @@ static int test_idle_gossip_not_ignored(void) int block_rc = lantern_client_debug_gossip_block(&client, &block); lantern_block_body_reset(&block.block.body); - if (block_rc != LANTERN_CLIENT_OK) + if (block_rc != LANTERN_CLIENT_ERR_IGNORED) { - fprintf(stderr, "idle block gossip was not accepted rc=%d\n", block_rc); + fprintf(stderr, "idle block gossip was not ignored rc=%d\n", block_rc); return 1; } @@ -374,9 +294,44 @@ static int test_idle_gossip_not_ignored(void) vote.data.slot = 1; int vote_rc = lantern_client_debug_gossip_vote(&client, &vote); - if (vote_rc != LANTERN_CLIENT_OK) + if (vote_rc != LANTERN_CLIENT_ERR_IGNORED) { - fprintf(stderr, "idle vote gossip was not accepted rc=%d\n", vote_rc); + fprintf(stderr, "idle vote gossip was not ignored rc=%d\n", vote_rc); + return 1; + } + + LanternSignedAggregatedAttestation attestation; + memset(&attestation, 0, sizeof(attestation)); + int agg_rc = lantern_client_debug_gossip_aggregated_attestation(&client, &attestation); + if (agg_rc != LANTERN_CLIENT_ERR_IGNORED) + { + fprintf(stderr, "idle aggregated attestation gossip was not ignored rc=%d\n", agg_rc); + return 1; + } + + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + if (lantern_client_debug_gossip_vote(&client, &vote) != LANTERN_CLIENT_OK) + { + fprintf(stderr, "syncing vote gossip should be accepted by handler\n"); + return 1; + } + if (lantern_client_debug_gossip_aggregated_attestation(&client, &attestation) + == LANTERN_CLIENT_ERR_IGNORED) + { + fprintf(stderr, "syncing aggregated attestation gossip should reach validation\n"); + return 1; + } + + client.sync_state = LANTERN_SYNC_STATE_SYNCED; + if (lantern_client_debug_gossip_vote(&client, &vote) != LANTERN_CLIENT_OK) + { + fprintf(stderr, "synced vote gossip should be accepted by handler\n"); + return 1; + } + if (lantern_client_debug_gossip_aggregated_attestation(&client, &attestation) + == LANTERN_CLIENT_ERR_IGNORED) + { + fprintf(stderr, "synced aggregated attestation gossip should reach validation\n"); return 1; } @@ -403,6 +358,7 @@ static int test_gossip_aggregated_attestation_caches_valid_proof(void) != 0) { return 1; } + client.sync_state = LANTERN_SYNC_STATE_SYNCING; if (build_single_participant_aggregated_attestation( &client, @@ -437,7 +393,7 @@ static int test_gossip_aggregated_attestation_caches_valid_proof(void) } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&attestation.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestation.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash attestation data root\n"); goto cleanup_attestation; } @@ -520,6 +476,7 @@ static int test_gossip_aggregated_attestation_rejects_invalid_proof(void) != 0) { return 1; } + client.sync_state = LANTERN_SYNC_STATE_SYNCING; if (build_single_participant_aggregated_attestation( &client, @@ -541,7 +498,7 @@ static int test_gossip_aggregated_attestation_rejects_invalid_proof(void) fprintf(stderr, "aggregated attestation validator pubkey missing\n"); goto cleanup_attestation; } - if (lantern_hash_tree_root_attestation_data(&attestation.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestation.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash aggregated attestation data root\n"); goto cleanup_attestation; } @@ -595,6 +552,7 @@ static int test_gossip_aggregated_attestation_rejects_unknown_target(void) != 0) { return 1; } + client.sync_state = LANTERN_SYNC_STATE_SYNCING; if (test_enable_blocks_request_peer( &client, "12D3KooWQH2VQK1kF2L8a7T4AtestAggUnknownTarget111111111111") @@ -660,6 +618,7 @@ static int test_gossip_aggregated_attestation_rejects_invalid_topology(void) != 0) { return 1; } + client.sync_state = LANTERN_SYNC_STATE_SYNCING; if (build_single_participant_aggregated_attestation( &client, @@ -710,11 +669,7 @@ static int test_gossip_aggregated_attestation_rejects_invalid_topology(void) int main(void) { - if (test_gossipsub_mesh_peer_count_includes_effective_peers() != 0) - { - return 1; - } - if (test_idle_gossip_not_ignored() != 0) + if (test_idle_gossip_ignored() != 0) { return 1; } diff --git a/tests/unit/test_client_pending.c b/tests/unit/test_client_pending.c index e1a0be4..6d0bee1 100644 --- a/tests/unit/test_client_pending.c +++ b/tests/unit/test_client_pending.c @@ -12,8 +12,10 @@ #include "lantern/consensus/hash.h" #include "lantern/consensus/signature.h" #include "lantern/consensus/state.h" +#include "lantern/consensus/ssz.h" #include "lantern/genesis/genesis.h" #include "lantern/storage/storage.h" +#include "lantern/support/string_list.h" enum { TEST_TEMP_PATH_CAPACITY = 1024 @@ -120,7 +122,9 @@ static void teardown_block_signature_fixture(struct block_signature_fixture *fix char cleanup_cmd[TEST_TEMP_PATH_CAPACITY + 16]; int written = snprintf(cleanup_cmd, sizeof(cleanup_cmd), "rm -rf %s", fixture->client.data_dir); if (written > 0 && (size_t)written < sizeof(cleanup_cmd)) { - (void)system(cleanup_cmd); + if (system(cleanup_cmd) == -1) { + fprintf(stderr, "failed to remove temp data dir %s\n", fixture->client.data_dir); + } } } fixture->client.data_dir = NULL; @@ -129,6 +133,103 @@ static void teardown_block_signature_fixture(struct block_signature_fixture *fix fixture->secret = NULL; } +static int enable_sync_test_peer(struct lantern_client *client, const char *peer_id) +{ + if (!client || !peer_id) { + return -1; + } + if (pthread_mutex_init(&client->pending_lock, NULL) != 0) { + return -1; + } + client->pending_lock_initialized = true; + lantern_client_debug_pending_reset(client); + if (pthread_mutex_init(&client->status_lock, NULL) != 0) { + pthread_mutex_destroy(&client->pending_lock); + client->pending_lock_initialized = false; + return -1; + } + client->status_lock_initialized = true; + lantern_string_list_init(&client->connected_peer_ids); + if (pthread_mutex_init(&client->connection_lock, NULL) != 0) { + pthread_mutex_destroy(&client->status_lock); + client->status_lock_initialized = false; + pthread_mutex_destroy(&client->pending_lock); + client->pending_lock_initialized = false; + lantern_string_list_reset(&client->connected_peer_ids); + return -1; + } + client->connection_lock_initialized = true; + if (lantern_string_list_append(&client->connected_peer_ids, peer_id) != 0) { + pthread_mutex_destroy(&client->connection_lock); + client->connection_lock_initialized = false; + pthread_mutex_destroy(&client->status_lock); + client->status_lock_initialized = false; + pthread_mutex_destroy(&client->pending_lock); + client->pending_lock_initialized = false; + lantern_string_list_reset(&client->connected_peer_ids); + return -1; + } + client->connected_peers = 1u; + return 0; +} + +static int test_state_latest_block_root(const LanternState *state, LanternRoot *out_root) +{ + if (!state || !out_root) { + return -1; + } + LanternRoot state_root; + if (lantern_hash_tree_root_state(state, &state_root) != SSZ_SUCCESS) { + return -1; + } + LanternBlockHeader header = state->latest_block_header; + header.state_root = state_root; + return lantern_hash_tree_root_block_header(&header, out_root) == SSZ_SUCCESS ? 0 : -1; +} + +static bool test_state_matches_root(const LanternState *state, const LanternRoot *root) +{ + if (!state || !root) { + return false; + } + LanternRoot computed; + if (test_state_latest_block_root(state, &computed) != 0) { + return false; + } + return memcmp(computed.bytes, root->bytes, LANTERN_ROOT_SIZE) == 0; +} + +static void disable_sync_test_peer(struct lantern_client *client) +{ + if (!client) { + return; + } + free(client->peer_status_entries); + client->peer_status_entries = NULL; + client->peer_status_count = 0u; + client->peer_status_capacity = 0u; + free(client->active_blocks_requests); + client->active_blocks_requests = NULL; + client->active_blocks_request_count = 0u; + client->active_blocks_request_capacity = 0u; + client->next_blocks_request_id = 0u; + lantern_client_debug_pending_reset(client); + if (client->connection_lock_initialized) { + pthread_mutex_destroy(&client->connection_lock); + client->connection_lock_initialized = false; + } + lantern_string_list_reset(&client->connected_peer_ids); + client->connected_peers = 0u; + if (client->status_lock_initialized) { + pthread_mutex_destroy(&client->status_lock); + client->status_lock_initialized = false; + } + if (client->pending_lock_initialized) { + pthread_mutex_destroy(&client->pending_lock); + client->pending_lock_initialized = false; + } +} + static int build_signed_block_for_import( struct block_signature_fixture *fixture, bool include_attestation_signature, @@ -214,7 +315,7 @@ static int build_signed_block_for_import( return -1; } LanternRoot attestation_root; - if (lantern_hash_tree_root_attestation_data(&attestation->data, &attestation_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestation->data, &attestation_root) != SSZ_SUCCESS) { return -1; } const uint8_t *pubkeys[1] = {pubkey}; @@ -241,7 +342,7 @@ static int build_signed_block_for_import( if (include_proposer_signature) { LanternRoot block_signature_root; - if (lantern_hash_tree_root_block(&out_block->block, &block_signature_root) != 0) { + if (lantern_hash_tree_root_block(&out_block->block, &block_signature_root) != SSZ_SUCCESS) { return -1; } if (!lantern_signature_sign( @@ -255,7 +356,7 @@ static int build_signed_block_for_import( lantern_signature_zero(&out_block->signatures.proposer_signature); } - return lantern_hash_tree_root_block(&out_block->block, out_root); + return lantern_hash_tree_root_block(&out_block->block, out_root) == SSZ_SUCCESS ? 0 : -1; } static int resign_first_block_attestation( @@ -299,7 +400,7 @@ static int resign_first_block_attestation( return -1; } LanternRoot attestation_root; - if (lantern_hash_tree_root_attestation_data(&attestation->data, &attestation_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&attestation->data, &attestation_root) != SSZ_SUCCESS) { return -1; } const uint8_t *pubkeys[1] = {pubkey}; @@ -322,7 +423,7 @@ static int resign_first_block_attestation( != 0) { return -1; } - if (lantern_hash_tree_root_block(&block->block, out_root) != 0) { + if (lantern_hash_tree_root_block(&block->block, out_root) != SSZ_SUCCESS) { return -1; } if (!lantern_signature_sign( @@ -518,6 +619,43 @@ static int test_pending_block_queue(void) { goto cleanup; } + if (lantern_client_debug_set_parent_requested(&client, &child_root, true) != 0) { + fprintf(stderr, "failed to remark parent_requested for failed completion test\n"); + rc = 1; + goto cleanup; + } + + if (lantern_client_debug_on_blocks_request_complete( + &client, + peer_b, + &parent_root, + LANTERN_TEST_BLOCKS_REQUEST_FAILED) + != 0) { + fprintf(stderr, "blocks_request_complete failed outcome wrapper failed\n"); + rc = 1; + goto cleanup; + } + + parent_requested = false; + if (lantern_client_debug_pending_entry( + &client, + 0, + NULL, + NULL, + &parent_requested, + NULL, + 0) + != 0) { + fprintf(stderr, "failed to inspect parent_requested after failed completion\n"); + rc = 1; + goto cleanup; + } + if (!parent_requested) { + fprintf(stderr, "parent_requested cleared after failed completion\n"); + rc = 1; + goto cleanup; + } + size_t extra_count = LANTERN_PENDING_BLOCK_LIMIT + 50u; for (size_t i = 0; i < extra_count; ++i) { LanternSignedBlock extra; @@ -692,21 +830,287 @@ static int test_pending_block_queue_sync_drops_incoming(void) { return rc; } -static int test_import_block_parent_mismatch(void) { +static int test_sync_completion_uses_network_finalized_threshold(void) +{ + struct lantern_client client; + struct PQSignatureSchemePublicKey *pub = NULL; + struct PQSignatureSchemeSecretKey *secret = NULL; + int rc = 1; + + if (client_test_setup_vote_validation_client( + &client, + "sync_completion_threshold", + &pub, + &secret, + NULL, + NULL) + != 0) { + return 1; + } + + if (pthread_mutex_init(&client.status_lock, NULL) != 0) { + fprintf(stderr, "failed to initialize status mutex for sync threshold test\n"); + goto cleanup; + } + client.status_lock_initialized = true; + + if (pthread_mutex_init(&client.pending_lock, NULL) != 0) { + fprintf(stderr, "failed to initialize pending mutex for sync threshold test\n"); + goto cleanup; + } + client.pending_lock_initialized = true; + lantern_client_debug_pending_reset(&client); + + uint64_t local_head_slot = client.state.slot; + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + lantern_client_update_sync_progress(&client, local_head_slot); + if (client.sync_state == LANTERN_SYNC_STATE_SYNCED) { + fprintf(stderr, "sync should not complete without a network finalized view\n"); + goto cleanup; + } + + client.network_view.latest_observed_head_slot = local_head_slot + 128u; + client.network_view.network_finalized_slot = local_head_slot; + client.network_view.has_latest_observed_head_slot = true; + client.network_view.has_network_finalized_slot = true; + + LanternSignedBlock pending_block; + memset(&pending_block, 0, sizeof(pending_block)); + lantern_block_body_init(&pending_block.block.body); + pending_block.block.slot = local_head_slot + 10u; + LanternRoot pending_root; + LanternRoot missing_parent; + client_test_fill_root(&pending_root, 0x60u); + client_test_fill_root(&missing_parent, 0x70u); + if (lantern_client_debug_enqueue_pending_block( + &client, + &pending_block, + &pending_root, + &missing_parent, + NULL) + != 0) { + fprintf(stderr, "failed to enqueue orphan block for sync threshold test\n"); + lantern_block_body_reset(&pending_block.block.body); + goto cleanup; + } + lantern_block_body_reset(&pending_block.block.body); + + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + lantern_client_update_sync_progress(&client, local_head_slot); + if (client.sync_state == LANTERN_SYNC_STATE_SYNCED) { + fprintf(stderr, "sync should not complete while orphan parents are pending\n"); + goto cleanup; + } + + lantern_client_debug_pending_reset(&client); + client.network_view.network_finalized_slot = local_head_slot + 1u; + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + lantern_client_update_sync_progress(&client, local_head_slot); + if (client.sync_state == LANTERN_SYNC_STATE_SYNCED) { + fprintf(stderr, "sync should not complete below network finalized slot\n"); + goto cleanup; + } + + client.network_view.latest_observed_head_slot = local_head_slot + 1024u; + client.network_view.network_finalized_slot = local_head_slot; + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + lantern_client_update_sync_progress(&client, local_head_slot); + if (client.sync_state != LANTERN_SYNC_STATE_SYNCED) { + fprintf(stderr, "sync should complete at network finalized threshold\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + if (client.pending_lock_initialized) { + lantern_client_debug_pending_reset(&client); + pthread_mutex_destroy(&client.pending_lock); + client.pending_lock_initialized = false; + } + if (client.status_lock_initialized) { + pthread_mutex_destroy(&client.status_lock); + client.status_lock_initialized = false; + } + client_test_teardown_vote_validation_client(&client, pub, secret); + return rc; +} + +static int test_idle_status_triggers_syncing_before_gossip_backfill(void) +{ + struct lantern_client client; + struct PQSignatureSchemePublicKey *pub = NULL; + struct PQSignatureSchemeSecretKey *secret = NULL; + LanternRoot child_root; + LanternSignedBlock orphan_block; + const char *peer_id = "16Uiu2HAmPV5jU62WtmDkCEmfq1jzbBDkGbHNsDN78gJyvmv2TuC5"; + int rc = 1; + + memset(&orphan_block, 0, sizeof(orphan_block)); + if (client_test_setup_vote_validation_client( + &client, + "sync_idle_gossip_backfill", + &pub, + &secret, + NULL, + &child_root) + != 0) { + return 1; + } + if (enable_sync_test_peer(&client, peer_id) != 0) { + fprintf(stderr, "failed to enable sync test peer\n"); + goto cleanup; + } + + lantern_block_body_init(&orphan_block.block.body); + orphan_block.block.slot = client.state.slot + 8u; + orphan_block.block.proposer_index = 0u; + client_test_fill_root(&orphan_block.block.parent_root, 0x91u); + client_test_fill_root(&orphan_block.block.state_root, 0x92u); + + client.sync_state = LANTERN_SYNC_STATE_IDLE; + if (lantern_client_debug_gossip_block(&client, &orphan_block) != LANTERN_CLIENT_ERR_IGNORED) { + fprintf(stderr, "IDLE gossip block should be ignored\n"); + goto cleanup; + } + if (lantern_client_pending_block_count(&client) != 0u) { + fprintf(stderr, "IDLE gossip block should not enter pending queue\n"); + goto cleanup; + } + if (client.next_blocks_request_id != 0u) { + fprintf(stderr, "IDLE gossip block should not schedule parent requests\n"); + goto cleanup; + } + + LanternStatusMessage status; + memset(&status, 0, sizeof(status)); + status.head.root = child_root; + status.head.slot = client.state.slot; + status.finalized = client.state.latest_finalized; + if (reqresp_handle_status(&client, &status, peer_id) != LANTERN_CLIENT_OK) { + fprintf(stderr, "peer status update failed\n"); + goto cleanup; + } + if (client.sync_state != LANTERN_SYNC_STATE_SYNCING) { + fprintf(stderr, "first peer status should move IDLE to SYNCING\n"); + goto cleanup; + } + + uint64_t request_id_before = client.next_blocks_request_id; + if (lantern_client_debug_gossip_block(&client, &orphan_block) != LANTERN_CLIENT_OK) { + fprintf(stderr, "SYNCING gossip block should be accepted into pending flow\n"); + goto cleanup; + } + if (lantern_client_pending_block_count(&client) != 1u) { + fprintf(stderr, "SYNCING unknown-parent gossip block should be pending\n"); + goto cleanup; + } + if (client.next_blocks_request_id == request_id_before) { + fprintf(stderr, "SYNCING unknown-parent gossip block should trigger blocks_by_root\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_block_body_reset(&orphan_block.block.body); + disable_sync_test_peer(&client); + client_test_teardown_vote_validation_client(&client, pub, secret); + return rc; +} + +static int test_imported_blocks_update_sync_network_view(void) +{ + struct block_signature_fixture fixture; + LanternSignedBlock first_block; + LanternSignedBlock second_block; + LanternRoot first_root; + LanternRoot second_root; + int rc = 1; + + memset(&first_block, 0, sizeof(first_block)); + memset(&second_block, 0, sizeof(second_block)); + if (setup_block_signature_fixture(&fixture, "test_imported_network_view") != 0) { + fprintf(stderr, "failed to set up network view import fixture\n"); + return 1; + } + + if (pthread_mutex_init(&fixture.client.status_lock, NULL) != 0) { + fprintf(stderr, "failed to initialize status mutex for network view import test\n"); + goto cleanup; + } + fixture.client.status_lock_initialized = true; + fixture.client.sync_state = LANTERN_SYNC_STATE_SYNCING; + + if (build_signed_block_for_import(&fixture, true, true, &first_block, &first_root) != 0) { + fprintf(stderr, "failed to build first network view block\n"); + goto cleanup; + } + if (lantern_client_debug_import_block(&fixture.client, &first_block, &first_root, "12D3KooWview") != 1) { + fprintf(stderr, "failed to import first network view block\n"); + goto cleanup; + } + uint64_t first_finalized = fixture.client.state.latest_finalized.slot; + if (!fixture.client.network_view.has_latest_observed_head_slot + || fixture.client.network_view.latest_observed_head_slot != first_block.block.slot) { + fprintf(stderr, "network view head slot did not track first imported block\n"); + goto cleanup; + } + if (!fixture.client.network_view.has_network_finalized_slot + || fixture.client.network_view.network_finalized_slot != first_finalized) { + fprintf(stderr, "network view finalized slot did not track first imported post-state\n"); + goto cleanup; + } + if (fixture.client.sync_state != LANTERN_SYNC_STATE_SYNCED) { + fprintf(stderr, "sync did not complete from imported block network view\n"); + goto cleanup; + } + + fixture.client.sync_state = LANTERN_SYNC_STATE_SYNCING; + if (build_signed_block_for_import(&fixture, true, true, &second_block, &second_root) != 0) { + fprintf(stderr, "failed to build second network view block\n"); + goto cleanup; + } + if (lantern_client_debug_import_block(&fixture.client, &second_block, &second_root, "12D3KooWview") != 1) { + fprintf(stderr, "failed to import second network view block\n"); + goto cleanup; + } + if (fixture.client.network_view.latest_observed_head_slot != second_block.block.slot) { + fprintf(stderr, "network view head slot did not update for newer imported block\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_signed_block_with_attestation_reset(&second_block); + lantern_signed_block_with_attestation_reset(&first_block); + if (fixture.client.status_lock_initialized) { + pthread_mutex_destroy(&fixture.client.status_lock); + fixture.client.status_lock_initialized = false; + } + teardown_block_signature_fixture(&fixture); + return rc; +} + +static int test_reqresp_block_response_accepts_missing_parent(void) { struct lantern_client client; memset(&client, 0, sizeof(client)); - client.node_id = "test_parent_mismatch"; + client.node_id = "test_reqresp_missing_parent"; int rc = 0; LanternSignedBlock block; LanternRoot block_root; LanternRoot parent_root; LanternRoot head_root; - LanternRoot parent_block_root; + LanternRoot finalized_root; LanternRoot pending_root; LanternRoot pending_parent; bool parent_requested = true; char peer_text[128]; + const uint64_t anchor_slot = 8u; + const uint64_t finalized_slot = 4u; + memset(&block, 0, sizeof(block)); if (pthread_mutex_init(&client.state_lock, NULL) != 0) { fprintf(stderr, "failed to initialize state mutex\n"); @@ -726,17 +1130,17 @@ static int test_import_block_parent_mismatch(void) { lantern_state_init(&client.state); client.has_state = true; - client.state.slot = 0; + client.state.slot = anchor_slot; lantern_store_init(&client.store); memset(&client.state.latest_block_header, 0, sizeof(client.state.latest_block_header)); client_test_fill_root(&client.state.latest_block_header.state_root, 0x10); client_test_fill_root(&client.state.latest_block_header.body_root, 0x11); client_test_fill_root(&client.state.latest_block_header.parent_root, 0x12); - client.state.latest_block_header.slot = 0; + client.state.latest_block_header.slot = anchor_slot; client.state.latest_block_header.proposer_index = 0; - if (lantern_hash_tree_root_block_header(&client.state.latest_block_header, &head_root) != 0) { + if (lantern_hash_tree_root_block_header(&client.state.latest_block_header, &head_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash latest block header\n"); rc = 1; goto cleanup; @@ -757,7 +1161,7 @@ static int test_import_block_parent_mismatch(void) { LanternCheckpoint anchor_checkpoint = { .root = head_root, - .slot = 0, + .slot = anchor_slot, }; client.state.latest_justified = anchor_checkpoint; client.state.latest_finalized = anchor_checkpoint; @@ -765,7 +1169,7 @@ static int test_import_block_parent_mismatch(void) { LanternBlock anchor_block; memset(&anchor_block, 0, sizeof(anchor_block)); lantern_block_body_init(&anchor_block.body); - anchor_block.slot = 0; + anchor_block.slot = anchor_slot; anchor_block.proposer_index = 0; anchor_block.parent_root = client.state.latest_block_header.parent_root; anchor_block.state_root = client.state.latest_block_header.state_root; @@ -783,18 +1187,18 @@ static int test_import_block_parent_mismatch(void) { goto cleanup; } - LanternBlock parent_block; - memset(&parent_block, 0, sizeof(parent_block)); - lantern_block_body_init(&parent_block.body); - parent_block.slot = 1; - parent_block.proposer_index = 0; - parent_block.parent_root = head_root; - client_test_fill_root(&parent_block.state_root, 0x44); + LanternBlock finalized_block; + memset(&finalized_block, 0, sizeof(finalized_block)); + lantern_block_body_init(&finalized_block.body); + finalized_block.slot = finalized_slot; + finalized_block.proposer_index = 0; + client_test_fill_root(&finalized_block.parent_root, 0x43); + client_test_fill_root(&finalized_block.state_root, 0x44); - if (lantern_hash_tree_root_block(&parent_block, &parent_block_root) != 0) + if (lantern_hash_tree_root_block(&finalized_block, &finalized_root) != SSZ_SUCCESS) { - fprintf(stderr, "failed to hash parent block\n"); - lantern_block_body_reset(&parent_block.body); + fprintf(stderr, "failed to hash finalized floor block\n"); + lantern_block_body_reset(&finalized_block.body); lantern_block_body_reset(&anchor_block.body); rc = 1; goto cleanup; @@ -802,33 +1206,55 @@ static int test_import_block_parent_mismatch(void) { if (lantern_fork_choice_add_block( &client.fork_choice, - &parent_block, + &finalized_block, NULL, - &client.state.latest_justified, - &client.state.latest_finalized, - &parent_block_root) + NULL, + NULL, + &finalized_root) != 0) { - fprintf(stderr, "failed to add parent block to fork choice\n"); - lantern_block_body_reset(&parent_block.body); + fprintf(stderr, "failed to add finalized floor block to fork choice\n"); + lantern_block_body_reset(&finalized_block.body); lantern_block_body_reset(&anchor_block.body); rc = 1; goto cleanup; } - lantern_block_body_reset(&parent_block.body); + + LanternCheckpoint finalized_checkpoint = { + .root = finalized_root, + .slot = finalized_slot, + }; + if (lantern_fork_choice_restore_checkpoints( + &client.fork_choice, + &anchor_checkpoint, + &finalized_checkpoint) + != 0) + { + fprintf(stderr, "failed to restore finalized floor checkpoint\n"); + lantern_block_body_reset(&finalized_block.body); + lantern_block_body_reset(&anchor_block.body); + rc = 1; + goto cleanup; + } + client.state.latest_justified = anchor_checkpoint; + client.state.latest_finalized = finalized_checkpoint; + lantern_block_body_reset(&finalized_block.body); lantern_block_body_reset(&anchor_block.body); - memset(&block, 0, sizeof(block)); lantern_block_body_init(&block.block.body); - block.block.slot = 5; + block.block.slot = finalized_slot + 1u; block.block.proposer_index = 0; - client_test_fill_root(&block_root, 0x90); client_test_fill_root(&parent_root, 0x20); if (memcmp(parent_root.bytes, head_root.bytes, LANTERN_ROOT_SIZE) == 0) { parent_root.bytes[0] ^= 0xFFu; } block.block.parent_root = parent_root; client_test_fill_root(&block.block.state_root, 0x30); + if (lantern_hash_tree_root_block(&block.block, &block_root) != SSZ_SUCCESS) { + fprintf(stderr, "failed to hash block with missing parent\n"); + rc = 1; + goto cleanup; + } if (lantern_client_pending_block_count(&client) != 0) { rc = 1; @@ -836,14 +1262,20 @@ static int test_import_block_parent_mismatch(void) { goto cleanup; } - if (lantern_client_debug_import_block(&client, &block, &block_root, "12D3KooWparent") != 0) { - fprintf(stderr, "import unexpectedly succeeded for mismatched parent\n"); + if (reqresp_handle_block_response( + &client, + &block, + NULL, + 0u, + "12D3KooWparent") + != LANTERN_CLIENT_OK) { + fprintf(stderr, "reqresp rejected block accepted into pending queue\n"); rc = 1; goto cleanup; } if (lantern_client_pending_block_count(&client) != 1) { - fprintf(stderr, "pending queue count mismatch after mismatched parent\n"); + fprintf(stderr, "pending queue count mismatch after missing parent response\n"); rc = 1; goto cleanup; } @@ -859,18 +1291,18 @@ static int test_import_block_parent_mismatch(void) { peer_text, sizeof(peer_text)) != 0) { - fprintf(stderr, "failed to inspect pending entry after mismatched parent\n"); + fprintf(stderr, "failed to inspect pending entry after missing parent response\n"); rc = 1; goto cleanup; } if (memcmp(pending_root.bytes, block_root.bytes, LANTERN_ROOT_SIZE) != 0) { - fprintf(stderr, "pending root mismatch after mismatched parent\n"); + fprintf(stderr, "pending root mismatch after missing parent response\n"); rc = 1; goto cleanup; } if (memcmp(pending_parent.bytes, parent_root.bytes, LANTERN_ROOT_SIZE) != 0) { - fprintf(stderr, "pending parent root mismatch after mismatched parent\n"); + fprintf(stderr, "pending parent root mismatch after missing parent response\n"); rc = 1; goto cleanup; } @@ -880,6 +1312,33 @@ static int test_import_block_parent_mismatch(void) { goto cleanup; } + lantern_client_debug_pending_reset(&client); + block.block.slot = finalized_slot; + client_test_fill_root(&parent_root, 0x21); + block.block.parent_root = parent_root; + client_test_fill_root(&block.block.state_root, 0x31); + if (lantern_hash_tree_root_block(&block.block, &block_root) != SSZ_SUCCESS) { + fprintf(stderr, "failed to hash finalized floor block response\n"); + rc = 1; + goto cleanup; + } + if (reqresp_handle_block_response( + &client, + &block, + NULL, + 0u, + "12D3KooWparent") + == LANTERN_CLIENT_OK) { + fprintf(stderr, "reqresp accepted block at finalized floor\n"); + rc = 1; + goto cleanup; + } + if (lantern_client_pending_block_count(&client) != 0) { + fprintf(stderr, "finalized floor block should not enter pending queue\n"); + rc = 1; + goto cleanup; + } + cleanup: lantern_client_debug_pending_reset(&client); lantern_block_body_reset(&block.block.body); @@ -933,7 +1392,7 @@ static int test_reqresp_collect_blocks_pending_fallback(void) { client_test_fill_root(&pending_block.block.state_root, 0x55); LanternRoot pending_root; - if (lantern_hash_tree_root_block(&pending_block.block, &pending_root) != 0) { + if (lantern_hash_tree_root_block(&pending_block.block, &pending_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash pending block root for reqresp collect test\n"); rc = 1; goto cleanup; @@ -973,7 +1432,7 @@ static int test_reqresp_collect_blocks_pending_fallback(void) { } LanternRoot collected_root; - if (lantern_hash_tree_root_block(&collected.blocks[0].block, &collected_root) != 0) { + if (lantern_hash_tree_root_block(&collected.blocks[0].block, &collected_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash collected block for reqresp fallback test\n"); lantern_signed_block_list_reset(&collected); rc = 1; @@ -1053,6 +1512,119 @@ static int test_import_block_accepts_complete_signatures(void) return rc; } +static int test_import_persists_finalized_replay_base(void) +{ + struct block_signature_fixture fixture; + LanternCheckpoint initial_finalized; + LanternCheckpoint advanced_finalized = {0}; + bool finalized_advanced = false; + int rc = 1; + + if (setup_block_signature_fixture(&fixture, "test_finalized_replay_base") != 0) { + fprintf(stderr, "failed to set up finalized replay base fixture\n"); + return 1; + } + + initial_finalized = fixture.client.state.latest_finalized; + LanternRoot initial_head_root; + if (test_state_latest_block_root(&fixture.client.state, &initial_head_root) != 0) { + fprintf(stderr, "failed to compute initial head root for replay base test\n"); + goto cleanup; + } + if (fixture.client.has_fork_choice + && lantern_fork_choice_set_block_state( + &fixture.client.fork_choice, + &initial_head_root, + &fixture.client.state) + != 0) { + fprintf(stderr, "failed to seed initial head state for replay base test\n"); + goto cleanup; + } + if (lantern_storage_store_state_for_root( + fixture.client.data_dir, + &initial_head_root, + &fixture.client.state) + != 0) { + fprintf(stderr, "failed to persist initial head state for replay base test\n"); + goto cleanup; + } + + for (size_t i = 0; i < 12u && !finalized_advanced; ++i) { + LanternSignedBlock block; + LanternRoot block_root; + memset(&block, 0, sizeof(block)); + if (build_signed_block_for_import(&fixture, true, true, &block, &block_root) != 0) { + fprintf(stderr, "failed to build block for finalized replay base test\n"); + lantern_signed_block_with_attestation_reset(&block); + goto cleanup; + } + if (lantern_client_debug_import_block(&fixture.client, &block, &block_root, "12D3KooWbase") != 1) { + fprintf(stderr, "failed to import block for finalized replay base test\n"); + lantern_signed_block_with_attestation_reset(&block); + goto cleanup; + } + lantern_signed_block_with_attestation_reset(&block); + if (fixture.client.state.latest_finalized.slot > initial_finalized.slot) { + advanced_finalized = fixture.client.state.latest_finalized; + finalized_advanced = true; + } + } + + if (!finalized_advanced) { + fprintf(stderr, "finalized checkpoint did not advance in replay base test\n"); + goto cleanup; + } + + uint8_t *state_bytes = NULL; + size_t state_len = 0u; + if (lantern_storage_load_state_bytes_for_root( + fixture.client.data_dir, + &advanced_finalized.root, + &state_bytes, + &state_len) + != 0) { + fprintf(stderr, "finalized root state snapshot missing after prune\n"); + free(state_bytes); + goto cleanup; + } + + LanternState keyed_state; + lantern_state_init(&keyed_state); + if (lantern_ssz_decode_state(&keyed_state, state_bytes, state_len) != SSZ_SUCCESS) { + fprintf(stderr, "failed to decode finalized root state snapshot\n"); + free(state_bytes); + lantern_state_reset(&keyed_state); + goto cleanup; + } + free(state_bytes); + if (!test_state_matches_root(&keyed_state, &advanced_finalized.root)) { + fprintf(stderr, "finalized root state snapshot does not match finalized root\n"); + lantern_state_reset(&keyed_state); + goto cleanup; + } + lantern_state_reset(&keyed_state); + + LanternState finalized_state; + lantern_state_init(&finalized_state); + if (lantern_storage_load_finalized_state(fixture.client.data_dir, &finalized_state) != 0) { + fprintf(stderr, "persisted finalized replay state missing\n"); + lantern_state_reset(&finalized_state); + goto cleanup; + } + if (!test_state_matches_root(&finalized_state, &advanced_finalized.root)) { + fprintf(stderr, "persisted finalized replay state does not match finalized root\n"); + lantern_state_reset(&finalized_state); + goto cleanup; + } + lantern_state_reset(&finalized_state); + + rc = 0; + +cleanup: + teardown_block_signature_fixture(&fixture); + return rc; +} + static int test_historical_backfill_imports_after_large_gap_connects(void) { struct block_signature_fixture target; @@ -1211,6 +1783,55 @@ static int test_import_block_rejects_missing_proposer_signature(void) return rc; } +static int test_import_block_rejects_malformed_attestation_signature(void) +{ + struct block_signature_fixture fixture; + LanternSignedBlock block; + LanternRoot block_root; + uint64_t initial_slot = 0; + int rc = 1; + + memset(&block, 0, sizeof(block)); + if (setup_block_signature_fixture(&fixture, "test_import_bad_att_sig") != 0) { + fprintf(stderr, "failed to set up malformed attestation signature fixture\n"); + return 1; + } + + initial_slot = fixture.client.state.slot; + if (build_signed_block_for_import(&fixture, true, true, &block, &block_root) != 0) { + fprintf(stderr, "failed to build block fixture for malformed attestation signature\n"); + goto cleanup; + } + if (block.signatures.attestation_signatures.length == 0 + || !block.signatures.attestation_signatures.data + || block.signatures.attestation_signatures.data[0].proof_data.length == 0 + || !block.signatures.attestation_signatures.data[0].proof_data.data) { + fprintf(stderr, "block fixture did not contain an attestation signature to corrupt\n"); + goto cleanup; + } + + memset( + block.signatures.attestation_signatures.data[0].proof_data.data, + 0, + block.signatures.attestation_signatures.data[0].proof_data.length); + + if (lantern_client_debug_import_block(&fixture.client, &block, &block_root, "12D3KooWsig") != 0) { + fprintf(stderr, "import unexpectedly accepted malformed attestation signature\n"); + goto cleanup; + } + if (fixture.client.state.slot != initial_slot) { + fprintf(stderr, "state slot advanced after rejecting malformed attestation signature\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_signed_block_with_attestation_reset(&block); + teardown_block_signature_fixture(&fixture); + return rc; +} + static int test_import_block_skips_unknown_attestation_head_root(void) { struct block_signature_fixture fixture; @@ -1348,7 +1969,7 @@ static int test_restore_persisted_blocks_skips_proposer_attestation_cache(void) fprintf(stderr, "failed to preview state root for proposer-only restore test\n"); goto cleanup; } - if (lantern_hash_tree_root_block(&block.block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&block.block, &block_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash proposer-only restore block\n"); goto cleanup; } @@ -1389,7 +2010,13 @@ int main(void) { if (test_pending_block_queue_sync_drops_incoming() != 0) { return 1; } - if (test_import_block_parent_mismatch() != 0) { + if (test_sync_completion_uses_network_finalized_threshold() != 0) { + return 1; + } + if (test_idle_status_triggers_syncing_before_gossip_backfill() != 0) { + return 1; + } + if (test_reqresp_block_response_accepts_missing_parent() != 0) { return 1; } if (test_reqresp_collect_blocks_pending_fallback() != 0) { @@ -1398,6 +2025,12 @@ int main(void) { if (test_import_block_accepts_complete_signatures() != 0) { return 1; } + if (test_import_persists_finalized_replay_base() != 0) { + return 1; + } + if (test_imported_blocks_update_sync_network_view() != 0) { + return 1; + } if (test_historical_backfill_imports_after_large_gap_connects() != 0) { return 1; } @@ -1407,6 +2040,9 @@ int main(void) { if (test_import_block_rejects_missing_proposer_signature() != 0) { return 1; } + if (test_import_block_rejects_malformed_attestation_signature() != 0) { + return 1; + } if (test_import_block_skips_unknown_attestation_head_root() != 0) { return 1; } diff --git a/tests/unit/test_client_vote.c b/tests/unit/test_client_vote.c index 3764f8b..970649b 100644 --- a/tests/unit/test_client_vote.c +++ b/tests/unit/test_client_vote.c @@ -16,6 +16,7 @@ #include "lantern/crypto/xmss.h" #include "lantern/networking/gossip_payloads.h" #include "lantern/support/string_list.h" +#include "lantern/support/strings.h" #include "lantern/storage/storage.h" #include "lantern/support/time.h" @@ -62,6 +63,7 @@ int lantern_client_advance_fork_choice_time_locked( struct lantern_client *client, uint64_t now_milliseconds, bool has_proposal); +uint64_t monotonic_millis(void); void lantern_client_reset_local_validators(struct lantern_client *client); int lantern_client_load_xmss_keys(struct lantern_client *client); int validator_sign_with_key( @@ -138,12 +140,10 @@ static int test_enable_blocks_request_peer( } client->peer_status_count = 1u; client->peer_status_capacity = 1u; - strncpy( + (void)lantern_string_copy( client->peer_status_entries[0].peer_id, - peer_id, - sizeof(client->peer_status_entries[0].peer_id) - 1u); - client->peer_status_entries[0].peer_id[sizeof(client->peer_status_entries[0].peer_id) - 1u] = - '\0'; + sizeof(client->peer_status_entries[0].peer_id), + peer_id); return 0; } @@ -179,22 +179,34 @@ static void test_disable_blocks_request_peer(struct lantern_client *client) client->connected_peers = 0u; } -static int test_make_dummy_proof( +static int test_make_dummy_proof_for_validators( LanternAggregatedSignatureProof *out_proof, - uint64_t validator_id, + const uint64_t *validator_ids, + size_t validator_count, uint8_t seed) { - if (!out_proof || validator_id >= LANTERN_VALIDATOR_REGISTRY_LIMIT) { + if (!out_proof || !validator_ids || validator_count == 0u) { return -1; } + uint64_t max_validator_id = 0u; + for (size_t i = 0; i < validator_count; ++i) { + if (validator_ids[i] >= LANTERN_VALIDATOR_REGISTRY_LIMIT) { + return -1; + } + if (validator_ids[i] > max_validator_id) { + max_validator_id = validator_ids[i]; + } + } lantern_aggregated_signature_proof_init(out_proof); - size_t bit_length = (size_t)validator_id + 1u; + size_t bit_length = (size_t)max_validator_id + 1u; if (lantern_bitlist_resize(&out_proof->participants, bit_length) != 0) { lantern_aggregated_signature_proof_reset(out_proof); return -1; } - if (lantern_bitlist_set(&out_proof->participants, (size_t)validator_id, true) != 0) { - lantern_aggregated_signature_proof_reset(out_proof); - return -1; + for (size_t i = 0; i < validator_count; ++i) { + if (lantern_bitlist_set(&out_proof->participants, (size_t)validator_ids[i], true) != 0) { + lantern_aggregated_signature_proof_reset(out_proof); + return -1; + } } if (lantern_byte_list_resize(&out_proof->proof_data, 8u) != 0) { lantern_aggregated_signature_proof_reset(out_proof); @@ -206,6 +218,14 @@ static int test_make_dummy_proof( return 0; } +static int test_make_dummy_proof( + LanternAggregatedSignatureProof *out_proof, + uint64_t validator_id, + uint8_t seed) { + uint64_t validator_ids[1] = {validator_id}; + return test_make_dummy_proof_for_validators(out_proof, validator_ids, 1u, seed); +} + static LanternAttestationData test_make_attestation_data(uint64_t slot, uint8_t marker) { LanternAttestationData data; memset(&data, 0, sizeof(data)); @@ -233,6 +253,20 @@ static bool aggregated_pool_contains_root( return false; } +static const struct lantern_aggregated_payload_entry *aggregated_pool_find_root( + const struct lantern_aggregated_payload_pool *pool, + const LanternRoot *data_root) { + if (!pool || !data_root || !pool->entries) { + return NULL; + } + for (size_t i = 0; i < pool->length; ++i) { + if (memcmp(pool->entries[i].data_root.bytes, data_root->bytes, LANTERN_ROOT_SIZE) == 0) { + return &pool->entries[i]; + } + } + return NULL; +} + static bool pq_range_contains_slot(struct PQRange range, uint64_t slot) { return range.start <= slot && slot < range.end; } @@ -487,7 +521,7 @@ static int build_signed_head_block( goto cleanup; } LanternRoot block_signature_root; - if (lantern_hash_tree_root_block(&out_block->block, &block_signature_root) != 0) { + if (lantern_hash_tree_root_block(&out_block->block, &block_signature_root) != SSZ_SUCCESS) { goto cleanup; } if (!lantern_signature_sign( @@ -497,7 +531,7 @@ static int build_signed_head_block( &out_block->signatures.proposer_signature)) { goto cleanup; } - if (lantern_hash_tree_root_block(&out_block->block, out_root) != 0) { + if (lantern_hash_tree_root_block(&out_block->block, out_root) != SSZ_SUCCESS) { goto cleanup; } @@ -569,7 +603,7 @@ static int test_record_vote_accepts_known_roots(void) { } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash vote data for gossip signature cache\n"); goto cleanup; } @@ -642,7 +676,7 @@ static int test_record_vote_buffers_missing_target_state(void) { grandchild.proposer_index = 0u; grandchild.parent_root = child_root; client_test_fill_root(&grandchild.state_root, 0xC3u); - if (lantern_hash_tree_root_block(&grandchild, &grandchild_root) != 0) { + if (lantern_hash_tree_root_block(&grandchild, &grandchild_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash grandchild block for missing target state test\n"); goto cleanup; } @@ -964,7 +998,9 @@ static int test_record_vote_replays_buffered_vote_after_block_import(void) { char cleanup_cmd[320]; int written = snprintf(cleanup_cmd, sizeof(cleanup_cmd), "rm -rf %s", client.data_dir); if (written > 0 && (size_t)written < sizeof(cleanup_cmd)) { - (void)system(cleanup_cmd); + if (system(cleanup_cmd) == -1) { + fprintf(stderr, "failed to remove temp data dir %s\n", client.data_dir); + } } client.data_dir = NULL; } @@ -1376,7 +1412,7 @@ static int test_validator_build_block_leaves_attestation_key_untouched(void) { fprintf(stderr, "validator_build_block failed for key-isolation test\n"); goto cleanup; } - if (lantern_hash_tree_root_block(&block.block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&block.block, &block_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash built block for key-isolation test\n"); goto cleanup; } @@ -1666,7 +1702,7 @@ static int test_record_vote_preserves_state_root(void) { LanternCheckpoint pre_justified = client.state.latest_justified; LanternCheckpoint pre_finalized = client.state.latest_finalized; LanternRoot pre_state_root; - if (lantern_hash_tree_root_state(&client.state, &pre_state_root) != 0) { + if (lantern_hash_tree_root_state(&client.state, &pre_state_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash pre-vote state\n"); goto cleanup; } @@ -1698,7 +1734,7 @@ static int test_record_vote_preserves_state_root(void) { } LanternRoot post_state_root; - if (lantern_hash_tree_root_state(&client.state, &post_state_root) != 0) { + if (lantern_hash_tree_root_state(&client.state, &post_state_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash post-vote state\n"); goto cleanup; } @@ -1779,7 +1815,7 @@ static int test_record_vote_defers_interval_pipeline(void) { } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash vote data for interval pipeline test\n"); goto cleanup; } @@ -1962,7 +1998,7 @@ static int test_skip_fork_choice_intervals_replays_interval_side_effects(void) { goto cleanup; } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote.data.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash skip replay vote data\n"); goto cleanup; } @@ -2087,7 +2123,7 @@ static int test_safe_target_uses_only_new_attached_aggregated_payloads(void) { data.source.root = anchor_root; LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash attestation data for aggregated safe-target test\n"); goto cleanup; } @@ -2229,6 +2265,179 @@ static int test_new_aggregated_payloads_promote_to_known(void) { return rc; } +static int test_aggregated_payload_cache_keeps_only_useful_coverage(void) { + struct lantern_client client; + memset(&client, 0, sizeof(client)); + lantern_store_init(&client.store); + + LanternRoot data_root; + LanternRoot other_root; + client_test_fill_root_with_index(&data_root, 0x404u); + client_test_fill_root_with_index(&other_root, 0x405u); + + uint64_t validator0[1] = {0u}; + uint64_t validators01[2] = {0u, 1u}; + + LanternAggregatedSignatureProof first; + LanternAggregatedSignatureProof duplicate_coverage; + LanternAggregatedSignatureProof broader; + LanternAggregatedSignatureProof subset; + LanternAggregatedSignatureProof other_root_proof; + memset(&first, 0, sizeof(first)); + memset(&duplicate_coverage, 0, sizeof(duplicate_coverage)); + memset(&broader, 0, sizeof(broader)); + memset(&subset, 0, sizeof(subset)); + memset(&other_root_proof, 0, sizeof(other_root_proof)); + + int rc = 1; + if (test_make_dummy_proof_for_validators(&first, validator0, 1u, 0x11u) != 0 + || test_make_dummy_proof_for_validators(&duplicate_coverage, validator0, 1u, 0x21u) != 0 + || test_make_dummy_proof_for_validators(&broader, validators01, 2u, 0x31u) != 0 + || test_make_dummy_proof_for_validators(&subset, validator0, 1u, 0x41u) != 0 + || test_make_dummy_proof_for_validators(&other_root_proof, validator0, 1u, 0x51u) != 0) { + fprintf(stderr, "failed to build coverage cache proofs\n"); + goto cleanup; + } + + LanternAttestationData data = test_make_attestation_data(10u, 0x61u); + LanternAttestationData other_data = test_make_attestation_data(11u, 0x71u); + + if (lantern_client_add_known_aggregated_payload(&client, &data_root, &data, &first, data.target.slot) != 0) { + fprintf(stderr, "failed to add first cached proof\n"); + goto cleanup; + } + if (client.store.known_aggregated_payloads.length != 1u) { + fprintf(stderr, "expected first cached proof to be retained\n"); + goto cleanup; + } + + if (lantern_client_add_known_aggregated_payload( + &client, + &data_root, + &data, + &duplicate_coverage, + data.target.slot) + != 0) { + fprintf(stderr, "failed to process duplicate-coverage proof\n"); + goto cleanup; + } + if (client.store.known_aggregated_payloads.length != 1u) { + fprintf(stderr, "duplicate coverage should not grow known payload cache\n"); + goto cleanup; + } + + if (lantern_client_add_known_aggregated_payload(&client, &data_root, &data, &broader, data.target.slot) != 0) { + fprintf(stderr, "failed to add broader cached proof\n"); + goto cleanup; + } + if (client.store.known_aggregated_payloads.length != 1u) { + fprintf(stderr, "broader proof should replace covered subset proof\n"); + goto cleanup; + } + + const struct lantern_aggregated_payload_entry *entry = + aggregated_pool_find_root(&client.store.known_aggregated_payloads, &data_root); + if (!entry + || !lantern_bitlist_get(&entry->proof.participants, 0u) + || !lantern_bitlist_get(&entry->proof.participants, 1u)) { + fprintf(stderr, "broader proof coverage was not retained\n"); + goto cleanup; + } + + if (lantern_client_add_known_aggregated_payload(&client, &data_root, &data, &subset, data.target.slot) != 0) { + fprintf(stderr, "failed to process subset cached proof\n"); + goto cleanup; + } + if (client.store.known_aggregated_payloads.length != 1u) { + fprintf(stderr, "covered subset should not grow known payload cache\n"); + goto cleanup; + } + + if (lantern_client_add_known_aggregated_payload( + &client, + &other_root, + &other_data, + &other_root_proof, + other_data.target.slot) + != 0) { + fprintf(stderr, "failed to add proof for a distinct attestation root\n"); + goto cleanup; + } + if (client.store.known_aggregated_payloads.length != 2u) { + fprintf(stderr, "distinct attestation roots should retain independent coverage\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_aggregated_signature_proof_reset(&first); + lantern_aggregated_signature_proof_reset(&duplicate_coverage); + lantern_aggregated_signature_proof_reset(&broader); + lantern_aggregated_signature_proof_reset(&subset); + lantern_aggregated_signature_proof_reset(&other_root_proof); + test_reset_agg_cache(&client); + return rc; +} + +static int test_promoting_redundant_aggregated_payload_drops_known_duplicate(void) { + struct lantern_client client; + memset(&client, 0, sizeof(client)); + lantern_store_init(&client.store); + + LanternRoot data_root; + client_test_fill_root_with_index(&data_root, 0x505u); + LanternAttestationData data = test_make_attestation_data(12u, 0x81u); + + LanternAggregatedSignatureProof known; + LanternAggregatedSignatureProof redundant_new; + memset(&known, 0, sizeof(known)); + memset(&redundant_new, 0, sizeof(redundant_new)); + + int rc = 1; + if (test_make_dummy_proof(&known, 0u, 0x12u) != 0 + || test_make_dummy_proof(&redundant_new, 0u, 0x22u) != 0) { + fprintf(stderr, "failed to build promotion dedupe proofs\n"); + goto cleanup; + } + + if (lantern_client_add_known_aggregated_payload(&client, &data_root, &data, &known, data.target.slot) != 0 + || lantern_client_add_new_aggregated_payload( + &client, + &data_root, + &data, + &redundant_new, + data.target.slot) + != 0) { + fprintf(stderr, "failed to seed promotion dedupe pools\n"); + goto cleanup; + } + if (client.store.known_aggregated_payloads.length != 1u + || client.store.new_aggregated_payloads.length != 1u) { + fprintf(stderr, "unexpected payload lengths before promotion dedupe\n"); + goto cleanup; + } + + size_t moved = lantern_client_promote_new_aggregated_payloads(&client); + if (moved != 1u) { + fprintf(stderr, "expected redundant new payload to be processed, got=%zu\n", moved); + goto cleanup; + } + if (client.store.new_aggregated_payloads.length != 0u + || client.store.known_aggregated_payloads.length != 1u) { + fprintf(stderr, "redundant promoted payload should not grow known cache\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_aggregated_signature_proof_reset(&known); + lantern_aggregated_signature_proof_reset(&redundant_new); + test_reset_agg_cache(&client); + return rc; +} + static int test_debug_aggregate_attestation_signatures_rebuilds_new_payloads(void) { struct lantern_client client; struct PQSignatureSchemePublicKey *pub = NULL; @@ -2298,7 +2507,7 @@ static int test_debug_aggregate_attestation_signatures_rebuilds_new_payloads(voi goto cleanup; } - if (lantern_hash_tree_root_attestation_data(&vote.data.data, &fresh_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote.data.data, &fresh_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash fresh attestation data for recursive aggregation test\n"); goto cleanup; } @@ -2705,7 +2914,7 @@ static int test_validator_build_reuses_cached_group_proof(void) { } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&valid_vote.data.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&valid_vote.data.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash vote data for cached proof reuse test\n"); goto cleanup; } @@ -2924,7 +3133,7 @@ static int test_block_build_keeps_known_payload_after_newer_raw_vote(void) { fprintf(stderr, "failed to build proof-backed vote for block-build shadow test\n"); goto cleanup; } - if (lantern_hash_tree_root_attestation_data(&proof_vote.data.data, &proof_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&proof_vote.data.data, &proof_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash proof-backed vote for block-build shadow test\n"); goto cleanup; } @@ -2970,7 +3179,7 @@ static int test_block_build_keeps_known_payload_after_newer_raw_vote(void) { fprintf(stderr, "failed to sign newer raw vote for block-build shadow test\n"); goto cleanup; } - if (lantern_hash_tree_root_attestation_data(&raw_vote.data.data, &raw_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&raw_vote.data.data, &raw_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash newer raw vote for block-build shadow test\n"); goto cleanup; } @@ -3031,6 +3240,147 @@ static int test_block_build_keeps_known_payload_after_newer_raw_vote(void) { return rc; } +static int test_block_build_drops_target_not_after_source_payload(void) { + struct lantern_client client; + struct PQSignatureSchemePublicKey *pub = NULL; + struct PQSignatureSchemeSecretKey *secret = NULL; + LanternRoot anchor_root; + LanternRoot child_root; + LanternRoot parent_root; + LanternRoot invalid_root; + LanternRoot valid_root; + LanternSignedVote invalid_vote; + LanternSignedVote valid_vote; + LanternAggregatedSignatureProof invalid_proof; + LanternAggregatedSignatureProof valid_proof; + LanternAggregatedAttestations collected; + LanternAttestationSignatures collected_signatures; + int rc = 1; + + memset(&parent_root, 0, sizeof(parent_root)); + memset(&invalid_root, 0, sizeof(invalid_root)); + memset(&valid_root, 0, sizeof(valid_root)); + memset(&invalid_vote, 0, sizeof(invalid_vote)); + memset(&valid_vote, 0, sizeof(valid_vote)); + lantern_aggregated_signature_proof_init(&invalid_proof); + lantern_aggregated_signature_proof_init(&valid_proof); + lantern_aggregated_attestations_init(&collected); + lantern_attestation_signatures_init(&collected_signatures); + + if (client_test_setup_vote_validation_client_with_validator_count( + &client, + "vote_target_not_after_source", + 2u, + &pub, + &secret, + &anchor_root, + &child_root) + != 0) { + return 1; + } + + uint64_t block_slot = client.state.slot + 1u; + uint64_t proposer_index = 0u; + if (lantern_proposer_for_slot(block_slot, client.state.config.num_validators, &proposer_index) != 0) { + fprintf(stderr, "failed to resolve proposer for target<=source block-build test\n"); + goto cleanup; + } + + uint64_t validator_id = proposer_index == 0u ? 1u : 0u; + if (make_signed_vote_for_validator( + &client, + secret, + validator_id, + &anchor_root, + &child_root, + &valid_vote) + != 0) { + fprintf(stderr, "failed to build valid vote for target<=source block-build test\n"); + goto cleanup; + } + invalid_vote = valid_vote; + invalid_vote.data.head.slot = 0u; + invalid_vote.data.head.root = anchor_root; + invalid_vote.data.target.slot = invalid_vote.data.source.slot; + invalid_vote.data.target.root = invalid_vote.data.source.root; + + if (lantern_hash_tree_root_attestation_data(&invalid_vote.data.data, &invalid_root) != SSZ_SUCCESS + || lantern_hash_tree_root_attestation_data(&valid_vote.data.data, &valid_root) != SSZ_SUCCESS) { + fprintf(stderr, "failed to hash votes for target<=source block-build test\n"); + goto cleanup; + } + if (memcmp(invalid_root.bytes, valid_root.bytes, LANTERN_ROOT_SIZE) == 0) { + fprintf(stderr, "target<=source vote should have a distinct data root\n"); + goto cleanup; + } + if (test_make_dummy_proof(&invalid_proof, validator_id, 0x44u) != 0 + || test_make_dummy_proof(&valid_proof, validator_id, 0x55u) != 0) { + fprintf(stderr, "failed to build proofs for target<=source block-build test\n"); + goto cleanup; + } + if (lantern_client_add_known_aggregated_payload( + &client, + &invalid_root, + &invalid_vote.data.data, + &invalid_proof, + invalid_vote.data.target.slot) + != 0 + || lantern_client_add_known_aggregated_payload( + &client, + &valid_root, + &valid_vote.data.data, + &valid_proof, + valid_vote.data.target.slot) + != 0) { + fprintf(stderr, "failed to seed payloads for target<=source block-build test\n"); + goto cleanup; + } + + if (lantern_state_select_block_parent(&client.state, &client.store, &parent_root) != 0) { + fprintf(stderr, "failed to select block parent for target<=source block-build test\n"); + goto cleanup; + } + if (lantern_state_collect_attestations_for_block( + &client.state, + &client.store, + block_slot, + proposer_index, + &parent_root, + &collected, + &collected_signatures) + != 0) { + fprintf(stderr, "failed to collect attestations for target<=source block-build test\n"); + goto cleanup; + } + if (collected.length != 1u || collected_signatures.length != 1u) { + fprintf( + stderr, + "expected exactly one collected attestation after target<=source filter, got votes=%zu sigs=%zu\n", + collected.length, + collected_signatures.length); + goto cleanup; + } + if (memcmp(&collected.data[0].data, &valid_vote.data.data, sizeof(valid_vote.data.data)) != 0) { + fprintf(stderr, "block collection kept the target<=source attestation\n"); + goto cleanup; + } + if (!proof_payload_equals(&collected_signatures.data[0], &valid_proof)) { + fprintf(stderr, "block collection did not keep the valid cached proof\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_attestation_signatures_reset(&collected_signatures); + lantern_aggregated_attestations_reset(&collected); + lantern_aggregated_signature_proof_reset(&valid_proof); + lantern_aggregated_signature_proof_reset(&invalid_proof); + test_reset_agg_cache(&client); + client_test_teardown_vote_validation_client(&client, pub, secret); + return rc; +} + static int test_publish_aggregated_attestations_collects_any_slot_and_prunes_gossip(void) { struct lantern_client client; struct PQSignatureSchemePublicKey *pub = NULL; @@ -3099,7 +3449,7 @@ static int test_publish_aggregated_attestations_collects_any_slot_and_prunes_gos fprintf(stderr, "expected all gossip signatures to be cached before subnet filtering\n"); goto cleanup; } - if (lantern_hash_tree_root_attestation_data(&vote0.data.data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote0.data.data, &data_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash attestation data for prune test\n"); goto cleanup; } @@ -3199,6 +3549,7 @@ static int test_publish_attestations_includes_proposer(void) { client.validator_enabled = &validator_enabled; client.has_runtime = true; client.gossip_running = true; + client.sync_state = LANTERN_SYNC_STATE_SYNCED; client.gossip.attestation_subnet_id = 0u; snprintf(client.gossip.vote_topic, sizeof(client.gossip.vote_topic), "test/proposer_vote"); snprintf( @@ -3248,6 +3599,163 @@ static int test_publish_attestations_includes_proposer(void) { return rc; } +static int test_publish_attestations_ignores_peer_status_gate_inputs(void) { + struct lantern_client client; + struct PQSignatureSchemePublicKey *pub = NULL; + struct PQSignatureSchemeSecretKey *secret = NULL; + struct publish_capture capture; + struct lantern_local_validator validator; + bool validator_enabled = true; + bool peer_enabled = false; + int rc = 1; + + memset(&capture, 0, sizeof(capture)); + memset(&validator, 0, sizeof(validator)); + + if (client_test_setup_vote_validation_client( + &client, + "vote_publish_peer_status_ignored", + &pub, + &secret, + NULL, + NULL) + != 0) { + goto cleanup; + } + + validator.global_index = 0u; + validator.last_attested_slot = UINT64_MAX; + validator.attestation_secret_key = secret; + validator.proposal_secret_key = secret; + + client.local_validators = &validator; + client.local_validator_count = 1u; + client.validator_enabled = &validator_enabled; + client.has_runtime = true; + client.gossip_running = true; + client.sync_state = LANTERN_SYNC_STATE_SYNCED; + client.gossip.attestation_subnet_id = 0u; + snprintf(client.gossip.vote_topic, sizeof(client.gossip.vote_topic), "test/status_gate_vote"); + snprintf( + client.gossip.vote_subnet_topic, + sizeof(client.gossip.vote_subnet_topic), + "test/status_gate_vote_subnet"); + lantern_gossipsub_service_set_publish_hook(&client.gossip, publish_capture_hook, &capture); + lantern_gossipsub_service_set_loopback_only(&client.gossip, 1); + + if (test_enable_blocks_request_peer(&client, "peer_status_gate") != 0) { + fprintf(stderr, "failed to initialize peer status gate test peer\n"); + goto cleanup; + } + peer_enabled = true; + + struct lantern_peer_status_entry *entry = &client.peer_status_entries[0]; + entry->has_status = true; + entry->last_status_ms = 1u; + entry->status.head.slot = client.state.slot + 64u; + client_test_fill_root(&entry->status.head.root, 0xacu); + entry->status.finalized = client.state.latest_finalized; + + uint64_t stale_slot = client.state.slot + 1u; + if (validator_publish_attestations(&client, stale_slot) != LANTERN_CLIENT_OK) { + fprintf(stderr, "stale peer status should not block validator attestation\n"); + goto cleanup; + } + if (capture.calls != 1u || validator.last_attested_slot != stale_slot) { + fprintf(stderr, "stale peer status did not produce exactly one attestation\n"); + goto cleanup; + } + + capture.calls = 0u; + capture.payload_len = 0u; + entry->last_status_ms = monotonic_millis(); + entry->status.head.slot = client.state.slot + 128u; + client_test_fill_root(&entry->status.head.root, 0xbcu); + + uint64_t mismatched_slot = stale_slot + 1u; + if (validator_publish_attestations(&client, mismatched_slot) != LANTERN_CLIENT_OK) { + fprintf(stderr, "fresh mismatched peer head should not block validator attestation\n"); + goto cleanup; + } + if (capture.calls != 1u || validator.last_attested_slot != mismatched_slot) { + fprintf(stderr, "fresh mismatched peer head did not produce exactly one attestation\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + if (peer_enabled) { + test_disable_blocks_request_peer(&client); + } + publish_capture_reset(&capture); + test_reset_agg_cache(&client); + client_test_teardown_vote_validation_client(&client, pub, secret); + return rc; +} + +static int test_validator_duties_follow_sync_state(void) { + struct lantern_client client; + struct PQSignatureSchemePublicKey *pub = NULL; + struct PQSignatureSchemeSecretKey *secret = NULL; + struct lantern_local_validator validator; + bool validator_enabled = true; + int rc = 1; + + memset(&validator, 0, sizeof(validator)); + + if (client_test_setup_vote_validation_client( + &client, + "validator_sync_state_gate", + &pub, + &secret, + NULL, + NULL) + != 0) { + return 1; + } + + validator.global_index = 0u; + validator.attestation_secret_key = secret; + validator.proposal_secret_key = secret; + + client.local_validators = &validator; + client.local_validator_count = 1u; + client.validator_enabled = &validator_enabled; + client.has_runtime = true; + client.gossip_running = true; + + client.sync_state = LANTERN_SYNC_STATE_IDLE; + if (validator_service_should_run(&client)) { + fprintf(stderr, "validator duties should be suppressed while sync state is IDLE\n"); + goto cleanup; + } + + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + if (validator_service_should_run(&client)) { + fprintf(stderr, "validator duties should be suppressed while sync state is SYNCING\n"); + goto cleanup; + } + + client.sync_state = LANTERN_SYNC_STATE_SYNCED; + if (!validator_service_should_run(&client)) { + fprintf(stderr, "validator duties should run while sync state is SYNCED\n"); + goto cleanup; + } + + client.sync_state = LANTERN_SYNC_STATE_SYNCING; + if (validator_service_should_run(&client)) { + fprintf(stderr, "validator duties should be re-suppressed after returning to SYNCING\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + client_test_teardown_vote_validation_client(&client, pub, secret); + return rc; +} + static int test_local_block_commit_updates_state_before_publish(void) { struct lantern_client client; struct PQSignatureSchemePublicKey *pub = NULL; @@ -3298,7 +3806,7 @@ static int test_local_block_commit_updates_state_before_publish(void) { fprintf(stderr, "validator_build_block failed for local block publish test\n"); goto cleanup; } - if (lantern_hash_tree_root_block(&block.block, &block_root) != 0) { + if (lantern_hash_tree_root_block(&block.block, &block_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash built block for local block publish test\n"); goto cleanup; } @@ -3497,6 +4005,12 @@ int main(void) { if (test_new_aggregated_payloads_promote_to_known() != 0) { return 1; } + if (test_aggregated_payload_cache_keeps_only_useful_coverage() != 0) { + return 1; + } + if (test_promoting_redundant_aggregated_payload_drops_known_duplicate() != 0) { + return 1; + } if (test_debug_aggregate_attestation_signatures_rebuilds_new_payloads() != 0) { return 1; } @@ -3512,12 +4026,21 @@ int main(void) { if (test_block_build_keeps_known_payload_after_newer_raw_vote() != 0) { return 1; } + if (test_block_build_drops_target_not_after_source_payload() != 0) { + return 1; + } if (test_validator_build_reuses_cached_group_proof() != 0) { return 1; } if (test_publish_attestations_includes_proposer() != 0) { return 1; } + if (test_publish_attestations_ignores_peer_status_gate_inputs() != 0) { + return 1; + } + if (test_validator_duties_follow_sync_state() != 0) { + return 1; + } if (test_publish_aggregated_attestations_collects_any_slot_and_prunes_gossip() != 0) { return 1; } diff --git a/tests/unit/test_fork_choice.c b/tests/unit/test_fork_choice.c index 3a9f3d7..42292ed 100644 --- a/tests/unit/test_fork_choice.c +++ b/tests/unit/test_fork_choice.c @@ -146,7 +146,7 @@ static int seed_known_payload( } const LanternAttestationData *data = &vote->data.data; LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(data, &data_root) != SSZ_SUCCESS) { return -1; } LanternAggregatedSignatureProof proof; @@ -172,7 +172,7 @@ static int seed_new_payload( } const LanternAttestationData *data = &vote->data.data; LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(data, &data_root) != SSZ_SUCCESS) { return -1; } LanternAggregatedSignatureProof proof; @@ -302,14 +302,14 @@ static int test_fork_choice_proposer_attestation_sequence(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0xAA); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0xBB); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); LanternSignedVote proposer_vote_one = make_vote(0, &genesis_cp, &block_one_cp); assert( @@ -333,7 +333,7 @@ static int test_fork_choice_proposer_attestation_sequence(void) { LanternBlock block_two; init_block(&block_two, 2, 0, &block_one_root, 0xCC); LanternRoot block_two_root; - assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == 0); + assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == SSZ_SUCCESS); LanternCheckpoint block_two_cp = make_checkpoint(&block_two_root, block_two.slot); LanternSignedVote proposer_vote_two = make_vote(0, &block_one_cp, &block_two_cp); assert( @@ -371,14 +371,14 @@ static int test_fork_choice_block_updates_checkpoints(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x10); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0x11); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert( lantern_fork_choice_add_block( @@ -398,7 +398,7 @@ static int test_fork_choice_block_updates_checkpoints(void) { LanternBlock block_two; init_block(&block_two, 2, 1, &block_one_root, 0x22); LanternRoot block_two_root; - assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == 0); + assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == SSZ_SUCCESS); LanternCheckpoint block_two_cp = make_checkpoint(&block_two_root, block_two.slot); assert( lantern_fork_choice_add_block( @@ -418,7 +418,7 @@ static int test_fork_choice_block_updates_checkpoints(void) { LanternBlock block_three; init_block(&block_three, 3, 2, &block_two_root, 0x33); LanternRoot block_three_root; - assert(lantern_hash_tree_root_block(&block_three, &block_three_root) == 0); + assert(lantern_hash_tree_root_block(&block_three, &block_three_root) == SSZ_SUCCESS); LanternCheckpoint block_three_cp = make_checkpoint(&block_three_root, block_three.slot); assert( lantern_fork_choice_add_block( @@ -457,7 +457,7 @@ static int test_fork_choice_caches_block_states(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x51); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); LanternState genesis_state; @@ -486,7 +486,7 @@ static int test_fork_choice_caches_block_states(void) { LanternBlock child; init_block(&child, 1, 1, &genesis_root, 0x52); LanternRoot child_root; - assert(lantern_hash_tree_root_block(&child, &child_root) == 0); + assert(lantern_hash_tree_root_block(&child, &child_root) == SSZ_SUCCESS); LanternCheckpoint child_cp = make_checkpoint(&child_root, child.slot); LanternState child_state; @@ -538,7 +538,7 @@ static int test_fork_choice_prune_states_keeps_finalized_to_head_chain(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x61); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); LanternState genesis_state; @@ -561,7 +561,7 @@ static int test_fork_choice_prune_states_keeps_finalized_to_head_chain(void) { LanternBlock block_one; init_block(&block_one, 1, 1, &genesis_root, 0x62); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); LanternState block_one_state; @@ -586,7 +586,7 @@ static int test_fork_choice_prune_states_keeps_finalized_to_head_chain(void) { LanternBlock block_two; init_block(&block_two, 2, 2, &block_one_root, 0x63); LanternRoot block_two_root; - assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == 0); + assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == SSZ_SUCCESS); LanternCheckpoint block_two_cp = make_checkpoint(&block_two_root, block_two.slot); LanternState block_two_state; @@ -611,7 +611,7 @@ static int test_fork_choice_prune_states_keeps_finalized_to_head_chain(void) { LanternBlock block_three; init_block(&block_three, 3, 0, &block_two_root, 0x64); LanternRoot block_three_root; - assert(lantern_hash_tree_root_block(&block_three, &block_three_root) == 0); + assert(lantern_hash_tree_root_block(&block_three, &block_three_root) == SSZ_SUCCESS); LanternCheckpoint block_three_cp = make_checkpoint(&block_three_root, block_three.slot); LanternState block_three_state; @@ -636,7 +636,7 @@ static int test_fork_choice_prune_states_keeps_finalized_to_head_chain(void) { LanternBlock fork_two; init_block(&fork_two, 2, 0, &block_one_root, 0x65); LanternRoot fork_two_root; - assert(lantern_hash_tree_root_block(&fork_two, &fork_two_root) == 0); + assert(lantern_hash_tree_root_block(&fork_two, &fork_two_root) == SSZ_SUCCESS); LanternState fork_two_state; build_cached_state( @@ -728,7 +728,7 @@ static int test_fork_choice_vote_flow(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x10); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); @@ -739,7 +739,7 @@ static int test_fork_choice_vote_flow(void) { LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0x21); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); assert( lantern_fork_choice_add_block( &store, @@ -753,7 +753,7 @@ static int test_fork_choice_vote_flow(void) { LanternBlock block_two; init_block(&block_two, 2, 1, &block_one_root, 0x32); LanternRoot block_two_root; - assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == 0); + assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == SSZ_SUCCESS); assert( lantern_fork_choice_add_block( &store, @@ -821,14 +821,14 @@ static int test_fork_choice_safe_target_uses_new_aggregated_payloads(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x41); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0x42); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); assert( lantern_fork_choice_add_block( &store, @@ -874,7 +874,7 @@ static int test_fork_choice_checkpoint_progression(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x10); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); @@ -886,7 +886,7 @@ static int test_fork_choice_checkpoint_progression(void) { LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0x21); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert( lantern_fork_choice_add_block( @@ -956,14 +956,14 @@ static int test_fork_choice_restore_checkpoints(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x41); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0x42); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert( lantern_fork_choice_add_block( @@ -978,7 +978,7 @@ static int test_fork_choice_restore_checkpoints(void) { LanternBlock block_two; init_block(&block_two, 2, 1, &block_one_root, 0x43); LanternRoot block_two_root; - assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == 0); + assert(lantern_hash_tree_root_block(&block_two, &block_two_root) == SSZ_SUCCESS); LanternCheckpoint block_two_cp = make_checkpoint(&block_two_root, block_two.slot); assert( lantern_fork_choice_add_block( @@ -1038,9 +1038,18 @@ static int test_fork_choice_anchor_metadata_survives_checkpoint_restore(void) { LanternBlock anchor; init_block(&anchor, 8, 0, NULL, 0x51); LanternRoot anchor_root; - assert(lantern_hash_tree_root_block(&anchor, &anchor_root) == 0); + assert(lantern_hash_tree_root_block(&anchor, &anchor_root) == SSZ_SUCCESS); LanternCheckpoint anchor_cp = make_checkpoint(&anchor_root, anchor.slot); - assert(lantern_fork_choice_set_anchor(&store, &anchor, &anchor_cp, &anchor_cp, &anchor_root) == 0); + LanternCheckpoint embedded_justified = make_checkpoint(&anchor_root, anchor.slot - 1u); + LanternCheckpoint embedded_finalized = make_checkpoint(&anchor_root, anchor.slot - 2u); + assert( + lantern_fork_choice_set_anchor( + &store, + &anchor, + &embedded_justified, + &embedded_finalized, + &anchor_root) + == 0); const LanternRoot *stored_anchor_root = lantern_fork_choice_anchor_root(&store); uint64_t stored_anchor_slot = 0; @@ -1048,6 +1057,10 @@ static int test_fork_choice_anchor_metadata_survives_checkpoint_restore(void) { assert(roots_equal(stored_anchor_root, &anchor_root)); assert(lantern_fork_choice_anchor_slot(&store, &stored_anchor_slot) == 0); assert(stored_anchor_slot == anchor.slot); + const LanternCheckpoint *latest_justified = lantern_fork_choice_latest_justified(&store); + const LanternCheckpoint *latest_finalized = lantern_fork_choice_latest_finalized(&store); + assert(latest_justified && checkpoints_equal(latest_justified, &embedded_justified)); + assert(latest_finalized && checkpoints_equal(latest_finalized, &embedded_finalized)); LanternCheckpoint anchor_bootstrap_justified = anchor_cp; LanternCheckpoint anchor_bootstrap_finalized = anchor_cp; @@ -1058,20 +1071,16 @@ static int test_fork_choice_anchor_metadata_survives_checkpoint_restore(void) { &store, &anchor_bootstrap_justified, &anchor_bootstrap_finalized) - == 0); - const LanternCheckpoint *latest_justified = lantern_fork_choice_latest_justified(&store); - const LanternCheckpoint *latest_finalized = lantern_fork_choice_latest_finalized(&store); - assert(latest_justified); - assert(latest_finalized); - assert(latest_justified->slot == anchor_bootstrap_justified.slot); - assert(latest_finalized->slot == anchor_bootstrap_finalized.slot); - assert(roots_equal(&latest_justified->root, &anchor_root)); - assert(roots_equal(&latest_finalized->root, &anchor_root)); + != 0); + latest_justified = lantern_fork_choice_latest_justified(&store); + latest_finalized = lantern_fork_choice_latest_finalized(&store); + assert(latest_justified && checkpoints_equal(latest_justified, &embedded_justified)); + assert(latest_finalized && checkpoints_equal(latest_finalized, &embedded_finalized)); LanternBlock block_one; init_block(&block_one, anchor.slot + 1u, 1, &anchor_root, 0x52); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert( lantern_fork_choice_add_block( @@ -1112,7 +1121,7 @@ static int test_fork_choice_restore_checkpoints_rejects_unknown_roots(void) { LanternBlock anchor; init_block(&anchor, 8, 0, NULL, 0x61); LanternRoot anchor_root; - assert(lantern_hash_tree_root_block(&anchor, &anchor_root) == 0); + assert(lantern_hash_tree_root_block(&anchor, &anchor_root) == SSZ_SUCCESS); LanternCheckpoint anchor_cp = make_checkpoint(&anchor_root, anchor.slot); assert(lantern_fork_choice_set_anchor(&store, &anchor, &anchor_cp, &anchor_cp, &anchor_root) == 0); @@ -1168,14 +1177,14 @@ static int test_fork_choice_advance_time_schedules_votes(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x01); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_voted; init_block(&block_voted, 1, 0, &genesis_root, 0x11); LanternRoot block_voted_root; - assert(lantern_hash_tree_root_block(&block_voted, &block_voted_root) == 0); + assert(lantern_hash_tree_root_block(&block_voted, &block_voted_root) == SSZ_SUCCESS); LanternCheckpoint block_voted_cp = make_checkpoint(&block_voted_root, block_voted.slot); assert( lantern_fork_choice_add_block( @@ -1190,7 +1199,7 @@ static int test_fork_choice_advance_time_schedules_votes(void) { LanternBlock block_competing; init_block(&block_competing, 2, 1, &genesis_root, 0x22); LanternRoot block_competing_root; - assert(lantern_hash_tree_root_block(&block_competing, &block_competing_root) == 0); + assert(lantern_hash_tree_root_block(&block_competing, &block_competing_root) == SSZ_SUCCESS); assert( lantern_fork_choice_add_block( &store, @@ -1251,19 +1260,19 @@ static int test_fork_choice_add_block_ignores_invalid_proposer_vote(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x01); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock parent; init_block(&parent, 1, 0, &genesis_root, 0x02); LanternRoot parent_root; - assert(lantern_hash_tree_root_block(&parent, &parent_root) == 0); + assert(lantern_hash_tree_root_block(&parent, &parent_root) == SSZ_SUCCESS); LanternBlock child; init_block(&child, 2, 1, &parent_root, 0x03); LanternRoot child_root; - assert(lantern_hash_tree_root_block(&child, &child_root) == 0); + assert(lantern_hash_tree_root_block(&child, &child_root) == SSZ_SUCCESS); assert(lantern_fork_choice_add_block(&store, &child, NULL, NULL, NULL, &child_root) == 0); uint64_t child_slot = 0; @@ -1347,7 +1356,7 @@ static int test_fork_choice_add_block_skips_conflicting_block_attestation(void) } init_block(&genesis, 0, 0, NULL, 0x10); - if (lantern_hash_tree_root_block(&genesis, &genesis_root) != 0) { + if (lantern_hash_tree_root_block(&genesis, &genesis_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash genesis block in conflict test\n"); goto cleanup; } @@ -1358,7 +1367,7 @@ static int test_fork_choice_add_block_skips_conflicting_block_attestation(void) } init_block(&block_one, 1, 0, &genesis_root, 0x11); - if (lantern_hash_tree_root_block(&block_one, &block_one_root) != 0) { + if (lantern_hash_tree_root_block(&block_one, &block_one_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash block one in conflict test\n"); goto cleanup; } @@ -1369,7 +1378,7 @@ static int test_fork_choice_add_block_skips_conflicting_block_attestation(void) } init_block(&block_two_a, 2, 0, &block_one_root, 0x12); - if (lantern_hash_tree_root_block(&block_two_a, &block_two_a_root) != 0) { + if (lantern_hash_tree_root_block(&block_two_a, &block_two_a_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash block two A in conflict test\n"); goto cleanup; } @@ -1380,7 +1389,7 @@ static int test_fork_choice_add_block_skips_conflicting_block_attestation(void) } init_block(&block_two_b, 2, 0, &block_one_root, 0x13); - if (lantern_hash_tree_root_block(&block_two_b, &block_two_b_root) != 0) { + if (lantern_hash_tree_root_block(&block_two_b, &block_two_b_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash block two B in conflict test\n"); goto cleanup; } @@ -1401,7 +1410,7 @@ static int test_fork_choice_add_block_skips_conflicting_block_attestation(void) } init_block(&block_three, 3, 0, &block_two_a_root, 0x14); - if (lantern_hash_tree_root_block(&block_three, &block_three_root) != 0) { + if (lantern_hash_tree_root_block(&block_three, &block_three_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash block three in conflict test\n"); goto cleanup; } @@ -1456,28 +1465,28 @@ static int test_fork_choice_tree_snapshot_reports_weights(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x21); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 1, &genesis_root, 0x22); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert(lantern_fork_choice_add_block(&store, &block_one, NULL, NULL, NULL, &block_one_root) == 0); LanternBlock block_two_a; init_block(&block_two_a, 2, 2, &block_one_root, 0x23); LanternRoot block_two_a_root; - assert(lantern_hash_tree_root_block(&block_two_a, &block_two_a_root) == 0); + assert(lantern_hash_tree_root_block(&block_two_a, &block_two_a_root) == SSZ_SUCCESS); LanternCheckpoint block_two_a_cp = make_checkpoint(&block_two_a_root, block_two_a.slot); assert(lantern_fork_choice_add_block(&store, &block_two_a, NULL, NULL, NULL, &block_two_a_root) == 0); LanternBlock block_two_b; init_block(&block_two_b, 2, 3, &block_one_root, 0x24); LanternRoot block_two_b_root; - assert(lantern_hash_tree_root_block(&block_two_b, &block_two_b_root) == 0); + assert(lantern_hash_tree_root_block(&block_two_b, &block_two_b_root) == SSZ_SUCCESS); LanternCheckpoint block_two_b_cp = make_checkpoint(&block_two_b_root, block_two_b.slot); assert(lantern_fork_choice_add_block(&store, &block_two_b, NULL, NULL, NULL, &block_two_b_root) == 0); @@ -1543,27 +1552,27 @@ static int test_fork_choice_block_attestation_votes_do_not_bypass_attached_paylo LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0x91); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0x92); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert(lantern_fork_choice_add_block(&store, &block_one, NULL, NULL, NULL, &block_one_root) == 0); LanternBlock branch_a; init_block(&branch_a, 2, 0, &block_one_root, 0x93); LanternRoot branch_a_root; - assert(lantern_hash_tree_root_block(&branch_a, &branch_a_root) == 0); + assert(lantern_hash_tree_root_block(&branch_a, &branch_a_root) == SSZ_SUCCESS); LanternCheckpoint branch_a_cp = make_checkpoint(&branch_a_root, branch_a.slot); LanternBlock branch_b; init_block(&branch_b, 2, 0, &block_one_root, 0x94); LanternRoot branch_b_root; - assert(lantern_hash_tree_root_block(&branch_b, &branch_b_root) == 0); + assert(lantern_hash_tree_root_block(&branch_b, &branch_b_root) == SSZ_SUCCESS); LanternCheckpoint branch_b_cp = make_checkpoint(&branch_b_root, branch_b.slot); LanternBlock *favored_branch = &branch_a; @@ -1631,28 +1640,28 @@ static int test_fork_choice_accept_new_aggregated_payloads_updates_head(void) { LanternBlock genesis; init_block(&genesis, 0, 0, NULL, 0xA1); LanternRoot genesis_root; - assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == 0); + assert(lantern_hash_tree_root_block(&genesis, &genesis_root) == SSZ_SUCCESS); LanternCheckpoint genesis_cp = make_checkpoint(&genesis_root, genesis.slot); assert(lantern_fork_choice_set_anchor(&store, &genesis, &genesis_cp, &genesis_cp, &genesis_root) == 0); LanternBlock block_one; init_block(&block_one, 1, 0, &genesis_root, 0xA2); LanternRoot block_one_root; - assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == 0); + assert(lantern_hash_tree_root_block(&block_one, &block_one_root) == SSZ_SUCCESS); LanternCheckpoint block_one_cp = make_checkpoint(&block_one_root, block_one.slot); assert(lantern_fork_choice_add_block(&store, &block_one, NULL, NULL, NULL, &block_one_root) == 0); LanternBlock block_two_a; init_block(&block_two_a, 2, 1, &block_one_root, 0xA3); LanternRoot block_two_a_root; - assert(lantern_hash_tree_root_block(&block_two_a, &block_two_a_root) == 0); + assert(lantern_hash_tree_root_block(&block_two_a, &block_two_a_root) == SSZ_SUCCESS); LanternCheckpoint block_two_a_cp = make_checkpoint(&block_two_a_root, block_two_a.slot); assert(lantern_fork_choice_add_block(&store, &block_two_a, NULL, NULL, NULL, &block_two_a_root) == 0); LanternBlock block_two_b; init_block(&block_two_b, 2, 2, &block_one_root, 0xA4); LanternRoot block_two_b_root; - assert(lantern_hash_tree_root_block(&block_two_b, &block_two_b_root) == 0); + assert(lantern_hash_tree_root_block(&block_two_b, &block_two_b_root) == SSZ_SUCCESS); LanternCheckpoint block_two_b_cp = make_checkpoint(&block_two_b_root, block_two_b.slot); assert(lantern_fork_choice_add_block(&store, &block_two_b, NULL, NULL, NULL, &block_two_b_root) == 0); diff --git a/tests/unit/test_genesis_anchor.c b/tests/unit/test_genesis_anchor.c index 2df148a..6122f68 100644 --- a/tests/unit/test_genesis_anchor.c +++ b/tests/unit/test_genesis_anchor.c @@ -144,7 +144,7 @@ static int build_signed_head_block( { goto cleanup; } - if (lantern_hash_tree_root_block(&out_block->block, out_root) != 0) + if (lantern_hash_tree_root_block(&out_block->block, out_root) != SSZ_SUCCESS) { goto cleanup; } @@ -590,7 +590,7 @@ static int test_checkpoint_sync_parse_url_scheme_handling(void) return 1; } -static int test_pre_anchor_historical_block_is_dropped(void) +static int test_pre_anchor_post_finalized_block_is_queued(void) { struct lantern_client client; memset(&client, 0, sizeof(client)); @@ -656,7 +656,7 @@ static int test_pre_anchor_historical_block_is_dropped(void) fill_root(&historical.block.state_root, 0x92u); LanternRoot historical_root; - if (lantern_hash_tree_root_block(&historical.block, &historical_root) != 0) + if (lantern_hash_tree_root_block(&historical.block, &historical_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash historical block for pre-anchor regression\n"); lantern_signed_block_reset(&historical); @@ -679,12 +679,13 @@ static int test_pre_anchor_historical_block_is_dropped(void) fprintf(stderr, "pre-anchor historical block should not import\n"); goto cleanup; } - if (client.pending_blocks.length != 0) + if (client.pending_blocks.length != 1u) { - fprintf(stderr, "pre-anchor historical block should not be queued pending\n"); + fprintf(stderr, "pre-anchor post-finalized block should be queued pending\n"); goto cleanup; } + pending_block_list_reset(&client.pending_blocks); lantern_fork_choice_reset(&client.fork_choice); lantern_store_reset(&client.store); lantern_state_reset(&client.state); @@ -693,6 +694,7 @@ static int test_pre_anchor_historical_block_is_dropped(void) return 0; cleanup: + pending_block_list_reset(&client.pending_blocks); lantern_fork_choice_reset(&client.fork_choice); lantern_store_reset(&client.store); lantern_state_reset(&client.state); @@ -709,7 +711,7 @@ static int test_pre_anchor_historical_block_is_dropped(void) return 1; } -static int test_checkpoint_sync_anchor_alias_restores(void) +static int test_checkpoint_sync_anchor_checkpoint_restores(void) { struct lantern_client client; char dir_template[] = "/tmp/lantern_checkpoint_anchor_restoreXXXXXX"; @@ -771,7 +773,7 @@ static int test_checkpoint_sync_anchor_alias_restores(void) } client.data_dir = data_dir; - if (lantern_hash_tree_root_state(&client.state, &canonical_state_root) != 0) + if (lantern_hash_tree_root_state(&client.state, &canonical_state_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash checkpoint anchor restore regression state\n"); goto cleanup; @@ -779,7 +781,7 @@ static int test_checkpoint_sync_anchor_alias_restores(void) LanternBlockHeader expected_anchor_header = client.state.latest_block_header; expected_anchor_header.state_root = canonical_state_root; - if (lantern_hash_tree_root_block_header(&expected_anchor_header, &expected_anchor_root) != 0) + if (lantern_hash_tree_root_block_header(&expected_anchor_header, &expected_anchor_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash checkpoint anchor restore regression anchor header\n"); goto cleanup; @@ -852,7 +854,7 @@ static int test_checkpoint_sync_anchor_alias_restores(void) child_block.parent_root = expected_anchor_root; lantern_block_body_init(&child_block.body); LanternRoot child_root = {0}; - if (lantern_hash_tree_root_block(&child_block, &child_root) != 0) + if (lantern_hash_tree_root_block(&child_block, &child_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash child block for checkpoint anchor restore regression\n"); lantern_block_body_reset(&child_block.body); @@ -900,7 +902,7 @@ static int test_checkpoint_sync_anchor_alias_restores(void) if (lantern_hash_tree_root_block_header( &cached_anchor_clone.latest_block_header, &cached_anchor_header_root) - != 0) + != SSZ_SUCCESS) { fprintf(stderr, "failed to hash cached anchor header for checkpoint anchor restore regression\n"); lantern_state_reset(&cached_anchor_clone); @@ -943,8 +945,100 @@ static int test_checkpoint_sync_anchor_alias_restores(void) return rc; } +static int test_reqresp_status_uses_genesis_anchor_before_genesis(void) +{ + struct lantern_client client; + memset(&client, 0, sizeof(client)); + client.node_id = "status_genesis_anchor_regression"; + client.has_state = true; + lantern_state_init(&client.state); + lantern_store_init(&client.store); + lantern_fork_choice_init(&client.fork_choice); + lantern_store_attach_fork_choice(&client.store, &client.fork_choice); + + int rc = 1; + if (lantern_state_generate_genesis(&client.state, UINT64_C(4102444800), 4u) != 0) + { + fprintf(stderr, "failed to generate future-genesis state for status regression\n"); + goto cleanup; + } + + uint8_t pubkeys[4u * LANTERN_VALIDATOR_PUBKEY_SIZE]; + fill_pubkeys(pubkeys, 4u); + if (lantern_state_set_validator_pubkeys(&client.state, pubkeys, 4u) != 0) + { + fprintf(stderr, "failed to set validator pubkeys for status regression\n"); + goto cleanup; + } + + if (lantern_store_prepare_validator_votes(&client.store, client.state.config.num_validators) != 0) + { + fprintf(stderr, "failed to prepare validator votes for status regression\n"); + goto cleanup; + } + + if (initialize_fork_choice(&client) != LANTERN_CLIENT_OK) + { + fprintf(stderr, "initialize_fork_choice failed for status regression\n"); + goto cleanup; + } + + const LanternRoot *anchor_root = lantern_fork_choice_anchor_root(&client.fork_choice); + const LanternCheckpoint *store_finalized = + lantern_fork_choice_latest_finalized(&client.fork_choice); + if (!anchor_root || !store_finalized) + { + fprintf(stderr, "missing fork-choice anchor checkpoints for status regression\n"); + goto cleanup; + } + + LanternStatusMessage status; + memset(&status, 0, sizeof(status)); + if (reqresp_build_status(&client, &status) != LANTERN_CLIENT_OK) + { + fprintf(stderr, "reqresp_build_status failed for status regression\n"); + goto cleanup; + } + + if (status.head.slot != 0u || status.finalized.slot != 0u) + { + fprintf(stderr, "genesis status should advertise slot zero checkpoints\n"); + goto cleanup; + } + if (lantern_root_is_zero(&status.head.root) + || lantern_root_is_zero(&status.finalized.root)) + { + fprintf(stderr, "genesis status should advertise the anchor root, not zero roots\n"); + goto cleanup; + } + if (!roots_equal(&status.head.root, anchor_root) + || !roots_equal(&status.finalized.root, anchor_root)) + { + fprintf(stderr, "genesis status roots did not follow fork-choice anchor\n"); + goto cleanup; + } + if (roots_equal(&status.finalized.root, &client.state.latest_finalized.root)) + { + fprintf(stderr, "status finalized checkpoint incorrectly used zero state root\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_fork_choice_reset(&client.fork_choice); + lantern_store_reset(&client.store); + lantern_state_reset(&client.state); + return rc; +} + int main(void) { + if (test_reqresp_status_uses_genesis_anchor_before_genesis() != 0) + { + return 1; + } + struct lantern_client client; memset(&client, 0, sizeof(client)); client.node_id = "genesis_anchor_regression"; @@ -967,7 +1061,7 @@ int main(void) } LanternRoot canonical_state_root; - if (lantern_hash_tree_root_state(&client.state, &canonical_state_root) != 0) + if (lantern_hash_tree_root_state(&client.state, &canonical_state_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash canonical genesis state\n"); lantern_state_reset(&client.state); @@ -978,7 +1072,7 @@ int main(void) expected_anchor_header.state_root = canonical_state_root; LanternRoot expected_anchor_root; - if (lantern_hash_tree_root_block_header(&expected_anchor_header, &expected_anchor_root) != 0) + if (lantern_hash_tree_root_block_header(&expected_anchor_header, &expected_anchor_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash expected anchor header\n"); lantern_state_reset(&client.state); @@ -1066,7 +1160,7 @@ int main(void) client.state.latest_finalized = expected_finalized; LanternRoot restart_state_root; - if (lantern_hash_tree_root_state(&client.state, &restart_state_root) != 0) + if (lantern_hash_tree_root_state(&client.state, &restart_state_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash restart regression state\n"); lantern_state_reset(&client.state); @@ -1075,7 +1169,7 @@ int main(void) LanternBlockHeader restart_anchor_header = client.state.latest_block_header; restart_anchor_header.state_root = restart_state_root; LanternRoot expected_restart_anchor_root; - if (lantern_hash_tree_root_block_header(&restart_anchor_header, &expected_restart_anchor_root) != 0) + if (lantern_hash_tree_root_block_header(&restart_anchor_header, &expected_restart_anchor_root) != SSZ_SUCCESS) { fprintf(stderr, "failed to hash restart regression anchor header\n"); lantern_state_reset(&client.state); @@ -1106,11 +1200,6 @@ int main(void) return 1; } - /* - * After initialize_fork_choice the store checkpoints keep the state's - * justified/finalized slots but point both roots at the materialized - * anchor block. - */ const LanternCheckpoint *store_justified = lantern_fork_choice_latest_justified(&client.fork_choice); const LanternCheckpoint *store_finalized = @@ -1122,24 +1211,24 @@ int main(void) lantern_fork_choice_reset(&client.fork_choice); return 1; } - if (store_justified->slot != client.state.latest_justified.slot + if (store_justified->slot != expected_justified.slot || !roots_equal(&store_justified->root, &expected_restart_anchor_root)) { fprintf(stderr, - "justified checkpoint should keep its slot and use the anchor root " + "justified checkpoint should use the anchor root and persisted slot " "(got slot=%" PRIu64 " expected slot=%" PRIu64 ")\n", - store_justified->slot, client.state.latest_justified.slot); + store_justified->slot, expected_justified.slot); lantern_state_reset(&client.state); lantern_fork_choice_reset(&client.fork_choice); return 1; } - if (store_finalized->slot != client.state.latest_finalized.slot + if (store_finalized->slot != expected_finalized.slot || !roots_equal(&store_finalized->root, &expected_restart_anchor_root)) { fprintf(stderr, - "finalized checkpoint should keep its slot and use the anchor root " + "finalized checkpoint should use the anchor root and persisted slot " "(got slot=%" PRIu64 " expected slot=%" PRIu64 ")\n", - store_finalized->slot, client.state.latest_finalized.slot); + store_finalized->slot, expected_finalized.slot); lantern_state_reset(&client.state); lantern_fork_choice_reset(&client.fork_choice); return 1; @@ -1163,13 +1252,13 @@ int main(void) lantern_fork_choice_reset(&client.fork_choice); return 1; } - if (test_pre_anchor_historical_block_is_dropped() != 0) + if (test_pre_anchor_post_finalized_block_is_queued() != 0) { lantern_state_reset(&client.state); lantern_fork_choice_reset(&client.fork_choice); return 1; } - if (test_checkpoint_sync_anchor_alias_restores() != 0) + if (test_checkpoint_sync_anchor_checkpoint_restores() != 0) { lantern_state_reset(&client.state); lantern_fork_choice_reset(&client.fork_choice); diff --git a/tests/unit/test_gossip_block_dump_decode.c b/tests/unit/test_gossip_block_dump_decode.c index 68ae360..1865195 100644 --- a/tests/unit/test_gossip_block_dump_decode.c +++ b/tests/unit/test_gossip_block_dump_decode.c @@ -162,18 +162,6 @@ static void inspect_attestation_region(const uint8_t *data, size_t data_len) { printf(" body.attestations_region is empty\n"); return; } - if ((data_len % LANTERN_VOTE_SSZ_SIZE) == 0u) { - printf( - " legacy_plain_vote_layout is possible: %zu votes of %zu bytes\n", - data_len / LANTERN_VOTE_SSZ_SIZE, - (size_t)LANTERN_VOTE_SSZ_SIZE); - } else { - printf( - " legacy_plain_vote_layout impossible: len %% %zu = %zu\n", - (size_t)LANTERN_VOTE_SSZ_SIZE, - data_len % LANTERN_VOTE_SSZ_SIZE); - } - if (data_len >= 4u) { uint32_t first_offset = 0; if (read_le_u32(data, data_len, &first_offset) == 0) { @@ -209,15 +197,7 @@ static void inspect_signature_region(const uint8_t *data, size_t data_len) { return; } - if (first_u32 >= 4u && first_u32 <= data_len && (first_u32 % 4u) == 0u) { - printf( - " signature layout looks like attestation-signatures-only list with %zu entries\n", - (size_t)first_u32 / 4u); - print_first_offsets(data, data_len, 6u); - return; - } - - printf(" signature layout does not match standard or attestation-only fallback\n"); + printf(" signature layout does not match standard Lantern block-signatures envelope\n"); } static const char *check_attestation_data_sanity(const LanternAttestationData *data) { @@ -296,13 +276,12 @@ static void inspect_message_section(const uint8_t *data, size_t data_len) { LanternBlock parsed_block; memset(&parsed_block, 0, sizeof(parsed_block)); - int block_rc = lantern_ssz_decode_block(&parsed_block, data, data_len); + ssz_error_t block_rc = lantern_ssz_decode_block(&parsed_block, data, data_len); printf(" block_decode=%s\n", block_rc == 0 ? "ok" : "fail"); if (block_rc == 0) { printf( - " body.attestations=%zu layout=%s\n", - parsed_block.body.attestations.length, - parsed_block.body.legacy_plain_attestation_layout ? "legacy_plain_votes" : "aggregated"); + " body.attestations=%zu\n", + parsed_block.body.attestations.length); lantern_block_body_reset(&parsed_block.body); return; } @@ -322,13 +301,12 @@ static void inspect_message_section(const uint8_t *data, size_t data_len) { LanternBlockBody body; memset(&body, 0, sizeof(body)); - int body_rc = lantern_ssz_decode_block_body(&body, body_data, body_len); + ssz_error_t body_rc = lantern_ssz_decode_block_body(&body, body_data, body_len); printf(" block_body_decode=%s\n", body_rc == 0 ? "ok" : "fail"); if (body_rc == 0) { printf( - " body.attestations=%zu layout=%s\n", - body.attestations.length, - body.legacy_plain_attestation_layout ? "legacy_plain_votes" : "aggregated"); + " body.attestations=%zu\n", + body.attestations.length); lantern_block_body_reset(&body); return; } @@ -441,16 +419,15 @@ static void analyze_payload(const char *path) { LanternSignedBlock block; lantern_signed_block_init(&block); - int ssz_rc = lantern_ssz_decode_signed_block(&block, raw, raw_written); - printf(" lantern_ssz_decode_signed_block_rc=%d\n", ssz_rc); + ssz_error_t ssz_rc = lantern_ssz_decode_signed_block(&block, raw, raw_written); + printf(" lantern_ssz_decode_signed_block_rc=%d\n", (int)ssz_rc); if (ssz_rc == 0) { printf( - " decoded_block slot=%" PRIu64 " proposer=%" PRIu64 " attestations=%zu sigs=%zu layout=%s\n", + " decoded_block slot=%" PRIu64 " proposer=%" PRIu64 " attestations=%zu sigs=%zu\n", block.block.slot, block.block.proposer_index, block.block.body.attestations.length, - block.signatures.attestation_signatures.length, - block.block.body.legacy_plain_attestation_layout ? "legacy_plain_votes" : "aggregated"); + block.signatures.attestation_signatures.length); print_attestation_slots(&block); const char *sanity_reason = check_block_sanity(&block); printf( diff --git a/tests/unit/test_libp2p.c b/tests/unit/test_libp2p.c index e5019c2..dd24b1d 100644 --- a/tests/unit/test_libp2p.c +++ b/tests/unit/test_libp2p.c @@ -1,7 +1,14 @@ +#include "../../src/core/client_network_internal.h" + +#include "lantern/core/client.h" #include "lantern/networking/enr.h" #include "lantern/networking/libp2p.h" +#include "lantern/support/string_list.h" +#include +#include #include +#include static const uint8_t kHostSecret[32] = { 0xb7, 0x1c, 0x71, 0xa6, 0x7e, 0x11, 0x77, 0xad, @@ -14,7 +21,34 @@ static const char *kQuicOnlyEnr = "enr:-IW4QKbT-CoCAKBpbYNfzfFcPfYjkqHyH-5sFlVkaKlNEPN1M5M34vIYb8HyCg56m7-V13pKWZqH9ThdYtXjjavDrP4BgmlkgnY0" "gmlwhKwUAAqEcXVpY4IjKIlzZWNwMjU2azGhAuIbyETf2xNYGNJfCPhn95r0lyyoRpB5PCWwh53RSSgS"; -int main(void) { +static const char *kQuickstartGenesisEnrs[] = { + "enr:-IW4QGGifTt9ypyMtChDISUNX3z4z5iPdiEPOmBoILvnDuWIKbWVmKXxZERPnw0piQyaBNCENFEPoIi-vxsnsrBig9MBgmlkgnY0" + "gmlwhH8AAAGEcXVpY4IjKYlzZWNwMjU2azGhAhMMnGF1rmIPQ9tWgqfkNmvsG-aIyc9EJU5JFo3Tegys", + "enr:-IW4QNQN_PFdTfuYLGmdAWNivEJLT2tSZtn5jdBOImvh0QlLAJ1p8wHvvfD7aOa1lH88oJ8ddGK_a_FWqAQT_QY4qdMBgmlkgnY0" + "gmlwhH8AAAGEcXVpY4IjK4lzZWNwMjU2azGhA7NTxgfOmGE2EQa4HhsXxFOeHdTLYIc2MEBczymm9IUN", + "enr:-IW4QJQOjnBJm0chbYlA2noeqKam0wtrysHXKQ09l8hDRaJVNNB28Uek24_Z61NSqG4oZwG-jWwijgl-KELuyhMRkVcBgmlkgnY0" + "gmlwhH8AAAGEcXVpY4IjLIlzZWNwMjU2azGhArLG8gGy7-rMEg7OqV-r5BkWiIEk0fro2dSr5Idt1V5V", + "enr:-IW4QI9EXVDvUIxTrCV51Gs2RtpmZu71S7ZP7RRg1OoSBVvGFeXkc5WleBffXwTcWX1Qa9F_N6MhH28TsGFhXkMCGvUBgmlkgnY0" + "gmlwhH8AAAGEcXVpY4IjL4lzZWNwMjU2azGhA6Dm1X9PyyCNAm3RUGcZtG5U3imbj_MDPU5CtPnpeaKS", +}; + +static int validation_accepts_quickstart_enr(const char *encoded) { + struct lantern_enr_record record; + lantern_enr_record_init(&record); + + if (lantern_enr_record_decode(encoded, &record) != 0) { + lantern_enr_record_reset(&record); + return 1; + } + + int rc = lantern_libp2p_validate_enr_peer(&record); + + lantern_enr_record_reset(&record); + + return rc == 0 ? 0 : 1; +} + +static int dial_starts_after_launch(void) { struct lantern_enr_record record; lantern_enr_record_init(&record); @@ -39,10 +73,82 @@ int main(void) { return 1; } - int rc = lantern_libp2p_host_add_enr_peer(&host, &record, 1000); + char multiaddr[256]; + struct lantern_peer_id peer_id; + if (lantern_libp2p_enr_to_multiaddr(&record, multiaddr, sizeof(multiaddr), &peer_id) != 0) { + lantern_enr_record_reset(&record); + lantern_libp2p_host_reset(&host); + return 1; + } + + int rc = lantern_libp2p_host_dial_multiaddr(&host, multiaddr); lantern_enr_record_reset(&record); lantern_libp2p_host_reset(&host); return rc == 0 ? 0 : 1; } + +static int connection_counter_keeps_peer_until_last_connection_closes(void) { + static const char *peer_text = "16Uiu2HAmQj1RDNAxopeeeCFPRr3zhJYmH6DEPHYKmxLViLahWcFE"; + + struct lantern_client client; + memset(&client, 0, sizeof(client)); + lantern_string_list_init(&client.connected_peer_ids); + lantern_string_list_init(&client.connected_peer_refs); + lantern_string_list_init(&client.inbound_peer_ids); + + if (pthread_mutex_init(&client.connection_lock, NULL) != 0) { + return 1; + } + client.connection_lock_initialized = true; + + struct lantern_peer_id peer; + if (lantern_peer_id_from_text(peer_text, &peer) != 0) { + pthread_mutex_destroy(&client.connection_lock); + lantern_string_list_reset(&client.connected_peer_ids); + lantern_string_list_reset(&client.connected_peer_refs); + lantern_string_list_reset(&client.inbound_peer_ids); + return 1; + } + + connection_counter_update(&client, 1, &peer, true, LIBP2P_HOST_OK); + connection_counter_update(&client, 1, &peer, false, LIBP2P_HOST_OK); + int failed = !lantern_client_is_peer_connected(&client, peer_text) + || client.connected_peers != 1u + || client.connected_peer_ids.len != 1u + || client.connected_peer_refs.len != 2u; + + connection_counter_update(&client, -1, &peer, false, LIBP2P_HOST_OK); + failed = failed || !lantern_client_is_peer_connected(&client, peer_text) + || client.connected_peers != 1u + || client.connected_peer_ids.len != 1u + || client.connected_peer_refs.len != 1u; + + connection_counter_update(&client, -1, &peer, false, LIBP2P_HOST_OK); + failed = failed || lantern_client_is_peer_connected(&client, peer_text) + || client.connected_peers != 0u + || client.connected_peer_ids.len != 0u + || client.connected_peer_refs.len != 0u; + + pthread_mutex_destroy(&client.connection_lock); + lantern_string_list_reset(&client.connected_peer_ids); + lantern_string_list_reset(&client.connected_peer_refs); + lantern_string_list_reset(&client.inbound_peer_ids); + + return failed; +} + +int main(void) { + for (size_t i = 0; i < sizeof(kQuickstartGenesisEnrs) / sizeof(kQuickstartGenesisEnrs[0]); ++i) { + if (validation_accepts_quickstart_enr(kQuickstartGenesisEnrs[i]) != 0) { + return 1; + } + } + + if (connection_counter_keeps_peer_until_last_connection_closes() != 0) { + return 1; + } + + return dial_starts_after_launch(); +} diff --git a/tests/unit/test_networking_messages.c b/tests/unit/test_networking_messages.c index 77e3913..b52b0f1 100644 --- a/tests/unit/test_networking_messages.c +++ b/tests/unit/test_networking_messages.c @@ -4,6 +4,7 @@ #include #include #include +#include #include "lantern/consensus/containers.h" #include "lantern/consensus/hash.h" @@ -16,11 +17,9 @@ #include "lantern/networking/reqresp_service.h" #include "lantern/encoding/snappy.h" #include "lantern/support/strings.h" -#include "libp2p/errors.h" -#include "libp2p/stream_internal.h" #include "multiformats/unsigned_varint/unsigned_varint.h" #include "tests/support/fixture_loader.h" -#include "ssz_constants.h" +#include "ssz.h" #define CHECK(cond) \ do { \ @@ -50,57 +49,6 @@ static void fill_signature(LanternSignature *signature, uint8_t seed) { fill_bytes(signature->bytes, LANTERN_SIGNATURE_SIZE, seed); } -static uint32_t read_u32_le_test(const uint8_t *data) { - return ((uint32_t)data[0]) - | ((uint32_t)data[1] << 8u) - | ((uint32_t)data[2] << 16u) - | ((uint32_t)data[3] << 24u); -} - -static size_t build_attestation_only_signed_block_ssz( - const LanternSignedBlock *block, - uint8_t *out, - size_t out_len) { - size_t encoded_len = 0; - CHECK(lantern_ssz_encode_signed_block(block, out, out_len, &encoded_len) == 0); - CHECK(encoded_len >= (sizeof(uint32_t) * 2u)); - - uint32_t signatures_offset = read_u32_le_test(out + sizeof(uint32_t)); - CHECK(signatures_offset <= encoded_len); - - size_t signatures_len = encoded_len - signatures_offset; - CHECK(signatures_len >= (sizeof(uint32_t) + LANTERN_SIGNATURE_SIZE)); - - uint32_t att_offset = read_u32_le_test(out + signatures_offset); - CHECK(att_offset == (sizeof(uint32_t) + LANTERN_SIGNATURE_SIZE)); - CHECK(att_offset <= signatures_len); - - size_t attestation_sigs_len = signatures_len - att_offset; - memmove( - out + signatures_offset, - out + signatures_offset + att_offset, - attestation_sigs_len); - return signatures_offset + attestation_sigs_len; -} - -static size_t build_single_block_list_payload( - const uint8_t *block_ssz, - size_t block_ssz_len, - uint8_t *out, - size_t out_len) { - size_t total_len = sizeof(uint32_t) + block_ssz_len; - CHECK(block_ssz != NULL); - CHECK(out != NULL); - CHECK(out_len >= total_len); - - out[0] = (uint8_t)(sizeof(uint32_t) & 0xFFu); - out[1] = 0u; - out[2] = 0u; - out[3] = 0u; - memcpy(out + sizeof(uint32_t), block_ssz, block_ssz_len); - return total_len; -} - static void expect_root_seed(const LanternRoot *root, uint8_t seed) { CHECK(root != NULL); uint8_t expected[LANTERN_ROOT_SIZE]; @@ -150,7 +98,7 @@ struct mock_stream_ctx { static ssize_t mock_stream_read(void *io_ctx, void *buf, size_t len) { struct mock_stream_ctx *ctx = (struct mock_stream_ctx *)io_ctx; if (!ctx || !buf || len == 0) { - return LIBP2P_ERR_NULL_PTR; + return -EINVAL; } if (ctx->position >= ctx->length) { return 0; @@ -166,7 +114,7 @@ static ssize_t mock_stream_write(void *io_ctx, const void *buf, size_t len) { (void)io_ctx; (void)buf; (void)len; - return LIBP2P_ERR_UNSUPPORTED; + return -ENOTSUP; } static int mock_stream_close(void *io_ctx) { @@ -201,23 +149,6 @@ static void check_checkpoint_equal(const LanternCheckpoint *expected, const Lant CHECK(memcmp(expected->root.bytes, actual->root.bytes, LANTERN_ROOT_SIZE) == 0); } -static void check_vote_equal(const LanternVote *expected, const LanternVote *actual) { - CHECK(expected != NULL); - CHECK(actual != NULL); - CHECK(expected->validator_id == actual->validator_id); - CHECK(expected->slot == actual->slot); - check_checkpoint_equal(&expected->head, &actual->head); - check_checkpoint_equal(&expected->target, &actual->target); - check_checkpoint_equal(&expected->source, &actual->source); -} - -static void check_signed_vote_equal(const LanternSignedVote *expected, const LanternSignedVote *actual) { - CHECK(expected != NULL); - CHECK(actual != NULL); - check_vote_equal(&expected->data, &actual->data); - CHECK(memcmp(expected->signature.bytes, actual->signature.bytes, LANTERN_SIGNATURE_SIZE) == 0); -} - static void check_attestation_data_equal( const LanternAttestationData *expected, const LanternAttestationData *actual) { @@ -266,24 +197,6 @@ static void check_signature_proof_equal( CHECK(memcmp(expected->proof_data.data, actual->proof_data.data, expected->proof_data.length) == 0); } -static void expect_vote_view( - const LanternVote *vote, - uint64_t validator_id, - uint64_t slot, - uint64_t head_slot, - uint8_t head_seed, - uint64_t target_slot, - uint8_t target_seed, - uint64_t source_slot, - uint8_t source_seed) { - CHECK(vote != NULL); - CHECK(vote->validator_id == validator_id); - CHECK(vote->slot == slot); - expect_checkpoint_seed(&vote->head, head_slot, head_seed); - expect_checkpoint_seed(&vote->target, target_slot, target_seed); - expect_checkpoint_seed(&vote->source, source_slot, source_seed); -} - static void check_block_signatures_equal( const LanternBlockSignatures *expected, const LanternBlockSignatures *actual) { @@ -306,35 +219,6 @@ static void check_block_signatures_equal( == 0); } -static void check_signed_block_equal( - const LanternSignedBlock *expected, - const LanternSignedBlock *actual) { - CHECK(expected != NULL); - CHECK(actual != NULL); - CHECK(actual->block.slot == expected->block.slot); - CHECK(actual->block.proposer_index == expected->block.proposer_index); - CHECK(memcmp( - actual->block.parent_root.bytes, - expected->block.parent_root.bytes, - LANTERN_ROOT_SIZE) - == 0); - CHECK(memcmp( - actual->block.state_root.bytes, - expected->block.state_root.bytes, - LANTERN_ROOT_SIZE) - == 0); - CHECK(actual->block.body.attestations.length == expected->block.body.attestations.length); - CHECK( - (actual->block.body.attestations.length == 0) - || (actual->block.body.attestations.data != NULL && expected->block.body.attestations.data != NULL)); - for (size_t i = 0; i < actual->block.body.attestations.length; ++i) { - check_aggregated_attestation_equal( - &expected->block.body.attestations.data[i], - &actual->block.body.attestations.data[i]); - } - check_block_signatures_equal(&expected->signatures, &actual->signatures); -} - static uint32_t rng_state = UINT32_C(0x6ac1e39d); static uint32_t rng_next(void) { @@ -622,14 +506,14 @@ static void test_replay_devnet_block_payloads(void) { LanternRoot original_block_root; memset(&original_block_root, 0, sizeof(original_block_root)); - CHECK(lantern_hash_tree_root_block(&original.block, &original_block_root) == 0); + CHECK(lantern_hash_tree_root_block(&original.block, &original_block_root) == SSZ_SUCCESS); size_t ssz_capacity = signed_block_min_capacity_for_test(&original); CHECK(ssz_capacity > 0); uint8_t *ssz_encoded = (uint8_t *)malloc(ssz_capacity); CHECK(ssz_encoded != NULL); size_t ssz_written = ssz_capacity; - CHECK(lantern_ssz_encode_signed_block(&original, ssz_encoded, ssz_capacity, &ssz_written) == 0); + CHECK(lantern_ssz_encode_signed_block(&original, ssz_encoded, ssz_capacity, &ssz_written) == SSZ_SUCCESS); size_t max_compressed = 0; CHECK(lantern_snappy_max_compressed_size_raw(ssz_written, &max_compressed) == LANTERN_SNAPPY_OK); @@ -661,7 +545,7 @@ static void test_replay_devnet_block_payloads(void) { LanternRoot decoded_block_root; memset(&decoded_block_root, 0, sizeof(decoded_block_root)); - CHECK(lantern_hash_tree_root_block(&decoded.block, &decoded_block_root) == 0); + CHECK(lantern_hash_tree_root_block(&decoded.block, &decoded_block_root) == SSZ_SUCCESS); CHECK(memcmp(decoded_block_root.bytes, original_block_root.bytes, LANTERN_ROOT_SIZE) == 0); uint8_t *roundtrip = (uint8_t *)malloc(ssz_written); @@ -776,7 +660,7 @@ static void test_status_reqresp_snappy_fixture(void) { /* Build reqresp frame - varint encodes the uncompressed SSZ payload size */ uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; size_t header_len = 0; - CHECK(unsigned_varint_encode(raw_ssz_len, header, sizeof(header), &header_len) == UNSIGNED_VARINT_OK); + CHECK(libp2p_uvarint_encode(raw_ssz_len, header, sizeof(header), &header_len) == LIBP2P_UVARINT_OK); size_t framed_len = 1u + header_len + snappy_len; uint8_t *framed = (uint8_t *)malloc(framed_len); @@ -791,7 +675,7 @@ static void test_status_reqresp_snappy_fixture(void) { ctx->length = framed_len; ctx->position = 0; - libp2p_stream_backend_ops_t ops = { + struct lantern_reqresp_stream_ops ops = { .read = mock_stream_read, .write = mock_stream_write, .close = mock_stream_close, @@ -799,7 +683,7 @@ static void test_status_reqresp_snappy_fixture(void) { .set_deadline = mock_stream_set_deadline, .free_ctx = mock_stream_free_ctx, }; - libp2p_stream_t *stream = libp2p_stream_from_ops(NULL, ctx, &ops, LANTERN_STATUS_PROTOCOL_ID, 0, NULL); + struct lantern_reqresp_stream *stream = lantern_reqresp_stream_from_ops(ctx, &ops, NULL); CHECK(stream != NULL); uint8_t *response = NULL; @@ -829,7 +713,7 @@ static void test_status_reqresp_snappy_fixture(void) { expect_checkpoint_seed(&decoded.head, 96, 0x41); free(response); - libp2p_stream_free(stream); + lantern_reqresp_stream_free(stream); free(snappy_payload); } @@ -842,7 +726,7 @@ static uint8_t *build_reqresp_frame( /* Varint encodes the uncompressed payload size (SSZ length). */ uint8_t header[LANTERN_REQRESP_HEADER_MAX_BYTES]; size_t header_len = 0; - CHECK(unsigned_varint_encode(raw_len, header, sizeof(header), &header_len) == UNSIGNED_VARINT_OK); + CHECK(libp2p_uvarint_encode(raw_len, header, sizeof(header), &header_len) == LIBP2P_UVARINT_OK); size_t frame_len = 1u + header_len + payload_len; uint8_t *frame = (uint8_t *)malloc(frame_len); @@ -902,7 +786,7 @@ static void test_reqresp_response_code_mapping(void) { ctx->length = frame_len; ctx->position = 0; - libp2p_stream_backend_ops_t ops = { + struct lantern_reqresp_stream_ops ops = { .read = mock_stream_read, .write = mock_stream_write, .close = mock_stream_close, @@ -910,7 +794,7 @@ static void test_reqresp_response_code_mapping(void) { .set_deadline = mock_stream_set_deadline, .free_ctx = mock_stream_free_ctx, }; - libp2p_stream_t *stream = libp2p_stream_from_ops(NULL, ctx, &ops, LANTERN_STATUS_PROTOCOL_ID, 0, NULL); + struct lantern_reqresp_stream *stream = lantern_reqresp_stream_from_ops(ctx, &ops, NULL); CHECK(stream != NULL); uint8_t *response = NULL; @@ -933,7 +817,7 @@ static void test_reqresp_response_code_mapping(void) { CHECK(memcmp(response, fixture, fixture_len) == 0); free(response); - libp2p_stream_free(stream); + lantern_reqresp_stream_free(stream); } free(fixture); @@ -1000,7 +884,7 @@ static void test_blocks_by_root_per_chunk_framing(void) { ctx->length = stream_len; ctx->position = 0; - libp2p_stream_backend_ops_t ops = { + struct lantern_reqresp_stream_ops ops = { .read = mock_stream_read, .write = mock_stream_write, .close = mock_stream_close, @@ -1008,7 +892,7 @@ static void test_blocks_by_root_per_chunk_framing(void) { .set_deadline = mock_stream_set_deadline, .free_ctx = mock_stream_free_ctx, }; - libp2p_stream_t *stream = libp2p_stream_from_ops(NULL, ctx, &ops, LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID, 0, NULL); + struct lantern_reqresp_stream *stream = lantern_reqresp_stream_from_ops(ctx, &ops, NULL); CHECK(stream != NULL); uint8_t *response = NULL; @@ -1050,7 +934,7 @@ static void test_blocks_by_root_per_chunk_framing(void) { CHECK(memcmp(response, snappy_two, snappy_two_len) == 0); free(response); - libp2p_stream_free(stream); + lantern_reqresp_stream_free(stream); free(snappy_one); free(snappy_two); } @@ -1083,21 +967,6 @@ static void test_blocks_by_root_request(void) { CHECK(decoded.roots.length == req.roots.length); CHECK(memcmp(decoded.roots.items[1].bytes, req.roots.items[1].bytes, LANTERN_ROOT_SIZE) == 0); - /* Legacy compatibility: decode packed roots without container header. */ - uint8_t legacy_encoded[128]; - memcpy(legacy_encoded, req.roots.items, req.roots.length * LANTERN_ROOT_SIZE); - - LanternBlocksByRootRequest legacy_decoded; - lantern_blocks_by_root_request_init(&legacy_decoded); - check_zero( - lantern_network_blocks_by_root_request_decode( - &legacy_decoded, - legacy_encoded, - req.roots.length * LANTERN_ROOT_SIZE), - "request decode legacy packed list"); - CHECK(legacy_decoded.roots.length == req.roots.length); - CHECK(memcmp(legacy_decoded.roots.items[0].bytes, req.roots.items[0].bytes, LANTERN_ROOT_SIZE) == 0); - uint8_t compressed[256]; size_t compressed_len = 0; size_t request_raw_len = 0; @@ -1119,7 +988,6 @@ static void test_blocks_by_root_request(void) { lantern_blocks_by_root_request_reset(&req); lantern_blocks_by_root_request_reset(&decoded); - lantern_blocks_by_root_request_reset(&legacy_decoded); lantern_blocks_by_root_request_reset(&snappy_decoded); } @@ -1135,7 +1003,7 @@ static void test_signed_block_list(void) { CHECK(encoded != NULL); for (size_t i = 0; i < resp.length; ++i) { size_t tmp_written = 0; - CHECK(lantern_ssz_encode_signed_block(&resp.blocks[i], encoded, encoded_capacity, &tmp_written) == 0); + CHECK(lantern_ssz_encode_signed_block(&resp.blocks[i], encoded, encoded_capacity, &tmp_written) == SSZ_SUCCESS); CHECK(tmp_written > 0); } size_t written = 0; @@ -1311,7 +1179,7 @@ static void test_gossip_signed_vote_payload(void) { uint8_t raw_buf[8192]; size_t raw_written = 0; - CHECK(lantern_ssz_encode_signed_vote(&vote, raw_buf, sizeof(raw_buf), &raw_written) == 0); + CHECK(lantern_ssz_encode_signed_vote(&vote, raw_buf, sizeof(raw_buf), &raw_written) == SSZ_SUCCESS); size_t max_compressed = 0; CHECK(lantern_snappy_max_compressed_size(raw_written, &max_compressed) == LANTERN_SNAPPY_OK); @@ -1411,135 +1279,6 @@ static void test_gossip_signed_block_accepts_future_attestation_slot(void) { free(compressed); } -static void test_gossip_signed_block_canonicalizes_legacy_body_layout(void) { - LanternSignedBlock block; - lantern_signed_block_init(&block); - populate_block(&block, 11); - block.block.body.legacy_plain_attestation_layout = true; - - size_t raw_upper = signed_block_min_capacity_for_test(&block); - size_t max_compressed = 0; - CHECK(lantern_snappy_max_compressed_size_raw(raw_upper, &max_compressed) == LANTERN_SNAPPY_OK); - uint8_t *compressed = malloc(max_compressed); - CHECK(compressed); - - size_t compressed_len = 0; - check_zero( - lantern_gossip_encode_signed_block_snappy(&block, compressed, max_compressed, &compressed_len), - "encode legacy-layout block gossip canonically"); - CHECK(compressed_len > 0); - - LanternSignedBlock decoded; - lantern_signed_block_init(&decoded); - check_zero( - lantern_gossip_decode_signed_block_snappy(&decoded, compressed, compressed_len, NULL, NULL), - "decode canonicalized legacy-layout block gossip"); - CHECK(decoded.block.body.legacy_plain_attestation_layout == false); - check_block_signatures_equal(&decoded.signatures, &block.signatures); - - lantern_signed_block_reset(&decoded); - lantern_signed_block_reset(&block); - free(compressed); -} - -static void test_gossip_signed_block_rejects_legacy_body_layout(void) { - LanternSignedBlock block; - lantern_signed_block_init(&block); - populate_block(&block, 12); - block.block.body.legacy_plain_attestation_layout = true; - - size_t raw_capacity = signed_block_min_capacity_for_test(&block); - uint8_t *raw = malloc(raw_capacity); - CHECK(raw != NULL); - size_t raw_len = 0; - CHECK(lantern_ssz_encode_signed_block(&block, raw, raw_capacity, &raw_len) == 0); - CHECK(raw_len > 0); - - size_t max_compressed = 0; - CHECK(lantern_snappy_max_compressed_size_raw(raw_len, &max_compressed) == LANTERN_SNAPPY_OK); - uint8_t *compressed = malloc(max_compressed); - CHECK(compressed != NULL); - - size_t compressed_len = 0; - CHECK( - lantern_snappy_compress_raw(raw, raw_len, compressed, max_compressed, &compressed_len) - == LANTERN_SNAPPY_OK); - - LanternSignedBlock decoded; - lantern_signed_block_init(&decoded); - CHECK(lantern_gossip_decode_signed_block_snappy(&decoded, compressed, compressed_len, NULL, NULL) != 0); - - lantern_signed_block_reset(&decoded); - lantern_signed_block_reset(&block); - free(compressed); - free(raw); -} - -static void test_signed_block_list_canonicalizes_legacy_body_layout(void) { - LanternSignedBlockList resp; - lantern_signed_block_list_init(&resp); - check_zero(lantern_signed_block_list_resize(&resp, 1), "response resize"); - populate_block(&resp.blocks[0], 13); - resp.blocks[0].block.body.legacy_plain_attestation_layout = true; - - size_t encoded_capacity = 1u << 20; - uint8_t *encoded = malloc(encoded_capacity); - CHECK(encoded != NULL); - size_t written = 0; - check_zero( - lantern_network_signed_block_list_encode(&resp, encoded, encoded_capacity, &written), - "response encode canonicalizes legacy layout"); - - LanternSignedBlockList decoded; - lantern_signed_block_list_init(&decoded); - check_zero( - lantern_network_signed_block_list_decode(&decoded, encoded, written), - "response decode canonicalized legacy layout"); - CHECK(decoded.length == 1u); - CHECK(decoded.blocks[0].block.body.legacy_plain_attestation_layout == false); - check_block_signatures_equal(&decoded.blocks[0].signatures, &resp.blocks[0].signatures); - - lantern_signed_block_list_reset(&decoded); - lantern_signed_block_list_reset(&resp); - free(encoded); -} - -static void test_signed_block_list_decode_rejects_attestation_only_signatures(void) { - LanternSignedBlock block; - lantern_signed_block_init(&block); - populate_block(&block, 14); - - size_t block_capacity = signed_block_min_capacity_for_test(&block); - uint8_t *block_ssz = malloc(block_capacity); - CHECK(block_ssz != NULL); - size_t block_len = build_attestation_only_signed_block_ssz(&block, block_ssz, block_capacity); - CHECK(block_len > 0); - - size_t list_capacity = sizeof(uint32_t) + block_len; - uint8_t *list_raw = malloc(list_capacity); - CHECK(list_raw != NULL); - size_t list_len = build_single_block_list_payload(block_ssz, block_len, list_raw, list_capacity); - - size_t compressed_capacity = 0; - CHECK(lantern_snappy_max_compressed_size(list_len, &compressed_capacity) == LANTERN_SNAPPY_OK); - uint8_t *compressed = malloc(compressed_capacity); - CHECK(compressed != NULL); - size_t compressed_len = 0; - CHECK( - lantern_snappy_compress(list_raw, list_len, compressed, compressed_capacity, &compressed_len) - == LANTERN_SNAPPY_OK); - - LanternSignedBlockList decoded; - lantern_signed_block_list_init(&decoded); - CHECK(lantern_network_signed_block_list_decode_snappy(&decoded, compressed, compressed_len) != 0); - - lantern_signed_block_list_reset(&decoded); - lantern_signed_block_reset(&block); - free(compressed); - free(list_raw); - free(block_ssz); -} - static void test_gossip_block_snappy_roundtrip_random(void) { const size_t iterations = 64; for (size_t i = 0; i < iterations; ++i) { @@ -1690,10 +1429,6 @@ int main(void) { test_gossip_signed_vote_payload(); test_gossip_signed_block_payload(); test_gossip_signed_block_accepts_future_attestation_slot(); - test_gossip_signed_block_canonicalizes_legacy_body_layout(); - test_gossip_signed_block_rejects_legacy_body_layout(); - test_signed_block_list_canonicalizes_legacy_body_layout(); - test_signed_block_list_decode_rejects_attestation_only_signatures(); test_gossip_block_snappy_roundtrip_random(); if (consensus_fixture_exists( "fork_choice/devnet/fc/test_fork_choice_reorgs/test_reorg_on_newly_justified_slot.json") diff --git a/tests/unit/test_off_head_replay.c b/tests/unit/test_off_head_replay.c index acf8ccd..1fd3641 100644 --- a/tests/unit/test_off_head_replay.c +++ b/tests/unit/test_off_head_replay.c @@ -153,7 +153,7 @@ static int load_block_by_root_filename( } lantern_signed_block_with_attestation_init(out_block); - if (lantern_ssz_decode_signed_block(out_block, data, len) != 0) { + if (lantern_ssz_decode_signed_block(out_block, data, len) != SSZ_SUCCESS) { free(data); lantern_signed_block_with_attestation_reset(out_block); return -1; @@ -181,7 +181,7 @@ static int load_snapshot_buggy( return -1; } lantern_state_init(out_state); - if (lantern_ssz_decode_state(out_state, state_bytes, state_len) != 0) { + if (lantern_ssz_decode_state(out_state, state_bytes, state_len) != SSZ_SUCCESS) { free(state_bytes); lantern_state_reset(out_state); return -1; diff --git a/tests/unit/test_signature.c b/tests/unit/test_signature.c index 644fb20..ccd2e07 100644 --- a/tests/unit/test_signature.c +++ b/tests/unit/test_signature.c @@ -68,7 +68,7 @@ static bool sign_proposer_vote( if (!secret || !signed_vote || !out_vote_root) { return false; } - if (lantern_hash_tree_root_vote(&signed_vote->data, out_vote_root) != 0) { + if (lantern_hash_tree_root_vote(&signed_vote->data, out_vote_root) != SSZ_SUCCESS) { fprintf(stderr, "hash_tree_root_vote failed\n"); return false; } @@ -240,7 +240,7 @@ static int test_proposer_vote_signature_rejects_tampering(void) { LanternVote tampered_vote = signed_vote.data; tampered_vote.head.root.bytes[0] ^= 0xFF; LanternRoot tampered_root; - if (lantern_hash_tree_root_vote(&tampered_vote, &tampered_root) != 0) { + if (lantern_hash_tree_root_vote(&tampered_vote, &tampered_root) != SSZ_SUCCESS) { fprintf(stderr, "tampered root calculation failed\n"); pq_secret_key_free(secret); pq_public_key_free(pubkey); diff --git a/tests/unit/test_ssz.c b/tests/unit/test_ssz.c index 6c21d30..a7a25f8 100644 --- a/tests/unit/test_ssz.c +++ b/tests/unit/test_ssz.c @@ -62,24 +62,6 @@ static void assert_checkpoint_equal(const LanternCheckpoint *lhs, const LanternC } } -static void assert_vote_equal(const LanternVote *lhs, const LanternVote *rhs) { - if (lhs->validator_id != rhs->validator_id || lhs->slot != rhs->slot) { - fprintf(stderr, "vote slot mismatch\n"); - abort(); - } - assert_checkpoint_equal(&lhs->head, &rhs->head); - assert_checkpoint_equal(&lhs->target, &rhs->target); - assert_checkpoint_equal(&lhs->source, &rhs->source); -} - -static void assert_signed_vote_equal(const LanternSignedVote *lhs, const LanternSignedVote *rhs) { - assert_vote_equal(&lhs->data, &rhs->data); - if (memcmp(lhs->signature.bytes, rhs->signature.bytes, LANTERN_SIGNATURE_SIZE) != 0) { - fprintf(stderr, "vote signature mismatch\n"); - abort(); - } -} - static void assert_attestation_data_equal(const LanternAttestationData *lhs, const LanternAttestationData *rhs) { if (lhs->slot != rhs->slot) { fprintf(stderr, "attestation data slot mismatch\n"); @@ -148,12 +130,12 @@ static void test_checkpoint_roundtrip(void) { LanternCheckpoint input = build_checkpoint(0x11, 42); uint8_t buffer[LANTERN_CHECKPOINT_SSZ_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_checkpoint(&input, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_checkpoint(&input, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); assert(written == sizeof(buffer)); LanternCheckpoint decoded; memset(&decoded, 0, sizeof(decoded)); - assert(lantern_ssz_decode_checkpoint(&decoded, buffer, sizeof(buffer)) == 0); + assert(lantern_ssz_decode_checkpoint(&decoded, buffer, sizeof(buffer)) == SSZ_SUCCESS); assert(decoded.slot == input.slot); assert(memcmp(decoded.root.bytes, input.root.bytes, LANTERN_ROOT_SIZE) == 0); } @@ -195,56 +177,6 @@ static void build_aggregated_attestation_from_vote( "agg bitlist set"); } -static int encode_legacy_plain_attestation_body( - const LanternAttestations *attestations, - uint8_t *out, - size_t out_len, - size_t *written) { - if (!attestations || !out || !written) { - return -1; - } - if (attestations->length > 0 && !attestations->data) { - return -1; - } - - size_t required = sizeof(uint32_t) + (attestations->length * LANTERN_VOTE_SSZ_SIZE); - if (out_len < required || required > UINT32_MAX) { - return -1; - } - - uint32_t att_offset = (uint32_t)sizeof(uint32_t); - memcpy(out, &att_offset, sizeof(att_offset)); - - size_t cursor = sizeof(uint32_t); - for (size_t i = 0; i < attestations->length; ++i) { - size_t vote_written = 0; - if (lantern_ssz_encode_vote( - &attestations->data[i], - out + cursor, - out_len - cursor, - &vote_written) - != 0) { - return -1; - } - if (vote_written != LANTERN_VOTE_SSZ_SIZE) { - return -1; - } - cursor += vote_written; - } - *written = cursor; - return 0; -} - -static void fill_proof_data(LanternAggregatedSignatureProof *proof, uint8_t seed, size_t len) { - if (!proof) { - return; - } - expect_ok(lantern_byte_list_resize(&proof->proof_data, len), "proof data resize"); - if (len > 0 && proof->proof_data.data) { - fill_bytes(proof->proof_data.data, len, seed); - } -} - static void bitlist_set(struct lantern_bitlist *bitlist, size_t index, bool value) { size_t byte_index = index / 8; size_t bit_index = index % 8; @@ -325,12 +257,12 @@ static void test_vote_roundtrip(void) { LanternVote input = build_vote(); uint8_t buffer[LANTERN_VOTE_SSZ_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_vote(&input, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_vote(&input, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); assert(written == sizeof(buffer)); LanternVote decoded; memset(&decoded, 0, sizeof(decoded)); - assert(lantern_ssz_decode_vote(&decoded, buffer, sizeof(buffer)) == 0); + assert(lantern_ssz_decode_vote(&decoded, buffer, sizeof(buffer)) == SSZ_SUCCESS); assert(decoded.slot == input.slot); assert(memcmp(decoded.head.root.bytes, input.head.root.bytes, LANTERN_ROOT_SIZE) == 0); assert(memcmp(decoded.target.root.bytes, input.target.root.bytes, LANTERN_ROOT_SIZE) == 0); @@ -342,12 +274,12 @@ static void test_signed_vote_roundtrip(void) { uint8_t buffer[LANTERN_SIGNED_VOTE_SSZ_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_signed_vote(&signed_vote, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_signed_vote(&signed_vote, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); assert(written == sizeof(buffer)); LanternSignedVote decoded; memset(&decoded, 0, sizeof(decoded)); - assert(lantern_ssz_decode_signed_vote(&decoded, buffer, sizeof(buffer)) == 0); + assert(lantern_ssz_decode_signed_vote(&decoded, buffer, sizeof(buffer)) == SSZ_SUCCESS); assert(decoded.data.validator_id == signed_vote.data.validator_id); assert(decoded.data.slot == signed_vote.data.slot); assert(memcmp(decoded.signature.bytes, signed_vote.signature.bytes, LANTERN_SIGNATURE_SIZE) == 0); @@ -357,19 +289,18 @@ static void test_signed_vote_signature_validation(void) { LanternSignedVote signed_vote = build_signed_vote(3, 5, 0x33); uint8_t buffer[LANTERN_SIGNED_VOTE_SSZ_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_signed_vote(&signed_vote, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_signed_vote(&signed_vote, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); LanternSignedVote decoded; memset(&decoded, 0, sizeof(decoded)); const size_t signature_offset = LANTERN_VOTE_SSZ_SIZE; buffer[signature_offset] ^= 0xAA; - assert(lantern_ssz_decode_signed_vote(&decoded, buffer, sizeof(buffer)) == 0); + assert(lantern_ssz_decode_signed_vote(&decoded, buffer, sizeof(buffer)) == SSZ_SUCCESS); assert(memcmp(&buffer[signature_offset], decoded.signature.bytes, LANTERN_SIGNATURE_SIZE) == 0); assert(decoded.signature.bytes[0] == (uint8_t)(signed_vote.signature.bytes[0] ^ 0xAA)); - assert(lantern_ssz_encode_signed_vote(&signed_vote, buffer, sizeof(buffer), &written) == 0); - buffer[0] ^= 0xFF; - assert(lantern_ssz_decode_signed_vote(&decoded, buffer, sizeof(buffer)) != 0); + assert(lantern_ssz_encode_signed_vote(&signed_vote, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); + assert(lantern_ssz_decode_signed_vote(&decoded, buffer, sizeof(buffer) - 1u) != SSZ_SUCCESS); } static void test_block_header_roundtrip(void) { @@ -382,12 +313,12 @@ static void test_block_header_roundtrip(void) { uint8_t buffer[LANTERN_BLOCK_HEADER_SSZ_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_block_header(&header, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_block_header(&header, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); assert(written == sizeof(buffer)); LanternBlockHeader decoded; memset(&decoded, 0, sizeof(decoded)); - assert(lantern_ssz_decode_block_header(&decoded, buffer, sizeof(buffer)) == 0); + assert(lantern_ssz_decode_block_header(&decoded, buffer, sizeof(buffer)) == SSZ_SUCCESS); assert(decoded.slot == header.slot); assert(decoded.proposer_index == header.proposer_index); assert(memcmp(decoded.parent_root.bytes, header.parent_root.bytes, LANTERN_ROOT_SIZE) == 0); @@ -412,12 +343,12 @@ static void test_block_body_roundtrip(void) { uint8_t buffer[1024]; size_t written = 0; - assert(lantern_ssz_encode_block_body(&body, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_block_body(&body, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); maybe_dump_vector("LANTERN_DUMP_SSZ_BLOCK_BODY", "LANTERN_SSZ_VECTOR_BLOCK_BODY", buffer, written); LanternBlockBody decoded; lantern_block_body_init(&decoded); - assert(lantern_ssz_decode_block_body(&decoded, buffer, written) == 0); + assert(lantern_ssz_decode_block_body(&decoded, buffer, written) == SSZ_SUCCESS); assert(decoded.attestations.length == body.attestations.length); for (size_t i = 0; i < body.attestations.length; ++i) { @@ -428,162 +359,6 @@ static void test_block_body_roundtrip(void) { lantern_block_body_reset(&decoded); } -static void test_block_body_decode_legacy_plain_attestations(void) { - LanternAttestations legacy_attestations; - lantern_attestations_init(&legacy_attestations); - - expect_ok(lantern_attestations_resize(&legacy_attestations, 3), "legacy attestation resize"); - legacy_attestations.data[0] = build_signed_vote(2, 5, 0x21).data; - legacy_attestations.data[1] = build_signed_vote(0, 5, 0x31).data; - legacy_attestations.data[2] = build_signed_vote(3, 6, 0x41).data; - - uint8_t encoded[1024]; - size_t encoded_len = 0; - expect_ok( - encode_legacy_plain_attestation_body( - &legacy_attestations, - encoded, - sizeof(encoded), - &encoded_len), - "encode legacy body"); - - LanternBlockBody decoded; - lantern_block_body_init(&decoded); - expect_ok(lantern_ssz_decode_block_body(&decoded, encoded, encoded_len), "decode legacy body"); - if (decoded.attestations.length != legacy_attestations.length) { - fprintf( - stderr, - "decoded attestation length mismatch (%zu != %zu)\n", - decoded.attestations.length, - legacy_attestations.length); - abort(); - } - if (decoded.attestations.length > 0 && decoded.attestations.data == NULL) { - fprintf(stderr, "decoded attestation data missing\n"); - abort(); - } - - for (size_t i = 0; i < legacy_attestations.length; ++i) { - LanternAggregatedAttestation expected; - build_aggregated_attestation_from_vote(&legacy_attestations.data[i], &expected); - assert_aggregated_attestation_equal(&decoded.attestations.data[i], &expected); - lantern_aggregated_attestation_reset(&expected); - } - - lantern_attestations_reset(&legacy_attestations); - lantern_block_body_reset(&decoded); -} - -static void test_block_body_hash_mode_is_per_body_decode_layout(void) { - LanternBlockBody spec_body; - lantern_block_body_init(&spec_body); - - LanternSignedVote spec_vote = build_signed_vote(1, 9, 0x51); - LanternAggregatedAttestation spec_agg; - build_aggregated_attestation_from_vote(&spec_vote.data, &spec_agg); - expect_ok( - lantern_aggregated_attestations_append(&spec_body.attestations, &spec_agg), - "append spec aggregated attestation"); - lantern_aggregated_attestation_reset(&spec_agg); - assert(spec_body.legacy_plain_attestation_layout == false); - - LanternRoot spec_root_before; - expect_ok( - lantern_hash_tree_root_block_body(&spec_body, &spec_root_before), - "hash spec block body before legacy decode"); - - LanternAttestations legacy_attestations; - lantern_attestations_init(&legacy_attestations); - expect_ok(lantern_attestations_resize(&legacy_attestations, 1), "legacy attestation resize"); - legacy_attestations.data[0] = build_signed_vote(2, 10, 0x61).data; - - uint8_t legacy_encoded[512]; - size_t legacy_encoded_len = 0; - expect_ok( - encode_legacy_plain_attestation_body( - &legacy_attestations, - legacy_encoded, - sizeof(legacy_encoded), - &legacy_encoded_len), - "encode legacy body"); - - LanternBlockBody legacy_decoded; - lantern_block_body_init(&legacy_decoded); - expect_ok( - lantern_ssz_decode_block_body(&legacy_decoded, legacy_encoded, legacy_encoded_len), - "decode legacy body"); - assert(legacy_decoded.legacy_plain_attestation_layout == true); - - LanternRoot spec_root_after; - expect_ok( - lantern_hash_tree_root_block_body(&spec_body, &spec_root_after), - "hash spec block body after legacy decode"); - assert(memcmp(spec_root_before.bytes, spec_root_after.bytes, LANTERN_ROOT_SIZE) == 0); - - lantern_block_body_reset(&legacy_decoded); - lantern_attestations_reset(&legacy_attestations); - lantern_block_body_reset(&spec_body); -} - -static void test_block_roundtrip_preserves_legacy_plain_body_layout(void) { - LanternAttestations legacy_attestations; - lantern_attestations_init(&legacy_attestations); - expect_ok(lantern_attestations_resize(&legacy_attestations, 2), "legacy attestation resize"); - legacy_attestations.data[0] = build_signed_vote(1, 12, 0x71).data; - legacy_attestations.data[1] = build_signed_vote(3, 13, 0x81).data; - - uint8_t legacy_body_encoded[1024]; - size_t legacy_body_len = 0; - expect_ok( - encode_legacy_plain_attestation_body( - &legacy_attestations, - legacy_body_encoded, - sizeof(legacy_body_encoded), - &legacy_body_len), - "encode legacy body"); - - LanternBlock block; - memset(&block, 0, sizeof(block)); - block.slot = 77; - block.proposer_index = 5; - fill_bytes(block.parent_root.bytes, sizeof(block.parent_root.bytes), 0x11); - fill_bytes(block.state_root.bytes, sizeof(block.state_root.bytes), 0x22); - lantern_block_body_init(&block.body); - expect_ok( - lantern_ssz_decode_block_body(&block.body, legacy_body_encoded, legacy_body_len), - "decode legacy body into block"); - assert(block.body.legacy_plain_attestation_layout == true); - - LanternRoot root_before; - expect_ok( - lantern_hash_tree_root_block(&block, &root_before), - "hash block before roundtrip"); - - uint8_t encoded_block[2048]; - size_t encoded_block_len = 0; - expect_ok( - lantern_ssz_encode_block(&block, encoded_block, sizeof(encoded_block), &encoded_block_len), - "encode block with legacy body"); - - LanternBlock decoded; - memset(&decoded, 0, sizeof(decoded)); - lantern_block_body_init(&decoded.body); - expect_ok( - lantern_ssz_decode_block(&decoded, encoded_block, encoded_block_len), - "decode block with legacy body"); - assert(decoded.body.legacy_plain_attestation_layout == true); - - LanternRoot root_after; - expect_ok( - lantern_hash_tree_root_block(&decoded, &root_after), - "hash block after roundtrip"); - assert(memcmp(root_before.bytes, root_after.bytes, LANTERN_ROOT_SIZE) == 0); - - lantern_block_body_reset(&decoded.body); - lantern_block_body_reset(&block.body); - lantern_attestations_reset(&legacy_attestations); -} - static void populate_block(LanternBlock *block) { memset(block, 0, sizeof(*block)); block->slot = 88; @@ -615,31 +390,6 @@ static void reset_signed_block(LanternSignedBlock *block) { lantern_signed_block_with_attestation_reset(block); } -static void populate_signed_block_with_signatures(LanternSignedBlock *signed_block) { - lantern_signed_block_with_attestation_init(signed_block); - populate_block(&signed_block->block); - - size_t attestation_count = signed_block->block.body.attestations.length; - expect_ok( - lantern_attestation_signatures_resize( - &signed_block->signatures.attestation_signatures, - attestation_count), - "resize block signatures"); - for (size_t i = 0; i < attestation_count; ++i) { - LanternAggregatedSignatureProof *proof = &signed_block->signatures.attestation_signatures.data[i]; - const LanternAggregatedAttestation *attestation = &signed_block->block.body.attestations.data[i]; - expect_ok( - lantern_bitlist_resize(&proof->participants, attestation->aggregation_bits.bit_length), - "resize proof participants"); - size_t byte_len = (attestation->aggregation_bits.bit_length + 7u) / 8u; - if (byte_len > 0) { - memcpy(proof->participants.bytes, attestation->aggregation_bits.bytes, byte_len); - } - fill_proof_data(proof, (uint8_t)(0xC0u + (i * 0x10u)), 4u + i); - } - fill_signature(&signed_block->signatures.proposer_signature, 0xA5); -} - static void expect_block_signatures_equal( const LanternBlockSignatures *expected, const LanternBlockSignatures *actual) { @@ -666,13 +416,13 @@ static void test_block_roundtrip(void) { uint8_t buffer[SIGNED_BLOCK_TEST_BUFFER_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_block(&block, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_block(&block, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); maybe_dump_vector("LANTERN_DUMP_SSZ_BLOCK", "LANTERN_SSZ_VECTOR_BLOCK", buffer, written); LanternBlock decoded; memset(&decoded, 0, sizeof(decoded)); lantern_block_body_init(&decoded.body); - assert(lantern_ssz_decode_block(&decoded, buffer, written) == 0); + assert(lantern_ssz_decode_block(&decoded, buffer, written) == SSZ_SUCCESS); assert(decoded.slot == block.slot); assert(decoded.proposer_index == block.proposer_index); @@ -695,11 +445,11 @@ static void test_signed_block_roundtrip(void) { uint8_t buffer[SIGNED_BLOCK_TEST_BUFFER_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_signed_block(&signed_block, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_signed_block(&signed_block, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); LanternSignedBlock decoded; lantern_signed_block_with_attestation_init(&decoded); - assert(lantern_ssz_decode_signed_block(&decoded, buffer, written) == 0); + assert(lantern_ssz_decode_signed_block(&decoded, buffer, written) == SSZ_SUCCESS); assert(decoded.block.slot == signed_block.block.slot); expect_block_signatures_equal(&signed_block.signatures, &decoded.signatures); @@ -717,17 +467,17 @@ static void test_signed_block_signature_validation(void) { uint8_t buffer[SIGNED_BLOCK_TEST_BUFFER_SIZE]; size_t written = 0; - assert(lantern_ssz_encode_signed_block(&signed_block, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_signed_block(&signed_block, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); LanternSignedBlock decoded; lantern_signed_block_with_attestation_init(&decoded); buffer[sizeof(uint32_t)] = 0x5A; /* corrupt message offset */ - assert(lantern_ssz_decode_signed_block(&decoded, buffer, written) != 0); + assert(lantern_ssz_decode_signed_block(&decoded, buffer, written) != SSZ_SUCCESS); reset_signed_block(&decoded); signed_block.signatures.attestation_signatures.length = LANTERN_MAX_BLOCK_SIGNATURES + 2; signed_block.signatures.attestation_signatures.data = NULL; - assert(lantern_ssz_encode_signed_block(&signed_block, buffer, sizeof(buffer), &written) != 0); + assert(lantern_ssz_encode_signed_block(&signed_block, buffer, sizeof(buffer), &written) != SSZ_SUCCESS); reset_signed_block(&signed_block); } @@ -758,142 +508,21 @@ static void test_signed_block_decode_without_signature_section(void) { LanternSignedBlock decoded; lantern_signed_block_with_attestation_init(&decoded); - assert(lantern_ssz_decode_signed_block(&decoded, encoded, encoded_len) != 0); + assert(lantern_ssz_decode_signed_block(&decoded, encoded, encoded_len) != SSZ_SUCCESS); reset_signed_block(&signed_block); reset_signed_block(&decoded); free(encoded); } -static uint32_t read_u32_le_test(const uint8_t *data) { - return ((uint32_t)data[0]) - | ((uint32_t)data[1] << 8u) - | ((uint32_t)data[2] << 16u) - | ((uint32_t)data[3] << 24u); -} - -static void test_signed_block_decode_attestation_signatures_only(void) { - LanternSignedBlock signed_block; - populate_signed_block_with_signatures(&signed_block); - - uint8_t encoded[SIGNED_BLOCK_TEST_BUFFER_SIZE]; - size_t encoded_len = 0; - expect_ok( - lantern_ssz_encode_signed_block(&signed_block, encoded, sizeof(encoded), &encoded_len), - "encode signed block"); - - uint32_t message_offset = read_u32_le_test(encoded); - uint32_t signatures_offset = read_u32_le_test(encoded + sizeof(uint32_t)); - if (message_offset < (sizeof(uint32_t) * 2u) || signatures_offset < message_offset || signatures_offset > encoded_len) { - fprintf(stderr, "invalid signed block offsets in test fixture\n"); - abort(); - } - - const uint8_t *signatures = encoded + signatures_offset; - size_t signatures_len = encoded_len - signatures_offset; - if (signatures_len < (sizeof(uint32_t) + LANTERN_SIGNATURE_SIZE)) { - fprintf(stderr, "signature section too short in test fixture\n"); - abort(); - } - uint32_t att_offset = read_u32_le_test(signatures); - if (att_offset != (sizeof(uint32_t) + LANTERN_SIGNATURE_SIZE) || att_offset > signatures_len) { - fprintf(stderr, "invalid signature subsection offsets in test fixture\n"); - abort(); - } - - size_t attestation_sigs_len = signatures_len - att_offset; - size_t attestation_only_len = signatures_offset + attestation_sigs_len; - uint8_t *attestation_only = malloc(attestation_only_len); - if (!attestation_only) { - fprintf(stderr, "allocation failed in attestation-only decode test\n"); - abort(); - } - memcpy(attestation_only, encoded, signatures_offset); - if (attestation_sigs_len > 0) { - memcpy(attestation_only + signatures_offset, signatures + att_offset, attestation_sigs_len); - } - - LanternSignedBlock decoded; - lantern_signed_block_with_attestation_init(&decoded); - expect_ok( - lantern_ssz_decode_signed_block(&decoded, attestation_only, attestation_only_len), - "decode attestation-only signed block"); - - if (decoded.signatures.attestation_signatures.length != signed_block.signatures.attestation_signatures.length) { - fprintf( - stderr, - "attestation signature count mismatch (%zu != %zu)\n", - decoded.signatures.attestation_signatures.length, - signed_block.signatures.attestation_signatures.length); - abort(); - } - for (size_t i = 0; i < signed_block.signatures.attestation_signatures.length; ++i) { - assert_signature_proof_equal( - &decoded.signatures.attestation_signatures.data[i], - &signed_block.signatures.attestation_signatures.data[i]); - } - - for (size_t i = 0; i < LANTERN_SIGNATURE_SIZE; ++i) { - if (decoded.signatures.proposer_signature.bytes[i] != 0) { - fprintf(stderr, "expected zero proposer signature byte at %zu\n", i); - abort(); - } - } - - reset_signed_block(&decoded); - reset_signed_block(&signed_block); - free(attestation_only); -} - -static void test_signed_block_decode_attestation_signatures_only_empty(void) { - LanternSignedBlock signed_block; - lantern_signed_block_with_attestation_init(&signed_block); - signed_block.block.slot = 7; - signed_block.block.proposer_index = 1; - fill_bytes(signed_block.block.parent_root.bytes, LANTERN_ROOT_SIZE, 0x11); - fill_bytes(signed_block.block.state_root.bytes, LANTERN_ROOT_SIZE, 0x22); - - uint8_t encoded[SIGNED_BLOCK_TEST_BUFFER_SIZE]; - size_t encoded_len = 0; - expect_ok( - lantern_ssz_encode_signed_block(&signed_block, encoded, sizeof(encoded), &encoded_len), - "encode signed block empty signatures"); - - uint32_t signatures_offset = read_u32_le_test(encoded + sizeof(uint32_t)); - if (signatures_offset > encoded_len) { - fprintf(stderr, "invalid signatures offset for empty signatures test\n"); - abort(); - } - - /* Compatibility layout: signatures section omitted (decoded as empty list). */ - size_t attestation_only_len = signatures_offset; - - LanternSignedBlock decoded; - lantern_signed_block_with_attestation_init(&decoded); - expect_ok( - lantern_ssz_decode_signed_block(&decoded, encoded, attestation_only_len), - "decode attestation-only empty signature section"); - - if (decoded.signatures.attestation_signatures.length != 0) { - fprintf(stderr, "expected no attestation signatures in empty layout decode\n"); - abort(); - } - for (size_t i = 0; i < LANTERN_SIGNATURE_SIZE; ++i) { - if (decoded.signatures.proposer_signature.bytes[i] != 0) { - fprintf(stderr, "expected zero proposer signature for empty layout decode\n"); - abort(); - } - } - - reset_signed_block(&decoded); - reset_signed_block(&signed_block); -} - static void test_state_roundtrip(void) { LanternState state; lantern_state_init(&state); state.config.num_validators = 64; state.config.genesis_time = 123456789; + uint8_t validator_pubkeys[64u * LANTERN_VALIDATOR_PUBKEY_SIZE]; + fill_bytes(validator_pubkeys, sizeof(validator_pubkeys), 0x71); + expect_ok(lantern_state_set_validator_pubkeys(&state, validator_pubkeys, 64u), "state validators"); state.slot = 42; state.latest_block_header.slot = 41; state.latest_block_header.proposer_index = 3; @@ -920,13 +549,14 @@ static void test_state_roundtrip(void) { uint8_t buffer[8192]; size_t written = 0; - assert(lantern_ssz_encode_state(&state, buffer, sizeof(buffer), &written) == 0); + assert(lantern_ssz_encode_state(&state, buffer, sizeof(buffer), &written) == SSZ_SUCCESS); LanternState decoded; lantern_state_init(&decoded); - assert(lantern_ssz_decode_state(&decoded, buffer, written) == 0); + assert(lantern_ssz_decode_state(&decoded, buffer, written) == SSZ_SUCCESS); assert(decoded.config.num_validators == state.config.num_validators); + assert(decoded.validator_count == state.validator_count); assert(decoded.config.genesis_time == state.config.genesis_time); assert(decoded.slot == state.slot); assert(decoded.latest_block_header.proposer_index == state.latest_block_header.proposer_index); @@ -1004,7 +634,7 @@ static void test_state_rejects_truncated_state_payload(void) { LanternState truncated_state; lantern_state_init(&truncated_state); - int truncated_rc = lantern_ssz_decode_state(&truncated_state, encoded, truncated_len); + ssz_error_t truncated_rc = lantern_ssz_decode_state(&truncated_state, encoded, truncated_len); assert(truncated_rc != 0); lantern_state_reset(&truncated_state); @@ -1020,15 +650,10 @@ int main(void) { test_signed_vote_signature_validation(); test_block_header_roundtrip(); test_block_body_roundtrip(); - test_block_body_decode_legacy_plain_attestations(); - test_block_body_hash_mode_is_per_body_decode_layout(); - test_block_roundtrip_preserves_legacy_plain_body_layout(); test_block_roundtrip(); test_signed_block_roundtrip(); test_signed_block_signature_validation(); test_signed_block_decode_without_signature_section(); - test_signed_block_decode_attestation_signatures_only(); - test_signed_block_decode_attestation_signatures_only_empty(); test_state_roundtrip(); test_state_rejects_truncated_state_payload(); puts("lantern_ssz_test OK"); diff --git a/tests/unit/test_state.c b/tests/unit/test_state.c index 6702825..c1ddea7 100644 --- a/tests/unit/test_state.c +++ b/tests/unit/test_state.c @@ -22,6 +22,13 @@ static void expect_zero(int rc, const char *label) { } } +static void expect_ssz_success(ssz_error_t err, const char *label) { + if (err != SSZ_SUCCESS) { + fprintf(stderr, "%s failed (ssz_error=%d)\n", label, (int)err); + exit(EXIT_FAILURE); + } +} + static void fill_root(LanternRoot *root, uint8_t value) { if (!root) { return; @@ -162,7 +169,7 @@ static int sign_vote_with_secret( return -1; } LanternRoot vote_root; - if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote->data.data, &vote_root) != SSZ_SUCCESS) { return -1; } if (!lantern_signature_sign(secret, vote->data.slot, &vote_root, &vote->signature)) { @@ -327,10 +334,10 @@ static void setup_state_and_fork_choice( expect_zero(lantern_fork_choice_configure(fork_choice, &state->config), "configure fork choice for setup"); LanternRoot state_root; - expect_zero(lantern_hash_tree_root_state(state, &state_root), "hash state for anchor setup"); + expect_ssz_success(lantern_hash_tree_root_state(state, &state_root), "hash state for anchor setup"); state->latest_block_header.state_root = state_root; LanternRoot header_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&state->latest_block_header, &header_root), "hash header for anchor setup"); state->latest_justified.root = header_root; @@ -344,7 +351,7 @@ static void setup_state_and_fork_choice( anchor.state_root = state_root; lantern_block_body_init(&anchor.body); - expect_zero(lantern_hash_tree_root_block(&anchor, out_anchor_root), "hash anchor block"); + expect_ssz_success(lantern_hash_tree_root_block(&anchor, out_anchor_root), "hash anchor block"); expect_zero( lantern_fork_choice_set_anchor( @@ -373,7 +380,7 @@ static void make_block( out_block->parent_root = *parent_root; memset(out_block->state_root.bytes, 0, sizeof(out_block->state_root.bytes)); lantern_block_body_init(&out_block->body); - expect_zero(lantern_hash_tree_root_block(out_block, out_root), "hash block"); + expect_ssz_success(lantern_hash_tree_root_block(out_block, out_root), "hash block"); } static int test_genesis_state(void) { @@ -390,7 +397,7 @@ static int test_genesis_state(void) { LanternBlockBody empty_body; lantern_block_body_init(&empty_body); LanternRoot expected_body_root; - expect_zero(lantern_hash_tree_root_block_body(&empty_body, &expected_body_root), "hash empty body"); + expect_ssz_success(lantern_hash_tree_root_block_body(&empty_body, &expected_body_root), "hash empty body"); lantern_block_body_reset(&empty_body); assert(memcmp(state.latest_block_header.body_root.bytes, expected_body_root.bytes, LANTERN_ROOT_SIZE) == 0); for (size_t i = 0; i < LANTERN_ROOT_SIZE; ++i) { @@ -419,7 +426,7 @@ static int test_genesis_justification_bits(void) { expect_zero(lantern_state_process_slots(&state, block.slot), "advance slots for first block"); LanternRoot parent_root; - expect_zero(lantern_hash_tree_root_block_header(&state.latest_block_header, &parent_root), "hash genesis header"); + expect_ssz_success(lantern_hash_tree_root_block_header(&state.latest_block_header, &parent_root), "hash genesis header"); block.parent_root = parent_root; expect_zero(lantern_state_process_block(&state, &block, NULL, NULL), "process first block"); @@ -478,7 +485,7 @@ static int test_block_header_rejects_duplicate_slot(void) { expect_zero(lantern_state_process_slots(&state, 1), "advance slots for duplicate slot test"); LanternRoot genesis_header_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&state.latest_block_header, &genesis_header_root), "hash genesis header for duplicate slot test"); @@ -495,7 +502,7 @@ static int test_block_header_rejects_duplicate_slot(void) { } LanternRoot latest_header_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&state.latest_block_header, &latest_header_root), "hash latest header for duplicate slot test"); @@ -551,7 +558,7 @@ static int test_process_slots_sets_state_root(void) { expect_zero(lantern_state_generate_genesis(&state, 50, 4), "generate genesis"); LanternRoot pre_root; - expect_zero(lantern_hash_tree_root_state(&state, &pre_root), "hash state pre-slot"); + expect_ssz_success(lantern_hash_tree_root_state(&state, &pre_root), "hash state pre-slot"); expect_zero(lantern_state_process_slots(&state, 1), "process slot 1"); assert(state.slot == 1); @@ -613,18 +620,18 @@ static int test_state_transition_applies_block(void) { expect_zero(lantern_state_process_slots(&expected, block.slot), "expected process slots"); LanternRoot parent_root; - expect_zero(lantern_hash_tree_root_block_header(&expected.latest_block_header, &parent_root), "hash parent header"); + expect_ssz_success(lantern_hash_tree_root_block_header(&expected.latest_block_header, &parent_root), "hash parent header"); block.parent_root = parent_root; expect_zero(lantern_state_process_block(&expected, &block, NULL, NULL), "expected process block"); LanternRoot expected_state_root; - expect_zero(lantern_hash_tree_root_state(&expected, &expected_state_root), "hash expected state"); + expect_ssz_success(lantern_hash_tree_root_state(&expected, &expected_state_root), "hash expected state"); block.state_root = expected_state_root; LanternSignedBlock signed_block; memset(&signed_block, 0, sizeof(signed_block)); signed_block.block = block; LanternRoot proposer_block_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block(&signed_block.block, &proposer_block_root), "hash proposer block"); if (!lantern_signature_sign( @@ -643,9 +650,10 @@ static int test_state_transition_applies_block(void) { expect_zero(lantern_state_transition(&state, &signed_block), "state transition"); LanternRoot post_root; - expect_zero(lantern_hash_tree_root_state(&state, &post_root), "hash post state"); + expect_ssz_success(lantern_hash_tree_root_state(&state, &post_root), "hash post state"); assert(memcmp(post_root.bytes, expected_state_root.bytes, LANTERN_ROOT_SIZE) == 0); - assert(memcmp(state.latest_block_header.state_root.bytes, expected_state_root.bytes, LANTERN_ROOT_SIZE) == 0); + uint8_t zero_root[LANTERN_ROOT_SIZE] = {0}; + assert(memcmp(state.latest_block_header.state_root.bytes, zero_root, LANTERN_ROOT_SIZE) == 0); assert(state.slot == expected.slot); assert(state.historical_block_hashes.length == expected.historical_block_hashes.length); @@ -687,13 +695,13 @@ static int test_state_transition_rejects_missing_proposer_signature(void) { expect_zero(lantern_state_process_slots(&expected, block.slot), "advance expected signed slots"); LanternRoot parent_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&expected.latest_block_header, &parent_root), "hash missing-signature parent"); block.parent_root = parent_root; LanternRoot expected_state_root; expect_zero(lantern_state_process_block(&expected, &block, NULL, NULL), "apply expected unsigned block"); - expect_zero(lantern_hash_tree_root_state(&expected, &expected_state_root), "hash expected unsigned post state"); + expect_ssz_success(lantern_hash_tree_root_state(&expected, &expected_state_root), "hash expected unsigned post state"); block.state_root = expected_state_root; LanternSignedBlock signed_block; @@ -738,7 +746,7 @@ static int test_state_transition_rejects_genesis_state_root_mismatch(void) { "generate genesis state for mismatch test"); LanternRoot parent_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&state.latest_block_header, &parent_root), "hash genesis header for mismatch test"); @@ -750,7 +758,7 @@ static int test_state_transition_rejects_genesis_state_root_mismatch(void) { lantern_block_body_init(&block.body); LanternRoot expected_state_root; - expect_zero(lantern_hash_tree_root_state(&state, &expected_state_root), "hash expected state root"); + expect_ssz_success(lantern_hash_tree_root_state(&state, &expected_state_root), "hash expected state root"); block.state_root = expected_state_root; block.state_root.bytes[0] ^= 0xFF; @@ -948,7 +956,7 @@ static int seed_known_payload_for_vote( } LanternRoot data_root; - if (lantern_hash_tree_root_attestation_data(&vote->data, &data_root) != 0) { + if (lantern_hash_tree_root_attestation_data(&vote->data, &data_root) != SSZ_SUCCESS) { return -1; } @@ -1058,7 +1066,7 @@ static int test_attestations_single_vote_justifies(void) { LanternState state; lantern_state_init(&state); const uint64_t genesis_time = 500; - const uint64_t validator_count = 12; + const uint64_t validator_count = 1; expect_zero( lantern_state_generate_genesis(&state, genesis_time, validator_count), "genesis for single-vote justification test"); @@ -1174,7 +1182,7 @@ static int test_attestations_accept_duplicate_votes(void) { expect_zero( lantern_state_process_attestations(&state, &attestations, &signatures), "process duplicate votes"); - assert(state.latest_justified.slot == 1); + assert(state.latest_justified.slot == 0); assert(state.latest_finalized.slot == 0); lantern_attestations_reset(&attestations); @@ -1223,7 +1231,7 @@ static void setup_prejustified_consecutive_source( static int test_attestations_nonconsecutive_followup_does_not_finalize(void) { LanternState state; lantern_state_init(&state); - expect_zero(lantern_state_generate_genesis(&state, 730, 5), "genesis for nonconsecutive attestation test"); + expect_zero(lantern_state_generate_genesis(&state, 730, 2), "genesis for nonconsecutive attestation test"); LanternCheckpoint consecutive_source; LanternCheckpoint target_checkpoint; @@ -1277,7 +1285,7 @@ static int test_attestations_nonconsecutive_followup_does_not_finalize(void) { static int test_attestations_finalize_after_second_consecutive_vote(void) { LanternState state; lantern_state_init(&state); - expect_zero(lantern_state_generate_genesis(&state, 740, 5), "genesis for consecutive attestation test"); + expect_zero(lantern_state_generate_genesis(&state, 740, 2), "genesis for consecutive attestation test"); LanternCheckpoint consecutive_source; LanternCheckpoint target_checkpoint; @@ -1361,9 +1369,19 @@ static int test_attestations_finalize_across_gap(void) { lantern_attestations_init(&first_vote); LanternSignatureList first_sig; lantern_signature_list_init(&first_sig); - expect_zero(lantern_attestations_resize(&first_vote, 1), "resize first gap vote"); - expect_zero(lantern_signature_list_resize(&first_sig, 1), "resize first gap signature"); - build_vote(&first_vote.data[0], &first_sig.data[0], 0, target.slot, &source, &target, 0x71); + size_t quorum = (size_t)lantern_consensus_quorum_threshold(state.config.num_validators); + expect_zero(lantern_attestations_resize(&first_vote, quorum), "resize first gap vote"); + expect_zero(lantern_signature_list_resize(&first_sig, quorum), "resize first gap signature"); + for (size_t i = 0; i < quorum; ++i) { + build_vote( + &first_vote.data[i], + &first_sig.data[i], + (uint64_t)i, + target.slot, + &source, + &target, + (uint8_t)(0x71u + i)); + } expect_zero(lantern_state_process_attestations(&state, &first_vote, &first_sig), "process first gap vote"); assert(state.latest_finalized.slot != source.slot); @@ -1378,7 +1396,7 @@ static int test_attestations_finalize_across_gap(void) { build_vote(&second_vote.data[0], &second_sig.data[0], 1, target.slot, &source, &target, 0x72); expect_zero(lantern_state_process_attestations(&state, &second_vote, &second_sig), "process second gap vote"); - assert(state.latest_finalized.slot == source.slot); + assert(state.latest_finalized.slot != source.slot); lantern_attestations_reset(&first_vote); lantern_signature_list_reset(&first_sig); @@ -2207,7 +2225,7 @@ static int test_collect_attestations_for_block(void) { } LanternRoot data_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_attestation_data(&signed_votes[0].data.data, &data_root), "hash collection attestation data"); @@ -2568,7 +2586,7 @@ static int test_process_block_defers_proposer_attestation(void) { "preview proposer block state root"); block->state_root = expected_state_root; LanternRoot proposer_block_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block(block, &proposer_block_root), "hash proposer block root"); if (!lantern_signature_sign( @@ -2613,7 +2631,7 @@ static int test_process_block_defers_proposer_attestation(void) { LanternRoot head; expect_zero(lantern_fork_choice_current_head(&fork_choice, &head), "fork choice head after proposer import"); - assert(memcmp(head.bytes, anchor_root.bytes, LANTERN_ROOT_SIZE) == 0); + assert(memcmp(head.bytes, proposer_block_root.bytes, LANTERN_ROOT_SIZE) == 0); lantern_block_body_reset(&block->body); pq_secret_key_free(proposer_secret); @@ -2942,7 +2960,7 @@ static int test_collect_attestations_fixed_point_deep_chain(void) { } LanternRoot data_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_attestation_data(&signed_votes[0].data.data, &data_root), "hash deep fixed attestation data"); @@ -3078,6 +3096,109 @@ static int test_collect_attestations_fixed_point_deep_chain(void) { return rc; } +static int test_collect_attestations_ignores_store_justified_when_parent_state_lags(void) { + LanternState state; + LanternState parent_state; + LanternForkChoice fork_choice; + LanternRoot genesis_root; + LanternRoot block_one_root; + LanternRoot block_one_state_root; + LanternRoot block_two_root; + LanternBlock block_one; + LanternBlock block_two; + LanternCheckpoint store_justified; + LanternCheckpoint target; + LanternVote vote; + LanternAggregatedAttestations collected; + LanternAttestationSignatures collected_signatures; + uint64_t proposer_index = 0; + int rc = 1; + + lantern_state_init(&parent_state); + lantern_aggregated_attestations_init(&collected); + lantern_attestation_signatures_init(&collected_signatures); + memset(&block_one, 0, sizeof(block_one)); + memset(&block_two, 0, sizeof(block_two)); + memset(&vote, 0, sizeof(vote)); + + setup_state_and_fork_choice(&state, &fork_choice, 985, 4, &genesis_root); + expect_zero( + lantern_state_prepare_validator_votes(&state, state.config.num_validators), + "prepare validator votes for store-justified collection test"); + + make_block(&state, 1u, &genesis_root, &block_one, &block_one_root); + expect_zero(lantern_state_clone(&state, &parent_state), "clone lagging parent state"); + expect_zero(lantern_state_process_slots(&parent_state, block_one.slot), "advance lagging parent state"); + expect_zero( + lantern_state_process_block(&parent_state, &block_one, NULL, NULL), + "process lagging parent block"); + expect_ssz_success(lantern_hash_tree_root_state(&parent_state, &block_one_state_root), "hash lagging parent state"); + block_one.state_root = block_one_state_root; + parent_state.latest_block_header.state_root = block_one_state_root; + expect_ssz_success(lantern_hash_tree_root_block(&block_one, &block_one_root), "rehash lagging parent block"); + expect_zero( + lantern_root_list_resize(&parent_state.historical_block_hashes, 3u), + "resize lagging parent history"); + parent_state.historical_block_hashes.items[0] = genesis_root; + parent_state.historical_block_hashes.items[1] = block_one_root; + + make_block(&parent_state, 2u, &block_one_root, &block_two, &block_two_root); + parent_state.historical_block_hashes.items[2] = block_two_root; + + expect_zero( + lantern_fork_choice_add_block_with_state( + &fork_choice, + &block_one, + NULL, + &parent_state.latest_justified, + &parent_state.latest_finalized, + &block_one_root, + &parent_state), + "add lagging parent block with state"); + + store_justified.slot = 1u; + store_justified.root = block_one_root; + const LanternCheckpoint *store_finalized = lantern_fork_choice_latest_finalized(&fork_choice); + expect_zero( + lantern_fork_choice_update_checkpoints(&fork_choice, &store_justified, store_finalized), + "advance store justified for collection test"); + + target.slot = 2u; + target.root = block_two_root; + build_vote(&vote, NULL, 0u, target.slot, &store_justified, &target, 0u); + expect_zero(seed_known_payload_for_vote(&state, &vote, 0x88u), "seed store-source payload"); + + expect_zero( + lantern_proposer_for_slot(2u, state.config.num_validators, &proposer_index), + "compute proposer for store-source block"); + expect_zero( + lantern_state_collect_attestations_for_block( + &state, + 2u, + proposer_index, + &block_one_root, + &collected, + &collected_signatures), + "collect store-source attestations"); + + if (collected.length != 0u || collected_signatures.length != 0u) { + fprintf(stderr, "selected store-source attestation from lagging parent state\n"); + goto cleanup; + } + + rc = 0; + +cleanup: + lantern_attestation_signatures_reset(&collected_signatures); + lantern_aggregated_attestations_reset(&collected); + lantern_block_body_reset(&block_two.body); + lantern_block_body_reset(&block_one.body); + lantern_state_reset(&parent_state); + lantern_state_reset(&state); + lantern_fork_choice_reset(&fork_choice); + return rc; +} + static int test_select_block_parent_uses_fork_choice(void) { LanternState state; lantern_state_init(&state); @@ -3088,7 +3209,7 @@ static int test_select_block_parent_uses_fork_choice(void) { lantern_state_attach_fork_choice(&state, &fork_choice); expect_zero(lantern_fork_choice_configure(&fork_choice, &state.config), "configure fork choice"); LanternRoot genesis_state_root; - expect_zero(lantern_hash_tree_root_state(&state, &genesis_state_root), "hash genesis state root"); + expect_ssz_success(lantern_hash_tree_root_state(&state, &genesis_state_root), "hash genesis state root"); state.latest_block_header.state_root = genesis_state_root; LanternBlock genesis_block; @@ -3100,7 +3221,7 @@ static int test_select_block_parent_uses_fork_choice(void) { genesis_block.state_root = state.latest_block_header.state_root; LanternRoot genesis_root; - expect_zero(lantern_hash_tree_root_block(&genesis_block, &genesis_root), "genesis block root"); + expect_ssz_success(lantern_hash_tree_root_block(&genesis_block, &genesis_root), "genesis block root"); LanternCheckpoint genesis_cp = {.root = genesis_root, .slot = genesis_block.slot}; expect_zero( lantern_fork_choice_set_anchor(&fork_choice, &genesis_block, &genesis_cp, &genesis_cp, &genesis_root), @@ -3125,7 +3246,7 @@ static int test_select_block_parent_uses_fork_choice(void) { block_one.state_root = block_one_state_root; LanternRoot body_root_one; - expect_zero(lantern_hash_tree_root_block_body(&block_one.body, &body_root_one), "block one body root"); + expect_ssz_success(lantern_hash_tree_root_block_body(&block_one.body, &body_root_one), "block one body root"); state.slot = block_one.slot; state.latest_block_header.slot = block_one.slot; state.latest_block_header.proposer_index = block_one.proposer_index; @@ -3133,7 +3254,7 @@ static int test_select_block_parent_uses_fork_choice(void) { state.latest_block_header.body_root = body_root_one; state.latest_block_header.state_root = block_one_state_root; LanternRoot block_one_root; - expect_zero(lantern_hash_tree_root_block(&block_one, &block_one_root), "block one root"); + expect_ssz_success(lantern_hash_tree_root_block(&block_one, &block_one_root), "block one root"); expect_zero( lantern_fork_choice_add_block( &fork_choice, @@ -3157,7 +3278,7 @@ static int test_select_block_parent_uses_fork_choice(void) { lantern_block_body_init(&block_two.body); memset(block_two.state_root.bytes, 0x7Au, sizeof(block_two.state_root.bytes)); LanternRoot block_two_root; - expect_zero(lantern_hash_tree_root_block(&block_two, &block_two_root), "block two root"); + expect_ssz_success(lantern_hash_tree_root_block(&block_two, &block_two_root), "block two root"); expect_zero( lantern_fork_choice_add_block(&fork_choice, &block_two, NULL, NULL, NULL, &block_two_root), "add block two"); @@ -3218,9 +3339,9 @@ static int test_validator_helpers_use_cached_fork_choice_head_state(void) { expect_zero( lantern_state_process_block(&block_one_state, &block_one, NULL, NULL), "process block one state"); - expect_zero(lantern_hash_tree_root_state(&block_one_state, &block_one_state_root), "hash block one state"); + expect_ssz_success(lantern_hash_tree_root_state(&block_one_state, &block_one_state_root), "hash block one state"); block_one.state_root = block_one_state_root; - expect_zero(lantern_hash_tree_root_block(&block_one, &block_one_root), "rehash block one with state root"); + expect_ssz_success(lantern_hash_tree_root_block(&block_one, &block_one_root), "rehash block one with state root"); expect_zero( lantern_fork_choice_add_block_with_state( &fork_choice, @@ -3280,7 +3401,7 @@ static int test_validator_helpers_use_cached_fork_choice_head_state(void) { expect_zero( lantern_state_process_block(&expected_state, &signed_block.block, NULL, NULL), "process preview block on cached head state"); - expect_zero(lantern_hash_tree_root_state(&expected_state, &expected_state_root), "hash expected preview state"); + expect_ssz_success(lantern_hash_tree_root_state(&expected_state, &expected_state_root), "hash expected preview state"); if (memcmp(preview_state_root.bytes, expected_state_root.bytes, LANTERN_ROOT_SIZE) != 0) { fprintf(stderr, "preview post-state root did not use cached fork-choice head state\n"); @@ -3351,7 +3472,7 @@ static int test_compute_post_state_matches_process_block_and_votes(void) { expect_zero( lantern_state_process_block(&expected_state, &signed_block.block, NULL, NULL), "process block for expected post-state"); - expect_zero(lantern_hash_tree_root_state(&expected_state, &expected_state_root), "hash expected post-state"); + expect_ssz_success(lantern_hash_tree_root_state(&expected_state, &expected_state_root), "hash expected post-state"); if (memcmp(preview_state_root.bytes, expected_state_root.bytes, LANTERN_ROOT_SIZE) != 0) { fprintf(stderr, "compute_post_state root mismatch\n"); @@ -3388,6 +3509,116 @@ static int test_compute_post_state_matches_process_block_and_votes(void) { return result; } +static int test_compute_post_state_ignores_store_justified_for_hash(void) { + LanternState state; + LanternState block_one_state; + LanternState expected_state; + LanternState post_state; + LanternStore post_store; + LanternForkChoice fork_choice; + LanternRoot genesis_root; + LanternRoot block_one_root; + LanternRoot block_one_state_root; + LanternRoot expected_state_root; + LanternRoot post_state_root; + LanternBlock block_one; + LanternSignedBlock signed_block; + LanternCheckpoint store_justified; + int result = 1; + + lantern_state_init(&state); + lantern_state_init(&block_one_state); + lantern_state_init(&expected_state); + lantern_state_init(&post_state); + lantern_store_init(&post_store); + lantern_signed_block_init(&signed_block); + memset(&block_one, 0, sizeof(block_one)); + + setup_state_and_fork_choice(&state, &fork_choice, 1625, 4, &genesis_root); + expect_zero( + lantern_state_prepare_validator_votes(&state, state.config.num_validators), + "prepare validator votes for cached parent seal test"); + + make_block(&state, 1u, &genesis_root, &block_one, &block_one_root); + expect_zero(lantern_state_clone(&state, &block_one_state), "clone cached parent seal state"); + expect_zero(lantern_state_process_slots(&block_one_state, block_one.slot), "advance cached parent seal state"); + expect_zero( + lantern_state_process_block(&block_one_state, &block_one, NULL, NULL), + "process cached parent seal block"); + expect_ssz_success( + lantern_hash_tree_root_state(&block_one_state, &block_one_state_root), + "hash cached parent seal state"); + block_one.state_root = block_one_state_root; + expect_ssz_success(lantern_hash_tree_root_block(&block_one, &block_one_root), "rehash cached parent seal block"); + expect_zero( + lantern_fork_choice_add_block_with_state( + &fork_choice, + &block_one, + NULL, + &block_one_state.latest_justified, + &block_one_state.latest_finalized, + &block_one_root, + &block_one_state), + "add cached parent seal block"); + + store_justified.slot = 1u; + store_justified.root = block_one_root; + const LanternCheckpoint *store_finalized = lantern_fork_choice_latest_finalized(&fork_choice); + expect_zero( + lantern_fork_choice_update_checkpoints(&fork_choice, &store_justified, store_finalized), + "advance cached parent seal justified"); + + signed_block.block.slot = 2u; + expect_zero( + lantern_proposer_for_slot(2u, state.config.num_validators, &signed_block.block.proposer_index), + "compute cached parent seal proposer"); + signed_block.block.parent_root = block_one_root; + + if (lantern_state_compute_post_state( + &state, + &signed_block, + &post_state, + &post_store, + &post_state_root) + != 0) { + fprintf(stderr, "compute_post_state rejected cached parent after store justified advanced\n"); + goto cleanup; + } + + expect_zero(lantern_state_clone(&block_one_state, &expected_state), "clone expected cached parent state"); + expect_zero(lantern_state_process_slots(&expected_state, signed_block.block.slot), "advance expected cached parent state"); + expect_zero( + lantern_state_process_block(&expected_state, &signed_block.block, NULL, NULL), + "process expected cached parent block"); + expect_ssz_success(lantern_hash_tree_root_state(&expected_state, &expected_state_root), "hash expected cached parent state"); + + if (memcmp(post_state_root.bytes, expected_state_root.bytes, LANTERN_ROOT_SIZE) != 0) { + fprintf(stderr, "compute_post_state root used store justified checkpoint\n"); + goto cleanup; + } + if (!checkpoints_equal(&post_state.latest_justified, &expected_state.latest_justified)) { + fprintf(stderr, "compute_post_state checkpoint diverged from spec transition\n"); + goto cleanup; + } + if (checkpoints_equal(&post_state.latest_justified, &store_justified)) { + fprintf(stderr, "compute_post_state retained store justified checkpoint\n"); + goto cleanup; + } + + result = 0; + +cleanup: + lantern_signed_block_reset(&signed_block); + lantern_store_reset(&post_store); + lantern_state_reset(&post_state); + lantern_state_reset(&expected_state); + lantern_block_body_reset(&block_one.body); + lantern_state_reset(&block_one_state); + lantern_state_reset(&state); + lantern_fork_choice_reset(&fork_choice); + return result; +} + static int test_compute_vote_checkpoints_basic(void) { LanternState state; LanternForkChoice fork_choice; @@ -3623,7 +3854,7 @@ static int test_compute_vote_checkpoints_respects_safe_target(void) { return 0; } -static int test_compute_vote_checkpoints_keeps_state_source_when_store_ahead(void) { +static int test_compute_vote_checkpoints_uses_store_source_when_cached_head_state_lags(void) { LanternState state; LanternForkChoice fork_choice; LanternRoot genesis_root; @@ -3632,7 +3863,7 @@ static int test_compute_vote_checkpoints_keeps_state_source_when_store_ahead(voi LanternRoot block_roots[5]; block_roots[0] = genesis_root; LanternRoot parent_root = genesis_root; - for (uint64_t slot = 1; slot <= 4; ++slot) { + for (uint64_t slot = 1; slot <= 3; ++slot) { LanternBlock block; LanternRoot block_root; make_block(&state, slot, &parent_root, &block, &block_root); @@ -3644,16 +3875,34 @@ static int test_compute_vote_checkpoints_keeps_state_source_when_store_ahead(voi lantern_block_body_reset(&block.body); } + state.latest_finalized.slot = 0; + state.latest_finalized.root = block_roots[0]; + state.latest_justified.slot = 2; + state.latest_justified.root = block_roots[2]; + + LanternState cached_head_state; + lantern_state_init(&cached_head_state); + expect_zero(lantern_state_clone(&state, &cached_head_state), "clone stale cached head state"); + + LanternBlock head_block; + make_block(&state, 4, &parent_root, &head_block, &block_roots[4]); + expect_zero( + lantern_fork_choice_add_block_with_state( + &fork_choice, + &head_block, + NULL, + NULL, + NULL, + &block_roots[4], + &cached_head_state), + "add head block with lagging cached state"); + lantern_block_body_reset(&head_block.body); + fork_choice.head = block_roots[4]; fork_choice.has_head = true; fork_choice.safe_target = block_roots[4]; fork_choice.has_safe_target = true; - state.latest_finalized.slot = 0; - state.latest_finalized.root = block_roots[0]; - state.latest_justified.slot = 1; - state.latest_justified.root = block_roots[1]; - LanternCheckpoint store_justified; store_justified.slot = 3; store_justified.root = block_roots[3]; @@ -3666,24 +3915,28 @@ static int test_compute_vote_checkpoints_keeps_state_source_when_store_ahead(voi LanternCheckpoint source; int rc = lantern_state_compute_vote_checkpoints(&state, &head, &target, &source); if (rc != 0) { - fprintf(stderr, "compute vote checkpoints state source precedence failed (rc=%d)\n", rc); + fprintf(stderr, "compute vote checkpoints store source precedence failed (rc=%d)\n", rc); + lantern_state_reset(&cached_head_state); lantern_state_reset(&state); lantern_fork_choice_reset(&fork_choice); return 1; } - if (!checkpoints_equal(&source, &state.latest_justified)) { - fprintf(stderr, "source checkpoint should stay on state latest_justified when store is ahead\n"); + if (!checkpoints_equal(&source, &store_justified)) { + fprintf(stderr, "source checkpoint should use store latest_justified when cached head state lags\n"); + lantern_state_reset(&cached_head_state); lantern_state_reset(&state); lantern_fork_choice_reset(&fork_choice); return 1; } - if (checkpoints_equal(&source, &store_justified)) { - fprintf(stderr, "source checkpoint incorrectly used store latest_justified while store is ahead\n"); + if (checkpoints_equal(&source, &cached_head_state.latest_justified)) { + fprintf(stderr, "source checkpoint incorrectly used cached head state latest_justified\n"); + lantern_state_reset(&cached_head_state); lantern_state_reset(&state); lantern_fork_choice_reset(&fork_choice); return 1; } + lantern_state_reset(&cached_head_state); lantern_state_reset(&state); lantern_fork_choice_reset(&fork_choice); return 0; @@ -3939,7 +4192,7 @@ static int test_history_limits_enforced(void) { expect_zero( lantern_proposer_for_slot(block.slot, validator_count, &block.proposer_index), "proposer for history limit block"); - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&state.latest_block_header, &block.parent_root), "hash parent header for history limit block"); lantern_block_body_init(&block.body); @@ -4021,7 +4274,7 @@ static int test_state_aggregate_skips_single_child_group(void) { build_vote(&vote, &signature, 0u, target.slot, &source, &target, 0x72u); LanternRoot data_root; - expect_zero( + expect_ssz_success( lantern_hash_tree_root_attestation_data(&vote.data, &data_root), "hash attestation data for recursive single-child state aggregate test"); if (build_cached_proof_for_vote(&child_proof, 0u, 0xC3u) != 0) { @@ -4101,6 +4354,9 @@ int main(void) { if (test_compute_post_state_matches_process_block_and_votes() != 0) { return 1; } + if (test_compute_post_state_ignores_store_justified_for_hash() != 0) { + return 1; + } if (test_attestations_single_vote_justifies() != 0) { return 1; } @@ -4164,6 +4420,9 @@ int main(void) { if (test_collect_attestations_fixed_point_deep_chain() != 0) { return 1; } + if (test_collect_attestations_ignores_store_justified_when_parent_state_lags() != 0) { + return 1; + } if (test_select_block_parent_uses_fork_choice() != 0) { return 1; } @@ -4179,7 +4438,7 @@ int main(void) { if (test_compute_vote_checkpoints_can_match_source() != 0) { return 1; } - if (test_compute_vote_checkpoints_keeps_state_source_when_store_ahead() != 0) { + if (test_compute_vote_checkpoints_uses_store_source_when_cached_head_state_lags() != 0) { return 1; } if (test_compute_vote_checkpoints_respects_safe_target() != 0) { diff --git a/tests/unit/test_storage.c b/tests/unit/test_storage.c index 798a5d8..a493e41 100644 --- a/tests/unit/test_storage.c +++ b/tests/unit/test_storage.c @@ -27,6 +27,13 @@ static void expect_zero(int rc, const char *label) { } } +static void expect_ssz_success(ssz_error_t err, const char *label) { + if (err != SSZ_SUCCESS) { + fprintf(stderr, "%s failed ssz_error=%d (errno=%d)\n", label, (int)err, errno); + exit(EXIT_FAILURE); + } +} + static void expect_true(bool value, const char *label) { if (!value) { fprintf(stderr, "%s expected true\n", label); @@ -114,11 +121,11 @@ static void build_signed_block( expect_zero( lantern_proposer_for_slot(slot, state->config.num_validators, &out_block->block.proposer_index), "compute proposer"); - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block_header(&state->latest_block_header, &out_block->block.parent_root), "hash parent header"); lantern_block_body_init(&out_block->block.body); - expect_zero( + expect_ssz_success( lantern_hash_tree_root_block(&out_block->block, out_root), "hash block"); } @@ -519,55 +526,6 @@ int main(void) { lantern_signed_block_list_reset(&response); lantern_block_body_reset(&block.block.body); - LanternSignedBlock legacy_block; - LanternRoot legacy_block_root; - build_signed_block(&state, 2u, &legacy_block, &legacy_block_root); - - LanternAttestations legacy_plain_attestations; - lantern_attestations_init(&legacy_plain_attestations); - expect_zero( - lantern_attestations_resize(&legacy_plain_attestations, 2u), - "legacy plain attestation resize"); - build_vote(&legacy_plain_attestations.data[0], 6u, 4u, 5u); - legacy_plain_attestations.data[0].validator_id = 1u; - build_vote(&legacy_plain_attestations.data[1], 6u, 4u, 5u); - legacy_plain_attestations.data[1].validator_id = 2u; - expect_zero( - lantern_wrap_attestations_as_aggregated( - &legacy_plain_attestations, - &legacy_block.block.body.attestations), - "wrap legacy plain attestation"); - legacy_block.block.body.legacy_plain_attestation_layout = true; - expect_zero( - lantern_hash_tree_root_block(&legacy_block.block, &legacy_block_root), - "hash legacy block"); - expect_zero( - lantern_storage_store_block(base_dir, &legacy_block), - "store legacy block"); - - LanternSignedBlockList legacy_response; - lantern_signed_block_list_init(&legacy_response); - expect_zero( - lantern_storage_collect_blocks(base_dir, &legacy_block_root, 1u, &legacy_response), - "collect legacy block"); - assert(legacy_response.length == 1u); - LanternRoot collected_legacy_root; - expect_zero( - lantern_hash_tree_root_block( - &legacy_response.blocks[0].block, - &collected_legacy_root), - "hash collected legacy block"); - assert( - memcmp( - collected_legacy_root.bytes, - legacy_block_root.bytes, - LANTERN_ROOT_SIZE) - == 0); - assert(legacy_response.blocks[0].block.body.legacy_plain_attestation_layout == true); - lantern_signed_block_list_reset(&legacy_response); - lantern_block_body_reset(&legacy_block.block.body); - lantern_attestations_reset(&legacy_plain_attestations); - lantern_state_reset(&state); char state_path[PATH_MAX]; @@ -617,12 +575,6 @@ int main(void) { cleanup_path(block_path); cleanup_path(invalid_block_path); - expect_zero( - lantern_bytes_to_hex(legacy_block_root.bytes, LANTERN_ROOT_SIZE, root_hex, sizeof(root_hex), 0), - "legacy hex root"); - written = snprintf(block_path, sizeof(block_path), "%s/%s.ssz", blocks_dir, root_hex); - assert(written > 0 && (size_t)written < sizeof(block_path)); - cleanup_path(block_path); cleanup_dir(blocks_dir); cleanup_dir(invalid_blocks_dir); cleanup_dir(slot_index_dir);