Accept verified DM originals after storage expiry

This commit is contained in:
evgeny committed 2026-10-26 18:42:45 +01:00
1 parent ab8713d88f
commit 25a8b905fc
2 files changed
+47 -6

No files matched your search

+5 -1
View File
@@ -581,7 +581,8 @@ static void dm_file_done(void* arg, int error) {
uint64_t expires = rc == SQLITE_ROW ? (uint64_t)sqlite3_column_int64(st, 4) : 0;
if (len <= sizeof(body) && len) memcpy(body, sqlite3_column_blob(st, 0), len);
sqlite3_finalize(st);
if (ready < 0 || !len || len > sizeof(body) || (custody && expires <= ntp_time_get_seconds(inst))) {
/* EXPIRED завершает копию M; endpoint может принять проверенный оригинал в актуальном поколении. */
if (!len || len > sizeof(body) || (custody && (ready < 0 || expires <= ntp_time_get_seconds(inst)))) {
DEBUG_WARN(DEBUG_CATEGORY_DM, "dm_media: completed task no longer admissible custody=%d ready=%d expiry=%llu",
custody, ready, (unsigned long long)expires);
goto fail;
@@ -603,6 +604,9 @@ static void dm_file_done(void* arg, int error) {
if (!custody) sqlite3_bind_blob(st, idx++, receipt, sizeof(receipt), SQLITE_STATIC);
sqlite3_bind_blob(st, idx, job->id, 16, SQLITE_STATIC);
if (dm_media_step(inst, st) || sqlite3_changes(inst->topo_sqlite_db) != 1) goto fail;
if (!custody && ready < 0)
DEBUG_INFO(DEBUG_CATEGORY_DM, "dm_media: recovered expired file conv=%s generation=%llu bytes=%llu",
job->conv, (unsigned long long)job->generation, (unsigned long long)job->size);
if (custody) {
uint8_t stored[24]; memcpy(stored, job->id, 16); memcpy(stored + 16, &expires, 8);
dm_media_send(inst, group, peer, DM_MEDIA_STORED, stored, sizeof(stored));
+42 -5
View File
@@ -1,6 +1,6 @@
/* test_dm_media — реальные A–S–B и S–M, независимые роли supernode/storage.
* Прямая доставка, offline custody, restart S/M при выключенном A, TTL,
* квота, точные квитанции, поздние STORE/STORED и отзыв активных операций.
* квота, точные квитанции, поздние STORE/STORED, возврат оригинала после EXPIRED и отзыв активных операций.
*/
#include <stdio.h>
#include <string.h>
@@ -45,6 +45,7 @@ struct media_test {
uint8_t body[2048], id[16], receipt[DM_MEDIA_RECEIPT_SIZE];
size_t body_len;
unsigned phase, ticks, heartbeats, work_ticks;
uint64_t recovery_start;
int failed;
void* timer;
};
@@ -604,13 +605,49 @@ static void tick(void* arg) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "DM expiry test: release checks=%u blocked=%u lost EXPIRED=%u restart=1 recovered PASS",
release_checks, expiry_blocks, expiry_notices);
#endif
utun_instance_destroy(a); t->nodes[A] = NULL;
if (start_node(t, B)) { failure(t, "receiver return after TTL"); return; }
t->phase++;
uasync_stop(t->ua); return;
}
break;
case 9:
if (value(b, "SELECT count(*) FROM dm_files WHERE dir=0 AND seq=3 AND ready=-1 AND receipt IS NULL") == 1) {
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "DM recovery test: B received EXPIRED while A offline; returning original author");
if (start_node(t, A)) { failure(t, "source return after EXPIRED"); return; }
t->phase++;
}
break;
case 10:
if (live(a, t->ids[B]) && live(b, t->ids[A])) {
t->recovery_start = ntp_time_get_seconds(b);
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "DM recovery test: A/B routes restored; waiting for verified original");
t->phase++;
}
break;
case 11:
if (file_correct(t) && value(a, "SELECT count(*) FROM dm_files WHERE dir=1 AND seq=3 AND receipt IS NOT NULL") == 1 &&
value(s, "SELECT count(*) FROM dm_media_jobs WHERE seq=3 AND receipt IS NOT NULL AND body IS NULL") == 1) {
if (dm_media_verify_receipt(a, t->receipt, t->body, t->body_len)) {
failure(t, "recovered file receipt does not match original body"); return;
}
control(t, B, t->ids[S], DM_MEDIA_EXPIRED, t->id, 16);
if (!file_correct(t) || value(m, "SELECT count(*) FROM dm_custody") ||
value(b, "SELECT count(*) FROM dm_files WHERE dir=0 AND seq=3 AND ready=1 AND receipt IS NOT NULL") != 1) {
failure(t, "late EXPIRED changed verified file or revived custody"); return;
}
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "DM recovery test: exact original, signed receipt, S cleanup, late EXPIRED PASS");
t->phase++; uasync_stop(t->ua); return;
}
if (ntp_time_get_seconds(b) >= t->recovery_start + 16) {
DEBUG_ERROR(DEBUG_CATEGORY_DM, "DM recovery test: timeout A_to_B=%d B_to_A=%d B_ready=%d B_receipt=%d",
live(a, t->ids[B]), live(b, t->ids[A]), value(b, "SELECT ready FROM dm_files WHERE dir=0 AND seq=3"),
value(b, "SELECT count(*) FROM dm_files WHERE dir=0 AND seq=3 AND receipt IS NOT NULL"));
failure(t, "available original cannot complete receive after EXPIRED"); return;
}
break;
}
if (t->failed) return;
if (t->ticks > 6000) { failure(t, "timeout"); return; }
if (t->ticks > 9000) { failure(t, "timeout"); return; }
t->timer = uasync_set_timeout(t->ua, 100, t, tick, "dm_media_test");
}
@@ -666,6 +703,6 @@ int main(void) {
printf("DM media allocations: %zu->%zu\n", allocations, u_get_allocated_count());
if (allocations != u_get_allocated_count()) t.failed = 1;
printf("test_dm_media: phase=%u heartbeats=%u %s artifacts=%s\n", t.phase, t.heartbeats,
!t.failed && t.phase == 9 ? "PASS" : "FAIL", t.dir);
return t.failed || t.phase != 9;
!t.failed && t.phase == 12 ? "PASS" : "FAIL", t.dir);
return t.failed || t.phase != 12;
}