asl/index-fs: add snapshot-anchor fast path for get and tests

This commit is contained in:
Carl Niklas Rydberg 2026-04-04 02:33:21 +02:00
parent bc55ed994b
commit 807816e0f2
2 changed files with 396 additions and 0 deletions

View file

@ -4089,6 +4089,29 @@ static amduat_asl_store_error_t amduat_asl_store_index_fs_get_indexed_impl(
return AMDUAT_ASL_STORE_ERR_INTEGRITY;
}
if (state.snapshot_id != 0u) {
/*
* Fast path: when the requested state is exactly a snapshot anchor,
* snapshot metadata is already a complete replay state.
*/
if (amduat_asl_store_index_fs_load_snapshot_replay(fs->root_path,
state.snapshot_id,
NULL,
&replay_state,
&anchor_logseq)) {
if (replay_state.state.log_position == anchor_logseq &&
state.log_position == anchor_logseq) {
err = amduat_asl_store_index_fs_scan_segments(fs,
&replay_state,
ref,
out_artifact);
amduat_asl_replay_free(&replay_state);
return err;
}
amduat_asl_replay_free(&replay_state);
}
}
if (!amduat_asl_store_index_fs_layout_build_log_path(fs->root_path,
&log_path)) {
return AMDUAT_ASL_STORE_ERR_IO;

View file

@ -156,6 +156,24 @@ static bool read_file(const char *path, uint8_t **out_bytes, size_t *out_len) {
return true;
}
static bool write_file(const char *path, const uint8_t *bytes, size_t len) {
FILE *fp;
size_t written;
if (path == NULL || (len != 0u && bytes == NULL)) {
return false;
}
fp = fopen(path, "wb");
if (fp == NULL) {
return false;
}
written = fwrite(bytes, 1u, len, fp);
if (fclose(fp) != 0) {
return false;
}
return written == len;
}
static bool build_log_path(const char *root, char **out_path) {
char *index_path = NULL;
bool ok;
@ -725,6 +743,349 @@ cleanup:
return exit_code;
}
static int test_snapshot_fast_path_with_corrupt_log(void) {
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_store_error_t err;
amduat_asl_index_state_t state;
amduat_artifact_t artifact;
amduat_artifact_t loaded;
amduat_reference_t ref;
uint8_t payload[10];
char *root;
char *log_path = NULL;
static const uint8_t corrupt_log[] = {'B', 'A', 'D'};
int exit_code = 1;
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
memset(&config, 0, sizeof(config));
config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1;
config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256;
if (!amduat_asl_store_index_fs_init(&fs, config, root)) {
fprintf(stderr, "index fs init failed\n");
goto cleanup;
}
amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs);
memset(payload, 0x4c, sizeof(payload));
artifact = amduat_artifact(amduat_octets(payload, sizeof(payload)));
ref = amduat_reference(0u, amduat_octets(NULL, 0u));
err = amduat_asl_store_put_indexed(&store, artifact, &ref, &state);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "put_indexed failed: %d\n", err);
goto cleanup;
}
err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "snapshot create failed: %d\n", err);
goto cleanup;
}
if (!amduat_asl_index_current_state(&store, &state)) {
fprintf(stderr, "current_state failed\n");
goto cleanup;
}
if (state.snapshot_id != 1u) {
fprintf(stderr, "expected snapshot_id=1\n");
goto cleanup;
}
if (!build_log_path(root, &log_path) ||
!write_file(log_path, corrupt_log, sizeof(corrupt_log))) {
fprintf(stderr, "corrupt log write failed\n");
goto cleanup;
}
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref, state, &loaded);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "get_indexed with snapshot fast path failed: %d\n", err);
goto cleanup;
}
if (!amduat_artifact_eq(artifact, loaded)) {
fprintf(stderr, "artifact mismatch from snapshot fast path\n");
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
exit_code = 0;
cleanup:
free(log_path);
amduat_reference_free(&ref);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
static int test_snapshot_fallback_replays_delta(void) {
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_store_error_t err;
amduat_asl_index_state_t snapshot_state;
amduat_asl_index_state_t fallback_state;
amduat_artifact_t artifact_a;
amduat_artifact_t loaded;
amduat_reference_t ref_a;
uint8_t payload_a[7];
char *root;
int exit_code = 1;
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
memset(&config, 0, sizeof(config));
config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1;
config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256;
if (!amduat_asl_store_index_fs_init(&fs, config, root)) {
fprintf(stderr, "index fs init failed\n");
goto cleanup;
}
amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs);
memset(payload_a, 0x11, sizeof(payload_a));
artifact_a = amduat_artifact(amduat_octets(payload_a, sizeof(payload_a)));
ref_a = amduat_reference(0u, amduat_octets(NULL, 0u));
err = amduat_asl_store_put_indexed(&store, artifact_a, &ref_a, &snapshot_state);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "put_indexed a failed: %d\n", err);
goto cleanup;
}
err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "snapshot create failed: %d\n", err);
goto cleanup;
}
if (!amduat_asl_index_current_state(&store, &snapshot_state)) {
fprintf(stderr, "snapshot current_state failed\n");
goto cleanup;
}
if (snapshot_state.log_position == 0u) {
fprintf(stderr, "snapshot log position was zero\n");
goto cleanup;
}
fallback_state = snapshot_state;
fallback_state.log_position -= 1u;
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref_a, fallback_state, &loaded);
if (err != AMDUAT_ASL_STORE_ERR_INTEGRITY) {
fprintf(stderr, "fallback expected integrity: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
exit_code = 0;
cleanup:
amduat_reference_free(&ref_a);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
static int test_snapshot_fast_path_not_found_and_tombstoned(void) {
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_store_error_t err;
amduat_asl_index_state_t state;
amduat_asl_index_state_t tombstone_state;
amduat_reference_t ref;
amduat_reference_t missing_ref;
amduat_artifact_t artifact;
amduat_artifact_t loaded;
uint8_t payload[8];
uint8_t *missing_digest = NULL;
int exit_code = 1;
char *root;
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
memset(&config, 0, sizeof(config));
config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1;
config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256;
if (!amduat_asl_store_index_fs_init(&fs, config, root)) {
fprintf(stderr, "index fs init failed\n");
goto cleanup;
}
amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs);
memset(payload, 0x66, sizeof(payload));
artifact = amduat_artifact(amduat_octets(payload, sizeof(payload)));
ref = amduat_reference(0u, amduat_octets(NULL, 0u));
missing_ref = amduat_reference(0u, amduat_octets(NULL, 0u));
err = amduat_asl_store_put_indexed(&store, artifact, &ref, &state);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "put_indexed failed: %d\n", err);
goto cleanup;
}
err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "snapshot create failed: %d\n", err);
goto cleanup;
}
if (!amduat_asl_index_current_state(&store, &state)) {
fprintf(stderr, "current_state failed\n");
goto cleanup;
}
missing_digest = (uint8_t *)malloc(ref.digest.len);
if (missing_digest == NULL || ref.digest.len == 0u) {
fprintf(stderr, "missing ref build failed\n");
goto cleanup;
}
memcpy(missing_digest, ref.digest.data, ref.digest.len);
missing_digest[0] ^= 0xffu;
missing_ref.hash_id = ref.hash_id;
missing_ref.digest = amduat_octets(missing_digest, ref.digest.len);
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, missing_ref, state, &loaded);
if (err != AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
fprintf(stderr, "snapshot not found expected: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
err = amduat_asl_store_tombstone(&store, ref, 1u, 2u, &tombstone_state);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "tombstone failed: %d\n", err);
goto cleanup;
}
err = amduat_asl_store_index_fs_snapshot_create(&fs, 2u, NULL, NULL);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "snapshot create after tombstone failed: %d\n", err);
goto cleanup;
}
if (!amduat_asl_index_current_state(&store, &state)) {
fprintf(stderr, "current_state after tombstone failed\n");
goto cleanup;
}
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref, state, &loaded);
if (err != AMDUAT_ASL_STORE_ERR_NOT_FOUND) {
fprintf(stderr, "snapshot tombstoned expected not found: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
exit_code = 0;
cleanup:
amduat_reference_free(&ref);
amduat_reference_free(&missing_ref);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
static int test_snapshot_fast_path_integrity_error_unchanged(void) {
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
amduat_asl_store_t store;
amduat_asl_store_error_t err;
amduat_asl_index_state_t state;
amduat_artifact_t artifact;
amduat_artifact_t loaded;
amduat_reference_t ref;
uint8_t payload[6];
char *root;
char *segment_path = NULL;
static const uint8_t corrupt_segment[] = {'X', 'X', 'X'};
int exit_code = 1;
root = make_temp_root();
if (root == NULL) {
fprintf(stderr, "temp root failed\n");
return 1;
}
memset(&config, 0, sizeof(config));
config.encoding_profile_id = AMDUAT_ENC_ASL1_CORE_V1;
config.hash_id = AMDUAT_HASH_ASL1_ID_SHA256;
if (!amduat_asl_store_index_fs_init(&fs, config, root)) {
fprintf(stderr, "index fs init failed\n");
goto cleanup;
}
amduat_asl_store_init(&store, config, amduat_asl_store_index_fs_ops(), &fs);
memset(payload, 0x31, sizeof(payload));
artifact = amduat_artifact(amduat_octets(payload, sizeof(payload)));
ref = amduat_reference(0u, amduat_octets(NULL, 0u));
err = amduat_asl_store_put_indexed(&store, artifact, &ref, &state);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "put_indexed failed: %d\n", err);
goto cleanup;
}
err = amduat_asl_store_index_fs_snapshot_create(&fs, 1u, NULL, NULL);
if (err != AMDUAT_ASL_STORE_OK) {
fprintf(stderr, "snapshot create failed: %d\n", err);
goto cleanup;
}
if (!amduat_asl_index_current_state(&store, &state)) {
fprintf(stderr, "current_state failed\n");
goto cleanup;
}
if (!build_segment_path(root, 1u, &segment_path) ||
!write_file(segment_path, corrupt_segment, sizeof(corrupt_segment))) {
fprintf(stderr, "segment corruption failed\n");
goto cleanup;
}
loaded = amduat_artifact(amduat_octets(NULL, 0u));
err = amduat_asl_store_get_indexed(&store, ref, state, &loaded);
if (err != AMDUAT_ASL_STORE_ERR_INTEGRITY) {
fprintf(stderr, "expected integrity error, got: %d\n", err);
amduat_artifact_free(&loaded);
goto cleanup;
}
amduat_artifact_free(&loaded);
exit_code = 0;
cleanup:
free(segment_path);
amduat_reference_free(&ref);
if (!remove_tree(root)) {
fprintf(stderr, "cleanup failed\n");
exit_code = 1;
}
free(root);
return exit_code;
}
static int test_segment_batch_packing(void) {
amduat_asl_store_config_t config;
amduat_asl_store_index_fs_t fs;
@ -1598,6 +1959,18 @@ int main(void) {
if (test_round_trip() != 0) {
return 1;
}
if (test_snapshot_fast_path_with_corrupt_log() != 0) {
return 1;
}
if (test_snapshot_fallback_replays_delta() != 0) {
return 1;
}
if (test_snapshot_fast_path_not_found_and_tombstoned() != 0) {
return 1;
}
if (test_snapshot_fast_path_integrity_error_unchanged() != 0) {
return 1;
}
if (test_snapshot_truncation() != 0) {
return 1;
}