@ -12,7 +12,7 @@
# include <sqlite3.h>
# define MS_ID "merkle_sync"
# define MS_BG_INTERVAL_MS 1 00
# define MS_BG_INTERVAL_MS 50 00
# define MS_PENDING_MAX 128
struct bucket_entry { uint8_t level ; uint8_t prefix_bytes ; uint64_t prefix ; } ;
@ -106,22 +106,37 @@ static int _recompute_bucket(struct merkle_sync* ms, const char* ns,
if ( count < 0 ) { DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → error " , MS_ID ) ; EVP_MD_CTX_free ( ctx ) ; return - 1 ; }
if ( count = = 0 ) {
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → empty, deleted " , MS_ID ) ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → empty " , MS_ID ) ;
EVP_MD_CTX_free ( ctx ) ;
sqlite3_stmt * d s = NULL ;
sqlite3_stmt * c s = NULL ;
sqlite3_prepare_v2 ( db ,
" DELETE FROM merkle_tree_hash WHERE namespace=? AND level=? AND prefix64=? " ,
- 1 , & ds , NULL ) ;
if ( ds ) { sqlite3_bind_text ( ds , 1 , ns , - 1 , SQLITE_STATIC ) ; sqlite3_bind_int ( ds , 2 , level ) ;
sqlite3_bind_int64 ( ds , 3 , ( sqlite3_int64 ) prefix64 ) ; sqlite3_step ( ds ) ; sqlite3_finalize ( ds ) ; }
" SELECT COUNT(*) FROM merkle_tree_hash WHERE namespace=? AND level=? AND prefix64=? " , - 1 , & cs , NULL ) ;
if ( cs ) { sqlite3_bind_text ( cs , 1 , ns , - 1 , SQLITE_STATIC ) ; sqlite3_bind_int ( cs , 2 , level ) ;
sqlite3_bind_int64 ( cs , 3 , ( sqlite3_int64 ) prefix64 ) ;
if ( sqlite3_step ( cs ) = = SQLITE_ROW & & sqlite3_column_int ( cs , 0 ) > 0 ) {
sqlite3_finalize ( cs ) ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → cleared (was non-empty) " , MS_ID ) ;
sqlite3_stmt * ds = NULL ;
sqlite3_prepare_v2 ( db ,
" DELETE FROM merkle_tree_hash WHERE namespace=? AND level=? AND prefix64=? " , - 1 , & ds , NULL ) ;
if ( ds ) { sqlite3_bind_text ( ds , 1 , ns , - 1 , SQLITE_STATIC ) ; sqlite3_bind_int ( ds , 2 , level ) ;
sqlite3_bind_int64 ( ds , 3 , ( sqlite3_int64 ) prefix64 ) ; sqlite3_step ( ds ) ; sqlite3_finalize ( ds ) ; }
} else { sqlite3_finalize ( cs ) ; }
}
return 0 ;
}
uint8_t hash [ MT_HASH_SIZE ] ;
EVP_DigestFinal_ex ( ctx , hash , NULL ) ;
uint8_t new_ hash[ MT_HASH_SIZE ] ;
EVP_DigestFinal_ex ( ctx , new_ hash, NULL ) ;
EVP_MD_CTX_free ( ctx ) ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → saved, count=%d " , MS_ID , count ) ;
const uint8_t * old_hash = merkle_sync_get_hash ( ms - > inst , ns , level , prefix64 ) ;
if ( memcmp ( old_hash , new_hash , MT_HASH_SIZE ) = = 0 ) {
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → unchanged (count=%d) " , MS_ID , count ) ;
return 0 ;
}
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: → saved, count=%d " , MS_ID , count ) ;
sqlite3_stmt * is = NULL ;
sqlite3_prepare_v2 ( db ,
" INSERT OR REPLACE INTO merkle_tree_hash(namespace, level, prefix64, hash, member_count) "
@ -130,7 +145,7 @@ static int _recompute_bucket(struct merkle_sync* ms, const char* ns,
sqlite3_bind_text ( is , 1 , ns , - 1 , SQLITE_STATIC ) ;
sqlite3_bind_int ( is , 2 , level ) ;
sqlite3_bind_int64 ( is , 3 , ( sqlite3_int64 ) prefix64 ) ;
sqlite3_bind_blob ( is , 4 , hash , MT_HASH_SIZE , SQLITE_STATIC ) ;
sqlite3_bind_blob ( is , 4 , new_ hash, MT_HASH_SIZE , SQLITE_STATIC ) ;
sqlite3_bind_int ( is , 5 , count ) ;
sqlite3_step ( is ) ; sqlite3_finalize ( is ) ;
}
@ -689,21 +704,33 @@ static void _bg_timer_cb(void* arg) {
if ( ! ms | | ! ms - > initialized | | ! ms - > inst ) return ;
sqlite3 * db = _db ( ms - > inst ) ; if ( ! db ) return ;
static int tick = 0 ; tick + + ;
sqlite3_stmt * cs = NULL ;
sqlite3_prepare_v2 ( db , " SELECT channel_id FROM channels " , - 1 , & cs , NULL ) ;
if ( ! cs ) return ;
while ( sqlite3_step ( cs ) = = SQLITE_ROW ) {
const char * ch_id = ( const char * ) sqlite3_column_text ( cs , 0 ) ;
if ( ! ch_id ) continue ;
int rc = merkle_sync_bg_check ( ms - > inst , ch_id ) ;
if ( rc < 0 ) continue ;
DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: bg_check ns=%s → %s " , MS_ID , ch_id , rc = = 0 ? " consistent " : " RECALCULATED " ) ;
break ;
if ( cs ) {
int ch_idx = 0 ;
while ( sqlite3_step ( cs ) = = SQLITE_ROW ) {
const char * ch_id = ( const char * ) sqlite3_column_text ( cs , 0 ) ;
if ( ! ch_id ) { ch_idx + + ; continue ; }
if ( ( tick % ( ch_idx + 1 ) ) = = 0 ) {
int rc = merkle_sync_bg_check ( ms - > inst , ch_id ) ;
if ( rc > = 0 ) DEBUG_TRACE ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: bg_check ns=%s → %s " , MS_ID , ch_id , rc = = 0 ? " consistent " : " RECALCULATED " ) ;
}
ch_idx + + ;
}
sqlite3_finalize ( cs ) ;
if ( tick % 12 = = 0 ) { /* every ~60s: try to checkpoint WAL, keep it under control */
int pn = 0 , ckpt = 0 ;
sqlite3_wal_checkpoint_v2 ( db , NULL , SQLITE_CHECKPOINT_PASSIVE , & pn , & ckpt ) ;
if ( pn > 0 | | ckpt > 0 )
DEBUG_DEBUG ( DEBUG_CATEGORY_MEMBER_SYNC , " %s: WAL checkpoint done: %d frames in log, %d checkpointed " , MS_ID , pn , ckpt ) ;
}
}
sqlite3_finalize ( cs ) ;
ms - > bg_timer = uasync_set_timeout ( ms - > inst - > ua , ( uint32_t ) ( MS_BG_INTERVAL_MS * 10 ) ,
ms , _bg_timer_cb , " ms_bg " ) ;
ms , _bg_timer_cb , " ms_bg " ) ;
}
/* ── Public lifecycle ── */