@@ -104,12 +104,21 @@ impl BlockchainEngine {
104104 } ;
105105
106106 let module_id = ModuleId :: new ( addr, ident) ;
107- let runtime = & self . runtime_pool [ 0 ] ;
107+ let snapshot_store = self . state_read ( ) . snapshot_store ( ) . ok ( ) ?;
108+ let runtime = self . runtime_pool [ 0 ]
109+ . spawn_isolated_worker_from_store ( snapshot_store)
110+ . ok ( ) ?;
108111 runtime. get_module_bytes ( & module_id)
109112 }
110113
111114 pub fn list_all_modules ( & self ) -> Vec < ( String , String ) > {
112- let runtime = & self . runtime_pool [ 0 ] ;
115+ let Ok ( snapshot_store) = self . state_read ( ) . snapshot_store ( ) else {
116+ return Vec :: new ( ) ;
117+ } ;
118+ let Ok ( runtime) = self . runtime_pool [ 0 ] . spawn_isolated_worker_from_store ( snapshot_store)
119+ else {
120+ return Vec :: new ( ) ;
121+ } ;
113122 runtime
114123 . list_modules ( )
115124 . into_iter ( )
@@ -359,12 +368,12 @@ impl BlockchainEngine {
359368 let ( computed_root, verified_state, to_execute) =
360369 self . prepare_checkpoint_state ( & checkpoint_to_apply) ?;
361370
362- if checkpoint_to_apply. transactions . is_empty ( ) {
363- let previous_checkpoint_root = {
364- let chain = self . blockchain . read ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
365- chain. latest_checkpoint ( ) . state_root . clone ( )
366- } ;
371+ let previous_checkpoint_root = {
372+ let chain = self . blockchain . read ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
373+ chain. latest_checkpoint ( ) . state_root . clone ( )
374+ } ;
367375
376+ if checkpoint_to_apply. transactions . is_empty ( ) {
368377 if checkpoint_to_apply. state_root != previous_checkpoint_root {
369378 info ! (
370379 "[SYNC] Canonicalizing empty checkpoint #{} root: advertised={}, canonical={}" ,
@@ -380,8 +389,18 @@ impl BlockchainEngine {
380389 & computed_root,
381390 & checkpoint_to_apply. state_root ,
382391 ) ? {
392+ if checkpoint_to_apply. state_root != previous_checkpoint_root {
393+ anyhow:: bail!(
394+ "[SYNC] Rejecting checkpoint #{} with non-canonical state root: advertised={}, computed={}, previous={}" ,
395+ checkpoint_to_apply. sequence,
396+ hex:: encode( & checkpoint_to_apply. state_root) ,
397+ hex:: encode( & computed_root) ,
398+ hex:: encode( & previous_checkpoint_root)
399+ ) ;
400+ }
401+
383402 info ! (
384- "[SYNC] Canonicalizing checkpoint #{} root after replay: advertised ={}, computed={}" ,
403+ "[SYNC] Canonicalizing provisional checkpoint #{} root after replay: advertised_previous_root ={}, computed={}" ,
385404 checkpoint_to_apply. sequence,
386405 hex:: encode( & checkpoint_to_apply. state_root) ,
387406 hex:: encode( & computed_root)
@@ -408,7 +427,8 @@ impl BlockchainEngine {
408427 type_args : & [ String ] ,
409428 args : & [ Vec < u8 > ] ,
410429 ) -> Result < serde_json:: Value > {
411- let runtime = & self . runtime_pool [ 0 ] ;
430+ let snapshot_store = self . state_read ( ) . snapshot_store ( ) ?;
431+ let runtime = self . runtime_pool [ 0 ] . spawn_isolated_worker_from_store ( snapshot_store) ?;
412432 runtime. execute_view_function ( package_addr, module_name, function_name, type_args, args)
413433 }
414434}
@@ -417,6 +437,19 @@ impl BlockchainEngine {
417437mod tests {
418438 use super :: * ;
419439 use centauri:: consensus:: Checkpoint ;
440+ use kanari_crypto:: keys:: { CurveType , generate_keypair} ;
441+ use kanari_move_runtime_v1:: state:: Account ;
442+ use kanari_types:: balance:: BalanceRecord ;
443+ use kanari_types:: kanari:: KANARI_TOKEN_TYPE ;
444+ use kanari_types:: transaction:: { SignedTransaction , Transaction } ;
445+ use move_core_types:: account_address:: AccountAddress ;
446+
447+ fn fund_account ( engine : & BlockchainEngine , address : & str , balance : u64 ) {
448+ let addr = AccountAddress :: from_hex_literal ( address) . unwrap ( ) ;
449+ let mut account = Account :: with_native_balance ( addr, balance) ;
450+ account. set_token_balance ( KANARI_TOKEN_TYPE . to_string ( ) , BalanceRecord :: new ( balance) ) ;
451+ engine. state_write ( ) . save_account ( & account) . unwrap ( ) ;
452+ }
420453
421454 #[ test]
422455 fn sync_checkpoint_from_data_applies_next_checkpoint ( ) {
@@ -456,4 +489,47 @@ mod tests {
456489 assert_eq ! ( chain. latest_checkpoint( ) . sequence, 1 ) ;
457490 assert_eq ! ( chain. latest_checkpoint( ) . state_root, previous_root) ;
458491 }
492+
493+ #[ test]
494+ fn sync_checkpoint_from_data_rejects_non_provisional_root_mismatch ( ) {
495+ let engine = BlockchainEngine :: new_in_memory ( ) . unwrap ( ) ;
496+ let sender = generate_keypair ( CurveType :: Ed25519 ) . unwrap ( ) ;
497+ let recipient = generate_keypair ( CurveType :: Ed25519 ) . unwrap ( ) ;
498+ fund_account ( & engine, & sender. address , 2_000_000 ) ;
499+
500+ let transaction = Transaction :: new_transfer_with_gas (
501+ sender. tagged_address ( ) ,
502+ recipient. address ,
503+ 1 ,
504+ 0 ,
505+ 1_000_000 ,
506+ 1 ,
507+ ) ;
508+ let mut signed_tx = SignedTransaction :: new ( transaction) ;
509+ signed_tx
510+ . sign ( & sender. private_key , sender. curve_type )
511+ . unwrap ( ) ;
512+
513+ let ( prev_hash, previous_root) = {
514+ let chain = engine. blockchain . read ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
515+ (
516+ chain. latest_checkpoint ( ) . hash ( ) . unwrap ( ) ,
517+ chain. latest_checkpoint ( ) . state_root . clone ( ) ,
518+ )
519+ } ;
520+ let mut bad_root = vec ! [ 9u8 ; 32 ] ;
521+ if bad_root == previous_root {
522+ bad_root = vec ! [ 8u8 ; 32 ] ;
523+ }
524+ let checkpoint = Checkpoint :: new ( 1 , vec ! [ ] , vec ! [ signed_tx] , bad_root, 42 , prev_hash) ;
525+ let sync_data = CheckpointSyncData { checkpoint } ;
526+
527+ let err = engine. sync_checkpoint_from_data ( & sync_data) . unwrap_err ( ) ;
528+
529+ assert ! (
530+ err. to_string( ) . contains( "non-canonical state root" ) ,
531+ "unexpected error: {err:#}"
532+ ) ;
533+ assert_eq ! ( engine. get_stats( ) . height, 0 ) ;
534+ }
459535}
0 commit comments