Verify channel SQL failure rollback and complete signed profiles through an independent reader
This commit is contained in:
1 parent
1d84765f08
commit
01c2ebd7d8
1 file changed
+83
-4
@@ -100,7 +100,8 @@ static void snapshot(sqlite3* db, uint8_t out[32]) {
|
||||
const char* queries[] = {
|
||||
"SELECT channel_id,name,owner_node_id,x25519_pubkey,x25519_privkey,ed25519_pubkey,ed25519_privkey,signature FROM channels ORDER BY channel_id",
|
||||
"SELECT namespace,level,prefix64,hash FROM merkle_tree_hash ORDER BY namespace,level,prefix64",
|
||||
"SELECT type,name,tbl_name,sql FROM sqlite_master ORDER BY type,name"
|
||||
"SELECT type,name,tbl_name,sql FROM sqlite_master ORDER BY type,name",
|
||||
"SELECT type,name,tbl_name,sql FROM sqlite_temp_master ORDER BY type,name"
|
||||
};
|
||||
EVP_MD_CTX* hash = EVP_MD_CTX_new(); assert(hash && EVP_DigestInit_ex(hash, EVP_sha256(), NULL) == 1);
|
||||
for (unsigned query = 0; query < sizeof(queries) / sizeof(queries[0]); query++) {
|
||||
@@ -146,11 +147,87 @@ static void event(struct UTUN_INSTANCE* inst, int type, const uint8_t* data, int
|
||||
for (int i = 1; i <= 5; i++) assert(sqlite3_column_int(st, i) == (i == 5 ? 64 : 32));
|
||||
assert(sqlite3_finalize(st) == SQLITE_OK);
|
||||
uint8_t key[32]; assert(member_sync_author_key(inst, channel, inst->node_id, key) == MS_AUTHOR_ACTIVE);
|
||||
assert(!memcmp(key, inst->my_ed25519_pubkey, 32)); channel_events++;
|
||||
assert(!memcmp(key, inst->my_ed25519_pubkey, 32));
|
||||
char query[256]; snprintf(query, sizeof(query), "SELECT join_sig,update_sig,update_ts,userinfo,signature,source FROM peers_%s WHERE node_id=?", channel);
|
||||
assert(sqlite3_prepare_v2(reader, query, -1, &st, NULL) == SQLITE_OK);
|
||||
assert(sqlite3_bind_int64(st, 1, (sqlite3_int64)inst->node_id) == SQLITE_OK && sqlite3_step(st) == SQLITE_ROW);
|
||||
assert(sqlite3_column_bytes(st, 0) == 64 && sqlite3_column_bytes(st, 1) == 64 && sqlite3_column_bytes(st, 4) == 64);
|
||||
assert(sqlite3_column_int64(st, 2) > 0 && !sqlite3_column_int(st, 5));
|
||||
uint8_t profile[MS_USERINFO_MAX + 73], signature[64];
|
||||
int bytes = member_sync_build_update_msg(sqlite3_column_blob(st, 0), (uint64_t)sqlite3_column_int64(st, 2),
|
||||
(const char*)sqlite3_column_text(st, 3), profile, sizeof(profile));
|
||||
assert(bytes > 0); memcpy(signature, sqlite3_column_blob(st, 1), 64); assert(sqlite3_finalize(st) == SQLITE_OK);
|
||||
assert(sc_ed25519_verify(inst->my_ed25519_pubkey, profile, (size_t)bytes, signature) == SC_OK);
|
||||
uint8_t writer[32], committed[32]; snapshot(inst->topo_sqlite_db, writer); snapshot(reader, committed);
|
||||
assert(!memcmp(writer, committed, 32)); channel_events++;
|
||||
}
|
||||
|
||||
static void refused(struct UTUN_INSTANCE* inst, const char* name) {
|
||||
uint8_t before[32], after[32]; snapshot(reader, before);
|
||||
unsigned events = channel_events; int count = channels(), groups = queue_entry_count(inst->topo_groups->group_list);
|
||||
uint64_t created = chat_core_create_channel_auto(inst, name);
|
||||
if (created || sql_fault != Q_NONE) DEBUG_ERROR(DEBUG_CATEGORY_CHAT_SYNC,
|
||||
"channel test: expected refusal name=%s ID=%llu pending_fault=%d", name, (unsigned long long)created, sql_fault);
|
||||
assert(!created && sql_fault == Q_NONE && sqlite3_get_autocommit(inst->topo_sqlite_db));
|
||||
snapshot(reader, after); assert(!memcmp(before, after, 32) && channels() == count && channel_events == events);
|
||||
assert(queue_entry_count(inst->topo_groups->group_list) == groups);
|
||||
}
|
||||
|
||||
static void retry(struct UTUN_INSTANCE* inst) {
|
||||
unsigned events = channel_events; int count = channels();
|
||||
assert(chat_core_create_channel_auto(inst, "successful SQL retry") && channels() == count + 1 && channel_events == events + 1);
|
||||
}
|
||||
|
||||
static int veto(void* arg) { (*(unsigned*)arg)++; return 1; }
|
||||
|
||||
static void sql_regressions(struct UTUN_INSTANCE* inst) {
|
||||
const char* queries[] = {"SELECT 1 FROM channels WHERE channel_id=?", "INSERT INTO channels(channel_id, name, owner_node_id,",
|
||||
"INSERT INTO \"peers_"};
|
||||
unsigned cases = 0;
|
||||
for (unsigned query = 0; query < 3; query++) {
|
||||
sql_prefix = queries[query]; sql_suffix = NULL;
|
||||
for (enum sql_fault point = Q_PREPARE; point <= Q_FINALIZE; point++) {
|
||||
if (point == Q_CHANGES && !query) continue;
|
||||
int bindings = point == Q_BIND ? (query == 0 ? 1 : query == 1 ? 8 : 11) : 1;
|
||||
for (int bind = 1; bind <= bindings; bind++) {
|
||||
sql_fault = point; sql_bind = bind; refused(inst, "SQL statement refusal"); retry(inst); cases++;
|
||||
}
|
||||
}
|
||||
}
|
||||
const char* exec[] = {"BEGIN IMMEDIATE /* channel creation */", "SAVEPOINT channel_put", "CREATE TABLE IF NOT EXISTS \"peers_",
|
||||
"CREATE INDEX IF NOT EXISTS \"idx_peers_", "CREATE INDEX IF NOT EXISTS \"idx_peers_",
|
||||
"CREATE TABLE IF NOT EXISTS feedback_", "RELEASE channel_put", "COMMIT /* channel creation */"};
|
||||
for (unsigned i = 0; i < sizeof(exec) / sizeof(exec[0]); i++) {
|
||||
sql_prefix = exec[i]; sql_suffix = i == 3 ? "_type_rtt\"" : i == 4 ? "_storage\"" : NULL;
|
||||
sql_fault = Q_EXEC; refused(inst, "SQL transaction/schema refusal"); retry(inst); cases++;
|
||||
}
|
||||
sql_prefix = sql_suffix = NULL;
|
||||
for (int allocation = 1; allocation <= 3; allocation++) {
|
||||
allocations_until_failure = allocation; sql_fault = Q_ALLOCATE;
|
||||
refused(inst, "SQL schema allocation refusal"); retry(inst); cases++;
|
||||
}
|
||||
const char* triggers[] = {
|
||||
"CREATE TEMP TRIGGER reject_create BEFORE INSERT ON channels BEGIN SELECT RAISE(ABORT,'channel create'); END",
|
||||
"CREATE TEMP TRIGGER reject_create BEFORE INSERT ON channels BEGIN SELECT RAISE(IGNORE); END"
|
||||
};
|
||||
for (unsigned i = 0; i < 2; i++) {
|
||||
assert(sqlite3_exec(inst->topo_sqlite_db, triggers[i], NULL, NULL, NULL) == SQLITE_OK);
|
||||
refused(inst, "channel trigger refusal");
|
||||
assert(sqlite3_exec(inst->topo_sqlite_db, "DROP TRIGGER reject_create", NULL, NULL, NULL) == SQLITE_OK); retry(inst);
|
||||
}
|
||||
unsigned commits = 0; sqlite3_commit_hook(inst->topo_sqlite_db, veto, &commits);
|
||||
refused(inst, "channel commit veto"); assert(commits == 1); sqlite3_commit_hook(inst->topo_sqlite_db, NULL, NULL); retry(inst);
|
||||
uint8_t before[32], after[32]; snapshot(reader, before); unsigned events = channel_events;
|
||||
assert(sqlite3_exec(inst->topo_sqlite_db, "BEGIN;INSERT INTO ui_state(key,value) VALUES('foreign-create','1')", NULL, NULL, NULL) == SQLITE_OK);
|
||||
assert(!chat_core_create_channel_auto(inst, "foreign transaction") && !sqlite3_get_autocommit(inst->topo_sqlite_db));
|
||||
snapshot(reader, after); assert(!memcmp(before, after, 32) && events == channel_events);
|
||||
assert(sqlite3_exec(inst->topo_sqlite_db, "ROLLBACK", NULL, NULL, NULL) == SQLITE_OK); retry(inst);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS channel SQL:%u faults/retries,ABORT/IGNORE/veto,foreign transaction; exact committed descriptions/schema/roots and runtime count preserved", cases);
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_INFO); size_t baseline = u_get_allocated_count();
|
||||
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); debug_set_category_level(DEBUG_CATEGORY_CHAT_SYNC, DEBUG_LEVEL_INFO);
|
||||
size_t baseline = u_get_allocated_count();
|
||||
utun_instance_set_tun_init_enabled(0);
|
||||
char directory[] = "/tmp/utun-channel-create-XXXXXX"; assert(mkdtemp(directory));
|
||||
char config[512], database[256];
|
||||
@@ -171,7 +248,9 @@ int main(void) {
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "channel test: profile bind failure ID=%llu channels=%d events=%u",
|
||||
(unsigned long long)created, channels(), channel_events);
|
||||
assert(!fail_profile_binding && !created && channels() == 1 && channel_events == 1);
|
||||
utun_instance_destroy(inst); active = NULL; assert(channels() == 1 && sqlite3_close(reader) == SQLITE_OK); reader = NULL;
|
||||
sql_regressions(inst);
|
||||
int committed = channels();
|
||||
utun_instance_destroy(inst); active = NULL; assert(channels() == committed && sqlite3_close(reader) == SQLITE_OK); reader = NULL;
|
||||
uasync_poll(ua, 0); uasync_destroy(ua, 0); assert(u_get_allocated_count() == baseline);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_CHAT_SYNC, "PASS channel creation: failed root signature leaves no channel/event; retry publishes valid membership after commit");
|
||||
return 0;
|
||||
|
||||
Reference in new issue
Block a user