Skip to content

Commit c290607

Browse files
committed
feat: enhance transaction execution with isolated runtime snapshots and improve state management
1 parent 239a5f7 commit c290607

18 files changed

Lines changed: 472 additions & 53 deletions

File tree

crates/kanari-core/src/engine.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,20 @@ impl BlockchainEngine {
326326
let mut executed_count = 0;
327327
let failed_count = 0;
328328
for signed_tx in transactions {
329+
let snapshot_runtime = if persist_objects {
330+
None
331+
} else {
332+
let snapshot_store = {
333+
let state = state_arc.read().unwrap_or_else(|e| e.into_inner());
334+
state.snapshot_store()?
335+
};
336+
Some(self.runtime_pool[0].spawn_isolated_worker_from_store(snapshot_store)?)
337+
};
338+
let runtime = snapshot_runtime.as_ref().unwrap_or(&self.runtime_pool[0]);
339+
329340
let changeset = self.execute_transaction_with_runtime_internal(
330341
&signed_tx.transaction,
331-
&self.runtime_pool[0],
342+
runtime,
332343
state_arc,
333344
false,
334345
timestamp,
@@ -369,12 +380,27 @@ impl BlockchainEngine {
369380
}
370381

371382
for wave in waves {
383+
let wave_snapshot_store = if persist_objects {
384+
None
385+
} else {
386+
let state = state_arc.read().unwrap_or_else(|e| e.into_inner());
387+
Some(state.snapshot_store()?)
388+
};
389+
372390
let results: Vec<Result<ChangeSet>> = if has_module_publish {
373391
wave.iter()
374392
.map(|signed_tx| {
393+
let snapshot_runtime = match &wave_snapshot_store {
394+
Some(store) => Some(
395+
self.runtime_pool[0]
396+
.spawn_isolated_worker_from_store(store.clone())?,
397+
),
398+
None => None,
399+
};
400+
let runtime = snapshot_runtime.as_ref().unwrap_or(&self.runtime_pool[0]);
375401
self.execute_transaction_with_runtime_internal(
376402
&signed_tx.transaction,
377-
&self.runtime_pool[0],
403+
runtime,
378404
state_arc,
379405
false,
380406
timestamp,
@@ -386,7 +412,17 @@ impl BlockchainEngine {
386412
wave.par_iter()
387413
.enumerate()
388414
.map(|(i, signed_tx)| {
389-
let runtime = &self.runtime_pool[i % self.runtime_pool.len()];
415+
let runtime_idx = i % self.runtime_pool.len();
416+
let snapshot_runtime = match &wave_snapshot_store {
417+
Some(store) => Some(
418+
self.runtime_pool[runtime_idx]
419+
.spawn_isolated_worker_from_store(store.clone())?,
420+
),
421+
None => None,
422+
};
423+
let runtime = snapshot_runtime
424+
.as_ref()
425+
.unwrap_or(&self.runtime_pool[runtime_idx]);
390426
self.execute_transaction_with_runtime_internal(
391427
&signed_tx.transaction,
392428
runtime,

crates/kanari-core/src/engine/apply_checkpoint.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,16 @@ impl BlockchainEngine {
3030
timestamp_ms: u64,
3131
persist_objects: bool,
3232
) -> Result<()> {
33-
let runtime = &self.runtime_pool[0];
33+
let snapshot_runtime = if persist_objects {
34+
None
35+
} else {
36+
let snapshot_store = {
37+
let state = state_arc.read().unwrap_or_else(|e| e.into_inner());
38+
state.snapshot_store()?
39+
};
40+
Some(self.runtime_pool[0].spawn_isolated_worker_from_store(snapshot_store)?)
41+
};
42+
let runtime = snapshot_runtime.as_ref().unwrap_or(&self.runtime_pool[0]);
3443
let mut state_write = state_arc.write().unwrap_or_else(|e| e.into_inner());
3544
let clock_id = runtime.ensure_system_clock(&mut state_write)?;
3645
let changeset = runtime.execute_clock_consensus_commit_prologue(clock_id, timestamp_ms)?;
@@ -184,7 +193,32 @@ impl BlockchainEngine {
184193
)?;
185194
}
186195

187-
self.finalize_checkpoint(checkpoint, verified_state)
196+
let checkpoint_transactions = checkpoint.transactions.clone();
197+
self.finalize_checkpoint(checkpoint, verified_state)?;
198+
self.persist_checkpoint_publish_modules(&checkpoint_transactions)
199+
}
200+
201+
fn persist_checkpoint_publish_modules(&self, transactions: &[SignedTransaction]) -> Result<()> {
202+
let mut persisted_any = false;
203+
204+
for signed_tx in transactions {
205+
let kanari_types::transaction::Transaction::PublishModule { module_bytes, .. } =
206+
&signed_tx.transaction
207+
else {
208+
continue;
209+
};
210+
211+
self.runtime_pool[0].persist_published_module_bytes(module_bytes)?;
212+
persisted_any = true;
213+
}
214+
215+
if persisted_any {
216+
for runtime in &self.runtime_pool {
217+
runtime.reload_vm_cache()?;
218+
}
219+
}
220+
221+
Ok(())
188222
}
189223

190224
pub fn apply_checkpoint(&self, checkpoint: Checkpoint) -> Result<()> {

crates/kanari-core/src/engine/dag_integration.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,19 @@ impl DagConsensusIntegration {
410410
return Ok(checkpoint);
411411
}
412412

413-
if !allow_root_override {
413+
if !allow_root_override
414+
|| BlockchainEngine::strict_checkpoint_roots_required()
415+
|| checkpoint.state_root != previous_checkpoint_root
416+
{
414417
anyhow::bail!(
415-
"{} Checkpoint {} state root mismatch: expected={}, computed={}",
418+
"{} Checkpoint {} state root mismatch: expected={}, computed={}, previous={}, allow_override={}, strict_roots={}",
416419
log_prefix,
417420
checkpoint.sequence,
418421
hex::encode(&checkpoint.state_root),
419-
hex::encode(&computed_root)
422+
hex::encode(&computed_root),
423+
hex::encode(&previous_checkpoint_root),
424+
allow_root_override,
425+
BlockchainEngine::strict_checkpoint_roots_required()
420426
);
421427
}
422428

crates/kanari-core/src/engine/mempool.rs

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -219,8 +219,12 @@ impl BlockchainEngine {
219219
}
220220
}
221221
let state_arc = Arc::new(RwLock::new(state_snapshot));
222+
let snapshot_store = {
223+
let state = state_arc.read().unwrap_or_else(|e| e.into_inner());
224+
state.snapshot_store()?
225+
};
222226
let runtime = self.runtime_pool[0]
223-
.spawn_isolated_worker()
227+
.spawn_isolated_worker_from_store(snapshot_store)
224228
.context("Failed to create isolated runtime for immediate execution")?;
225229
let changeset =
226230
self.execute_transaction_with_runtime(&tx, &runtime, &state_arc, None)?;
@@ -245,16 +249,24 @@ impl BlockchainEngine {
245249
txs.into_par_iter()
246250
.map(|tx| {
247251
let thread_idx = rayon::current_thread_index().unwrap_or(0);
248-
let runtime = &self.runtime_pool[thread_idx % self.runtime_pool.len()];
249-
250-
let result = self.execute_transaction_with_runtime_internal(
251-
&tx.transaction,
252-
runtime,
253-
state_arc,
254-
true,
255-
None,
256-
false,
257-
);
252+
let runtime_idx = thread_idx % self.runtime_pool.len();
253+
254+
let result = (|| {
255+
let snapshot_store = {
256+
let state = state_arc.read().unwrap_or_else(|e| e.into_inner());
257+
state.snapshot_store()?
258+
};
259+
let runtime = self.runtime_pool[runtime_idx]
260+
.spawn_isolated_worker_from_store(snapshot_store)?;
261+
self.execute_transaction_with_runtime_internal(
262+
&tx.transaction,
263+
&runtime,
264+
state_arc,
265+
true,
266+
None,
267+
false,
268+
)
269+
})();
258270

259271
let final_result = match result {
260272
Ok(cs) => Ok((tx.transaction_hash().to_vec(), cs)),

crates/kanari-core/src/engine/queries.rs

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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 {
417437
mod 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
}

crates/kanari-node/src/app.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use anyhow::Result;
55
use kanari_core::BlockchainEngine;
66
use kanari_core::engine::AccountInfo;
77
use kanari_crypto::wallet::list_wallet_files;
8-
use kanari_rpc_server::start_server;
8+
use kanari_rpc_server::start_server_with_transaction_broadcaster;
99
use kanari_types::address::Address as KanariAddress;
1010
use kanari_types::kanari::{KANARI_TOKEN_TYPE, KanariModule};
1111
use libp2p::identity::Keypair;
@@ -421,8 +421,27 @@ pub async fn run_node(
421421

422422
let engine_for_rpc = engine.clone();
423423
let bind_addr_clone = bind_addr.clone();
424+
let rpc_network_tx = network_tx.clone();
424425
tokio::spawn(async move {
425-
if let Err(e) = start_server(engine_for_rpc, &bind_addr_clone).await {
426+
let transaction_broadcaster = Arc::new(
427+
move |signed_tx: &kanari_types::transaction::SignedTransaction| {
428+
serialize_and_queue_message(
429+
&rpc_network_tx,
430+
signed_tx,
431+
P2PMessage::NewTransaction,
432+
"Failed to serialize transaction for broadcast",
433+
"Failed to queue transaction broadcast",
434+
);
435+
},
436+
);
437+
438+
if let Err(e) = start_server_with_transaction_broadcaster(
439+
engine_for_rpc,
440+
&bind_addr_clone,
441+
transaction_broadcaster,
442+
)
443+
.await
444+
{
426445
tracing::error!("RPC server error: {}", e);
427446
}
428447
});

0 commit comments

Comments
 (0)