diff --git a/src/storage/storage.c b/src/storage/storage.c index 60f969d..77a345e 100644 --- a/src/storage/storage.c +++ b/src/storage/storage.c @@ -70,6 +70,13 @@ struct lantern_storage_checkpoint_record { LanternCheckpoint finalized; }; +/* + * Internal helpers for filesystem/path handling, SSZ size estimation, and + * atomic file reads/writes. + * + * Public API: see include/lantern/storage/storage.h + */ + static int ensure_directory(const char *path) { if (!path) { return -1; @@ -94,14 +101,14 @@ static int join_path(const char *base, const char *leaf, char **out_path) { if (!base || !leaf || !out_path) { return -1; } - size_t base_len = strlen(base); - size_t leaf_len = strlen(leaf); - int needs_sep = 0; + const size_t base_len = strlen(base); + const size_t leaf_len = strlen(leaf); + bool needs_sep = false; if (base_len > 0) { - char tail = base[base_len - 1]; + const char tail = base[base_len - 1]; needs_sep = (tail != '/' && tail != '\\'); } - size_t total = base_len + (needs_sep ? 1 : 0) + leaf_len + 1; + const size_t total = base_len + (needs_sep ? 1u : 0u) + leaf_len + 1u; char *buffer = malloc(total); if (!buffer) { return -1; @@ -331,60 +338,59 @@ static int write_atomic_file(const char *path, const uint8_t *data, size_t data_ if (!path || !data || data_len == 0) { return -1; } - size_t path_len = strlen(path); - char *tmp_path = malloc(path_len + 5); + int rc = -1; + FILE *fp = NULL; + char *tmp_path = NULL; + + const size_t path_len = strlen(path); + tmp_path = malloc(path_len + sizeof(".tmp")); if (!tmp_path) { - return -1; + goto cleanup; } memcpy(tmp_path, path, path_len); - memcpy(tmp_path + path_len, ".tmp", 4); - tmp_path[path_len + 4] = '\0'; + memcpy(tmp_path + path_len, ".tmp", sizeof(".tmp")); - FILE *fp = fopen(tmp_path, "wb"); + fp = fopen(tmp_path, "wb"); if (!fp) { - free(tmp_path); - return -1; + goto cleanup; } - size_t written = fwrite(data, 1, data_len, fp); + const size_t written = fwrite(data, 1u, data_len, fp); if (written != data_len) { - fclose(fp); - free(tmp_path); - return -1; + goto cleanup; } if (fflush(fp) != 0) { - fclose(fp); - free(tmp_path); - return -1; + goto cleanup; } #if defined(_WIN32) if (_commit(_fileno(fp)) != 0) { - fclose(fp); - free(tmp_path); - return -1; + goto cleanup; } #else if (fsync(fileno(fp)) != 0) { - fclose(fp); - free(tmp_path); - return -1; + goto cleanup; } #endif - if (fclose(fp) != 0) { - free(tmp_path); - return -1; + const int close_rc = fclose(fp); + fp = NULL; + if (close_rc != 0) { + goto cleanup; } #if defined(_WIN32) if (remove(path) != 0 && errno != ENOENT) { - free(tmp_path); - return -1; + goto cleanup; } #endif if (rename(tmp_path, path) != 0) { - free(tmp_path); - return -1; + goto cleanup; + } + rc = 0; + +cleanup: + if (fp) { + fclose(fp); } free(tmp_path); - return 0; + return rc; } static int read_file_buffer(const char *path, uint8_t **out_data, size_t *out_len) { @@ -395,55 +401,63 @@ static int read_file_buffer(const char *path, uint8_t **out_data, size_t *out_le if (!fp) { return (errno == ENOENT) ? 1 : -1; } + int rc = -1; + uint8_t *buffer = NULL; + if (fseek(fp, 0, SEEK_END) != 0) { - fclose(fp); - return -1; + goto cleanup; } - long file_size = ftell(fp); + const long file_size = ftell(fp); if (file_size < 0) { - fclose(fp); - return -1; + goto cleanup; } if (fseek(fp, 0, SEEK_SET) != 0) { - fclose(fp); - return -1; + goto cleanup; } if (file_size == 0) { - fclose(fp); - return 1; + rc = 1; + goto cleanup; } - uint8_t *buffer = malloc((size_t)file_size); + buffer = malloc((size_t)file_size); if (!buffer) { - fclose(fp); - return -1; + goto cleanup; } - size_t read = fread(buffer, 1, (size_t)file_size, fp); - fclose(fp); + const size_t read = fread(buffer, 1u, (size_t)file_size, fp); if (read != (size_t)file_size) { - free(buffer); - return -1; + goto cleanup; } + *out_data = buffer; *out_len = (size_t)file_size; - return 0; + buffer = NULL; + rc = 0; + +cleanup: + fclose(fp); + free(buffer); + return rc; } static int write_state_meta(const char *data_dir, const LanternState *state) { if (!data_dir || !state) { return -1; } - struct lantern_storage_state_meta meta = { + int rc = -1; + char *meta_path = NULL; + + const struct lantern_storage_state_meta meta = { .version = LANTERN_STORAGE_STATE_META_VERSION, .reserved = 0, .historical_roots_offset = state->historical_roots_offset, .justified_slots_offset = state->justified_slots_offset, }; - char *meta_path = NULL; if (join_path(data_dir, LANTERN_STORAGE_STATE_META_FILE, &meta_path) != 0) { - return -1; + goto cleanup; } - int rc = write_atomic_file(meta_path, (const uint8_t *)&meta, sizeof(meta)); - free(meta_path); + rc = write_atomic_file(meta_path, (const uint8_t *)&meta, sizeof(meta)); + +cleanup: + free_path(meta_path); return rc; } @@ -464,28 +478,34 @@ static int read_state_meta(const char *data_dir, struct lantern_storage_state_me if (!data_dir || !meta) { return -1; } + int rc = -1; char *meta_path = NULL; - if (join_path(data_dir, LANTERN_STORAGE_STATE_META_FILE, &meta_path) != 0) { - return -1; - } uint8_t *buffer = NULL; size_t len = 0; - int rc = read_file_buffer(meta_path, &buffer, &len); - free(meta_path); + + if (join_path(data_dir, LANTERN_STORAGE_STATE_META_FILE, &meta_path) != 0) { + goto cleanup; + } + rc = read_file_buffer(meta_path, &buffer, &len); if (rc != 0) { - free(buffer); - return rc; + goto cleanup; } if (len != sizeof(*meta)) { - free(buffer); - return -1; + rc = -1; + goto cleanup; } memcpy(meta, buffer, sizeof(*meta)); - free(buffer); if (meta->version != LANTERN_STORAGE_STATE_META_VERSION) { - return -1; + rc = -1; + goto cleanup; } - return 0; + + rc = 0; + +cleanup: + free_path(meta_path); + free(buffer); + return rc; } static int build_blocks_dir(const char *data_dir, char **out_path) { @@ -510,113 +530,155 @@ static int build_slot_index_dir(const char *data_dir, char **out_path) { return rc; } +/** + * Ensure all storage directories exist under `data_dir`. + * + * @param data_dir Base directory path. + * @return 0 on success. + * @return -1 on invalid parameters or filesystem errors. + */ int lantern_storage_prepare(const char *data_dir) { if (!data_dir) { return -1; } + + int rc = -1; + char *blocks_dir = NULL; + char *states_dir = NULL; + char *indices_dir = NULL; + char *slot_dir = NULL; + if (ensure_directory(data_dir) != 0) { - return -1; + goto cleanup; } - char *blocks_dir = NULL; if (build_blocks_dir(data_dir, &blocks_dir) != 0) { - return -1; + goto cleanup; } - int rc = ensure_directory(blocks_dir); - free_path(blocks_dir); - if (rc != 0) { - return rc; + if (ensure_directory(blocks_dir) != 0) { + goto cleanup; } - char *states_dir = NULL; if (build_states_dir(data_dir, &states_dir) != 0) { - return -1; + goto cleanup; } - rc = ensure_directory(states_dir); - free_path(states_dir); - if (rc != 0) { - return rc; + if (ensure_directory(states_dir) != 0) { + goto cleanup; } - char *indices_dir = NULL; if (build_indices_dir(data_dir, &indices_dir) != 0) { - return -1; + goto cleanup; } - rc = ensure_directory(indices_dir); - free_path(indices_dir); - if (rc != 0) { - return rc; + if (ensure_directory(indices_dir) != 0) { + goto cleanup; } - char *slot_dir = NULL; if (build_slot_index_dir(data_dir, &slot_dir) != 0) { - return -1; + goto cleanup; + } + if (ensure_directory(slot_dir) != 0) { + goto cleanup; } - rc = ensure_directory(slot_dir); + + rc = 0; + +cleanup: + free_path(blocks_dir); + free_path(states_dir); + free_path(indices_dir); free_path(slot_dir); return rc; } +/** + * Persist `state` under `data_dir` using SSZ (`state.ssz`) plus `state.meta`. + * + * @param data_dir Base directory path. + * @param state State to persist. + * @return 0 on success. + * @return -1 on invalid parameters, encoding failure, or filesystem errors. + */ int lantern_storage_save_state(const char *data_dir, const LanternState *state) { if (!data_dir || !state || state->config.num_validators == 0) { return -1; } - size_t encoded_size = state_encoded_size(state); + int rc = -1; + uint8_t *buffer = NULL; + char *state_path = NULL; + + const size_t encoded_size = state_encoded_size(state); if (encoded_size == 0) { - return -1; + goto cleanup; } - uint8_t *buffer = malloc(encoded_size); + + buffer = malloc(encoded_size); if (!buffer) { - return -1; + goto cleanup; } size_t written = 0; if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != 0 || written != encoded_size) { - free(buffer); - return -1; + goto cleanup; } - char *state_path = NULL; if (join_path(data_dir, LANTERN_STORAGE_STATE_FILE, &state_path) != 0) { - free(buffer); - return -1; + goto cleanup; } - int rc = write_atomic_file(state_path, buffer, written); - free(state_path); - free(buffer); + rc = write_atomic_file(state_path, buffer, written); if (rc != 0) { - return rc; + goto cleanup; } - return write_state_meta(data_dir, state); + + rc = write_state_meta(data_dir, state); + +cleanup: + free_path(state_path); + free(buffer); + return rc; } +/** + * Load a persisted state from `data_dir/state.ssz`. + * + * On success, the contents of `state` are replaced. + * + * @param data_dir Base directory path. + * @param state Output state (replaced on success). + * @return 0 on success. + * @return 1 if the state file is missing or empty. + * @return -1 on invalid parameters or decode/validation errors. + */ int lantern_storage_load_state(const char *data_dir, LanternState *state) { if (!data_dir || !state) { return -1; } + + int rc = -1; char *state_path = NULL; - if (join_path(data_dir, LANTERN_STORAGE_STATE_FILE, &state_path) != 0) { - return -1; - } uint8_t *data = NULL; size_t data_len = 0; - int read_rc = read_file_buffer(state_path, &data, &data_len); - free(state_path); - if (read_rc != 0) { - return read_rc; - } + LanternState decoded; lantern_state_init(&decoded); + bool decoded_owned = true; + + if (join_path(data_dir, LANTERN_STORAGE_STATE_FILE, &state_path) != 0) { + goto cleanup; + } + rc = read_file_buffer(state_path, &data, &data_len); + if (rc != 0) { + goto cleanup; + } if (lantern_ssz_decode_state(&decoded, data, data_len) != 0) { - free(data); - lantern_state_reset(&decoded); - return -1; + rc = -1; + goto cleanup; } free(data); + data = NULL; if (decoded.config.num_validators == 0) { - lantern_state_reset(&decoded); - return -1; + rc = -1; + goto cleanup; } if (lantern_state_prepare_validator_votes(&decoded, decoded.config.num_validators) != 0) { - lantern_state_reset(&decoded); - return -1; + rc = -1; + goto cleanup; } struct lantern_storage_state_meta meta; - int meta_rc = read_state_meta(data_dir, &meta); + const int meta_rc = read_state_meta(data_dir, &meta); if (meta_rc == 0) { decoded.historical_roots_offset = meta.historical_roots_offset; decoded.justified_slots_offset = meta.justified_slots_offset; @@ -625,21 +687,44 @@ int lantern_storage_load_state(const char *data_dir, LanternState *state) { decoded.justified_slots_offset = decoded.latest_finalized.slot == UINT64_MAX ? 0u : (decoded.latest_finalized.slot + 1u); } else { - lantern_state_reset(&decoded); - return -1; + rc = -1; + goto cleanup; } + lantern_state_reset(state); *state = decoded; - return 0; + decoded_owned = false; + rc = 0; + +cleanup: + free_path(state_path); + free(data); + if (decoded_owned) { + lantern_state_reset(&decoded); + } + return rc; } +/** + * Persist all present validator votes to `data_dir/votes.bin`. + * + * @param data_dir Base directory path. + * @param state State containing validator votes. + * @return 0 on success. + * @return -1 on invalid parameters, encoding failure, or filesystem errors. + */ int lantern_storage_save_votes(const char *data_dir, const LanternState *state) { if (!data_dir || !state || state->config.num_validators == 0) { return -1; } - size_t capacity = lantern_state_validator_capacity(state); + + int rc = -1; + uint8_t *buffer = NULL; + char *votes_path = NULL; + + const size_t capacity = lantern_state_validator_capacity(state); if (capacity == 0) { - return -1; + goto cleanup; } size_t present = 0; for (size_t i = 0; i < capacity; ++i) { @@ -647,18 +732,17 @@ int lantern_storage_save_votes(const char *data_dir, const LanternState *state) present++; } } - struct lantern_storage_votes_header header; - memset(&header, 0, sizeof(header)); + struct lantern_storage_votes_header header = {0}; memcpy(header.magic, LANTERN_STORAGE_VOTES_MAGIC, sizeof(header.magic)); header.version = LANTERN_STORAGE_VOTES_VERSION; header.validator_count = capacity; header.record_count = present; - size_t payload_size = present * (sizeof(uint64_t) + LANTERN_SIGNED_VOTE_SSZ_SIZE); - size_t total_size = sizeof(header) + payload_size; - uint8_t *buffer = malloc(total_size); + const size_t payload_size = present * (sizeof(uint64_t) + LANTERN_SIGNED_VOTE_SSZ_SIZE); + const size_t total_size = sizeof(header) + payload_size; + buffer = malloc(total_size); if (!buffer) { - return -1; + goto cleanup; } uint8_t *cursor = buffer; memcpy(cursor, &header, sizeof(header)); @@ -668,17 +752,15 @@ int lantern_storage_save_votes(const char *data_dir, const LanternState *state) if (!lantern_state_validator_has_vote(state, i)) { continue; } - uint64_t validator_index = (uint64_t)i; - uint8_t index_bytes[sizeof(uint64_t)]; - for (size_t b = 0; b < sizeof(uint64_t); ++b) { - index_bytes[b] = (uint8_t)((validator_index >> (8u * b)) & 0xFFu); + const uint64_t validator_index = (uint64_t)i; + for (size_t b = 0; b < sizeof(validator_index); ++b) { + cursor[b] = (uint8_t)((validator_index >> (8u * b)) & 0xFFu); } - memcpy(cursor, index_bytes, sizeof(index_bytes)); - cursor += sizeof(index_bytes); + cursor += sizeof(validator_index); + LanternSignedVote signed_vote; if (lantern_state_get_signed_validator_vote(state, i, &signed_vote) != 0) { - free(buffer); - return -1; + goto cleanup; } size_t vote_written = 0; if (lantern_ssz_encode_signed_vote( @@ -688,47 +770,57 @@ int lantern_storage_save_votes(const char *data_dir, const LanternState *state) &vote_written) != 0 || vote_written != LANTERN_SIGNED_VOTE_SSZ_SIZE) { - free(buffer); - return -1; + goto cleanup; } cursor += LANTERN_SIGNED_VOTE_SSZ_SIZE; } - char *votes_path = NULL; if (join_path(data_dir, LANTERN_STORAGE_VOTES_FILE, &votes_path) != 0) { - free(buffer); - return -1; + goto cleanup; } - int rc = write_atomic_file(votes_path, buffer, total_size); - free(votes_path); + rc = write_atomic_file(votes_path, buffer, total_size); + +cleanup: + free_path(votes_path); free(buffer); return rc; } +/** + * Load persisted validator votes from `data_dir/votes.bin` into `state`. + * + * @param data_dir Base directory path. + * @param state State to populate with loaded votes. + * @return 0 on success. + * @return 1 if the votes file is missing or empty. + * @return -1 on invalid parameters or decode/validation errors. + */ int lantern_storage_load_votes(const char *data_dir, LanternState *state) { if (!data_dir || !state) { return -1; } + + int rc = -1; char *votes_path = NULL; - if (join_path(data_dir, LANTERN_STORAGE_VOTES_FILE, &votes_path) != 0) { - return -1; - } uint8_t *data = NULL; size_t data_len = 0; - int read_rc = read_file_buffer(votes_path, &data, &data_len); - free(votes_path); - if (read_rc != 0) { - return read_rc; + + if (join_path(data_dir, LANTERN_STORAGE_VOTES_FILE, &votes_path) != 0) { + goto cleanup; + } + rc = read_file_buffer(votes_path, &data, &data_len); + if (rc != 0) { + goto cleanup; } if (data_len < sizeof(struct lantern_storage_votes_header)) { - free(data); - return -1; + rc = -1; + goto cleanup; } struct lantern_storage_votes_header header; memcpy(&header, data, sizeof(header)); if (memcmp(header.magic, LANTERN_STORAGE_VOTES_MAGIC, sizeof(header.magic)) != 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } bool has_signatures = false; size_t signed_vote_size = 0; @@ -741,25 +833,25 @@ int lantern_storage_load_votes(const char *data_dir, LanternState *state) { has_signatures = true; signed_vote_size = LANTERN_SIGNED_VOTE_SSZ_SIZE; } else { - free(data); - return -1; + rc = -1; + goto cleanup; } if (header.validator_count == 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } if (state->config.num_validators == 0) { state->config.num_validators = header.validator_count; } if (state->config.num_validators != header.validator_count) { - free(data); - return -1; + rc = -1; + goto cleanup; } if (lantern_state_prepare_validator_votes(state, state->config.num_validators) != 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } - size_t capacity = lantern_state_validator_capacity(state); + const size_t capacity = lantern_state_validator_capacity(state); for (size_t i = 0; i < capacity; ++i) { lantern_state_clear_validator_vote(state, i); } @@ -770,92 +862,110 @@ int lantern_storage_load_votes(const char *data_dir, LanternState *state) { const size_t encoded_vote_size = has_signatures ? signed_vote_size : LANTERN_VOTE_SSZ_SIZE; while (records_read < header.record_count) { if (remaining < sizeof(uint64_t) + encoded_vote_size) { - free(data); - return -1; + rc = -1; + goto cleanup; } uint64_t validator_index = 0; - for (size_t b = 0; b < sizeof(uint64_t); ++b) { + for (size_t b = 0; b < sizeof(validator_index); ++b) { validator_index |= ((uint64_t)cursor[b]) << (8u * b); } - cursor += sizeof(uint64_t); - remaining -= sizeof(uint64_t); + cursor += sizeof(validator_index); + remaining -= sizeof(validator_index); if (validator_index >= state->validator_votes_len) { - free(data); - return -1; + rc = -1; + goto cleanup; } if (has_signatures) { LanternSignedVote signed_vote; memset(&signed_vote, 0, sizeof(signed_vote)); if (lantern_ssz_decode_signed_vote(&signed_vote, cursor, signed_vote_size) != 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } cursor += signed_vote_size; remaining -= signed_vote_size; if (lantern_state_set_signed_validator_vote(state, (size_t)validator_index, &signed_vote) != 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } } else { LanternVote vote; memset(&vote, 0, sizeof(vote)); if (lantern_ssz_decode_vote(&vote, cursor, LANTERN_VOTE_SSZ_SIZE) != 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } cursor += LANTERN_VOTE_SSZ_SIZE; remaining -= LANTERN_VOTE_SSZ_SIZE; if (lantern_state_set_validator_vote(state, (size_t)validator_index, &vote) != 0) { - free(data); - return -1; + rc = -1; + goto cleanup; } } records_read++; } + + rc = 0; + +cleanup: + free_path(votes_path); free(data); - return 0; + return rc; } +/** + * Store a signed block under `data_dir/blocks/.ssz`. + * + * The operation is idempotent: if the block already exists on disk, this + * function returns success without modifying it. + * + * @param data_dir Base directory path. + * @param block Block to persist. + * @return 0 on success. + * @return -1 on invalid parameters, encoding failure, or filesystem errors. + */ int lantern_storage_store_block(const char *data_dir, const LanternSignedBlock *block) { if (!data_dir || !block) { return -1; } + + int rc = -1; char *blocks_dir = NULL; + char *block_path = NULL; + uint8_t *buffer = NULL; + if (build_blocks_dir(data_dir, &blocks_dir) != 0) { - return -1; + goto cleanup; } if (ensure_directory(blocks_dir) != 0) { - free_path(blocks_dir); - return -1; + goto cleanup; } + LanternRoot root; if (lantern_hash_tree_root_block(&block->message.block, &root) != 0) { - free_path(blocks_dir); - return -1; + goto cleanup; } char root_hex[2u * LANTERN_ROOT_SIZE + 1u]; if (lantern_bytes_to_hex(root.bytes, LANTERN_ROOT_SIZE, root_hex, sizeof(root_hex), 0) != 0) { - free_path(blocks_dir); - return -1; + goto cleanup; } char filename[sizeof(root_hex) + 4]; - int written = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); + const int written = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); if (written < 0 || (size_t)written >= sizeof(filename)) { - free_path(blocks_dir); - return -1; + goto cleanup; } - char *block_path = NULL; + if (join_path(blocks_dir, filename, &block_path) != 0) { - free_path(blocks_dir); - return -1; + goto cleanup; } - free_path(blocks_dir); + struct stat st; if (stat(block_path, &st) == 0) { - free_path(block_path); - return 0; + rc = 0; + goto cleanup; } - size_t encoded_size = signed_block_encoded_size(block); + + const size_t encoded_size = signed_block_encoded_size(block); if (encoded_size == 0) { lantern_log_warn( "storage", @@ -865,16 +975,14 @@ int lantern_storage_store_block(const char *data_dir, const LanternSignedBlock * block->message.block.body.attestations.length, block->message.block.body.legacy_plain_attestation_layout ? "true" : "false", block->signatures.attestation_signatures.length); - free_path(block_path); - return -1; + goto cleanup; } - uint8_t *buffer = malloc(encoded_size); + buffer = malloc(encoded_size); if (!buffer) { - free_path(block_path); - return -1; + goto cleanup; } size_t written_size = 0; - int encode_rc = lantern_ssz_encode_signed_block(block, buffer, encoded_size, &written_size); + const int encode_rc = lantern_ssz_encode_signed_block(block, buffer, encoded_size, &written_size); if (encode_rc != 0 || written_size == 0 || written_size > encoded_size) { @@ -886,16 +994,27 @@ int lantern_storage_store_block(const char *data_dir, const LanternSignedBlock * encode_rc, encoded_size, written_size); - free(buffer); - free_path(block_path); - return -1; + goto cleanup; } - int rc = write_atomic_file(block_path, buffer, written_size); + + rc = write_atomic_file(block_path, buffer, written_size); + +cleanup: free(buffer); free_path(block_path); + free_path(blocks_dir); return rc; } +/** + * Persist `state` under `data_dir/states` using the given `root` as filename. + * + * @param data_dir Base directory path. + * @param root Root used to build the on-disk filename. + * @param state State to persist. + * @return 0 on success. + * @return -1 on invalid parameters, encoding failure, or filesystem errors. + */ int lantern_storage_store_state_for_root( const char *data_dir, const LanternRoot *root, @@ -903,75 +1022,79 @@ int lantern_storage_store_state_for_root( if (!data_dir || !root || !state || state->config.num_validators == 0) { return -1; } - size_t encoded_size = state_encoded_size(state); + + int rc = -1; + uint8_t *buffer = NULL; + char *states_dir = NULL; + char *state_path = NULL; + char *meta_path = NULL; + + const size_t encoded_size = state_encoded_size(state); if (encoded_size == 0) { - return -1; + goto cleanup; } - uint8_t *buffer = malloc(encoded_size); + buffer = malloc(encoded_size); if (!buffer) { - return -1; + goto cleanup; } size_t written = 0; if (lantern_ssz_encode_state(state, buffer, encoded_size, &written) != 0 || written != encoded_size) { - free(buffer); - return -1; + goto cleanup; } - char *states_dir = NULL; if (build_states_dir(data_dir, &states_dir) != 0) { - free(buffer); - return -1; + goto cleanup; } if (ensure_directory(states_dir) != 0) { - free_path(states_dir); - free(buffer); - return -1; + goto cleanup; } char root_hex[2u * LANTERN_ROOT_SIZE + 1u]; if (lantern_bytes_to_hex(root->bytes, LANTERN_ROOT_SIZE, root_hex, sizeof(root_hex), 0) != 0) { - free_path(states_dir); - free(buffer); - return -1; + goto cleanup; } char filename[sizeof(root_hex) + 4]; - int name_written = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); + const int name_written = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); if (name_written < 0 || (size_t)name_written >= sizeof(filename)) { - free_path(states_dir); - free(buffer); - return -1; + goto cleanup; } - char *state_path = NULL; if (join_path(states_dir, filename, &state_path) != 0) { - free_path(states_dir); - free(buffer); - return -1; + goto cleanup; } - int rc = write_atomic_file(state_path, buffer, written); - free(buffer); + rc = write_atomic_file(state_path, buffer, written); if (rc != 0) { - free_path(state_path); - free_path(states_dir); - return rc; + goto cleanup; } + char meta_name[sizeof(root_hex) + 6]; - int meta_written = snprintf(meta_name, sizeof(meta_name), "%s.meta", root_hex); + const int meta_written = snprintf(meta_name, sizeof(meta_name), "%s.meta", root_hex); if (meta_written < 0 || (size_t)meta_written >= sizeof(meta_name)) { - free_path(state_path); - free_path(states_dir); - return -1; + rc = -1; + goto cleanup; } - char *meta_path = NULL; if (join_path(states_dir, meta_name, &meta_path) != 0) { - free_path(state_path); - free_path(states_dir); - return -1; + rc = -1; + goto cleanup; } - int meta_rc = write_state_meta_path(meta_path, state); + rc = write_state_meta_path(meta_path, state); + +cleanup: free_path(meta_path); free_path(state_path); free_path(states_dir); - return meta_rc; + free(buffer); + return rc; } +/** + * Load the raw persisted SSZ bytes for a state stored under `data_dir/states`. + * + * @param data_dir Base directory path. + * @param root Root used to build the on-disk filename. + * @param out_data Output buffer (caller must free) on success. + * @param out_len Output length on success. + * @return 0 on success. + * @return 1 if the state file is missing or empty. + * @return -1 on invalid parameters or filesystem errors. + */ int lantern_storage_load_state_bytes_for_root( const char *data_dir, const LanternRoot *root, @@ -983,44 +1106,62 @@ int lantern_storage_load_state_bytes_for_root( *out_data = NULL; *out_len = 0; + int rc = -1; char *states_dir = NULL; + char *state_path = NULL; + uint8_t *data = NULL; + size_t len = 0; + if (build_states_dir(data_dir, &states_dir) != 0) { - return -1; + goto cleanup; } char root_hex[2u * LANTERN_ROOT_SIZE + 1u]; if (lantern_bytes_to_hex(root->bytes, LANTERN_ROOT_SIZE, root_hex, sizeof(root_hex), 0) != 0) { - free_path(states_dir); - return -1; + goto cleanup; } char filename[sizeof(root_hex) + 4]; - int name_written = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); + const int name_written = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); if (name_written < 0 || (size_t)name_written >= sizeof(filename)) { - free_path(states_dir); - return -1; + goto cleanup; } - char *state_path = NULL; if (join_path(states_dir, filename, &state_path) != 0) { - free_path(states_dir); - return -1; + goto cleanup; } - free_path(states_dir); - uint8_t *data = NULL; - size_t len = 0; - int read_rc = read_file_buffer(state_path, &data, &len); - free_path(state_path); - if (read_rc != 0) { + rc = read_file_buffer(state_path, &data, &len); + if (rc != 0) { if (data) { free(data); + data = NULL; } - return read_rc > 0 ? 1 : -1; + rc = (rc > 0) ? 1 : -1; + goto cleanup; } + *out_data = data; *out_len = len; - return 0; + data = NULL; + rc = 0; + +cleanup: + free_path(state_path); + free_path(states_dir); + free(data); + return rc; } +/** + * Persist the mapping from a slot number to its block root on disk. + * + * Writes `root` into `data_dir/indices/slots/.root` using an + * atomic write so readers never see a partial file. + * + * @param data_dir Base storage directory. + * @param slot Slot number to index. + * @param root 32-byte block root for the slot. + * @return 0 on success, -1 on error. + */ int lantern_storage_store_slot_root( const char *data_dir, uint64_t slot, @@ -1028,31 +1169,46 @@ int lantern_storage_store_slot_root( if (!data_dir || !root) { return -1; } + + int rc = -1; char *slot_dir = NULL; + char *slot_path = NULL; + if (build_slot_index_dir(data_dir, &slot_dir) != 0) { - return -1; + goto cleanup; } if (ensure_directory(slot_dir) != 0) { - free_path(slot_dir); - return -1; + goto cleanup; } + char filename[64]; - int written = snprintf(filename, sizeof(filename), "%" PRIu64 ".root", slot); + const int written = snprintf(filename, sizeof(filename), "%" PRIu64 ".root", slot); if (written < 0 || (size_t)written >= sizeof(filename)) { - free_path(slot_dir); - return -1; + goto cleanup; } - char *slot_path = NULL; if (join_path(slot_dir, filename, &slot_path) != 0) { - free_path(slot_dir); - return -1; + goto cleanup; } - free_path(slot_dir); - int rc = write_atomic_file(slot_path, root->bytes, LANTERN_ROOT_SIZE); + + rc = write_atomic_file(slot_path, root->bytes, LANTERN_ROOT_SIZE); + +cleanup: free_path(slot_path); + free_path(slot_dir); return rc; } +/** + * Persist the current head slot and root so the node can resume after restart. + * + * Writes a compact `{slot, root}` record into `data_dir/indices/head.bin` + * using an atomic write. + * + * @param data_dir Base storage directory. + * @param slot Head slot number. + * @param root 32-byte head block root. + * @return 0 on success, -1 on error. + */ int lantern_storage_store_head_root( const char *data_dir, uint64_t slot, @@ -1060,29 +1216,44 @@ int lantern_storage_store_head_root( if (!data_dir || !root) { return -1; } + + int rc = -1; char *indices_dir = NULL; + char *head_path = NULL; + if (build_indices_dir(data_dir, &indices_dir) != 0) { - return -1; + goto cleanup; } if (ensure_directory(indices_dir) != 0) { - free_path(indices_dir); - return -1; + goto cleanup; } - char *head_path = NULL; if (join_path(indices_dir, LANTERN_STORAGE_HEAD_FILE, &head_path) != 0) { - free_path(indices_dir); - return -1; + goto cleanup; } - free_path(indices_dir); - struct lantern_storage_head_record record = { + + const struct lantern_storage_head_record record = { .slot = slot, .root = *root, }; - int rc = write_atomic_file(head_path, (const uint8_t *)&record, sizeof(record)); + rc = write_atomic_file(head_path, (const uint8_t *)&record, sizeof(record)); + +cleanup: free_path(head_path); + free_path(indices_dir); return rc; } +/** + * Persist justified and finalized checkpoint data to disk. + * + * Writes both checkpoints as a single record into + * `data_dir/indices/checkpoints.bin` using an atomic write. + * + * @param data_dir Base storage directory. + * @param justified Justified checkpoint to store. + * @param finalized Finalized checkpoint to store. + * @return 0 on success, -1 on error. + */ int lantern_storage_store_checkpoints( const char *data_dir, const LanternCheckpoint *justified, @@ -1090,32 +1261,49 @@ int lantern_storage_store_checkpoints( if (!data_dir || !justified || !finalized) { return -1; } + + int rc = -1; char *indices_dir = NULL; + char *checkpoint_path = NULL; + if (build_indices_dir(data_dir, &indices_dir) != 0) { - return -1; + goto cleanup; } if (ensure_directory(indices_dir) != 0) { - free_path(indices_dir); - return -1; + goto cleanup; } - char *checkpoint_path = NULL; if (join_path(indices_dir, LANTERN_STORAGE_CHECKPOINTS_FILE, &checkpoint_path) != 0) { - free_path(indices_dir); - return -1; + goto cleanup; } - free_path(indices_dir); - struct lantern_storage_checkpoint_record record = { + + const struct lantern_storage_checkpoint_record record = { .justified = *justified, .finalized = *finalized, }; - int rc = write_atomic_file( + rc = write_atomic_file( checkpoint_path, (const uint8_t *)&record, sizeof(record)); + +cleanup: free_path(checkpoint_path); + free_path(indices_dir); return rc; } +/** + * Collect signed blocks from disk that match the given set of roots. + * + * For each root in `roots`, looks up `data_dir/blocks/.ssz`, decodes + * the block, verifies its hash-tree-root matches the requested root, and + * appends it to `out_blocks`. Missing blocks are silently skipped. + * + * @param data_dir Base storage directory. + * @param roots Array of block roots to look up. + * @param root_count Number of entries in `roots`. + * @param out_blocks Output collection (resized to 0 on entry, filled on success). + * @return 0 on success, -1 on error. + */ int lantern_storage_collect_blocks( const char *data_dir, const LanternRoot *roots, @@ -1127,27 +1315,28 @@ int lantern_storage_collect_blocks( if (lantern_blocks_by_root_response_resize(out_blocks, 0) != 0) { return -1; } + + int rc = -1; char *blocks_dir = NULL; + if (build_blocks_dir(data_dir, &blocks_dir) != 0) { - return -1; + goto cleanup; } - struct lantern_log_metadata meta = {0}; + + const struct lantern_log_metadata meta = {0}; for (size_t i = 0; i < root_count; ++i) { char root_hex[2u * LANTERN_ROOT_SIZE + 1u]; if (lantern_bytes_to_hex(roots[i].bytes, LANTERN_ROOT_SIZE, root_hex, sizeof(root_hex), 0) != 0) { - free_path(blocks_dir); - return -1; + goto cleanup; } char filename[sizeof(root_hex) + 4]; - int wrote = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); + const int wrote = snprintf(filename, sizeof(filename), "%s.ssz", root_hex); if (wrote < 0 || (size_t)wrote >= sizeof(filename)) { - free_path(blocks_dir); - return -1; + goto cleanup; } char *block_path = NULL; if (join_path(blocks_dir, filename, &block_path) != 0) { - free_path(blocks_dir); - return -1; + goto cleanup; } lantern_log_trace( "storage", @@ -1155,9 +1344,10 @@ int lantern_storage_collect_blocks( "collect_blocks search root=%s path=%s", root_hex, block_path ? block_path : "null"); + uint8_t *data = NULL; size_t data_len = 0; - int read_rc = read_file_buffer(block_path, &data, &data_len); + const int read_rc = read_file_buffer(block_path, &data, &data_len); free_path(block_path); if (read_rc != 0) { lantern_log_debug( @@ -1168,28 +1358,25 @@ int lantern_storage_collect_blocks( read_rc); continue; } - size_t current = out_blocks->length; + + const size_t current = out_blocks->length; if (lantern_blocks_by_root_response_resize(out_blocks, current + 1) != 0) { free(data); - free_path(blocks_dir); - return -1; + goto cleanup; } LanternSignedBlock *dest = &out_blocks->blocks[current]; if (lantern_ssz_decode_signed_block(dest, data, data_len) != 0) { free(data); - free_path(blocks_dir); - return -1; + goto cleanup; } LanternRoot computed; if (lantern_hash_tree_root_block(&dest->message.block, &computed) != 0) { free(data); - free_path(blocks_dir); - return -1; + goto cleanup; } if (memcmp(computed.bytes, roots[i].bytes, LANTERN_ROOT_SIZE) != 0) { free(data); - free_path(blocks_dir); - return -1; + goto cleanup; } lantern_log_trace( "storage", @@ -1200,10 +1387,29 @@ int lantern_storage_collect_blocks( dest->message.block.body.attestations.length); free(data); } + rc = 0; + +cleanup: free_path(blocks_dir); - return 0; + return rc; } +/** + * Iterate over every persisted block in the blocks directory. + * + * Opens `data_dir/blocks/`, reads each `.ssz` file, decodes the signed + * block, computes its hash-tree-root, and calls `visitor` with the block, + * root, and caller-supplied `context`. Iteration stops early if the + * visitor returns non-zero (its return value is propagated). + * + * @param data_dir Base storage directory. + * @param visitor Callback invoked for each block. + * @param context Opaque pointer forwarded to the visitor. + * @return 0 on success (all blocks visited). + * @return 1 if the blocks directory does not exist. + * @return -1 on I/O or decoding errors; positive visitor return values + * are forwarded as-is. + */ int lantern_storage_iterate_blocks( const char *data_dir, lantern_storage_block_visitor_fn visitor, @@ -1211,22 +1417,27 @@ int lantern_storage_iterate_blocks( if (!data_dir || !visitor) { return -1; } + + int rc = -1; char *blocks_dir = NULL; + DIR *dir = NULL; + if (build_blocks_dir(data_dir, &blocks_dir) != 0) { - return -1; + goto cleanup; } - DIR *dir = opendir(blocks_dir); + dir = opendir(blocks_dir); if (!dir) { - free_path(blocks_dir); - return (errno == ENOENT) ? 1 : -1; + rc = (errno == ENOENT) ? 1 : -1; + goto cleanup; } + + rc = 0; struct dirent *entry = NULL; - int rc = 0; while ((entry = readdir(dir)) != NULL) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { continue; } - size_t len = strlen(entry->d_name); + const size_t len = strlen(entry->d_name); if (len < 5 || strcmp(entry->d_name + len - 4, ".ssz") != 0) { continue; } @@ -1237,7 +1448,7 @@ int lantern_storage_iterate_blocks( } uint8_t *data = NULL; size_t data_len = 0; - int read_rc = read_file_buffer(block_path, &data, &data_len); + const int read_rc = read_file_buffer(block_path, &data, &data_len); free_path(block_path); if (read_rc != 0) { if (read_rc == 1) { @@ -1261,7 +1472,7 @@ int lantern_storage_iterate_blocks( rc = -1; break; } - int visit_rc = visitor(&block, &root, context); + const int visit_rc = visitor(&block, &root, context); lantern_signed_block_with_attestation_reset(&block); free(data); if (visit_rc != 0) { @@ -1269,7 +1480,11 @@ int lantern_storage_iterate_blocks( break; } } - closedir(dir); + +cleanup: + if (dir) { + closedir(dir); + } free_path(blocks_dir); return rc; }