Skip to content

Commit f0e3333

Browse files
committed
chore(security): stage follow-up atomic module fixes
1 parent becfe9f commit f0e3333

1 file changed

Lines changed: 198 additions & 0 deletions

File tree

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
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

Comments
 (0)