|
| 1 | +#!/usr/bin/env python3 |
| 2 | +from pathlib import Path |
| 3 | +import re |
| 4 | + |
| 5 | +ROOT = Path(__file__).resolve().parents[1] |
| 6 | + |
| 7 | + |
| 8 | +def load(path: str) -> str: |
| 9 | + return (ROOT / path).read_text() |
| 10 | + |
| 11 | + |
| 12 | +def save(path: str, text: str) -> None: |
| 13 | + (ROOT / path).write_text(text) |
| 14 | + |
| 15 | + |
| 16 | +def replace_once(text: str, old: str, new: str, label: str) -> str: |
| 17 | + count = text.count(old) |
| 18 | + if count != 1: |
| 19 | + raise RuntimeError(f"{label}: expected one match, found {count}") |
| 20 | + return text.replace(old, new, 1) |
| 21 | + |
| 22 | + |
| 23 | +# Use the protocol-selected gas configuration in all feature modes. |
| 24 | +path = "crates/kanari-core/src/engine/produce_dag_vertex.rs" |
| 25 | +s = load(path) |
| 26 | +s = replace_once( |
| 27 | + s, |
| 28 | + "kanari_types::gas_v2::GasConfig::default().max_gas_per_block", |
| 29 | + "kanari_types::GasConfig::default().max_gas_per_block", |
| 30 | + "protocol-selected checkpoint gas config", |
| 31 | +) |
| 32 | +save(path, s) |
| 33 | + |
| 34 | +# Restore gas_v2 as an internal implementation detail. |
| 35 | +path = "crates/kanari-types/src/lib.rs" |
| 36 | +s = load(path) |
| 37 | +s = replace_once( |
| 38 | + s, |
| 39 | + '#[cfg(feature = "zero-gas")]\npub mod gas_v2;', |
| 40 | + '#[cfg(feature = "zero-gas")]\nmod gas_v2;', |
| 41 | + "restore gas module visibility", |
| 42 | +) |
| 43 | +save(path, s) |
| 44 | + |
| 45 | +# Core needs the bytecode parser to derive canonical module IDs for the atomic batch. |
| 46 | +path = "crates/kanari-core/Cargo.toml" |
| 47 | +s = load(path) |
| 48 | +if "move-binary-format" not in s: |
| 49 | + s = replace_once( |
| 50 | + s, |
| 51 | + "move-core-types = { workspace = true }\n", |
| 52 | + "move-core-types = { workspace = true }\nmove-binary-format = { workspace = true }\n", |
| 53 | + "add module parser dependency", |
| 54 | + ) |
| 55 | +save(path, s) |
| 56 | + |
| 57 | +# Put published module blobs and the module index in the same RocksDB WriteBatch |
| 58 | +# as state, checkpoint metadata, transaction indexes, and receipts. |
| 59 | +path = "crates/kanari-core/src/engine.rs" |
| 60 | +s = load(path) |
| 61 | +s = replace_once( |
| 62 | + s, |
| 63 | + ''' let mut updates = Vec::new(); |
| 64 | +
|
| 65 | + let mut slim_chain = chain.clone();''', |
| 66 | + ''' let mut updates = Vec::new(); |
| 67 | + let mut module_index = store |
| 68 | + .load::<Vec<String>>(b"module_index")? |
| 69 | + .unwrap_or_default(); |
| 70 | + let mut module_index_changed = false; |
| 71 | +
|
| 72 | + let mut slim_chain = chain.clone();''', |
| 73 | + "load module index for atomic checkpoint", |
| 74 | +) |
| 75 | +s = replace_once( |
| 76 | + s, |
| 77 | + ''' for tx in checkpoint.transactions.iter() { |
| 78 | + let tx_hash = tx.transaction_hash().to_vec();''', |
| 79 | + ''' for tx in checkpoint.transactions.iter() { |
| 80 | + if let Transaction::PublishModule { module_bytes, .. } = &tx.transaction { |
| 81 | + let compiled = |
| 82 | + move_binary_format::CompiledModule::deserialize_with_defaults(module_bytes) |
| 83 | + .map_err(|error| { |
| 84 | + anyhow::anyhow!( |
| 85 | + "Failed to decode published module during checkpoint commit: {:?}", |
| 86 | + error |
| 87 | + ) |
| 88 | + })?; |
| 89 | + let module_id = compiled.self_id(); |
| 90 | + let module_key = format!( |
| 91 | + "module:{}:{}", |
| 92 | + module_id.address().to_hex_literal(), |
| 93 | + module_id.name().as_str() |
| 94 | + ); |
| 95 | + updates.push(( |
| 96 | + module_key.as_bytes().to_vec(), |
| 97 | + bcs::to_bytes(module_bytes)?, |
| 98 | + )); |
| 99 | + if !module_index.iter().any(|entry| entry == &module_key) { |
| 100 | + module_index.push(module_key); |
| 101 | + module_index_changed = true; |
| 102 | + } |
| 103 | + } |
| 104 | +
|
| 105 | + let tx_hash = tx.transaction_hash().to_vec();''', |
| 106 | + "atomically persist published modules", |
| 107 | +) |
| 108 | +s = replace_once( |
| 109 | + s, |
| 110 | + ''' updates.push(( |
| 111 | + Self::recent_transaction_hashes_key().to_vec(), |
| 112 | + bcs::to_bytes(&recent_hashes)?, |
| 113 | + )); |
| 114 | + } |
| 115 | +
|
| 116 | + for receipt in receipts {''', |
| 117 | + ''' updates.push(( |
| 118 | + Self::recent_transaction_hashes_key().to_vec(), |
| 119 | + bcs::to_bytes(&recent_hashes)?, |
| 120 | + )); |
| 121 | + if module_index_changed { |
| 122 | + module_index.sort(); |
| 123 | + module_index.dedup(); |
| 124 | + updates.push((b"module_index".to_vec(), bcs::to_bytes(&module_index)?)); |
| 125 | + } |
| 126 | + } |
| 127 | +
|
| 128 | + for receipt in receipts {''', |
| 129 | + "commit module index atomically", |
| 130 | +) |
| 131 | +save(path, s) |
| 132 | + |
| 133 | +# Do not re-execute and persist modules before the atomic checkpoint batch. |
| 134 | +path = "crates/kanari-core/src/engine/apply_checkpoint.rs" |
| 135 | +s = load(path) |
| 136 | +s, count = re.subn( |
| 137 | + r'''impl BlockchainEngine \{\n fn requires_runtime_side_effect_persistence\(transactions: &\[SignedTransaction\]\) -> bool \{.*?\n \}\n\n''', |
| 138 | + "impl BlockchainEngine {\n", |
| 139 | + s, |
| 140 | + count=1, |
| 141 | + flags=re.S, |
| 142 | +) |
| 143 | +if count != 1: |
| 144 | + raise RuntimeError(f"remove side-effect predicate: expected one match, found {count}") |
| 145 | +s, count = re.subn( |
| 146 | + r''' if Self::requires_runtime_side_effect_persistence\(&to_execute\) \{.*?\n \}\n\n self\.finalize_checkpoint''', |
| 147 | + ''' // Canonical state and published module bytes are committed together by |
| 148 | + // finalize_checkpoint. Re-executing here would create pre-commit side effects. |
| 149 | + let _ = to_execute; |
| 150 | +
|
| 151 | + self.finalize_checkpoint''', |
| 152 | + s, |
| 153 | + count=1, |
| 154 | + flags=re.S, |
| 155 | +) |
| 156 | +if count != 1: |
| 157 | + raise RuntimeError(f"remove side-effect replay: expected one match, found {count}") |
| 158 | +save(path, s) |
| 159 | + |
| 160 | +# A cache reload after an atomic module commit must rebuild the in-memory module set |
| 161 | +# from the canonical store rather than retaining a stale pre-commit set. |
| 162 | +path = "move-execution/v1/kanari-move-runtime-v1/src/move_runtime/mod.rs" |
| 163 | +s = load(path) |
| 164 | +s = replace_once( |
| 165 | + s, |
| 166 | + ''' pub fn reload_vm_cache(&self) -> Result<()> { |
| 167 | + let new_vm = MoveVM::new(self.all_natives.as_ref().clone()) |
| 168 | + .map_err(|e| anyhow::anyhow!("Failed to reload MoveVM: {:?}", e))?; |
| 169 | +
|
| 170 | + // Replace the VM instance to clear internal caches. |
| 171 | + *self.write_vm() = new_vm; |
| 172 | +
|
| 173 | + // Preload published modules so follow-up executions can resolve dependencies immediately. |
| 174 | + self.preload_system_modules_into_vm()?;''', |
| 175 | + ''' pub fn reload_vm_cache(&self) -> Result<()> { |
| 176 | + let new_vm = MoveVM::new(self.all_natives.as_ref().clone()) |
| 177 | + .map_err(|e| anyhow::anyhow!("Failed to reload MoveVM: {:?}", e))?; |
| 178 | +
|
| 179 | + let refreshed_modules = self |
| 180 | + .state |
| 181 | + .get_all_module_ids()? |
| 182 | + .into_iter() |
| 183 | + .collect::<HashSet<_>>(); |
| 184 | + *self |
| 185 | + .published_modules |
| 186 | + .write() |
| 187 | + .unwrap_or_else(|poisoned| poisoned.into_inner()) = refreshed_modules; |
| 188 | +
|
| 189 | + // Replace the VM instance to clear internal caches. |
| 190 | + *self.write_vm() = new_vm; |
| 191 | +
|
| 192 | + // Preload published modules so follow-up executions can resolve dependencies immediately. |
| 193 | + self.preload_system_modules_into_vm()?;''', |
| 194 | + "refresh module index during cache reload", |
| 195 | +) |
| 196 | +save(path, s) |
| 197 | + |
| 198 | +print("Applied follow-up atomic module and feature-mode fixes.") |
0 commit comments