From 10e9ffe4507bf180285a9d1f4261826c35975d4a Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Sat, 20 Dec 2025 10:03:02 +1000 Subject: [PATCH 1/8] Update --- CMakeLists.txt | 19 ++ src/crypto/hash_sig.c | 410 ++++++++++++++++++++++++------ src/internal/yaml_parser.c | 42 ++- tests/unit/test_yaml_parser_cve.c | 60 +++++ tools/leanSpec | 2 +- 5 files changed, 453 insertions(+), 80 deletions(-) create mode 100644 tests/unit/test_yaml_parser_cve.c diff --git a/CMakeLists.txt b/CMakeLists.txt index fe39444..f0fd014 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,6 +225,24 @@ if(LANTERN_BUILD_TESTS) target_link_libraries(lantern_snappy_test PRIVATE lantern) add_test(NAME lantern_snappy COMMAND lantern_snappy_test) + add_executable(lantern_yaml_parser_cve_test + tests/unit/test_yaml_parser_cve.c + src/internal/yaml_parser.c + ) + target_include_directories( + lantern_yaml_parser_cve_test + PRIVATE + ${PROJECT_SOURCE_DIR}/src + ) + target_compile_options( + lantern_yaml_parser_cve_test + PRIVATE + -fsanitize=address + -fno-omit-frame-pointer + ) + target_link_options(lantern_yaml_parser_cve_test PRIVATE -fsanitize=address) + add_test(NAME lantern_yaml_parser_cve COMMAND lantern_yaml_parser_cve_test) + add_executable(lantern_networking_messages_test tests/unit/test_networking_messages.c tests/support/fixture_loader.c @@ -336,6 +354,7 @@ if(LANTERN_BUILD_TESTS) lantern_state lantern_slot_clock lantern_snappy + lantern_yaml_parser_cve lantern_networking_messages lantern_libp2p lantern_fork_choice diff --git a/src/crypto/hash_sig.c b/src/crypto/hash_sig.c index 140c3e1..141c254 100644 --- a/src/crypto/hash_sig.c +++ b/src/crypto/hash_sig.c @@ -1,7 +1,14 @@ +/** + * @file hash_sig.c + * @brief Hash-signature key loading helpers + * + * Provides helpers to load post-quantum hash signature keys from JSON or SSZ + * data sources. + */ + #include "lantern/crypto/hash_sig.h" -#include -#include +#include #include #include #include @@ -9,128 +16,379 @@ #include "pq-bindings-c-rust.h" -static int read_file_bytes(const char *path, uint8_t **out_data, size_t *out_length) { - if (!path || !out_data || !out_length) { - return -1; +#include "lantern/support/secure_mem.h" + + +/* ============================================================================ + * Constants + * ============================================================================ */ + +static const char HASH_SIG_JSON_SUFFIX[] = ".json"; +static const size_t HASH_SIG_JSON_SUFFIX_LEN = sizeof(HASH_SIG_JSON_SUFFIX) - 1u; + + +/* ============================================================================ + * Error Codes + * ============================================================================ */ + +typedef enum +{ + LANTERN_HASH_SIG_OK = 0, + LANTERN_HASH_SIG_ERR_INVALID_PARAM = -1, + LANTERN_HASH_SIG_ERR_IO = -2, + LANTERN_HASH_SIG_ERR_OUT_OF_MEMORY = -3, + LANTERN_HASH_SIG_ERR_OVERFLOW = -4, + LANTERN_HASH_SIG_ERR_TRUNCATED = -5, + LANTERN_HASH_SIG_ERR_DESERIALIZE = -6, +} lantern_hash_sig_error_t; + + +/* ============================================================================ + * Local Helpers + * ============================================================================ */ + +/** + * @brief Securely clear and free a buffer. + * + * @param data Buffer to clear (may be NULL) + * @param length Buffer length in bytes + * + * @note Thread safety: This function is thread-safe. + */ +static void hash_sig_secure_free(uint8_t *data, size_t length) +{ + if (!data) + { + return; + } + + if (length > 0) + { + lantern_secure_zero(data, length); + } + + free(data); +} + + +/** + * @brief Read a file into a newly allocated buffer. + * + * @param path File path to read + * @param out_data Output buffer (caller owns on success) + * @param out_length Output buffer length in bytes + * + * @return LANTERN_HASH_SIG_OK on success + * @return LANTERN_HASH_SIG_ERR_INVALID_PARAM on invalid inputs or empty file + * @return LANTERN_HASH_SIG_ERR_IO on filesystem errors + * @return LANTERN_HASH_SIG_ERR_OUT_OF_MEMORY on allocation failure + * @return LANTERN_HASH_SIG_ERR_OVERFLOW on size overflow + * @return LANTERN_HASH_SIG_ERR_TRUNCATED on short read + * + * @note Thread safety: This function is thread-safe. + */ +static int read_file_bytes(const char *path, uint8_t **out_data, size_t *out_length) +{ + if (!path || !out_data || !out_length) + { + return LANTERN_HASH_SIG_ERR_INVALID_PARAM; } + + *out_data = NULL; + *out_length = 0; + FILE *fp = fopen(path, "rb"); - if (!fp) { - return -1; + if (!fp) + { + return LANTERN_HASH_SIG_ERR_IO; } - if (fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - return -1; + + int result = LANTERN_HASH_SIG_ERR_IO; + long file_size_long = 0; + size_t file_size = 0; + uint8_t *buffer = NULL; + + if (fseek(fp, 0, SEEK_END) != 0) + { + result = LANTERN_HASH_SIG_ERR_IO; + goto cleanup; } - long file_size = ftell(fp); - if (file_size < 0) { - fclose(fp); - return -1; + + file_size_long = ftell(fp); + if (file_size_long < 0) + { + result = LANTERN_HASH_SIG_ERR_IO; + goto cleanup; } - if (fseek(fp, 0, SEEK_SET) != 0) { - fclose(fp); - return -1; + + if (file_size_long == 0) + { + result = LANTERN_HASH_SIG_ERR_INVALID_PARAM; + goto cleanup; } - uint8_t *buffer = malloc((size_t)file_size); - if (!buffer) { - fclose(fp); - return -1; + + if ((unsigned long long)file_size_long > (unsigned long long)SIZE_MAX) + { + result = LANTERN_HASH_SIG_ERR_OVERFLOW; + goto cleanup; } - size_t read_len = fread(buffer, 1, (size_t)file_size, fp); - fclose(fp); - if (read_len != (size_t)file_size) { - free(buffer); - return -1; + + file_size = (size_t)file_size_long; + + if (fseek(fp, 0, SEEK_SET) != 0) + { + result = LANTERN_HASH_SIG_ERR_IO; + goto cleanup; + } + + buffer = malloc(file_size * sizeof(*buffer)); + if (!buffer) + { + result = LANTERN_HASH_SIG_ERR_OUT_OF_MEMORY; + goto cleanup; + } + + size_t read_len = fread(buffer, sizeof(*buffer), file_size, fp); + if (read_len != file_size) + { + result = LANTERN_HASH_SIG_ERR_TRUNCATED; + goto cleanup; } + *out_data = buffer; *out_length = read_len; - return 0; + buffer = NULL; + result = LANTERN_HASH_SIG_OK; + +cleanup: + if (buffer) + { + hash_sig_secure_free(buffer, file_size); + } + fclose(fp); + return result; +} + + +/** + * @brief Check whether a file path ends with ".json". + * + * @param path File path to check + * + * @return true if the path ends with ".json", false otherwise + * + * @note Thread safety: This function is thread-safe. + */ +static bool is_json_file(const char *path) +{ + if (!path) + { + return false; + } + + size_t len = strlen(path); + if (len < HASH_SIG_JSON_SUFFIX_LEN) + { + return false; + } + + return strcmp(path + len - HASH_SIG_JSON_SUFFIX_LEN, HASH_SIG_JSON_SUFFIX) == 0; } + +/* ============================================================================ + * Public API + * ============================================================================ */ + +/** + * Load a secret key from SSZ-encoded bytes. + * + * @param data Input buffer containing SSZ-encoded secret key bytes + * @param length Length of the input buffer in bytes + * @param out_key Output secret key handle (allocated by the PQ library) + * + * @return LANTERN_HASH_SIG_OK on success + * @return LANTERN_HASH_SIG_ERR_INVALID_PARAM on invalid inputs + * @return LANTERN_HASH_SIG_ERR_DESERIALIZE on parse failure + * + * @note Ownership: Caller must free `*out_key` with `pq_secret_key_free`. + * @note Thread safety: This function is thread-safe. + */ int lantern_hash_sig_load_secret_bytes( const uint8_t *data, size_t length, - struct PQSignatureSchemeSecretKey **out_key) { - if (!data || length == 0 || !out_key) { - return -1; + struct PQSignatureSchemeSecretKey **out_key) +{ + if (!data || length == 0 || !out_key) + { + return LANTERN_HASH_SIG_ERR_INVALID_PARAM; } - // Use SSZ format (compatible with Ream's leanSig) + + *out_key = NULL; + enum PQSigningError rc = pq_secret_key_deserialize(data, length, out_key); - return (rc == Success && out_key && *out_key) ? 0 : -1; + if (rc != Success || !*out_key) + { + return LANTERN_HASH_SIG_ERR_DESERIALIZE; + } + + return LANTERN_HASH_SIG_OK; } + +/** + * Load a public key from SSZ-encoded bytes. + * + * @param data Input buffer containing SSZ-encoded public key bytes + * @param length Length of the input buffer in bytes + * @param out_key Output public key handle (allocated by the PQ library) + * + * @return LANTERN_HASH_SIG_OK on success + * @return LANTERN_HASH_SIG_ERR_INVALID_PARAM on invalid inputs + * @return LANTERN_HASH_SIG_ERR_DESERIALIZE on parse failure + * + * @note Ownership: Caller must free `*out_key` with `pq_public_key_free`. + * @note Thread safety: This function is thread-safe. + */ int lantern_hash_sig_load_public_bytes( const uint8_t *data, size_t length, - struct PQSignatureSchemePublicKey **out_key) { - if (!data || length == 0 || !out_key) { - return -1; + struct PQSignatureSchemePublicKey **out_key) +{ + if (!data || length == 0 || !out_key) + { + return LANTERN_HASH_SIG_ERR_INVALID_PARAM; } - // Use SSZ format (compatible with Ream's leanSig) + + *out_key = NULL; + enum PQSigningError rc = pq_public_key_deserialize(data, length, out_key); - return (rc == Success && out_key && *out_key) ? 0 : -1; -} + if (rc != Success || !*out_key) + { + return LANTERN_HASH_SIG_ERR_DESERIALIZE; + } -static int is_json_file(const char *path) { - if (!path) return 0; - size_t len = strlen(path); - return len > 5 && strcmp(path + len - 5, ".json") == 0; + return LANTERN_HASH_SIG_OK; } + +/** + * Load a secret key from a JSON or SSZ file. + * + * @param path File path to read + * @param out_key Output secret key handle (allocated by the PQ library) + * + * @return LANTERN_HASH_SIG_OK on success + * @return LANTERN_HASH_SIG_ERR_INVALID_PARAM on invalid inputs + * @return LANTERN_HASH_SIG_ERR_IO on filesystem errors + * @return LANTERN_HASH_SIG_ERR_OUT_OF_MEMORY on allocation failure + * @return LANTERN_HASH_SIG_ERR_OVERFLOW on size overflow + * @return LANTERN_HASH_SIG_ERR_TRUNCATED on short read + * @return LANTERN_HASH_SIG_ERR_DESERIALIZE on parse failure + * + * @note Ownership: Caller must free `*out_key` with `pq_secret_key_free`. + * @note Thread safety: This function is thread-safe. + */ int lantern_hash_sig_load_secret_file( const char *path, - struct PQSignatureSchemeSecretKey **out_key) { - if (!path || !out_key) { - return -1; + struct PQSignatureSchemeSecretKey **out_key) +{ + if (!path || !out_key) + { + return LANTERN_HASH_SIG_ERR_INVALID_PARAM; } + + *out_key = NULL; + uint8_t *data = NULL; size_t length = 0; - if (read_file_bytes(path, &data, &length) != 0) { - return -1; + int result = read_file_bytes(path, &data, &length); + if (result != LANTERN_HASH_SIG_OK) + { + return result; } - int rc; - if (is_json_file(path)) { - // JSON format + if (is_json_file(path)) + { enum PQSigningError err = pq_secret_key_from_json(data, length, out_key); - rc = (err == Success && out_key && *out_key) ? 0 : -1; - free(data); - } else { - // SSZ format - rc = lantern_hash_sig_load_secret_bytes(data, length, out_key); - free(data); - } - return rc; + result = (err == Success && *out_key) + ? LANTERN_HASH_SIG_OK + : LANTERN_HASH_SIG_ERR_DESERIALIZE; + } + else + { + result = lantern_hash_sig_load_secret_bytes(data, length, out_key); + } + + hash_sig_secure_free(data, length); + return result; } + +/** + * Load a public key from a JSON or SSZ file. + * + * @param path File path to read + * @param out_key Output public key handle (allocated by the PQ library) + * + * @return LANTERN_HASH_SIG_OK on success + * @return LANTERN_HASH_SIG_ERR_INVALID_PARAM on invalid inputs + * @return LANTERN_HASH_SIG_ERR_IO on filesystem errors + * @return LANTERN_HASH_SIG_ERR_OUT_OF_MEMORY on allocation failure + * @return LANTERN_HASH_SIG_ERR_OVERFLOW on size overflow + * @return LANTERN_HASH_SIG_ERR_TRUNCATED on short read + * @return LANTERN_HASH_SIG_ERR_DESERIALIZE on parse failure + * + * @note Ownership: Caller must free `*out_key` with `pq_public_key_free`. + * @note Thread safety: This function is thread-safe. + */ int lantern_hash_sig_load_public_file( const char *path, - struct PQSignatureSchemePublicKey **out_key) { - if (!path || !out_key) { - return -1; + struct PQSignatureSchemePublicKey **out_key) +{ + if (!path || !out_key) + { + return LANTERN_HASH_SIG_ERR_INVALID_PARAM; } + + *out_key = NULL; + uint8_t *data = NULL; size_t length = 0; - if (read_file_bytes(path, &data, &length) != 0) { - return -1; + int result = read_file_bytes(path, &data, &length); + if (result != LANTERN_HASH_SIG_OK) + { + return result; } - int rc; - if (is_json_file(path)) { - // JSON format + if (is_json_file(path)) + { enum PQSigningError err = pq_public_key_from_json(data, length, out_key); - rc = (err == Success && out_key && *out_key) ? 0 : -1; - free(data); - } else { - // SSZ format - rc = lantern_hash_sig_load_public_bytes(data, length, out_key); - free(data); - } - return rc; + result = (err == Success && *out_key) + ? LANTERN_HASH_SIG_OK + : LANTERN_HASH_SIG_ERR_DESERIALIZE; + } + else + { + result = lantern_hash_sig_load_public_bytes(data, length, out_key); + } + + free(data); + return result; } -bool lantern_hash_sig_is_available(void) { + +/** + * Check whether the hash-signature backend is available. + * + * @return true when the hash signature scheme reports a positive lifetime + * + * @note Thread safety: This function is thread-safe. + */ +bool lantern_hash_sig_is_available(void) +{ /* - * pq_get_lifetime() is part of the public c-hash-sig API. A non-zero + * pq_get_lifetime() is part of the public c-hash-sig API. A non-zero * lifetime means the Rust bindings initialised correctly and returned the * scheme configuration constants. */ diff --git a/src/internal/yaml_parser.c b/src/internal/yaml_parser.c index 7455e00..80ac912 100644 --- a/src/internal/yaml_parser.c +++ b/src/internal/yaml_parser.c @@ -5,6 +5,8 @@ #include #include +enum { LANTERN_YAML_MAX_STACK_DEPTH = 64 }; + static int get_indentation(const char *line) { int count = 0; while (*line == ' ' || *line == '\t') { @@ -121,6 +123,20 @@ static void commit_current( current->capacity = 0; } +static void free_yaml_object(LanternYamlObject *object) { + if (!object || !object->pairs) { + return; + } + for (size_t i = 0; i < object->num_pairs; ++i) { + free(object->pairs[i].key); + free(object->pairs[i].value); + } + free(object->pairs); + object->pairs = NULL; + object->num_pairs = 0; + object->capacity = 0; +} + LanternYamlObject *lantern_yaml_read_array(const char *file_path, const char *array_name, size_t *out_count) { if (!file_path || !array_name || !out_count) { return NULL; @@ -134,10 +150,11 @@ LanternYamlObject *lantern_yaml_read_array(const char *file_path, const char *ar } int stack_size = 0; - char *keys_stack[64] = {0}; - int indent_stack[64] = {0}; + char *keys_stack[LANTERN_YAML_MAX_STACK_DEPTH] = {0}; + int indent_stack[LANTERN_YAML_MAX_STACK_DEPTH] = {0}; int in_target_array = 0; int array_depth = -1; + int parse_error = 0; LanternYamlObject current = {.pairs = NULL, .num_pairs = 0, .capacity = 0}; LanternYamlObject *objects = NULL; @@ -187,6 +204,13 @@ LanternYamlObject *lantern_yaml_read_array(const char *file_path, const char *ar char *key_copy = strdup(key); if (!key_copy) { + parse_error = 1; + break; + } + + if (stack_size >= LANTERN_YAML_MAX_STACK_DEPTH) { + free(key_copy); + parse_error = 1; break; } @@ -196,7 +220,9 @@ LanternYamlObject *lantern_yaml_read_array(const char *file_path, const char *ar char *full_path = build_path(keys_stack, stack_size); if (!full_path) { - continue; + pop_stack(keys_stack, &stack_size); + parse_error = 1; + break; } if (strcmp(full_path, array_name) == 0) { @@ -218,6 +244,16 @@ LanternYamlObject *lantern_yaml_read_array(const char *file_path, const char *ar fclose(fp); + if (parse_error) { + free_yaml_object(¤t); + lantern_yaml_free_objects(objects, *out_count); + for (int i = 0; i < stack_size; ++i) { + free(keys_stack[i]); + } + *out_count = 0; + return NULL; + } + if (in_target_array) { commit_current(¤t, &objects, &capacity, out_count); } diff --git a/tests/unit/test_yaml_parser_cve.c b/tests/unit/test_yaml_parser_cve.c new file mode 100644 index 0000000..124c601 --- /dev/null +++ b/tests/unit/test_yaml_parser_cve.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +#include "internal/yaml_parser.h" + +static int write_deep_yaml(const char *path, size_t depth) { + if (!path || depth == 0) { + return -1; + } + + FILE *fp = fopen(path, "w"); + if (!fp) { + return -1; + } + + for (size_t i = 0; i < depth; ++i) { + for (size_t j = 0; j < i; ++j) { + if (fputc(' ', fp) == EOF) { + fclose(fp); + return -1; + } + } + if (fprintf(fp, "k%zu:\n", i) < 0) { + fclose(fp); + return -1; + } + } + + if (fclose(fp) != 0) { + return -1; + } + + return 0; +} + +int main(void) { + char path_template[] = "/tmp/lantern_yaml_cveXXXXXX"; + int fd = mkstemp(path_template); + if (fd < 0) { + perror("mkstemp"); + return 1; + } + close(fd); + + if (write_deep_yaml(path_template, 128) != 0) { + fprintf(stderr, "failed to write yaml payload\n"); + unlink(path_template); + return 1; + } + + size_t count = 0; + LanternYamlObject *objects = lantern_yaml_read_array(path_template, "root.array", &count); + lantern_yaml_free_objects(objects, count); + + unlink(path_template); + puts("lantern_yaml_parser_cve_test completed without crash"); + return 0; +} diff --git a/tools/leanSpec b/tools/leanSpec index d717c2d..836b17f 160000 --- a/tools/leanSpec +++ b/tools/leanSpec @@ -1 +1 @@ -Subproject commit d717c2d5f7fbdb22aa2c7aea4e1cb6054504d9a6 +Subproject commit 836b17f34ec4b96e9dd35baef59bf8af1ba432d4 From 20a8be2be32696e25ad03613a7d08c493a871403 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:26:32 +1000 Subject: [PATCH 2/8] Restrict size for blocks_by_root request --- CMakeLists.txt | 18 ---------- src/networking/messages.c | 16 +++++++++ tests/unit/test_yaml_parser_cve.c | 60 ------------------------------- 3 files changed, 16 insertions(+), 78 deletions(-) delete mode 100644 tests/unit/test_yaml_parser_cve.c diff --git a/CMakeLists.txt b/CMakeLists.txt index f0fd014..1319113 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -225,24 +225,6 @@ if(LANTERN_BUILD_TESTS) target_link_libraries(lantern_snappy_test PRIVATE lantern) add_test(NAME lantern_snappy COMMAND lantern_snappy_test) - add_executable(lantern_yaml_parser_cve_test - tests/unit/test_yaml_parser_cve.c - src/internal/yaml_parser.c - ) - target_include_directories( - lantern_yaml_parser_cve_test - PRIVATE - ${PROJECT_SOURCE_DIR}/src - ) - target_compile_options( - lantern_yaml_parser_cve_test - PRIVATE - -fsanitize=address - -fno-omit-frame-pointer - ) - target_link_options(lantern_yaml_parser_cve_test PRIVATE -fsanitize=address) - add_test(NAME lantern_yaml_parser_cve COMMAND lantern_yaml_parser_cve_test) - add_executable(lantern_networking_messages_test tests/unit/test_networking_messages.c tests/support/fixture_loader.c diff --git a/src/networking/messages.c b/src/networking/messages.c index 9dfdc4d..c664559 100644 --- a/src/networking/messages.c +++ b/src/networking/messages.c @@ -401,6 +401,14 @@ int lantern_network_blocks_by_root_request_decode_snappy( if (lantern_snappy_uncompressed_length(data, data_len, &raw_len) != LANTERN_SNAPPY_OK) { return -1; } + if (raw_len > LANTERN_REQRESP_MAX_CHUNK_BYTES) { + lantern_log_warn( + "reqresp", + NULL, + "blocks_by_root request too large raw_len=%zu", + raw_len); + return -1; + } uint8_t *raw = alloc_scratch(raw_len); if (!raw) { return -1; @@ -643,6 +651,14 @@ int lantern_network_blocks_by_root_response_decode_snappy( if (lantern_snappy_uncompressed_length(data, data_len, &raw_len) != LANTERN_SNAPPY_OK) { return -1; } + if (raw_len > LANTERN_REQRESP_MAX_CHUNK_BYTES) { + lantern_log_warn( + "reqresp", + NULL, + "blocks_by_root response too large raw_len=%zu", + raw_len); + return -1; + } uint8_t *raw = alloc_scratch(raw_len); if (!raw) { return -1; diff --git a/tests/unit/test_yaml_parser_cve.c b/tests/unit/test_yaml_parser_cve.c deleted file mode 100644 index 124c601..0000000 --- a/tests/unit/test_yaml_parser_cve.c +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include - -#include "internal/yaml_parser.h" - -static int write_deep_yaml(const char *path, size_t depth) { - if (!path || depth == 0) { - return -1; - } - - FILE *fp = fopen(path, "w"); - if (!fp) { - return -1; - } - - for (size_t i = 0; i < depth; ++i) { - for (size_t j = 0; j < i; ++j) { - if (fputc(' ', fp) == EOF) { - fclose(fp); - return -1; - } - } - if (fprintf(fp, "k%zu:\n", i) < 0) { - fclose(fp); - return -1; - } - } - - if (fclose(fp) != 0) { - return -1; - } - - return 0; -} - -int main(void) { - char path_template[] = "/tmp/lantern_yaml_cveXXXXXX"; - int fd = mkstemp(path_template); - if (fd < 0) { - perror("mkstemp"); - return 1; - } - close(fd); - - if (write_deep_yaml(path_template, 128) != 0) { - fprintf(stderr, "failed to write yaml payload\n"); - unlink(path_template); - return 1; - } - - size_t count = 0; - LanternYamlObject *objects = lantern_yaml_read_array(path_template, "root.array", &count); - lantern_yaml_free_objects(objects, count); - - unlink(path_template); - puts("lantern_yaml_parser_cve_test completed without crash"); - return 0; -} From b1c8e8a960823d9b0013e809565075e71afe6db9 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Sat, 20 Dec 2025 20:11:19 +1000 Subject: [PATCH 3/8] Disable HTTP module --- CMakeLists.txt | 1 - src/core/client.c | 17 +- src/http/server.c | 1331 +-------------------------------------------- 3 files changed, 15 insertions(+), 1334 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1319113..fe39444 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -336,7 +336,6 @@ if(LANTERN_BUILD_TESTS) lantern_state lantern_slot_clock lantern_snappy - lantern_yaml_parser_cve lantern_networking_messages lantern_libp2p lantern_fork_choice diff --git a/src/core/client.c b/src/core/client.c index aad20a1..5c4ff88 100644 --- a/src/core/client.c +++ b/src/core/client.c @@ -1870,24 +1870,15 @@ static void shutdown_state_and_runtime(struct lantern_client *client) */ static lantern_client_error client_start_apis(struct lantern_client *client) { - struct lantern_http_server_config http_config; - memset(&http_config, 0, sizeof(http_config)); - http_config.port = client->http_port; - http_config.callbacks.context = client; - http_config.callbacks.snapshot_head = http_snapshot_head; - http_config.callbacks.validator_count = http_validator_count_cb; - http_config.callbacks.validator_info = http_validator_info_cb; - http_config.callbacks.set_validator_status = http_set_validator_status_cb; - if (lantern_http_server_start(&client->http_server, &http_config) != 0) + if (client->http_port != 0) { - lantern_log_error( + lantern_log_warn( "client", &(const struct lantern_log_metadata){.validator = client->node_id}, - "failed to start HTTP server on port %" PRIu16, + "HTTP API disabled; ignoring --http-port %" PRIu16, client->http_port); - return LANTERN_CLIENT_ERR_NETWORK; } - client->http_running = true; + client->http_running = false; struct lantern_metrics_callbacks metrics_callbacks; memset(&metrics_callbacks, 0, sizeof(metrics_callbacks)); diff --git a/src/http/server.c b/src/http/server.c index 599ed62..44cb410 100644 --- a/src/http/server.c +++ b/src/http/server.c @@ -1,1226 +1,16 @@ /** * @file server.c - * @brief Lean API HTTP server. + * @brief Lean API HTTP server (disabled stub). * - * Exposes a minimal JSON HTTP API: - * - GET /lean/v1/head - * - GET /lean/v1/validators - * - POST /lean/v1/validators/{index}/(activate|deactivate) - * - * @spec RFC 9110/9112 (HTTP) and POSIX sockets/pthreads. + * The HTTP API is intentionally disabled. The CLI flags remain accepted, but + * the server does not start. This file provides no-op stubs to satisfy the + * public API while removing the server implementation. */ #include "lantern/http/server.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include -#include -#include - -#include "lantern/http/common.h" -#include "lantern/support/log.h" -#include "lantern/support/strings.h" - -static const size_t LANTERN_HTTP_READ_BUFFER_SIZE = 4096; -static const size_t LANTERN_HTTP_BODY_INITIAL_CAP = 512; -static const int LANTERN_HTTP_LISTEN_BACKLOG = 16; -static const size_t LANTERN_HTTP_ROOT_HEX_CAP = (2u * LANTERN_ROOT_SIZE) + 3u; - -enum -{ - LANTERN_HTTP_METHOD_CAP = 8, - LANTERN_HTTP_PATH_CAP = 256, -}; - -/** - * HTTP server module-specific error codes. - */ -typedef enum -{ - LANTERN_HTTP_SERVER_OK = 0, - LANTERN_HTTP_SERVER_ERR_INVALID_PARAM = -1, - LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY = -2, - LANTERN_HTTP_SERVER_ERR_OVERFLOW = -3, - LANTERN_HTTP_SERVER_ERR_IO = -4, - LANTERN_HTTP_SERVER_ERR_FORMATTING = -5, - LANTERN_HTTP_SERVER_ERR_MALFORMED_REQUEST = -6, -} lantern_http_server_error_t; - -struct lantern_http_body_buffer -{ - char *data; /**< Heap buffer (NUL-terminated). */ - size_t len; /**< Bytes written (excluding terminator). */ - size_t cap; /**< Allocated capacity in bytes. */ -}; - -/** - * Initialize a dynamic HTTP body buffer. - * - * @param buf Buffer to initialize (modified in place). - * @param initial_cap Initial allocation size in bytes (0 uses default). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY on allocation failure. - * - * @note Thread safety: This function is thread-safe. - */ -static int http_buffer_init(struct lantern_http_body_buffer *buf, size_t initial_cap) -{ - if (!buf) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - size_t capacity = initial_cap != 0 ? initial_cap : LANTERN_HTTP_BODY_INITIAL_CAP; - buf->data = malloc(capacity); - if (!buf->data) - { - return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY; - } - - buf->len = 0; - buf->cap = capacity; - buf->data[0] = '\0'; - return 0; -} - - -/** - * @brief Free resources owned by an HTTP body buffer. - * - * @param buf Buffer to free (may be NULL). - * - * @note Thread safety: This function is thread-safe. - */ -static void http_buffer_free(struct lantern_http_body_buffer *buf) -{ - if (!buf) - { - return; - } - - free(buf->data); - buf->data = NULL; - buf->len = 0; - buf->cap = 0; -} - - -/** - * Ensure an HTTP body buffer has space for an additional number of bytes. - * - * @param buf Buffer to grow (modified in place). - * @param extra Additional bytes required (excluding terminator). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY on allocation failure. - * @return LANTERN_HTTP_SERVER_ERR_OVERFLOW on size overflow. - * - * @note Thread safety: This function is thread-safe. - */ -static int http_buffer_reserve(struct lantern_http_body_buffer *buf, size_t extra) -{ - if (!buf) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - if (extra == 0) - { - return 0; - } - - if (buf->len > SIZE_MAX - extra - 1) - { - return LANTERN_HTTP_SERVER_ERR_OVERFLOW; - } - - size_t required = buf->len + extra + 1; - if (required <= buf->cap) - { - return 0; - } - - size_t new_cap = buf->cap != 0 ? buf->cap : LANTERN_HTTP_BODY_INITIAL_CAP; - while (new_cap < required) - { - if (new_cap > SIZE_MAX / 2) - { - return LANTERN_HTTP_SERVER_ERR_OVERFLOW; - } - new_cap *= 2; - } - - char *data = realloc(buf->data, new_cap); - if (!data) - { - return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY; - } - - buf->data = data; - buf->cap = new_cap; - return 0; -} - - -/** - * Append raw bytes to an HTTP body buffer. - * - * @param buf Buffer to append to (modified in place). - * @param data Bytes to append. - * @param len Number of bytes to append. - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY on allocation failure. - * @return LANTERN_HTTP_SERVER_ERR_OVERFLOW on size overflow. - * - * @note Thread safety: This function is thread-safe. - */ -static int http_buffer_append(struct lantern_http_body_buffer *buf, const char *data, size_t len) -{ - if (!buf) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - if (len == 0) - { - return 0; - } - if (!data) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - int result = http_buffer_reserve(buf, len); - if (result != 0) - { - return result; - } - - memcpy(buf->data + buf->len, data, len); - buf->len += len; - buf->data[buf->len] = '\0'; - return 0; -} - - -/** - * Append a NUL-terminated string to an HTTP body buffer. - * - * @param buf Buffer to append to (modified in place). - * @param str String to append (not modified). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY on allocation failure. - * @return LANTERN_HTTP_SERVER_ERR_OVERFLOW on size overflow. - * - * @note Thread safety: This function is thread-safe. - */ -static int http_buffer_append_cstr(struct lantern_http_body_buffer *buf, const char *str) -{ - if (!buf || !str) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - return http_buffer_append(buf, str, strlen(str)); -} - - -/** - * Append formatted text to an HTTP body buffer. - * - * @param buf Buffer to append to (modified in place). - * @param fmt printf-style format string. - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY on allocation failure. - * @return LANTERN_HTTP_SERVER_ERR_OVERFLOW on size overflow. - * @return LANTERN_HTTP_SERVER_ERR_FORMATTING on formatting failure. - * - * @note Thread safety: This function is thread-safe. - */ -static int http_buffer_appendf(struct lantern_http_body_buffer *buf, const char *fmt, ...) -{ - if (!buf || !fmt) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - va_list args; - va_start(args, fmt); - va_list args_copy; - va_copy(args_copy, args); - - int required = vsnprintf(NULL, 0, fmt, args); - va_end(args); - if (required < 0) - { - va_end(args_copy); - return LANTERN_HTTP_SERVER_ERR_FORMATTING; - } - - size_t extra = (size_t)required; - int result = http_buffer_reserve(buf, extra); - if (result != 0) - { - va_end(args_copy); - return result; - } - - int written = vsnprintf(buf->data + buf->len, buf->cap - buf->len, fmt, args_copy); - va_end(args_copy); - if (written < 0 || (size_t)written != extra) - { - return LANTERN_HTTP_SERVER_ERR_FORMATTING; - } - - buf->len += extra; - return 0; -} - -/** - * Append a JSON-escaped string value to a buffer (no surrounding quotes). - * - * @param buf Buffer to append to (modified in place). - * @param str String to JSON-escape (may be NULL, treated as empty). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_OUT_OF_MEMORY on allocation failure. - * @return LANTERN_HTTP_SERVER_ERR_OVERFLOW on size overflow. - * @return LANTERN_HTTP_SERVER_ERR_FORMATTING on formatting failure. - * - * @note Thread safety: This function is thread-safe. - */ -static int http_buffer_append_json_escaped(struct lantern_http_body_buffer *buf, const char *str) -{ - if (!buf) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - const char *text = str ? str : ""; - for (; *text != '\0'; ++text) - { - unsigned char c = (unsigned char)(*text); - switch (c) - { - case '"': - { - int rc = http_buffer_append(buf, "\\\"", 2); - if (rc != 0) - { - return rc; - } - break; - } - case '\\': - { - int rc = http_buffer_append(buf, "\\\\", 2); - if (rc != 0) - { - return rc; - } - break; - } - case '\b': - { - int rc = http_buffer_append(buf, "\\b", 2); - if (rc != 0) - { - return rc; - } - break; - } - case '\f': - { - int rc = http_buffer_append(buf, "\\f", 2); - if (rc != 0) - { - return rc; - } - break; - } - case '\n': - { - int rc = http_buffer_append(buf, "\\n", 2); - if (rc != 0) - { - return rc; - } - break; - } - case '\r': - { - int rc = http_buffer_append(buf, "\\r", 2); - if (rc != 0) - { - return rc; - } - break; - } - case '\t': - { - int rc = http_buffer_append(buf, "\\t", 2); - if (rc != 0) - { - return rc; - } - break; - } - default: - { - if (c < 0x20) - { - char escaped[7]; - int written = snprintf(escaped, sizeof(escaped), "\\u%04x", (unsigned)c); - if (written < 0 || (size_t)written >= sizeof(escaped)) - { - return LANTERN_HTTP_SERVER_ERR_FORMATTING; - } - int rc = http_buffer_append(buf, escaped, (size_t)written); - if (rc != 0) - { - return rc; - } - break; - } - int rc = http_buffer_append(buf, (const char *)&c, 1); - if (rc != 0) - { - return rc; - } - break; - } - } - } - - return 0; -} - - -/** - * Convert a root to a 0x-prefixed hex string. - * - * @param root Root to encode. - * @param out Output buffer (NUL-terminated on return when out_len > 0). - * @param out_len Output buffer length. - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_FORMATTING on encoding failure. - * - * @note Thread safety: This function is thread-safe. - */ -static int root_to_hex(const LanternRoot *root, char *out, size_t out_len) -{ - if (!out || out_len == 0) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - out[0] = '\0'; - if (!root || out_len < LANTERN_HTTP_ROOT_HEX_CAP) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - if (lantern_bytes_to_hex(root->bytes, LANTERN_ROOT_SIZE, out, out_len, 1) != 0) - { - out[0] = '\0'; - return LANTERN_HTTP_SERVER_ERR_FORMATTING; - } - - return 0; -} - - -/** - * Send an HTTP JSON response. - * - * @param client_fd Client socket file descriptor. - * @param status_code HTTP status code. - * @param status_text HTTP status text. - * @param json_body JSON body (may be NULL when json_len is 0). - * @param json_len JSON body length in bytes. - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_FORMATTING on header formatting failure. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: Caller must ensure exclusive access to client_fd. - */ -static int send_json_response( - int client_fd, - int status_code, - const char *status_text, - const char *json_body, - size_t json_len) -{ - int rc = lantern_http_send_response( - client_fd, - status_code, - status_text, - "application/json", - json_body, - json_len); - if (rc == 0) - { - return 0; - } - - if (rc == -1) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - if (rc == -3) - { - return LANTERN_HTTP_SERVER_ERR_FORMATTING; - } - return LANTERN_HTTP_SERVER_ERR_IO; -} - - -/** - * Send a simple static JSON error response. - * - * @param client_fd Client socket file descriptor. - * @param status_code HTTP status code. - * @param status_text HTTP status text. - * @param json_body JSON body (may be NULL). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_FORMATTING on header formatting failure. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: Caller must ensure exclusive access to client_fd. - */ -static int send_json_error( - int client_fd, - int status_code, - const char *status_text, - const char *json_body) -{ - if (!json_body) - { - return send_json_response(client_fd, status_code, status_text, NULL, 0); - } - - return send_json_response(client_fd, status_code, status_text, json_body, strlen(json_body)); -} - - -/** - * Send a JSON error response and set the status code output. - * - * @param client_fd Client socket file descriptor. - * @param status_code HTTP status code. - * @param status_text HTTP status text. - * @param json_body JSON body. - * @param out_status_code Output status code (modified in place). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_FORMATTING on header formatting failure. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: Caller must ensure exclusive access to client_fd. - */ -static int send_json_error_status( - int client_fd, - int status_code, - const char *status_text, - const char *json_body, - int *out_status_code) -{ - if (!out_status_code) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - *out_status_code = status_code; - return send_json_error(client_fd, status_code, status_text, json_body); -} - - -/** - * Handle the `GET /lean/v1/head` endpoint. - * - * @param server HTTP server instance. - * @param client_fd Client socket file descriptor. - * @param out_status_code Output HTTP status code (modified in place). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: This function is thread-safe if callbacks are thread-safe. - */ -static int handle_get_head( - struct lantern_http_server *server, - int client_fd, - int *out_status_code) -{ - if (!server || client_fd < 0 || !out_status_code) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - if (!server->callbacks.snapshot_head) - { - return send_json_error_status( - client_fd, - 501, - "Not Implemented", - "{\"error\":\"head query unsupported\"}", - out_status_code); - } - - struct lantern_http_head_snapshot snapshot; - if (server->callbacks.snapshot_head(server->callbacks.context, &snapshot) != 0) - { - return send_json_error_status( - client_fd, - 503, - "Service Unavailable", - "{\"error\":\"head snapshot unavailable\"}", - out_status_code); - } - - char head_hex[LANTERN_HTTP_ROOT_HEX_CAP]; - char justified_hex[LANTERN_HTTP_ROOT_HEX_CAP]; - char finalized_hex[LANTERN_HTTP_ROOT_HEX_CAP]; - if (root_to_hex(&snapshot.head_root, head_hex, sizeof(head_hex)) != 0 - || root_to_hex(&snapshot.justified.root, justified_hex, sizeof(justified_hex)) != 0 - || root_to_hex(&snapshot.finalized.root, finalized_hex, sizeof(finalized_hex)) != 0) - { - return send_json_error_status( - client_fd, - 500, - "Internal Server Error", - "{\"error\":\"root encoding failed\"}", - out_status_code); - } - - char body[512]; - int written = snprintf( - body, - sizeof(body), - "{" - "\"slot\":%" PRIu64 "," - "\"head_root\":\"%s\"," - "\"justified\":{\"slot\":%" PRIu64 ",\"root\":\"%s\"}," - "\"finalized\":{\"slot\":%" PRIu64 ",\"root\":\"%s\"}" - "}", - snapshot.slot, - head_hex, - snapshot.justified.slot, - justified_hex, - snapshot.finalized.slot, - finalized_hex); - if (written < 0 || (size_t)written >= sizeof(body)) - { - return send_json_error_status( - client_fd, - 500, - "Internal Server Error", - "{\"error\":\"head response too large\"}", - out_status_code); - } - - int rc = send_json_response(client_fd, 200, "OK", body, (size_t)written); - if (rc != 0) - { - return rc; - } - - *out_status_code = 200; - return 0; -} - - -/** - * Handle the `GET /lean/v1/validators` endpoint. - * - * @param server HTTP server instance. - * @param client_fd Client socket file descriptor. - * @param out_status_code Output HTTP status code (modified in place). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: This function is thread-safe if callbacks are thread-safe. - */ -static int handle_get_validators( - struct lantern_http_server *server, - int client_fd, - int *out_status_code) -{ - if (!server || client_fd < 0 || !out_status_code) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - if (!server->callbacks.validator_count || !server->callbacks.validator_info) - { - return send_json_error_status( - client_fd, - 501, - "Not Implemented", - "{\"error\":\"validator listing unsupported\"}", - out_status_code); - } - - size_t count = server->callbacks.validator_count(server->callbacks.context); - - struct lantern_http_body_buffer body; - bool body_initialized = false; - int result = http_buffer_init(&body, LANTERN_HTTP_BODY_INITIAL_CAP); - if (result != 0) - { - goto internal_error; - } - body_initialized = true; - - result = http_buffer_append_cstr(&body, "{\"validators\":["); - if (result != 0) - { - goto internal_error; - } - - for (size_t i = 0; i < count; ++i) - { - struct lantern_http_validator_info info; - if (server->callbacks.validator_info(server->callbacks.context, i, &info) != 0) - { - result = send_json_error_status( - client_fd, - 503, - "Service Unavailable", - "{\"error\":\"validator snapshot unavailable\"}", - out_status_code); - goto cleanup; - } - - if (i > 0) - { - result = http_buffer_append(&body, ",", 1); - if (result != 0) - { - goto internal_error; - } - } - - result = http_buffer_appendf( - &body, - "{\"index\":%" PRIu64 ",\"enabled\":%s,\"label\":\"", - info.global_index, - info.enabled ? "true" : "false"); - if (result != 0) - { - goto internal_error; - } - - result = http_buffer_append_json_escaped(&body, info.label); - if (result != 0) - { - goto internal_error; - } - - result = http_buffer_append_cstr(&body, "\"}"); - if (result != 0) - { - goto internal_error; - } - } - - result = http_buffer_append_cstr(&body, "]}"); - if (result != 0) - { - goto internal_error; - } - - result = send_json_response(client_fd, 200, "OK", body.data, body.len); - if (result != 0) - { - goto cleanup; - } - - *out_status_code = 200; - result = 0; - goto cleanup; - -internal_error: - { - const char *error_body = "{\"error\":\"allocator failure\"}"; - if (result == LANTERN_HTTP_SERVER_ERR_FORMATTING) - { - error_body = "{\"error\":\"formatting failure\"}"; - } - result = send_json_error_status( - client_fd, - 500, - "Internal Server Error", - error_body, - out_status_code); - } - -cleanup: - if (body_initialized) - { - http_buffer_free(&body); - } - - return result; -} - - -/** - * Handle validator activation and deactivation requests. - * - * Supports `POST /lean/v1/validators/{index}/activate` and - * `POST /lean/v1/validators/{index}/deactivate`. - * - * @param server HTTP server instance. - * @param client_fd Client socket file descriptor. - * @param path Request path. - * @param out_status_code Output HTTP status code (modified in place). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: This function is thread-safe if callbacks are thread-safe. - */ -static int handle_post_validator_action( - struct lantern_http_server *server, - int client_fd, - const char *path, - int *out_status_code) -{ - if (!server || client_fd < 0 || !path || !out_status_code) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - if (!server->callbacks.set_validator_status) - { - return send_json_error_status( - client_fd, - 501, - "Not Implemented", - "{\"error\":\"validator control unsupported\"}", - out_status_code); - } - - static const char prefix[] = "/lean/v1/validators/"; - size_t prefix_len = strlen(prefix); - if (strncmp(path, prefix, prefix_len) != 0) - { - return send_json_error_status( - client_fd, - 404, - "Not Found", - "{\"error\":\"unknown validator path\"}", - out_status_code); - } - - const char *rest = path + prefix_len; - if (!*rest) - { - return send_json_error_status( - client_fd, - 400, - "Bad Request", - "{\"error\":\"missing validator index\"}", - out_status_code); - } - - errno = 0; - char *endptr = NULL; - uint64_t index = strtoull(rest, &endptr, 10); - if (errno != 0 || rest == endptr) - { - return send_json_error_status( - client_fd, - 400, - "Bad Request", - "{\"error\":\"invalid validator index\"}", - out_status_code); - } - if (!endptr || *endptr != '/') - { - return send_json_error_status( - client_fd, - 400, - "Bad Request", - "{\"error\":\"missing validator action\"}", - out_status_code); - } - - const char *action = endptr + 1; - bool should_enable = false; - if (strcmp(action, "activate") == 0) - { - should_enable = true; - } - else if (strcmp(action, "deactivate") == 0) - { - should_enable = false; - } - else - { - return send_json_error_status( - client_fd, - 404, - "Not Found", - "{\"error\":\"unknown validator action\"}", - out_status_code); - } - - if (server->callbacks.set_validator_status( - server->callbacks.context, - index, - should_enable) - != 0) - { - return send_json_error_status( - client_fd, - 404, - "Not Found", - "{\"error\":\"validator not found\"}", - out_status_code); - } - - int rc = send_json_response(client_fd, 204, "No Content", NULL, 0); - if (rc != 0) - { - return rc; - } - - *out_status_code = 204; - return 0; -} - - -/** - * Dispatch an HTTP request to the appropriate handler. - * - * @param server HTTP server instance. - * @param client_fd Client socket file descriptor. - * @param method Request method. - * @param path Request path. - * @param out_status_code Output HTTP status code (modified in place). - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_IO on send failure. - * - * @note Thread safety: This function is thread-safe if callbacks are thread-safe. - */ -static int dispatch_request( - struct lantern_http_server *server, - int client_fd, - const char *method, - const char *path, - int *out_status_code) -{ - if (!server || client_fd < 0 || !method || !path || !out_status_code) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - if (strcmp(method, "GET") == 0) - { - if (strcmp(path, "/lean/v1/head") == 0) - { - return handle_get_head(server, client_fd, out_status_code); - } - if (strcmp(path, "/lean/v1/validators") == 0) - { - return handle_get_validators(server, client_fd, out_status_code); - } - } - else if (strcmp(method, "POST") == 0) - { - return handle_post_validator_action(server, client_fd, path, out_status_code); - } - - return send_json_error_status( - client_fd, - 404, - "Not Found", - "{\"error\":\"unknown endpoint\"}", - out_status_code); -} - - -/** - * Convert a peer address to a printable string. - * - * @param peer_addr Peer address (may be NULL). - * @param out Output buffer (NUL-terminated on return). - * @param out_len Output buffer length. - * - * @note Thread safety: This function is thread-safe. - */ -static void peer_to_text(const struct sockaddr_in *peer_addr, char *out, size_t out_len) -{ - if (!out || out_len == 0) - { - return; - } - - const char *fallback = "unknown"; - if (!peer_addr) - { - strncpy(out, fallback, out_len - 1); - out[out_len - 1] = '\0'; - return; - } - - if (!inet_ntop(AF_INET, &peer_addr->sin_addr, out, out_len)) - { - strncpy(out, fallback, out_len - 1); - out[out_len - 1] = '\0'; - } -} - - -/** - * Parse a minimal HTTP request line (method and path). - * - * @param request Request bytes (NUL-terminated). - * @param method Output method buffer (NUL-terminated on success). - * @param method_len Method buffer length. - * @param path Output path buffer (NUL-terminated on success). - * @param path_len Path buffer length. - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_MALFORMED_REQUEST on parse failure. - * - * @note Thread safety: This function is thread-safe. - */ -static int parse_request_line( - const char *request, - char *method, - size_t method_len, - char *path, - size_t path_len) -{ - if (!request || !method || method_len == 0 || !path || path_len == 0) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - const char *space = strchr(request, ' '); - if (!space) - { - return LANTERN_HTTP_SERVER_ERR_MALFORMED_REQUEST; - } - - size_t method_written = (size_t)(space - request); - if (method_written == 0 || method_written >= method_len) - { - return LANTERN_HTTP_SERVER_ERR_MALFORMED_REQUEST; - } - - memcpy(method, request, method_written); - method[method_written] = '\0'; - - const char *cursor = space; - while (*cursor == ' ') - { - ++cursor; - } - if (*cursor == '\0') - { - return LANTERN_HTTP_SERVER_ERR_MALFORMED_REQUEST; - } - - const char *path_start = cursor; - while (*cursor != '\0' - && *cursor != ' ' - && *cursor != '\r' - && *cursor != '\n' - && *cursor != '\t') - { - ++cursor; - } - - size_t path_written = (size_t)(cursor - path_start); - if (path_written == 0 || path_written >= path_len) - { - return LANTERN_HTTP_SERVER_ERR_MALFORMED_REQUEST; - } - - memcpy(path, path_start, path_written); - path[path_written] = '\0'; - return 0; -} - - -/** - * Handle a single client connection. - * - * @param server HTTP server instance. - * @param client_fd Client socket file descriptor. - * @param peer_addr Peer address (may be NULL). - * - * @note Thread safety: This function is thread-safe if callbacks are thread-safe. - */ -static void handle_client_connection( - struct lantern_http_server *server, - int client_fd, - const struct sockaddr_in *peer_addr) -{ - if (!server || client_fd < 0) - { - return; - } - - char peer_text[INET_ADDRSTRLEN]; - peer_to_text(peer_addr, peer_text, sizeof(peer_text)); - - char buffer[LANTERN_HTTP_READ_BUFFER_SIZE]; - ssize_t received = -1; - do - { - received = recv(client_fd, buffer, sizeof(buffer) - 1, 0); - } while (received < 0 && errno == EINTR); - - if (received <= 0) - { - return; - } - buffer[(size_t)received] = '\0'; - - char method[LANTERN_HTTP_METHOD_CAP]; - char path[LANTERN_HTTP_PATH_CAP]; - int http_status = 500; - - int result = parse_request_line(buffer, method, sizeof(method), path, sizeof(path)); - if (result != 0) - { - int rc = send_json_error( - client_fd, - 400, - "Bad Request", - "{\"error\":\"malformed request\"}"); - if (rc == 0) - { - lantern_log_info( - "http", - &(const struct lantern_log_metadata){.peer = peer_text}, - "malformed request -> 400"); - } - else - { - lantern_log_error( - "http", - &(const struct lantern_log_metadata){.peer = peer_text}, - "failed to send 400 response rc=%d", - rc); - } - return; - } - - result = dispatch_request(server, client_fd, method, path, &http_status); - if (result != 0) - { - lantern_log_error( - "http", - &(const struct lantern_log_metadata){.peer = peer_text}, - "%s %s failed rc=%d", - method, - path, - result); - return; - } - - lantern_log_info( - "http", - &(const struct lantern_log_metadata){.peer = peer_text}, - "%s %s -> %d", - method, - path, - http_status); -} - - -/** - * Thread entry point for the HTTP server accept loop. - * - * @param arg Pointer to struct lantern_http_server. - * @return NULL. - * - * @note Thread safety: This function is not thread-safe; it is intended to run - * as a single server thread created by lantern_http_server_start(). - */ -static void *lantern_http_server_thread(void *arg) -{ - struct lantern_http_server *server = arg; - if (!server) - { - return NULL; - } - - int listen_fd = server->listen_fd; - for (;;) - { - struct sockaddr_in peer; - socklen_t peer_len = sizeof(peer); - int client_fd = accept(listen_fd, (struct sockaddr *)&peer, &peer_len); - if (client_fd < 0) - { - if (errno == EINTR) - { - continue; - } - if (errno == EBADF || errno == EINVAL) - { - break; - } - lantern_log_error("http", NULL, "accept failed errno=%d", errno); - continue; - } - - handle_client_connection(server, client_fd, &peer); - close(client_fd); - } - - return NULL; -} - - -/** - * Initialize an HTTP server structure. - * - * @param server Server instance to initialize (modified in place). - * - * @note Thread safety: Caller must not call concurrently with start/stop. - */ void lantern_http_server_init(struct lantern_http_server *server) { if (!server) @@ -1235,14 +25,6 @@ void lantern_http_server_init(struct lantern_http_server *server) server->port = 0; } - -/** - * Reset an HTTP server structure, stopping it if running. - * - * @param server Server instance to reset (modified in place). - * - * @note Thread safety: Caller must not call concurrently with start/stop. - */ void lantern_http_server_reset(struct lantern_http_server *server) { if (!server) @@ -1254,102 +36,22 @@ void lantern_http_server_reset(struct lantern_http_server *server) lantern_http_server_init(server); } - -/** - * Start the HTTP server. - * - * Creates a listening socket and starts a background thread to accept incoming - * connections and serve the Lean API endpoints. - * - * @param server Server instance to start (modified in place). - * @param config Server configuration (callbacks must be set). - * - * @spec POSIX sockets and pthreads. - * - * @return 0 on success. - * @return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM on invalid parameters. - * @return LANTERN_HTTP_SERVER_ERR_IO on socket/bind/listen/thread creation failure. - * - * @note Thread safety: Caller must serialize start/stop operations. - */ int lantern_http_server_start( struct lantern_http_server *server, const struct lantern_http_server_config *config) { if (!server || !config) { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - if (!config->callbacks.snapshot_head - || !config->callbacks.validator_count - || !config->callbacks.validator_info - || !config->callbacks.set_validator_status) - { - return LANTERN_HTTP_SERVER_ERR_INVALID_PARAM; - } - - int fd = socket(AF_INET, SOCK_STREAM, 0); - if (fd < 0) - { - lantern_log_error("http", NULL, "socket creation failed errno=%d", errno); - return LANTERN_HTTP_SERVER_ERR_IO; - } - - int opt = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) - { - lantern_log_warn("http", NULL, "setsockopt(SO_REUSEADDR) failed errno=%d", errno); - } - - struct sockaddr_in addr; - memset(&addr, 0, sizeof(addr)); - addr.sin_family = AF_INET; - addr.sin_addr.s_addr = htonl(INADDR_ANY); - addr.sin_port = htons(config->port); - - if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) - { - lantern_log_error("http", NULL, "bind failed errno=%d", errno); - close(fd); - return LANTERN_HTTP_SERVER_ERR_IO; + return -1; } - if (listen(fd, LANTERN_HTTP_LISTEN_BACKLOG) != 0) - { - lantern_log_error("http", NULL, "listen failed errno=%d", errno); - close(fd); - return LANTERN_HTTP_SERVER_ERR_IO; - } - - server->listen_fd = fd; - server->callbacks = config->callbacks; - server->port = config->port; - server->running = 1; + server->listen_fd = -1; + server->running = 0; server->thread_started = 0; - - int create_rc = pthread_create(&server->thread, NULL, lantern_http_server_thread, server); - if (create_rc != 0) - { - lantern_log_error("http", NULL, "pthread_create failed rc=%d", create_rc); - close(fd); - server->listen_fd = -1; - server->running = 0; - return LANTERN_HTTP_SERVER_ERR_IO; - } - - server->thread_started = 1; - lantern_log_info("http", NULL, "http server listening port=%" PRIu16, server->port); - return 0; + server->port = 0; + return -1; } - -/** - * Stop the HTTP server if running. - * - * @param server Server instance to stop (modified in place). - * - * @note Thread safety: Caller must serialize start/stop operations. - */ void lantern_http_server_stop(struct lantern_http_server *server) { if (!server) @@ -1358,18 +60,7 @@ void lantern_http_server_stop(struct lantern_http_server *server) } server->running = 0; - - int listen_fd = server->listen_fd; server->listen_fd = -1; - if (listen_fd >= 0) - { - (void)shutdown(listen_fd, SHUT_RDWR); - close(listen_fd); - } - - if (server->thread_started) - { - pthread_join(server->thread, NULL); - server->thread_started = 0; - } + server->thread_started = 0; + server->port = 0; } From 397135513a48eb2c36512285e7250c5035512b2a Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:13:14 +1000 Subject: [PATCH 4/8] Limit RLP decoding nesting depth to 64 --- CMakeLists.txt | 1 + src/encoding/rlp.c | 44 ++++++++++++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fe39444..250dd5e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -187,6 +187,7 @@ if(LANTERN_BUILD_TESTS) target_link_libraries(lantern_rlp_test PRIVATE lantern) add_test(NAME lantern_rlp COMMAND lantern_rlp_test) + add_executable(lantern_enr_test tests/unit/test_enr.c) target_link_libraries(lantern_enr_test PRIVATE lantern) add_test(NAME lantern_enr COMMAND lantern_enr_test) diff --git a/src/encoding/rlp.c b/src/encoding/rlp.c index cf6fe43..27da99e 100644 --- a/src/encoding/rlp.c +++ b/src/encoding/rlp.c @@ -28,6 +28,7 @@ typedef enum static const size_t RLP_SHORT_PAYLOAD_MAX = 55; static const size_t RLP_INITIAL_LIST_CAPACITY = 4; +static const size_t LANTERN_RLP_MAX_NESTING_DEPTH = 64; static const uint8_t RLP_PREFIX_SINGLE_BYTE_MAX = 0x7Fu; static const uint8_t RLP_PREFIX_SHORT_STRING_BASE = 0x80u; @@ -48,12 +49,13 @@ struct lantern_rlp_cursor size_t offset; /**< Current read offset */ }; -static int decode_item(struct lantern_rlp_cursor *cursor, struct lantern_rlp_view *view); +static int decode_item(struct lantern_rlp_cursor *cursor, struct lantern_rlp_view *view, size_t depth); static int decode_list_payload( struct lantern_rlp_cursor *cursor, size_t payload_length, - struct lantern_rlp_view *view); + struct lantern_rlp_view *view, + size_t depth); /** * @brief Zero an RLP view (does not free child items). @@ -654,7 +656,8 @@ static int decode_long_string_item( static int decode_short_list_item( struct lantern_rlp_cursor *cursor, struct lantern_rlp_view *view, - uint8_t prefix) + uint8_t prefix, + size_t depth) { size_t payload_length = (size_t)prefix - (size_t)RLP_PREFIX_SHORT_LIST_BASE; if (!rlp_cursor_can_read(cursor, cursor->offset, payload_length)) @@ -663,7 +666,7 @@ static int decode_short_list_item( } const uint8_t *payload_start = cursor->data + cursor->offset; - int result = decode_list_payload(cursor, payload_length, view); + int result = decode_list_payload(cursor, payload_length, view, depth); if (result != LANTERN_RLP_OK) { return result; @@ -681,7 +684,8 @@ static int decode_short_list_item( static int decode_long_list_item( struct lantern_rlp_cursor *cursor, struct lantern_rlp_view *view, - uint8_t prefix) + uint8_t prefix, + size_t depth) { size_t len_of_len = (size_t)prefix - (size_t)RLP_PREFIX_LONG_LIST_BASE; size_t payload_length = 0; @@ -702,7 +706,7 @@ static int decode_long_list_item( } const uint8_t *payload_start = cursor->data + cursor->offset; - result = decode_list_payload(cursor, payload_length, view); + result = decode_list_payload(cursor, payload_length, view, depth); if (result != LANTERN_RLP_OK) { return result; @@ -717,13 +721,18 @@ static int decode_long_list_item( /** * @brief Decode an RLP item. */ -static int decode_item(struct lantern_rlp_cursor *cursor, struct lantern_rlp_view *view) +static int decode_item(struct lantern_rlp_cursor *cursor, struct lantern_rlp_view *view, size_t depth) { if (!cursor || !view) { return LANTERN_RLP_ERR_INVALID_PARAM; } + if (depth > LANTERN_RLP_MAX_NESTING_DEPTH) + { + return LANTERN_RLP_ERR_INVALID_ENCODING; + } + rlp_view_zero(view); if (cursor->offset >= cursor->length) @@ -751,10 +760,20 @@ static int decode_item(struct lantern_rlp_cursor *cursor, struct lantern_rlp_vie if (prefix <= RLP_PREFIX_SHORT_LIST_MAX) { - return decode_short_list_item(cursor, view, prefix); + size_t next_depth = depth + 1; + if (next_depth > LANTERN_RLP_MAX_NESTING_DEPTH) + { + return LANTERN_RLP_ERR_INVALID_ENCODING; + } + return decode_short_list_item(cursor, view, prefix, next_depth); } - return decode_long_list_item(cursor, view, prefix); + size_t next_depth = depth + 1; + if (next_depth > LANTERN_RLP_MAX_NESTING_DEPTH) + { + return LANTERN_RLP_ERR_INVALID_ENCODING; + } + return decode_long_list_item(cursor, view, prefix, next_depth); } @@ -764,7 +783,8 @@ static int decode_item(struct lantern_rlp_cursor *cursor, struct lantern_rlp_vie static int decode_list_payload( struct lantern_rlp_cursor *cursor, size_t payload_length, - struct lantern_rlp_view *view) + struct lantern_rlp_view *view, + size_t depth) { if (!cursor || !view) { @@ -791,7 +811,7 @@ static int decode_list_payload( goto cleanup; } - result = decode_item(&nested, &items[count]); + result = decode_item(&nested, &items[count], depth); if (result != LANTERN_RLP_OK) { goto cleanup; @@ -860,7 +880,7 @@ int lantern_rlp_decode( .offset = 0, }; - int result = decode_item(&cursor, out_view); + int result = decode_item(&cursor, out_view, 0); if (result != LANTERN_RLP_OK) { lantern_rlp_view_reset(out_view); From 53992c48337012b758d084d9d6e8c214be706926 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:43:08 +1000 Subject: [PATCH 5/8] Enable syncing using blocks by root --- CMakeLists.txt | 1 + external/c-libp2p | 2 +- include/lantern/networking/reqresp_service.h | 5 + src/core/client_debug.c | 6 +- src/core/client_network.c | 20 ++ src/core/client_network_internal.h | 11 + src/core/client_reqresp.c | 1 + src/core/client_reqresp_blocks.c | 326 ++++++++++++------- src/core/client_reqresp_stream.c | 9 +- src/core/client_sync.c | 272 +++++++++++++++- src/core/client_sync_blocks.c | 54 ++- src/core/client_sync_internal.h | 30 +- src/core/client_sync_votes.c | 141 ++++++++ src/core/client_validator.c | 2 +- src/genesis/genesis_parse_registry.c | 18 + src/networking/gossipsub_service.c | 58 ++++ src/networking/messages.c | 136 +++++++- src/networking/reqresp_service.c | 325 +++++++++++++++--- 18 files changed, 1214 insertions(+), 203 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 250dd5e..1b9b546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ if(LANTERN_BUILD_TESTS) target_link_libraries(lantern_snappy_test PRIVATE lantern) add_test(NAME lantern_snappy COMMAND lantern_snappy_test) + add_executable(lantern_networking_messages_test tests/unit/test_networking_messages.c tests/support/fixture_loader.c diff --git a/external/c-libp2p b/external/c-libp2p index 8f057d5..f6ccab5 160000 --- a/external/c-libp2p +++ b/external/c-libp2p @@ -1 +1 @@ -Subproject commit 8f057d52f7d610958542b3cfb8f087a3d1df90a6 +Subproject commit f6ccab50bc7ae5c0e3000a4ebd8c1285579e74b5 diff --git a/include/lantern/networking/reqresp_service.h b/include/lantern/networking/reqresp_service.h index f65ed62..dd60f89 100644 --- a/include/lantern/networking/reqresp_service.h +++ b/include/lantern/networking/reqresp_service.h @@ -7,6 +7,7 @@ #include #include "lantern/networking/messages.h" +#include "lantern/support/string_list.h" #include "libp2p/stream.h" #include "peer_id/peer_id.h" @@ -14,6 +15,7 @@ #define LANTERN_REQRESP_STATUS_PROTOCOL_LEGACY "/leanconsensus/req/status/1/" #define LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_SNAPPY "/leanconsensus/req/lean_blocks_by_root/1/ssz_snappy" #define LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_LEGACY "/leanconsensus/req/blocks_by_root/1/ssz_snappy" +#define LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_BARE "/leanconsensus/req/blocks_by_root/1/" #define LANTERN_REQRESP_STATUS_PROTOCOL LANTERN_REQRESP_STATUS_PROTOCOL_SNAPPY #define LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_SNAPPY #define LANTERN_REQRESP_STATUS_PREVIEW_BYTES 256u @@ -57,6 +59,7 @@ enum lantern_reqresp_protocol_kind { #define LANTERN_STATUS_PROTOCOL_ID_LEGACY LANTERN_REQRESP_STATUS_PROTOCOL_LEGACY #define LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL #define LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_LEGACY LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_LEGACY +#define LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_BARE LANTERN_REQRESP_BLOCKS_BY_ROOT_PROTOCOL_BARE #define LANTERN_STATUS_PREVIEW_BYTES LANTERN_REQRESP_STATUS_PREVIEW_BYTES struct libp2p_host; @@ -94,9 +97,11 @@ struct lantern_reqresp_service { struct libp2p_protocol_server *status_server_legacy; struct libp2p_protocol_server *blocks_server; struct libp2p_protocol_server *blocks_server_legacy; + struct libp2p_protocol_server *blocks_server_bare; struct libp2p_subscription *event_subscription; int lock_initialized; pthread_mutex_t lock; + struct lantern_string_list legacy_peers; }; #ifdef __cplusplus diff --git a/src/core/client_debug.c b/src/core/client_debug.c index c3ad981..736528f 100644 --- a/src/core/client_debug.c +++ b/src/core/client_debug.c @@ -80,7 +80,8 @@ int lantern_client_debug_import_block( block_root, &(const struct lantern_log_metadata){ .validator = client->node_id, - .peer = peer_id_text}) + .peer = peer_id_text}, + true) ? 1 : 0; } @@ -141,7 +142,8 @@ int lantern_client_debug_enqueue_pending_block( block, block_root, parent_root, - peer_id_text); + peer_id_text, + false); return LANTERN_CLIENT_OK; } diff --git a/src/core/client_network.c b/src/core/client_network.c index e1a6dd0..2552bfa 100644 --- a/src/core/client_network.c +++ b/src/core/client_network.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -249,6 +250,25 @@ void request_status_now(struct lantern_client *client, const peer_id_t *peer, co { 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; diff --git a/src/core/client_network_internal.h b/src/core/client_network_internal.h index 3e327ca..0052fc0 100644 --- a/src/core/client_network_internal.h +++ b/src/core/client_network_internal.h @@ -96,6 +96,16 @@ struct lantern_peer_status_entry uint32_t outstanding_status_requests; /**< Number of outstanding status requests */ }; +/** + * Blocks-by-root protocol variants for fallback handling. + */ +enum lantern_blocks_req_variant +{ + LANTERN_BLOCKS_REQ_VARIANT_PRIMARY = 0, + LANTERN_BLOCKS_REQ_VARIANT_LEGACY_SNAPPY = 1, + LANTERN_BLOCKS_REQ_VARIANT_BARE = 2, +}; + /** * Block request context for async operations. @@ -110,6 +120,7 @@ struct block_request_ctx LanternRoot root; /**< Block root being requested */ const char *protocol_id; /**< Protocol ID string */ bool using_legacy; /**< True if using legacy protocol */ + enum lantern_blocks_req_variant variant; /**< Protocol variant */ }; diff --git a/src/core/client_reqresp.c b/src/core/client_reqresp.c index 5850ace..f75088d 100644 --- a/src/core/client_reqresp.c +++ b/src/core/client_reqresp.c @@ -1114,6 +1114,7 @@ void lantern_client_on_blocks_request_complete( if (outcome == LANTERN_BLOCKS_REQUEST_SUCCESS && peer_id && peer_id[0] != '\0') { + lantern_client_request_pending_parent_after_blocks(client, peer_id, request_root); lantern_client_request_status_after_blocks_success(client, peer_id); } } diff --git a/src/core/client_reqresp_blocks.c b/src/core/client_reqresp_blocks.c index 09265c1..81b96e0 100644 --- a/src/core/client_reqresp_blocks.c +++ b/src/core/client_reqresp_blocks.c @@ -56,6 +56,18 @@ static bool lantern_client_process_stream_block_chunk( bool *saw_block); 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_variant( + struct lantern_client *client, + const char *peer_id_text, + const LanternRoot *root, + enum lantern_blocks_req_variant variant); + +static bool blocks_next_variant( + enum lantern_blocks_req_variant current, + enum lantern_blocks_req_variant *out_next); +static const char *blocks_protocol_id_for_variant(enum lantern_blocks_req_variant variant); +static bool blocks_variant_uses_raw_snappy(enum lantern_blocks_req_variant variant); +static bool blocks_variant_is_legacy(enum lantern_blocks_req_variant variant); /* ============================================================================ @@ -79,6 +91,52 @@ static void block_request_ctx_free(struct block_request_ctx *ctx) free(ctx); } +static bool blocks_next_variant( + enum lantern_blocks_req_variant current, + enum lantern_blocks_req_variant *out_next) +{ + if (!out_next) + { + return false; + } + switch (current) + { + case LANTERN_BLOCKS_REQ_VARIANT_PRIMARY: + *out_next = LANTERN_BLOCKS_REQ_VARIANT_LEGACY_SNAPPY; + return true; + case LANTERN_BLOCKS_REQ_VARIANT_LEGACY_SNAPPY: + *out_next = LANTERN_BLOCKS_REQ_VARIANT_BARE; + return true; + default: + break; + } + return false; +} + +static const char *blocks_protocol_id_for_variant(enum lantern_blocks_req_variant variant) +{ + switch (variant) + { + case LANTERN_BLOCKS_REQ_VARIANT_LEGACY_SNAPPY: + return LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_LEGACY; + case LANTERN_BLOCKS_REQ_VARIANT_BARE: + return LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_BARE; + case LANTERN_BLOCKS_REQ_VARIANT_PRIMARY: + default: + return LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID; + } +} + +static bool blocks_variant_uses_raw_snappy(enum lantern_blocks_req_variant variant) +{ + return variant != LANTERN_BLOCKS_REQ_VARIANT_PRIMARY; +} + +static bool blocks_variant_is_legacy(enum lantern_blocks_req_variant variant) +{ + return variant != LANTERN_BLOCKS_REQ_VARIANT_PRIMARY; +} + /* ============================================================================ * Block Chunk Processing @@ -119,8 +177,10 @@ static bool lantern_client_process_stream_block_chunk( free(chunk); return false; } + size_t raw_len = 0; - if (lantern_snappy_uncompressed_length(chunk, chunk_len, &raw_len) != LANTERN_SNAPPY_OK || 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", @@ -142,7 +202,8 @@ static bool lantern_client_process_stream_block_chunk( return false; } size_t written = raw_len; - if (lantern_snappy_decompress(chunk, chunk_len, raw_block, raw_len, &written) != LANTERN_SNAPPY_OK) + int snappy_rc = lantern_snappy_decompress(chunk, chunk_len, raw_block, raw_len, &written); + if (snappy_rc != LANTERN_SNAPPY_OK) { lantern_log_error( "reqresp", @@ -156,7 +217,8 @@ static bool lantern_client_process_stream_block_chunk( LanternSignedBlock streamed_block; lantern_signed_block_with_attestation_init(&streamed_block); - if (lantern_ssz_decode_signed_block(&streamed_block, raw_block, written) != 0) + int decode_rc = lantern_ssz_decode_signed_block(&streamed_block, raw_block, written); + if (decode_rc != 0) { lantern_log_error( "reqresp", @@ -201,7 +263,8 @@ static bool lantern_client_process_stream_block_chunk( &streamed_block, &computed, ctx->peer_text[0] ? ctx->peer_text : NULL, - "reqresp"); + "reqresp", + true); lantern_signed_block_with_attestation_reset(&streamed_block); if (saw_block) { @@ -270,15 +333,18 @@ static void *block_request_worker(void *arg) LanternBlocksByRootResponse response_msg; lantern_blocks_by_root_response_init(&response_msg); + uint8_t *raw_request = NULL; uint8_t *payload = NULL; uint8_t *response = NULL; bool request_success = false; - bool schedule_legacy = false; - bool attempt_legacy = false; - struct lantern_client *legacy_client = NULL; - LanternRoot legacy_root = ctx->root; - char legacy_peer[sizeof(ctx->peer_text)]; - legacy_peer[0] = '\0'; + bool schedule_retry = false; + bool attempt_retry = false; + enum lantern_blocks_req_variant retry_variant = LANTERN_BLOCKS_REQ_VARIANT_PRIMARY; + bool can_retry = blocks_next_variant(ctx->variant, &retry_variant); + struct lantern_client *retry_client = NULL; + LanternRoot retry_root = ctx->root; + char retry_peer[sizeof(ctx->peer_text)]; + retry_peer[0] = '\0'; if (lantern_root_list_resize(&request.roots, 1) != 0) { @@ -286,77 +352,99 @@ static void *block_request_worker(void *arg) "reqresp", &meta, "failed to size blocks_by_root request"); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } request.roots.items[0] = ctx->root; + bool use_raw_snappy = blocks_variant_uses_raw_snappy(ctx->variant); size_t raw_size = sizeof(uint32_t) + (request.roots.length * LANTERN_ROOT_SIZE); + 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"); + schedule_retry = can_retry; + 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"); + schedule_retry = can_retry; + goto cleanup; + } + size_t max_payload = 0; - if (lantern_snappy_max_compressed_size(raw_size, &max_payload) != LANTERN_SNAPPY_OK) + int max_rc = use_raw_snappy + ? lantern_snappy_max_compressed_size_raw(raw_written, &max_payload) + : 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"); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } - payload = (uint8_t *)malloc(max_payload); + 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"); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } size_t payload_len = 0; - if (lantern_network_blocks_by_root_request_encode_snappy(&request, payload, max_payload, &payload_len, NULL) != 0 - || payload_len == 0) + int snappy_rc = use_raw_snappy + ? lantern_snappy_compress_raw(raw_request, raw_written, payload, max_payload, &payload_len) + : 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"); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } - if (raw_size > 0) + if (raw_written > 0) { - uint8_t *plain_bytes = (uint8_t *)malloc(raw_size); - size_t plain_written = raw_size; - if (plain_bytes - && lantern_network_blocks_by_root_request_encode(&request, plain_bytes, raw_size, &plain_written) == 0) + size_t plain_preview = raw_written < LANTERN_STATUS_PREVIEW_BYTES + ? raw_written + : LANTERN_STATUS_PREVIEW_BYTES; + if (plain_preview > 0) { - size_t plain_preview = plain_written < LANTERN_STATUS_PREVIEW_BYTES - ? plain_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) { - char plain_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; - if (lantern_bytes_to_hex( - plain_bytes, - plain_preview, - plain_hex, - sizeof(plain_hex), - 0) - == 0) - { - lantern_log_trace( - "reqresp", - &meta, - "blocks_by_root request roots_hex=%s%s", - plain_hex, - (plain_written > plain_preview) ? "..." : ""); - } + lantern_log_trace( + "reqresp", + &meta, + "blocks_by_root request roots_hex=%s%s", + plain_hex, + (raw_written > plain_preview) ? "..." : ""); } } - free(plain_bytes); } size_t payload_preview = payload_len < LANTERN_STATUS_PREVIEW_BYTES ? payload_len : LANTERN_STATUS_PREVIEW_BYTES; @@ -389,7 +477,7 @@ static void *block_request_worker(void *arg) &meta, "failed to encode blocks_by_root header length=%zu", payload_len); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } @@ -410,7 +498,7 @@ static void *block_request_worker(void *arg) &meta, "failed to write blocks_by_root request err=%zd", write_err); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } @@ -419,7 +507,8 @@ static void *block_request_worker(void *arg) uint8_t *initial_chunk = NULL; size_t initial_chunk_len = 0; bool initial_chunk_pending = false; - bool response_code_pending = true; + bool expect_response_code = !blocks_variant_uses_raw_snappy(ctx->variant); + bool response_code_pending = expect_response_code; bool saw_block = false; if (ctx->using_legacy) @@ -435,7 +524,7 @@ static void *block_request_worker(void *arg) &response_len, &read_err, &response_code, - NULL) + expect_response_code ? NULL : &response_code_pending) != 0) { lantern_log_error( @@ -443,7 +532,7 @@ static void *block_request_worker(void *arg) &meta, "failed to read blocks_by_root response err=%zd", read_err); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } @@ -480,7 +569,7 @@ static void *block_request_worker(void *arg) "blocks_by_root response returned code=%u payload_len=%zu", (unsigned)response_code, response_len); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } @@ -546,7 +635,7 @@ static void *block_request_worker(void *arg) } else { - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } } @@ -591,7 +680,8 @@ static void *block_request_worker(void *arg) &response_msg.blocks[i], &computed, ctx->peer_text[0] ? ctx->peer_text : NULL, - "reqresp"); + "reqresp", + true); } request_success = (response_msg.length > 0); @@ -609,7 +699,7 @@ static void *block_request_worker(void *arg) if (!lantern_client_process_stream_block_chunk(ctx, initial_chunk, initial_chunk_len, &meta, &saw_block)) { initial_chunk = NULL; - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } initial_chunk = NULL; @@ -644,7 +734,7 @@ static void *block_request_worker(void *arg) "failed to read blocks_by_root chunk err=%zd", read_err); free(chunk); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } if (chunk_code != LANTERN_REQRESP_RESPONSE_SUCCESS) @@ -656,7 +746,7 @@ static void *block_request_worker(void *arg) (unsigned)chunk_code, chunk_len); free(chunk); - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } if (chunk_len == 0 || !chunk) @@ -667,7 +757,7 @@ static void *block_request_worker(void *arg) if (!lantern_client_process_stream_block_chunk(ctx, chunk, chunk_len, &meta, &saw_block)) { - schedule_legacy = !ctx->using_legacy; + schedule_retry = can_retry; goto cleanup; } } @@ -675,19 +765,20 @@ static void *block_request_worker(void *arg) } cleanup: - if (!request_success && schedule_legacy && ctx->client && !ctx->using_legacy && ctx->peer_text[0] && !attempt_legacy) + if (!request_success && schedule_retry && ctx->client && ctx->peer_text[0] && !attempt_retry) { - attempt_legacy = true; - legacy_client = ctx->client; - legacy_root = ctx->root; - strncpy(legacy_peer, ctx->peer_text, sizeof(legacy_peer) - 1u); - legacy_peer[sizeof(legacy_peer) - 1u] = '\0'; + attempt_retry = true; + retry_client = ctx->client; + retry_root = ctx->root; + strncpy(retry_peer, ctx->peer_text, sizeof(retry_peer) - 1u); + retry_peer[sizeof(retry_peer) - 1u] = '\0'; char retry_root_hex[(LANTERN_ROOT_SIZE * 2u) + 3u]; - format_root_hex(&legacy_root, retry_root_hex, sizeof(retry_root_hex)); + format_root_hex(&retry_root, retry_root_hex, sizeof(retry_root_hex)); lantern_log_info( "reqresp", &meta, - "retrying blocks_by_root with legacy protocol root=%s", + "retrying blocks_by_root with fallback protocol=%s root=%s", + blocks_protocol_id_for_variant(retry_variant), retry_root_hex[0] ? retry_root_hex : "0x0"); } lantern_blocks_by_root_response_reset(&response_msg); @@ -695,8 +786,13 @@ static void *block_request_worker(void *arg) { free(initial_chunk); } + if (request_success && ctx->variant != LANTERN_BLOCKS_REQ_VARIANT_PRIMARY && ctx->client && ctx->peer_text[0]) + { + lantern_reqresp_service_hint_peer_legacy(&ctx->client->reqresp, ctx->peer_text, true); + } free(response); free(payload); + free(raw_request); lantern_blocks_by_root_request_reset(&request); libp2p_stream_free(stream); if (ctx->client) @@ -710,14 +806,14 @@ static void *block_request_worker(void *arg) block_request_ctx_free(ctx); - if (attempt_legacy && legacy_client && legacy_peer[0]) + if (attempt_retry && retry_client && retry_peer[0]) { - if (lantern_client_schedule_blocks_request(legacy_client, legacy_peer, &legacy_root, true) != 0) + if (schedule_blocks_request_variant(retry_client, retry_peer, &retry_root, retry_variant) != 0) { lantern_client_on_blocks_request_complete( - legacy_client, - legacy_peer, - &legacy_root, + retry_client, + retry_peer, + &retry_root, LANTERN_BLOCKS_REQUEST_FAILED); } } @@ -778,7 +874,9 @@ static void block_request_on_open(libp2p_stream_t *stream, void *user_data, int ctx->protocol_id, err); bool attempted = false; - if (!ctx->using_legacy && ctx->client && ctx->peer_text[0]) + enum lantern_blocks_req_variant next_variant = LANTERN_BLOCKS_REQ_VARIANT_PRIMARY; + bool can_retry = blocks_next_variant(ctx->variant, &next_variant); + if (can_retry && ctx->client && ctx->peer_text[0]) { LanternRoot root = ctx->root; struct lantern_client *client = ctx->client; @@ -788,14 +886,15 @@ static void block_request_on_open(libp2p_stream_t *stream, void *user_data, int lantern_log_info( "reqresp", &meta, - "retrying blocks_by_root with legacy protocol after dial failure"); + "retrying blocks_by_root with fallback protocol=%s after dial failure", + blocks_protocol_id_for_variant(next_variant)); if (stream) { libp2p_stream_free(stream); } block_request_ctx_free(ctx); attempted = true; - if (lantern_client_schedule_blocks_request(client, peer_copy, &root, true) != 0) + if (schedule_blocks_request_variant(client, peer_copy, &root, next_variant) != 0) { lantern_client_on_blocks_request_complete( client, @@ -879,36 +978,11 @@ static void block_request_on_open(libp2p_stream_t *stream, void *user_data, int * Public Block Request API * ============================================================================ */ -/** - * 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. - * - * Protocol selection: - * - Modern protocol preferred by default - * - Legacy protocol used if peer prefers it or use_legacy is true - * - Automatic fallback to legacy on modern protocol failures - * - * @param client Client instance - * @param peer_id_text Peer ID string - * @param root Block root to request - * @param use_legacy True to use legacy protocol - * @return 0 on success - * @return LANTERN_CLIENT_ERR_INVALID_PARAM if any parameter is NULL, the peer ID is invalid, or the 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( +static int schedule_blocks_request_variant( struct lantern_client *client, const char *peer_id_text, const LanternRoot *root, - bool use_legacy) + enum lantern_blocks_req_variant variant) { if (!client || !peer_id_text || !root) { @@ -946,23 +1020,11 @@ int lantern_client_schedule_blocks_request( } ctx->client = client; ctx->root = *root; + ctx->variant = variant; + ctx->using_legacy = blocks_variant_is_legacy(variant); + ctx->protocol_id = blocks_protocol_id_for_variant(variant); strncpy(ctx->peer_text, peer_id_text, sizeof(ctx->peer_text) - 1); ctx->peer_text[sizeof(ctx->peer_text) - 1] = '\0'; -#if defined(LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_LEGACY) - bool prefer_legacy = false; - if (!use_legacy && ctx->peer_text[0] != '\0') - { - prefer_legacy = lantern_reqresp_service_peer_prefers_legacy(&client->reqresp, ctx->peer_text) != 0; - } - bool effective_legacy = use_legacy || prefer_legacy; - ctx->protocol_id = - effective_legacy ? LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_LEGACY : LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID; - ctx->using_legacy = effective_legacy; -#else - (void)use_legacy; - ctx->protocol_id = LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID; - ctx->using_legacy = false; -#endif if (peer_id_create_from_string(peer_id_text, &ctx->peer_id) != PEER_ID_SUCCESS) { @@ -1007,3 +1069,39 @@ int lantern_client_schedule_blocks_request( } 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. + * + * Protocol selection: + * - Modern protocol preferred by default + * - Legacy protocol used only when explicitly requested + * - Automatic fallback to legacy on modern protocol failures + * + * @param client Client instance + * @param peer_id_text Peer ID string + * @param root Block root to request + * @param use_legacy True to use legacy protocol + * @return 0 on success + * @return LANTERN_CLIENT_ERR_INVALID_PARAM if any parameter is NULL, the peer ID is invalid, or the 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( + struct lantern_client *client, + const char *peer_id_text, + const LanternRoot *root, + bool use_legacy) +{ + enum lantern_blocks_req_variant variant = + use_legacy ? LANTERN_BLOCKS_REQ_VARIANT_LEGACY_SNAPPY : LANTERN_BLOCKS_REQ_VARIANT_PRIMARY; + return schedule_blocks_request_variant(client, peer_id_text, root, variant); +} diff --git a/src/core/client_reqresp_stream.c b/src/core/client_reqresp_stream.c index 6db2623..b22d891 100644 --- a/src/core/client_reqresp_stream.c +++ b/src/core/client_reqresp_stream.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -645,7 +646,7 @@ static void log_varint_header_details( header_hex[0] = '\0'; } - lantern_log_info( + lantern_log_debug( "reqresp", meta, "%s payload_len=%" PRIu64 " header_hex=%s", @@ -796,7 +797,7 @@ static void log_payload_read_complete( { payload_hex[0] = '\0'; } - lantern_log_info( + lantern_log_debug( "reqresp", meta, "%s payload read complete bytes=%zu%s%s", @@ -997,7 +998,7 @@ int lantern_reqresp_read_response_chunk( (unsigned)frame_code, (unsigned)header_first_byte); - return read_varint_payload_chunk( + int payload_rc = read_varint_payload_chunk( stream, header_first_byte, out_data, @@ -1005,6 +1006,8 @@ int lantern_reqresp_read_response_chunk( out_err, &meta, "chunk"); + + return payload_rc; } diff --git a/src/core/client_sync.c b/src/core/client_sync.c index 554f04c..42744e4 100644 --- a/src/core/client_sync.c +++ b/src/core/client_sync.c @@ -18,6 +18,7 @@ #include "client_internal.h" #include +#include #include #include @@ -201,7 +202,7 @@ int gossip_block_handler( 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, NULL, peer_id_text, "gossip"); + lantern_client_record_block(client, block, NULL, peer_id_text, "gossip", false); return LANTERN_CLIENT_OK; } @@ -1018,12 +1019,243 @@ void lantern_client_pending_remove_by_root(struct lantern_client *client, const * * @note Thread safety: Acquires pending_lock */ +static bool try_schedule_blocks_request( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *root, + bool ignore_backoff) +{ + if (!client || !peer_text || !peer_text[0] || !root || lantern_root_is_zero(root)) + { + return false; + } + if (client->debug_disable_block_requests) + { + return false; + } + + bool state_locked = lantern_client_lock_state(client); + bool known = false; + if (state_locked) + { + known = lantern_client_block_known_locked(client, root, NULL); + } + lantern_client_unlock_state(client, state_locked); + if (known) + { + return false; + } + + if (pthread_mutex_lock(&client->status_lock) != 0) + { + return false; + } + + struct lantern_peer_status_entry *entry = + lantern_client_ensure_status_entry_locked(client, peer_text); + if (!entry) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + + if (entry->requested_head) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + + uint64_t now_ms = monotonic_millis(); + if (!ignore_backoff) + { + uint64_t backoff_ms = blocks_request_backoff_ms(entry->consecutive_blocks_failures); + if (entry->consecutive_blocks_failures == 0 + && backoff_ms < LANTERN_BLOCKS_REQUEST_MIN_POLL_MS) + { + backoff_ms = LANTERN_BLOCKS_REQUEST_MIN_POLL_MS; + } + + if (entry->last_blocks_request_ms != 0 + && now_ms < entry->last_blocks_request_ms + backoff_ms) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + } + + entry->requested_head = true; + entry->last_blocks_request_ms = now_ms; + pthread_mutex_unlock(&client->status_lock); + + if (lantern_client_schedule_blocks_request(client, peer_text, root, false) != 0) + { + lantern_client_on_blocks_request_complete( + client, + peer_text, + root, + LANTERN_BLOCKS_REQUEST_ABORTED); + return false; + } + + return true; +} + +static void mark_pending_parent_requested( + struct lantern_client *client, + const LanternRoot *child_root, + bool requested) +{ + if (!client || !child_root) + { + return; + } + + bool locked = lantern_client_lock_pending(client); + if (!locked) + { + return; + } + + for (size_t i = 0; i < client->pending_blocks.length; ++i) + { + struct lantern_pending_block *entry = &client->pending_blocks.items[i]; + if (memcmp(entry->root.bytes, child_root->bytes, LANTERN_ROOT_SIZE) == 0) + { + entry->parent_requested = requested; + break; + } + } + + lantern_client_unlock_pending(client, locked); +} + +struct pending_parent_candidate +{ + LanternRoot child_root; + LanternRoot parent_root; +}; + +void lantern_client_request_pending_parent_after_blocks( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *request_root) +{ + if (!client || !peer_text || !peer_text[0]) + { + return; + } + + struct pending_parent_candidate candidates[LANTERN_PENDING_BLOCK_LIMIT]; + size_t candidate_count = 0; + LanternRoot requested_root = {0}; + bool has_requested_root = false; + bool prefer_requested_root = false; + struct pending_parent_candidate requested_candidate = {0}; + if (request_root && !lantern_root_is_zero(request_root)) + { + requested_root = *request_root; + has_requested_root = true; + } + + bool locked = lantern_client_lock_pending(client); + if (!locked) + { + return; + } + + if (has_requested_root) + { + for (size_t i = 0; i < client->pending_blocks.length; ++i) + { + struct lantern_pending_block *entry = &client->pending_blocks.items[i]; + if (memcmp(entry->root.bytes, requested_root.bytes, LANTERN_ROOT_SIZE) != 0) + { + continue; + } + if (lantern_root_is_zero(&entry->parent_root)) + { + break; + } + requested_candidate.child_root = entry->root; + requested_candidate.parent_root = entry->parent_root; + prefer_requested_root = true; + break; + } + } + + for (size_t i = 0; i < client->pending_blocks.length; ++i) + { + struct lantern_pending_block *entry = &client->pending_blocks.items[i]; + if (entry->parent_requested) + { + continue; + } + if (lantern_root_is_zero(&entry->parent_root)) + { + continue; + } + if (entry->peer_text[0] != '\0' && strcmp(entry->peer_text, peer_text) != 0) + { + continue; + } + if (has_requested_root + && memcmp(entry->parent_root.bytes, requested_root.bytes, LANTERN_ROOT_SIZE) == 0) + { + continue; + } + if (candidate_count >= LANTERN_PENDING_BLOCK_LIMIT) + { + break; + } + candidates[candidate_count].child_root = entry->root; + candidates[candidate_count].parent_root = entry->parent_root; + candidate_count += 1u; + } + + lantern_client_unlock_pending(client, locked); + + if (prefer_requested_root) + { + if (try_schedule_blocks_request( + client, + peer_text, + &requested_candidate.parent_root, + true)) + { + char parent_hex[ROOT_HEX_BUFFER_LEN]; + char child_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(&requested_candidate.parent_root, parent_hex, sizeof(parent_hex)); + format_root_hex(&requested_candidate.child_root, child_hex, sizeof(child_hex)); + mark_pending_parent_requested(client, &requested_candidate.child_root, true); + } + return; + } + + for (size_t i = 0; i < candidate_count; ++i) + { + if (try_schedule_blocks_request( + client, + peer_text, + &candidates[i].parent_root, + true)) + { + char parent_hex[ROOT_HEX_BUFFER_LEN]; + char child_hex[ROOT_HEX_BUFFER_LEN]; + format_root_hex(&candidates[i].parent_root, parent_hex, sizeof(parent_hex)); + format_root_hex(&candidates[i].child_root, child_hex, sizeof(child_hex)); + mark_pending_parent_requested(client, &candidates[i].child_root, true); + break; + } + } +} + void lantern_client_enqueue_pending_block( struct lantern_client *client, const LanternSignedBlock *block, const LanternRoot *block_root, const LanternRoot *parent_root, - const char *peer_text) + const char *peer_text, + bool request_parent) { if (!client || !block || !block_root || !parent_root) { @@ -1044,6 +1276,17 @@ void lantern_client_enqueue_pending_block( if (existing) { + bool should_request = request_parent && !existing->parent_requested + && peer_text && *peer_text; + LanternRoot request_root = existing->parent_root; + LanternRoot child_root = existing->root; + char peer_copy[PEER_TEXT_BUFFER_LEN]; + peer_copy[0] = '\0'; + if (should_request) + { + strncpy(peer_copy, peer_text, sizeof(peer_copy) - 1u); + peer_copy[sizeof(peer_copy) - 1u] = '\0'; + } if (peer_text && *peer_text) { if (existing->peer_text[0] == '\0' || strcmp(existing->peer_text, peer_text) != 0) @@ -1051,10 +1294,15 @@ void lantern_client_enqueue_pending_block( strncpy(existing->peer_text, peer_text, sizeof(existing->peer_text) - 1u); existing->peer_text[sizeof(existing->peer_text) - 1u] = '\0'; } - /* Do NOT immediately request parent via req/resp - rely on gossip to deliver it. - req/resp should only be used for sync recovery, not for normal block propagation. */ } lantern_client_unlock_pending(client, locked); + if (should_request) + { + if (try_schedule_blocks_request(client, peer_copy, &request_root, false)) + { + mark_pending_parent_requested(client, &child_root, true); + } + } return; } @@ -1093,9 +1341,6 @@ void lantern_client_enqueue_pending_block( .peer = peer_text && *peer_text ? peer_text : NULL, }; - /* Do NOT immediately request parent via req/resp - rely on gossip to deliver it. - req/resp should only be used for sync recovery, not for normal block propagation. - The parent_requested flag is no longer used for immediate requests. */ entry->parent_requested = false; lantern_client_unlock_pending(client, locked); @@ -1107,6 +1352,17 @@ void lantern_client_enqueue_pending_block( block->message.block.slot, block_hex[0] ? block_hex : "0x0", parent_hex[0] ? parent_hex : "0x0"); + + if (request_parent && peer_text && *peer_text) + { + char peer_copy[PEER_TEXT_BUFFER_LEN]; + strncpy(peer_copy, peer_text, sizeof(peer_copy) - 1u); + peer_copy[sizeof(peer_copy) - 1u] = '\0'; + if (try_schedule_blocks_request(client, peer_copy, &parent_root_local, false)) + { + mark_pending_parent_requested(client, &block_root_local, true); + } + } } @@ -1185,7 +1441,7 @@ void lantern_client_process_pending_children( .validator = client->node_id, .peer = peer_copy[0] ? peer_copy : NULL, }; - (void)lantern_client_import_block(client, &replay, &child_root, &meta); + (void)lantern_client_import_block(client, &replay, &child_root, &meta, true); lantern_signed_block_with_attestation_reset(&replay); } } diff --git a/src/core/client_sync_blocks.c b/src/core/client_sync_blocks.c index 880ccc0..2eb3a3b 100644 --- a/src/core/client_sync_blocks.c +++ b/src/core/client_sync_blocks.c @@ -114,6 +114,12 @@ static bool signed_block_signatures_are_valid( attestations->length); return false; } + if (block->message.block.slot == 0 + && attestations->length == 0 + && block->signatures.length == 0) + { + return true; + } if (block->signatures.length == 0) { lantern_log_warn( @@ -221,14 +227,15 @@ static bool should_process_block( uint64_t local_slot, bool root_known, uint64_t known_slot, - const struct lantern_log_metadata *meta) + const struct lantern_log_metadata *meta, + bool allow_historical) { if (root_known && slot <= known_slot) { lantern_log_trace("state", meta, "skipping known block slot=%" PRIu64, slot); return false; } - if (slot < local_slot && !root_known) + if (slot < local_slot && !root_known && !allow_historical) { lantern_log_debug( "state", @@ -259,7 +266,8 @@ static bool handle_block_parent_locked( const LanternSignedBlock *block, const LanternRoot *block_root, const struct lantern_log_metadata *meta, - bool *state_locked) + bool *state_locked, + bool allow_historical) { if (!client || !block || !block_root || !state_locked || !*state_locked) { @@ -278,7 +286,13 @@ static bool handle_block_parent_locked( const char *peer_text = meta && meta->peer ? meta->peer : NULL; lantern_client_unlock_state(client, *state_locked); *state_locked = false; - lantern_client_enqueue_pending_block(client, block, block_root, &parent_root, peer_text); + lantern_client_enqueue_pending_block( + client, + block, + block_root, + &parent_root, + peer_text, + allow_historical); return false; } @@ -372,7 +386,13 @@ static bool handle_block_parent_locked( lantern_client_unlock_state(client, *state_locked); *state_locked = false; - lantern_client_enqueue_pending_block(client, block, block_root, &parent_root, peer_text); + lantern_client_enqueue_pending_block( + client, + block, + block_root, + &parent_root, + peer_text, + false); return false; } @@ -653,7 +673,8 @@ bool lantern_client_import_block( struct lantern_client *client, const LanternSignedBlock *block, const LanternRoot *block_root, - const struct lantern_log_metadata *meta) + const struct lantern_log_metadata *meta, + bool allow_historical) { if (!client || !block || !client->has_state) { @@ -684,12 +705,24 @@ bool lantern_client_import_block( uint64_t known_slot = 0; bool root_known = lantern_client_block_known_locked(client, &block_root_local, &known_slot); - if (!should_process_block(block->message.block.slot, local_slot, root_known, known_slot, meta)) + if (!should_process_block( + block->message.block.slot, + local_slot, + root_known, + known_slot, + meta, + allow_historical)) { goto cleanup; } - if (!handle_block_parent_locked(client, block, &block_root_local, meta, &state_locked)) + if (!handle_block_parent_locked( + client, + block, + &block_root_local, + meta, + &state_locked, + allow_historical)) { goto cleanup; } @@ -754,7 +787,8 @@ void lantern_client_record_block( const LanternSignedBlock *block, const LanternRoot *root, const char *peer_text, - const char *context) + const char *context, + bool allow_historical) { if (!client || !block) { @@ -814,5 +848,5 @@ void lantern_client_record_block( } } - lantern_client_import_block(client, block, selected_root, &meta); + lantern_client_import_block(client, block, selected_root, &meta, allow_historical); } diff --git a/src/core/client_sync_internal.h b/src/core/client_sync_internal.h index 239a443..cc50d04 100644 --- a/src/core/client_sync_internal.h +++ b/src/core/client_sync_internal.h @@ -62,6 +62,9 @@ struct lantern_vote_rejection_info { bool has_reason; /**< True if rejection reason is set */ char message[256]; /**< Rejection reason message */ + bool has_unknown_root; /**< True if rejection is due to unknown checkpoint root */ + LanternRoot unknown_root; /**< Unknown checkpoint root */ + uint64_t unknown_slot; /**< Slot of unknown checkpoint */ }; @@ -303,6 +306,7 @@ bool lantern_client_validate_vote_constraints( * @param block Signed block to import * @param block_root Precomputed block root (may be NULL) * @param meta Logging metadata + * @param allow_historical True to allow importing blocks older than local slot * @return true if block was imported successfully * * @note Thread safety: Acquires state_lock and pending_lock @@ -311,7 +315,8 @@ bool lantern_client_import_block( struct lantern_client *client, const LanternSignedBlock *block, const LanternRoot *block_root, - const struct lantern_log_metadata *meta); + const struct lantern_log_metadata *meta, + bool allow_historical); /** @@ -324,6 +329,7 @@ bool lantern_client_import_block( * @param root Precomputed block root (may be NULL) * @param peer_text Peer ID string (may be NULL) * @param context Description of source for logging + * @param allow_historical True to allow importing blocks older than local slot * * @note Thread safety: Acquires state_lock via lantern_client_import_block */ @@ -332,7 +338,8 @@ void lantern_client_record_block( const LanternSignedBlock *block, const LanternRoot *root, const char *peer_text, - const char *context); + const char *context, + bool allow_historical); /** @@ -441,7 +448,24 @@ void lantern_client_enqueue_pending_block( const LanternSignedBlock *block, const LanternRoot *block_root, const LanternRoot *parent_root, - const char *peer_text); + const char *peer_text, + bool request_parent); + +/** + * Attempt to request the parent of a pending block after a blocks_by_root success. + * + * Uses the peer that just completed a blocks_by_root request to backfill + * missing parents for queued blocks. + * + * @param client Client instance + * @param peer_text Peer ID string (must be non-NULL/non-empty) + * + * @note Thread safety: Thread-safe; acquires pending_lock internally + */ +void lantern_client_request_pending_parent_after_blocks( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *request_root); /** diff --git a/src/core/client_sync_votes.c b/src/core/client_sync_votes.c index eacd8c7..9a1a250 100644 --- a/src/core/client_sync_votes.c +++ b/src/core/client_sync_votes.c @@ -34,6 +34,136 @@ enum VOTE_ROOT_HEX_BUFFER_LEN = (LANTERN_ROOT_SIZE * 2u) + 3u, }; +static bool pending_contains_root(struct lantern_client *client, const LanternRoot *root) +{ + if (!client || !root) + { + return false; + } + + bool locked = lantern_client_lock_pending(client); + if (!locked) + { + return false; + } + + bool found = false; + for (size_t i = 0; i < client->pending_blocks.length; ++i) + { + struct lantern_pending_block *entry = &client->pending_blocks.items[i]; + if (memcmp(entry->root.bytes, root->bytes, LANTERN_ROOT_SIZE) == 0 + || memcmp(entry->parent_root.bytes, root->bytes, LANTERN_ROOT_SIZE) == 0) + { + found = true; + break; + } + } + + lantern_client_unlock_pending(client, locked); + return found; +} + +static bool should_request_missing_root( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *root) +{ + if (!client || !peer_text || !peer_text[0] || !root || lantern_root_is_zero(root)) + { + return false; + } + + bool state_locked = lantern_client_lock_state(client); + bool known = false; + if (state_locked) + { + known = lantern_client_block_known_locked(client, root, NULL); + } + lantern_client_unlock_state(client, state_locked); + + if (known) + { + return false; + } + + if (pthread_mutex_lock(&client->status_lock) != 0) + { + return false; + } + + struct lantern_peer_status_entry *entry = + lantern_client_ensure_status_entry_locked(client, peer_text); + if (!entry) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + + if (entry->requested_head) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + + uint64_t now_ms = monotonic_millis(); + uint64_t backoff_ms = blocks_request_backoff_ms(entry->consecutive_blocks_failures); + if (entry->consecutive_blocks_failures == 0 + && backoff_ms < LANTERN_BLOCKS_REQUEST_MIN_POLL_MS) + { + backoff_ms = LANTERN_BLOCKS_REQUEST_MIN_POLL_MS; + } + + if (entry->last_blocks_request_ms != 0 + && now_ms < entry->last_blocks_request_ms + backoff_ms) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + + if (pending_contains_root(client, root)) + { + pthread_mutex_unlock(&client->status_lock); + return false; + } + + entry->requested_head = true; + entry->last_blocks_request_ms = now_ms; + pthread_mutex_unlock(&client->status_lock); + return true; +} + +static void request_missing_checkpoint_root( + struct lantern_client *client, + const char *peer_text, + const LanternRoot *root, + uint64_t slot) +{ + if (!should_request_missing_root(client, peer_text, root)) + { + return; + } + + char root_hex[VOTE_ROOT_HEX_BUFFER_LEN]; + format_root_hex(root, root_hex, sizeof(root_hex)); + lantern_log_info( + "reqresp", + &(const struct lantern_log_metadata){ + .validator = client ? client->node_id : NULL, + .peer = (peer_text && *peer_text) ? peer_text : NULL}, + "requesting missing checkpoint root=%s slot=%" PRIu64, + root_hex[0] ? root_hex : "0x0", + slot); + + if (lantern_client_schedule_blocks_request(client, peer_text, root, false) != 0) + { + lantern_client_on_blocks_request_complete( + client, + peer_text, + root, + LANTERN_BLOCKS_REQUEST_ABORTED); + } +} + /* ============================================================================ * External Functions (from client_sync.c) @@ -127,6 +257,9 @@ static bool validate_vote_checkpoint( name, root_hex[0] ? root_hex : "0x0", checkpoint->slot); + out_rejection->has_unknown_root = true; + out_rejection->unknown_root = checkpoint->root; + out_rejection->unknown_slot = checkpoint->slot; } return false; } @@ -768,6 +901,14 @@ void lantern_client_record_vote( lantern_client_note_vote_outcome(client, peer_text, &vote_copy, vote_processed); if (!vote_processed) { + if (rejection.has_unknown_root) + { + request_missing_checkpoint_root( + client, + peer_text, + &rejection.unknown_root, + rejection.unknown_slot); + } const char *reason_text = rejection.has_reason ? rejection.message : "unknown"; lantern_log_info( "gossip", diff --git a/src/core/client_validator.c b/src/core/client_validator.c index ec0bdcf..813c6bd 100644 --- a/src/core/client_validator.c +++ b/src/core/client_validator.c @@ -882,7 +882,7 @@ int validator_propose_block(struct lantern_client *client, uint64_t slot, size_t slot, block.message.block.proposer_index); - lantern_client_record_block(client, &block, NULL, NULL, "local"); + lantern_client_record_block(client, &block, NULL, NULL, "local", false); rc = lantern_client_publish_block(client, &block); if (rc != LANTERN_CLIENT_OK) { diff --git a/src/genesis/genesis_parse_registry.c b/src/genesis/genesis_parse_registry.c index 024f11d..3d98641 100644 --- a/src/genesis/genesis_parse_registry.c +++ b/src/genesis/genesis_parse_registry.c @@ -18,6 +18,7 @@ #include #include "internal/yaml_parser.h" +#include "lantern/consensus/containers.h" #include "lantern/support/strings.h" static const size_t GENESIS_SMALL_LINE_BUFFER_LEN = 1024; @@ -315,6 +316,11 @@ static int collect_registry_mapping_indices( result = LANTERN_GENESIS_ERR_INVALID_DATA; break; } + if (value >= (uint64_t)LANTERN_VALIDATOR_REGISTRY_LIMIT) + { + result = LANTERN_GENESIS_ERR_INVALID_DATA; + break; + } if (count == cap) { @@ -396,6 +402,10 @@ static int validate_registry_index_coverage( } size_t record_count = max_index + 1; + if (record_count > (size_t)LANTERN_VALIDATOR_REGISTRY_LIMIT) + { + return LANTERN_GENESIS_ERR_INVALID_DATA; + } bool *seen = calloc(record_count, sizeof(*seen)); if (!seen) { @@ -583,6 +593,14 @@ static int scan_registry_objects( { return LANTERN_GENESIS_ERR_OVERFLOW; } + if (have_explicit_indices && max_index >= (size_t)LANTERN_VALIDATOR_REGISTRY_LIMIT) + { + return LANTERN_GENESIS_ERR_INVALID_DATA; + } + if (!have_explicit_indices && object_count > (size_t)LANTERN_VALIDATOR_REGISTRY_LIMIT) + { + return LANTERN_GENESIS_ERR_INVALID_DATA; + } *out_has_pubkey_field = has_pubkey_field; *out_have_explicit_indices = have_explicit_indices; diff --git a/src/networking/gossipsub_service.c b/src/networking/gossipsub_service.c index b9a54e6..ac9b3b6 100644 --- a/src/networking/gossipsub_service.c +++ b/src/networking/gossipsub_service.c @@ -168,6 +168,58 @@ static size_t signed_block_min_capacity(const LanternSignedBlock *block) { return total; } +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 block_offset = SSZ_BYTE_SIZE_OF_UINT32; + size_t body_header = SSZ_BYTE_SIZE_OF_UINT32; + size_t att_bytes = (size_t)LANTERN_MAX_ATTESTATIONS * LANTERN_VOTE_SSZ_SIZE; + size_t proposer_bytes = LANTERN_VOTE_SSZ_SIZE; + size_t signatures_bytes = (size_t)LANTERN_MAX_BLOCK_SIGNATURES * LANTERN_SIGNATURE_SIZE; + size_t total = offsets + block_fixed; + if (block_offset > SIZE_MAX - total) { + return 0; + } + total += block_offset; + if (total > SIZE_MAX - proposer_bytes) { + return 0; + } + total += proposer_bytes; + 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; + } + total += signatures_bytes; + return total; +} + +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 default_max = block_max > vote_max ? block_max : vote_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; + } + return default_max; +} + static libp2p_err_t lantern_gossipsub_message_id_cb( const libp2p_gossipsub_message_t *msg, uint8_t **out_id, @@ -190,6 +242,12 @@ static libp2p_err_t lantern_gossipsub_message_id_cb( if (msg->data && msg->data_len > 0) { size_t expected = 0; if (lantern_snappy_uncompressed_length(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; diff --git a/src/networking/messages.c b/src/networking/messages.c index c664559..b8f86eb 100644 --- a/src/networking/messages.c +++ b/src/networking/messages.c @@ -1,6 +1,7 @@ #include "lantern/networking/messages.h" #include +#include #include #include @@ -12,12 +13,7 @@ #include "lantern/support/log.h" #include "lantern/support/strings.h" -/* Status SNAPPY framing mode: always framed (RFC 7493 style) to match Zeam. */ - -static bool status_payload_is_framed(const uint8_t *data, size_t len) { - /* Minimal framed header: chunk type + 24‑bit length + "sNaPpY" magic */ - return len >= 10 && data[0] == 0xff && memcmp(data + 4, "sNaPpY", 6) == 0; -} +/* Status SNAPPY framing mode: accept framed or raw payloads. */ static int write_u32_le(uint32_t value, uint8_t *out, size_t out_len) { if (!out || out_len < sizeof(uint32_t)) { @@ -41,6 +37,121 @@ static int read_u32_le(const uint8_t *data, size_t data_len, uint32_t *value) { return 0; } +static int lantern_network_blocks_by_root_response_decode_prefixed( + LanternBlocksByRootResponse *resp, + const uint8_t *data, + size_t data_len) { + if (!resp || (!data && data_len > 0)) { + return -1; + } + if (lantern_blocks_by_root_response_resize(resp, 0) != 0) { + return -1; + } + if (data_len == 0) { + return 0; + } + if (data_len < sizeof(uint32_t)) { + return -1; + } + + uint32_t first_offset = 0; + if (read_u32_le(data, data_len, &first_offset) != 0) { + return -1; + } + if (first_offset > data_len || (first_offset % sizeof(uint32_t)) != 0) { + return -1; + } + + size_t count = first_offset / sizeof(uint32_t); + if (count == 0 || count > LANTERN_MAX_REQUEST_BLOCKS) { + return -1; + } + if (lantern_blocks_by_root_response_resize(resp, count) != 0) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + + size_t offsets_len = count * sizeof(uint32_t); + if (data_len < offsets_len) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + + const uint8_t *offsets_region = data; + const uint8_t *payload_region = data; + size_t payload_total = data_len; + + uint32_t prev_offset = first_offset; + for (size_t i = 0; i < count; ++i) { + size_t offset_index = i * sizeof(uint32_t); + uint32_t start_offset = 0; + if (read_u32_le(offsets_region + offset_index, data_len - offset_index, &start_offset) != 0) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + if (start_offset < offsets_len || start_offset > payload_total) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + if (i > 0 && start_offset < prev_offset) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + + uint32_t end_offset = (uint32_t)payload_total; + if (i + 1 < count) { + size_t next_index = (i + 1) * sizeof(uint32_t); + if (read_u32_le(offsets_region + next_index, data_len - next_index, &end_offset) != 0) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + if (end_offset < start_offset || end_offset > payload_total) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + } + + size_t span = (size_t)end_offset - (size_t)start_offset; + if (span <= sizeof(uint32_t)) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + + uint32_t sig_count = 0; + if (read_u32_le(payload_region + start_offset, span, &sig_count) != 0) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + if (sig_count > LANTERN_MAX_BLOCK_SIGNATURES) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + + LanternSignedBlock *entry = &resp->blocks[i]; + if (lantern_ssz_decode_signed_block( + entry, + payload_region + start_offset + sizeof(uint32_t), + span - sizeof(uint32_t)) + != 0) { + lantern_blocks_by_root_response_reset(resp); + return -1; + } + + if (sig_count > 0 && entry->signatures.length != (size_t)sig_count) { + lantern_log_debug( + "reqresp", + NULL, + "blocks_by_root legacy sig count mismatch prefix=%u decoded=%zu", + sig_count, + entry->signatures.length); + } + + prev_offset = start_offset; + } + + return 0; +} + static int ensure_block_capacity(LanternBlocksByRootResponse *resp, size_t required) { if (!resp) { return -1; @@ -257,10 +368,6 @@ int lantern_network_status_decode_snappy( if (!status || !data) { return -1; } - /* Enforce framed snappy: reject raw payloads */ - if (!status_payload_is_framed(data, data_len)) { - return -1; - } uint8_t raw[2u * LANTERN_CHECKPOINT_SSZ_SIZE]; size_t raw_written = sizeof(raw); int rc = lantern_snappy_decompress(data, data_len, raw, sizeof(raw), &raw_written); @@ -685,6 +792,15 @@ int lantern_network_blocks_by_root_response_decode_snappy( } int decode_rc = lantern_network_blocks_by_root_response_decode(resp, raw, written); if (decode_rc != 0) { + int legacy_rc = lantern_network_blocks_by_root_response_decode_prefixed(resp, raw, written); + if (legacy_rc == 0) { + lantern_log_info( + "reqresp", + NULL, + "blocks_by_root decoded with legacy prefixed format"); + free(raw); + return 0; + } size_t preview = written < LANTERN_STATUS_PREVIEW_BYTES ? written : LANTERN_STATUS_PREVIEW_BYTES; char preview_hex[(LANTERN_STATUS_PREVIEW_BYTES * 2u) + 1u]; if (preview > 0 diff --git a/src/networking/reqresp_service.c b/src/networking/reqresp_service.c index 87ad2ed..b029a48 100644 --- a/src/networking/reqresp_service.c +++ b/src/networking/reqresp_service.c @@ -111,6 +111,7 @@ struct status_request_ctx { char peer_text[128]; const char *protocol_id; uint64_t debug_trace_id; + bool legacy_framing; }; struct status_request_worker_args { @@ -118,11 +119,23 @@ struct status_request_worker_args { libp2p_stream_t *stream; }; +enum status_payload_kind { + STATUS_PAYLOAD_FRAMED_SNAPPY = 0, + STATUS_PAYLOAD_RAW_SNAPPY = 1, + STATUS_PAYLOAD_RAW_SSZ = 2, +}; + 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 legacy_framing, + bool notify_on_failure); static void lantern_reqresp_service_clear(struct lantern_reqresp_service *service) { if (!service) { return; @@ -137,7 +150,10 @@ static void lantern_reqresp_service_clear(struct lantern_reqresp_service *servic service->status_server_legacy = NULL; service->blocks_server = NULL; service->blocks_server_legacy = NULL; + service->blocks_server_bare = NULL; service->event_subscription = NULL; + lantern_string_list_reset(&service->legacy_peers); + lantern_string_list_init(&service->legacy_peers); } void lantern_reqresp_service_init(struct lantern_reqresp_service *service) { @@ -146,6 +162,7 @@ void lantern_reqresp_service_init(struct lantern_reqresp_service *service) { } memset(service, 0, sizeof(*service)); service->lock_initialized = 0; + lantern_string_list_init(&service->legacy_peers); } static void destroy_lock(struct lantern_reqresp_service *service) { @@ -179,6 +196,9 @@ void lantern_reqresp_service_reset(struct lantern_reqresp_service *service) { if (service->blocks_server_legacy && host) { (void)libp2p_host_unlisten(host, service->blocks_server_legacy); } + if (service->blocks_server_bare && host) { + (void)libp2p_host_unlisten(host, service->blocks_server_bare); + } destroy_lock(service); lantern_reqresp_service_clear(service); @@ -199,17 +219,71 @@ void lantern_reqresp_service_hint_peer_legacy( struct lantern_reqresp_service *service, const char *peer_id_text, bool legacy) { - (void)service; - (void)peer_id_text; - (void)legacy; + if (!service || !peer_id_text || peer_id_text[0] == '\0') { + return; + } + ensure_lock(service); + if (!service->lock_initialized) { + return; + } + + if (pthread_mutex_lock(&service->lock) != 0) { + return; + } + + bool found = false; + for (size_t i = 0; i < service->legacy_peers.len; ++i) { + if (service->legacy_peers.items[i] + && strcmp(service->legacy_peers.items[i], peer_id_text) == 0) { + found = true; + break; + } + } + + if (legacy) { + if (!found) { + (void)lantern_string_list_append(&service->legacy_peers, peer_id_text); + } + } else if (found) { + for (size_t i = 0; i < service->legacy_peers.len; ++i) { + if (service->legacy_peers.items[i] + && strcmp(service->legacy_peers.items[i], peer_id_text) == 0) { + free(service->legacy_peers.items[i]); + for (size_t j = i; j + 1 < service->legacy_peers.len; ++j) { + service->legacy_peers.items[j] = service->legacy_peers.items[j + 1]; + } + service->legacy_peers.len--; + service->legacy_peers.items[service->legacy_peers.len] = NULL; + break; + } + } + } + + pthread_mutex_unlock(&service->lock); } int lantern_reqresp_service_peer_prefers_legacy( const struct lantern_reqresp_service *service, const char *peer_id_text) { - (void)service; - (void)peer_id_text; - return 0; + if (!service || !peer_id_text || peer_id_text[0] == '\0') { + return 0; + } + if (!service->lock_initialized) { + return 0; + } + if (pthread_mutex_lock((pthread_mutex_t *)&service->lock) != 0) { + return 0; + } + int result = 0; + for (size_t i = 0; i < service->legacy_peers.len; ++i) { + if (service->legacy_peers.items[i] + && strcmp(service->legacy_peers.items[i], peer_id_text) == 0) { + result = 1; + break; + } + } + pthread_mutex_unlock((pthread_mutex_t *)&service->lock); + return result; } static void describe_peer(const peer_id_t *peer, char *buffer, size_t length) { @@ -282,6 +356,38 @@ static void log_payload_preview( ellipsis); } +static bool payload_is_snappy_framed(const uint8_t *data, size_t len) { + return data + && len >= 10 + && data[0] == 0xff + && memcmp(data + 4, "sNaPpY", 6) == 0; +} + +static int decode_status_payload( + const uint8_t *data, + size_t data_len, + LanternStatusMessage *out_status, + enum status_payload_kind *out_kind) { + if (!data || !out_status) { + return -1; + } + bool framed = payload_is_snappy_framed(data, data_len); + if (lantern_network_status_decode_snappy(out_status, data, data_len) == 0) { + if (out_kind) { + *out_kind = framed ? STATUS_PAYLOAD_FRAMED_SNAPPY : STATUS_PAYLOAD_RAW_SNAPPY; + } + return 0; + } + const size_t expected_len = 2u * LANTERN_CHECKPOINT_SSZ_SIZE; + if (data_len == expected_len && lantern_network_status_decode(out_status, data, data_len) == 0) { + if (out_kind) { + *out_kind = STATUS_PAYLOAD_RAW_SSZ; + } + return 0; + } + return -1; +} + static int read_payload_bytes( libp2p_stream_t *stream, const char *label, @@ -644,11 +750,7 @@ static int send_response_chunk( return -1; } - /* Force response-code for status RPC (Zeam expects code byte) */ bool include_code = include_response_code; - if (protocol_id && strstr(protocol_id, "/status/1/ssz_snappy") != NULL) { - include_code = true; - } lantern_log_debug( "reqresp", @@ -815,16 +917,16 @@ static void *status_worker(void *arg) { char peer_text[128]; describe_peer(libp2p_stream_remote_peer(stream), peer_text, sizeof(peer_text)); + enum status_payload_kind request_kind = STATUS_PAYLOAD_FRAMED_SNAPPY; 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 include_response_code=%s", + "status[%" PRIu64 "] stream protocol=%s", trace_id, - protocol_id ? protocol_id : "-", - include_response_code ? "true" : "false"); + protocol_id ? protocol_id : "-"); lantern_log_trace( "reqresp", @@ -866,7 +968,7 @@ static void *status_worker(void *arg) { LanternStatusMessage remote_status; memset(&remote_status, 0, sizeof(remote_status)); if (request_len == 0 - || lantern_network_status_decode_snappy(&remote_status, request, request_len) != 0) { + || decode_status_payload(request, request_len, &remote_status, &request_kind) != 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); @@ -876,6 +978,19 @@ static void *status_worker(void *arg) { } free(request); + include_response_code = (request_kind == STATUS_PAYLOAD_FRAMED_SNAPPY); + lantern_log_debug( + "reqresp", + &stream_meta, + "status[%" PRIu64 "] request framing=%s include_response_code=%s", + trace_id, + request_kind == STATUS_PAYLOAD_RAW_SSZ ? "raw_ssz" + : (request_kind == STATUS_PAYLOAD_RAW_SNAPPY ? "raw_snappy" : "framed"), + include_response_code ? "true" : "false"); + if (request_kind != STATUS_PAYLOAD_FRAMED_SNAPPY && peer_text[0] != '\0') { + lantern_reqresp_service_hint_peer_legacy(service, peer_text, true); + } + 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'; @@ -911,27 +1026,49 @@ static void *status_worker(void *arg) { return NULL; } - size_t max_payload = 0; - if (lantern_snappy_max_compressed_size(2u * LANTERN_CHECKPOINT_SSZ_SIZE, &max_payload) != LANTERN_SNAPPY_OK) { + 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); close_stream(stream); return NULL; } - uint8_t *buffer = (uint8_t *)malloc(max_payload); + bool use_raw_ssz = (request_kind == STATUS_PAYLOAD_RAW_SSZ); + bool use_raw_snappy = (request_kind == STATUS_PAYLOAD_RAW_SNAPPY); + size_t max_payload = response_raw_len; + if (!use_raw_ssz) { + int snappy_rc = use_raw_snappy + ? lantern_snappy_max_compressed_size_raw(response_raw_len, &max_payload) + : lantern_snappy_max_compressed_size(response_raw_len, &max_payload); + if (snappy_rc != LANTERN_SNAPPY_OK) { + log_stream_error("encode", protocol_id, peer_text); + close_stream(stream); + return NULL; + } + } + + uint8_t *buffer = (uint8_t *)malloc(max_payload > 0 ? max_payload : 1u); if (!buffer) { log_stream_error("encode", protocol_id, peer_text); close_stream(stream); return NULL; } - size_t response_raw_len = 0; size_t written = 0; - if (lantern_network_status_encode_snappy(&response, buffer, max_payload, &written, &response_raw_len) != 0) { - free(buffer); - log_stream_error("encode", protocol_id, peer_text); - close_stream(stream); - return NULL; + if (use_raw_ssz) { + memcpy(buffer, raw_status, response_raw_len); + written = response_raw_len; + } else { + int snappy_rc = use_raw_snappy + ? lantern_snappy_compress_raw(raw_status, response_raw_len, buffer, max_payload, &written) + : lantern_snappy_compress(raw_status, response_raw_len, buffer, max_payload, &written); + if (snappy_rc != LANTERN_SNAPPY_OK) { + free(buffer); + log_stream_error("encode", protocol_id, peer_text); + close_stream(stream); + return NULL; + } } log_payload_preview("status response raw", peer_text, buffer, written); @@ -964,7 +1101,7 @@ static void *status_worker(void *arg) { free(buffer); close_stream(stream); - lantern_log_info( + lantern_log_debug( "network", &(const struct lantern_log_metadata){.peer = peer_text}, "served status request"); @@ -1011,8 +1148,22 @@ static void *status_request_worker(void *arg) { goto finish; } + bool legacy_framing = ctx->legacy_framing; + 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; + } + size_t max_payload = 0; - if (lantern_snappy_max_compressed_size(2u * LANTERN_CHECKPOINT_SSZ_SIZE, &max_payload) != LANTERN_SNAPPY_OK) { + int max_rc = legacy_framing + ? lantern_snappy_max_compressed_size_raw(payload_raw_len, &max_payload) + : lantern_snappy_max_compressed_size(payload_raw_len, &max_payload); + if (max_rc != LANTERN_SNAPPY_OK) { lantern_log_error( "reqresp", &meta, @@ -1020,7 +1171,7 @@ static void *status_request_worker(void *arg) { goto finish; } - uint8_t *payload = (uint8_t *)malloc(max_payload); + uint8_t *payload = (uint8_t *)malloc(max_payload > 0 ? max_payload : 1u); if (!payload) { lantern_log_error( "reqresp", @@ -1030,8 +1181,10 @@ static void *status_request_worker(void *arg) { } size_t payload_len = 0; - size_t payload_raw_len = 0; - if (lantern_network_status_encode_snappy(&local_status, payload, max_payload, &payload_len, &payload_raw_len) != 0) { + int snappy_rc = legacy_framing + ? lantern_snappy_compress_raw(raw_status, payload_raw_len, payload, max_payload, &payload_len) + : lantern_snappy_compress(raw_status, payload_raw_len, payload, max_payload, &payload_len); + if (snappy_rc != LANTERN_SNAPPY_OK) { lantern_log_error( "reqresp", &meta, @@ -1076,8 +1229,9 @@ static void *status_request_worker(void *arg) { lantern_log_debug( "reqresp", &meta, - "status[%" PRIu64 "] expect_response_code=true", - trace_id); + "status[%" PRIu64 "] expect_response_code=%s", + trace_id, + legacy_framing ? "false" : "true"); lantern_log_debug( "reqresp", @@ -1147,6 +1301,7 @@ static void *status_request_worker(void *arg) { size_t response_len = 0; ssize_t read_err = 0; uint8_t response_code = LANTERN_REQRESP_RESPONSE_SUCCESS; + bool response_code_pending = !legacy_framing; rc = lantern_reqresp_read_response_chunk( service, stream, @@ -1155,7 +1310,7 @@ static void *status_request_worker(void *arg) { &response_len, &read_err, &response_code, - NULL); + legacy_framing ? &response_code_pending : NULL); if (rc != 0) { lantern_log_error( "reqresp", @@ -1194,7 +1349,7 @@ static void *status_request_worker(void *arg) { LanternStatusMessage remote_status; memset(&remote_status, 0, sizeof(remote_status)); if (response_len == 0 - || lantern_network_status_decode_snappy(&remote_status, response, response_len) != 0) { + || decode_status_payload(response, response_len, &remote_status, NULL) != 0) { lantern_log_error( "reqresp", &meta, @@ -1240,12 +1395,26 @@ static void *status_request_worker(void *arg) { remote_status.finalized.slot); handle_remote_status(service, &remote_status, ctx->peer_text); + if (legacy_framing && peer_text[0] != '\0') { + lantern_reqresp_service_hint_peer_legacy(service, peer_text, true); + } failure_code = LIBP2P_ERR_OK; finish: + bool scheduled_retry = false; + if (failure_code != LIBP2P_ERR_OK && !legacy_framing && ctx->peer_text[0] != '\0') { + if (status_request_launch(service, &ctx->peer_id, ctx->peer_text, true, false) == 0) { + lantern_log_info( + "reqresp", + &meta, + "status[%" PRIu64 "] retrying with legacy framing", + trace_id); + scheduled_retry = true; + } + } close_stream(stream); status_request_ctx_free(ctx); - if (failure_code != LIBP2P_ERR_OK) { + if (failure_code != LIBP2P_ERR_OK && !scheduled_retry) { status_request_notify_failure(service, peer_text[0] ? peer_text : NULL, failure_code); } return NULL; @@ -1383,10 +1552,12 @@ static int clone_peer_id(peer_id_t *dest, const peer_id_t *src) { return 0; } -int lantern_reqresp_service_request_status( +static int status_request_launch( struct lantern_reqresp_service *service, const peer_id_t *peer_id, - const char *peer_id_text) { + const char *peer_id_text, + bool legacy_framing, + bool notify_on_failure) { if (!service || !service->host || !peer_id || !peer_id->bytes || peer_id->size == 0) { return -1; } @@ -1397,6 +1568,7 @@ int lantern_reqresp_service_request_status( } ctx->service = service; ctx->debug_trace_id = lantern_reqresp_debug_sequence_next(); + ctx->legacy_framing = legacy_framing; struct lantern_log_metadata meta = { .peer = NULL, @@ -1416,15 +1588,18 @@ int lantern_reqresp_service_request_status( lantern_log_trace( "reqresp", &meta, - "status[%" PRIu64 "] scheduling request", - ctx->debug_trace_id); + "status[%" PRIu64 "] scheduling request%s", + ctx->debug_trace_id, + legacy_framing ? " (legacy framing)" : ""); if (clone_peer_id(&ctx->peer_id, peer_id) != 0) { lantern_log_warn( "reqresp", &meta, "failed to clone peer id for status request"); - status_request_notify_failure(service, meta.peer, LIBP2P_ERR_INTERNAL); + if (notify_on_failure) { + status_request_notify_failure(service, meta.peer, LIBP2P_ERR_INTERNAL); + } status_request_ctx_free(ctx); return -1; } @@ -1442,13 +1617,26 @@ int lantern_reqresp_service_request_status( &meta, "libp2p open stream failed rc=%d", rc); - status_request_notify_failure(service, meta.peer, rc); + if (notify_on_failure) { + status_request_notify_failure(service, meta.peer, rc); + } status_request_ctx_free(ctx); return -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) { + bool prefer_legacy = false; + if (service && peer_id_text && peer_id_text[0] != '\0') { + prefer_legacy = lantern_reqresp_service_peer_prefers_legacy(service, peer_id_text) != 0; + } + return status_request_launch(service, peer_id, peer_id_text, prefer_legacy, true); +} + static void *blocks_worker(void *arg) { struct blocks_stream_ctx *ctx = (struct blocks_stream_ctx *)arg; if (!ctx) { @@ -1468,14 +1656,14 @@ static void *blocks_worker(void *arg) { char peer_text[128]; describe_peer(libp2p_stream_remote_peer(stream), peer_text, sizeof(peer_text)); bool include_response_code = true; + bool use_raw_snappy = false; const struct lantern_log_metadata meta = {.peer = peer_text[0] ? peer_text : NULL}; lantern_log_debug( "reqresp", &meta, - "blocks_by_root stream protocol=%s include_response_code=%s", - protocol_id ? protocol_id : "-", - include_response_code ? "true" : "false"); + "blocks_by_root stream protocol=%s", + protocol_id ? protocol_id : "-"); libp2p_stream_set_read_interest(stream, true); @@ -1503,6 +1691,19 @@ static void *blocks_worker(void *arg) { log_payload_preview("blocks_by_root request raw", peer_text, request, request_len); + bool request_framed = payload_is_snappy_framed(request, request_len); + include_response_code = request_framed; + use_raw_snappy = !request_framed; + lantern_log_debug( + "reqresp", + &meta, + "blocks_by_root request framing=%s include_response_code=%s", + request_framed ? "framed" : "raw", + include_response_code ? "true" : "false"); + if (!request_framed && peer_text[0] != '\0') { + lantern_reqresp_service_hint_peer_legacy(service, peer_text, true); + } + LanternBlocksByRootRequest decoded_request; lantern_blocks_by_root_request_init(&decoded_request); int decode_rc = lantern_network_blocks_by_root_request_decode_snappy( @@ -1623,8 +1824,10 @@ static void *blocks_worker(void *arg) { } size_t max_compressed = 0; - if (lantern_snappy_max_compressed_size(ssz_written, &max_compressed) != LANTERN_SNAPPY_OK - || max_compressed == 0) { + int max_rc = use_raw_snappy + ? lantern_snappy_max_compressed_size_raw(ssz_written, &max_compressed) + : 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_blocks_by_root_response_reset(&response); @@ -1648,13 +1851,20 @@ static void *blocks_worker(void *arg) { } size_t compressed_len = 0; - if (lantern_snappy_compress( - ssz_buffer, - ssz_written, - snappy_buffer, - snappy_capacity, - &compressed_len) - != LANTERN_SNAPPY_OK) { + int snappy_rc = use_raw_snappy + ? lantern_snappy_compress_raw( + ssz_buffer, + ssz_written, + snappy_buffer, + snappy_capacity, + &compressed_len) + : 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_blocks_by_root_response_reset(&response); @@ -1786,6 +1996,10 @@ static void blocks_on_open_legacy(libp2p_stream_t *stream, void *user_data) { blocks_on_open_impl(stream, user_data, LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_LEGACY); } +static void blocks_on_open_bare(libp2p_stream_t *stream, void *user_data) { + blocks_on_open_impl(stream, user_data, LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_BARE); +} + int lantern_reqresp_service_start( struct lantern_reqresp_service *service, const struct lantern_reqresp_service_config *config) { @@ -1843,6 +2057,15 @@ int lantern_reqresp_service_start( return -1; } #endif +#if defined(LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_BARE) + libp2p_protocol_def_t blocks_def_bare = blocks_def; + blocks_def_bare.protocol_id = LANTERN_BLOCKS_BY_ROOT_PROTOCOL_ID_BARE; + blocks_def_bare.on_open = blocks_on_open_bare; + if (libp2p_host_listen_protocol(service->host, &blocks_def_bare, &service->blocks_server_bare) != 0) { + lantern_reqresp_service_reset(service); + return -1; + } +#endif lantern_log_info( "network", From 5925531f1b6e9344d8c7069da6c69249ed08a008 Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:48:28 +1000 Subject: [PATCH 6/8] Update log.c --- src/support/log.c | 53 ++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/support/log.c b/src/support/log.c index d5688bc..81a30cc 100644 --- a/src/support/log.c +++ b/src/support/log.c @@ -273,6 +273,7 @@ void lantern_log_log( 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) { @@ -299,32 +300,7 @@ void lantern_log_log( } } - /* Add peer if present (truncate long peer IDs) */ - if (metadata && metadata->peer && metadata->peer[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; - } - } - /* Truncate peer ID if too long (show first 8 chars) */ - size_t peer_len = strlen(metadata->peer); - if (peer_len > 16) { - int w = snprintf(ctx_cursor, ctx_remaining, "peer=%.8s..", metadata->peer); - if (w > 0 && (size_t)w < ctx_remaining) { - ctx_cursor += w; - ctx_remaining -= (size_t)w; - } - } else { - int w = snprintf(ctx_cursor, ctx_remaining, "peer=%s", metadata->peer); - 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 @@ -348,8 +324,17 @@ void lantern_log_log( formatted); /* Add context if present */ - if (context[0]) { - fprintf(target, " %s%s%s", ANSI_DIM, context, ANSI_RESET); + 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 { @@ -363,8 +348,16 @@ void lantern_log_log( formatted); /* Add context if present */ - if (context[0]) { - fprintf(target, " %s", context); + 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"); } From 9f20eeb45180fe20a5d6c2e161a6ad748b5d181b Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Wed, 31 Dec 2025 22:58:17 +1000 Subject: [PATCH 7/8] Update; --- scripts/fixtures/generate_networking_ssz.py | 13 ++++++++----- tools/lean-quickstart | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/scripts/fixtures/generate_networking_ssz.py b/scripts/fixtures/generate_networking_ssz.py index de8b96f..cce1385 100755 --- a/scripts/fixtures/generate_networking_ssz.py +++ b/scripts/fixtures/generate_networking_ssz.py @@ -17,6 +17,7 @@ from lean_spec.subspecs.networking.config import MAX_REQUEST_BLOCKS from lean_spec.subspecs.containers import ( + AggregatedAttestation, Attestation, AttestationData, Block, @@ -24,7 +25,7 @@ BlockWithAttestation, Checkpoint, ) -from lean_spec.subspecs.containers.block.types import Attestations +from lean_spec.subspecs.containers.block import AggregatedAttestations from lean_spec.subspecs.containers.slot import Slot from lean_spec.types import Bytes32, Uint64 from lean_spec.types.byte_arrays import BaseBytes @@ -173,15 +174,16 @@ def make_signed_block(seed: int, base_slot: int, proposer_index: int, attestatio make_attestation(seed + (i * 5), (proposer_index + i + seed) % 16, base_slot + i + 1) for i in range(attestation_count) ] + aggregated_attestations = AggregatedAttestation.aggregate_by_data(attestations) block = Block( slot=Slot(base_slot), proposer_index=Uint64(proposer_index), parent_root=Bytes32(repeating_bytes(seed, 32)), state_root=Bytes32(repeating_bytes(seed + 0x50, 32)), - body=BlockBody(attestations=Attestations(data=attestations)), + body=BlockBody(attestations=AggregatedAttestations(data=aggregated_attestations)), ) proposer_att = make_attestation(seed + 0x80, (proposer_index + 3) % 16, base_slot + attestation_count + 4) - signatures = make_signatures(seed + 0xA0, attestation_count + 1) + signatures = make_signatures(seed + 0xA0, len(aggregated_attestations) + 1) return LanternSignedBlockWithAttestation( message=LanternBlockWithAttestation(block=block, proposer_attestation=proposer_att), signature=signatures, @@ -198,15 +200,16 @@ def make_gossip_signed_block( make_gossip_attestation(seed + (i * 5), (proposer_index + i + seed) % 16, vote_slot) for i, vote_slot in enumerate(attestation_vote_slots) ] + aggregated_attestations = AggregatedAttestation.aggregate_by_data(attestations) block = Block( slot=Slot(block_slot), proposer_index=Uint64(proposer_index), parent_root=Bytes32(repeating_bytes(seed, 32)), state_root=Bytes32(repeating_bytes(seed + 0x50, 32)), - body=BlockBody(attestations=Attestations(data=list(attestations))), + body=BlockBody(attestations=AggregatedAttestations(data=aggregated_attestations)), ) proposer_att = make_gossip_attestation(seed + 0x80, (proposer_index + 3) % 16, block_slot + 2) - signatures = make_signatures(seed + 0xA0, len(attestations) + 1) + signatures = make_signatures(seed + 0xA0, len(aggregated_attestations) + 1) return LanternSignedBlockWithAttestation( message=LanternBlockWithAttestation(block=block, proposer_attestation=proposer_att), signature=signatures, diff --git a/tools/lean-quickstart b/tools/lean-quickstart index 3ca3f07..41671ad 160000 --- a/tools/lean-quickstart +++ b/tools/lean-quickstart @@ -1 +1 @@ -Subproject commit 3ca3f077b946b43f5d0f803ed04c4e048ace7936 +Subproject commit 41671adad33346aa525c9c1cc85bfbc23515afda From 61587d16520d8c0343dedb0acdc5f9410f140a5c Mon Sep 17 00:00:00 2001 From: uink45 <79078981+uink45@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:07:53 +1000 Subject: [PATCH 8/8] Update generate_networking_ssz.py --- scripts/fixtures/generate_networking_ssz.py | 45 ++++++++++++++------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/scripts/fixtures/generate_networking_ssz.py b/scripts/fixtures/generate_networking_ssz.py index cce1385..4643f32 100755 --- a/scripts/fixtures/generate_networking_ssz.py +++ b/scripts/fixtures/generate_networking_ssz.py @@ -17,15 +17,10 @@ from lean_spec.subspecs.networking.config import MAX_REQUEST_BLOCKS from lean_spec.subspecs.containers import ( - AggregatedAttestation, Attestation, AttestationData, - Block, - BlockBody, - BlockWithAttestation, Checkpoint, ) -from lean_spec.subspecs.containers.block import AggregatedAttestations from lean_spec.subspecs.containers.slot import Slot from lean_spec.types import Bytes32, Uint64 from lean_spec.types.byte_arrays import BaseBytes @@ -36,6 +31,7 @@ # The leanSpec XMSS Signature is now a variable-length SSZ container, so we define # a custom fixed-size byte array type for fixture generation. LANTERN_SIGNATURE_SIZE = 3112 +LANTERN_MAX_ATTESTATIONS = 4096 class LanternSignature(BaseBytes): @@ -58,10 +54,33 @@ class LanternSignedAttestation(Container): signature: LanternSignature +class LanternAttestations(SSZList): + """Lantern block body attestations (per-validator, not aggregated).""" + + ELEMENT_TYPE = Attestation + LIMIT = LANTERN_MAX_ATTESTATIONS + + +class LanternBlockBody(Container): + """Block body matching Lantern's per-validator attestation encoding.""" + + attestations: LanternAttestations + + +class LanternBlock(Container): + """Block container matching Lantern's SSZ layout.""" + + slot: Slot + proposer_index: Uint64 + parent_root: Bytes32 + state_root: Bytes32 + body: LanternBlockBody + + class LanternBlockWithAttestation(Container): """Block with proposer attestation for Lantern fixture generation.""" - block: Block + block: LanternBlock proposer_attestation: Attestation @@ -174,16 +193,15 @@ def make_signed_block(seed: int, base_slot: int, proposer_index: int, attestatio make_attestation(seed + (i * 5), (proposer_index + i + seed) % 16, base_slot + i + 1) for i in range(attestation_count) ] - aggregated_attestations = AggregatedAttestation.aggregate_by_data(attestations) - block = Block( + block = LanternBlock( slot=Slot(base_slot), proposer_index=Uint64(proposer_index), parent_root=Bytes32(repeating_bytes(seed, 32)), state_root=Bytes32(repeating_bytes(seed + 0x50, 32)), - body=BlockBody(attestations=AggregatedAttestations(data=aggregated_attestations)), + body=LanternBlockBody(attestations=LanternAttestations(data=attestations)), ) proposer_att = make_attestation(seed + 0x80, (proposer_index + 3) % 16, base_slot + attestation_count + 4) - signatures = make_signatures(seed + 0xA0, len(aggregated_attestations) + 1) + signatures = make_signatures(seed + 0xA0, len(attestations) + 1) return LanternSignedBlockWithAttestation( message=LanternBlockWithAttestation(block=block, proposer_attestation=proposer_att), signature=signatures, @@ -200,16 +218,15 @@ def make_gossip_signed_block( make_gossip_attestation(seed + (i * 5), (proposer_index + i + seed) % 16, vote_slot) for i, vote_slot in enumerate(attestation_vote_slots) ] - aggregated_attestations = AggregatedAttestation.aggregate_by_data(attestations) - block = Block( + block = LanternBlock( slot=Slot(block_slot), proposer_index=Uint64(proposer_index), parent_root=Bytes32(repeating_bytes(seed, 32)), state_root=Bytes32(repeating_bytes(seed + 0x50, 32)), - body=BlockBody(attestations=AggregatedAttestations(data=aggregated_attestations)), + body=LanternBlockBody(attestations=LanternAttestations(data=list(attestations))), ) proposer_att = make_gossip_attestation(seed + 0x80, (proposer_index + 3) % 16, block_slot + 2) - signatures = make_signatures(seed + 0xA0, len(aggregated_attestations) + 1) + signatures = make_signatures(seed + 0xA0, len(attestations) + 1) return LanternSignedBlockWithAttestation( message=LanternBlockWithAttestation(block=block, proposer_attestation=proposer_att), signature=signatures,