1use crate::hash_encoded;
2use alloc::{borrow::Cow, vec::Vec};
3use codec::{Decode, DecodeAll, Encode};
4use corevm_host::{
5 fs, CoreVmInstruction, CoreVmOutput, PageInfo, PageNum, Range, RangeSet, StorageKey, VmSpec,
6 VmState,
7};
8use jam_types::{
9 AccumulateItem, Balance, Hash, Memo, SegmentTreeRoot, ServiceId, SignedGas, Slot, UnsignedGas,
10 WorkError, WorkOutput,
11};
12use log::debug;
13
14mod messages;
15
16const ALL_STORAGE_KEYS_WITHOUT_PARAMS: &[StorageKey] = {
17 use StorageKey::*;
18 &[
19 Gas,
20 StateHash,
21 VmSpec,
22 VideoMode,
23 AudioMode,
24 ExecEnvRef,
25 Owner,
26 StoredPages,
27 IncomingServiceMessages,
28 OutgoingServiceMessages,
29 ]
30};
31
32const _: () = assert!(ALL_STORAGE_KEYS_WITHOUT_PARAMS.len() == StorageKey::COUNT_WITHOUT_PARAMS);
33
34const REMOVE_ON_RESET_KEYS: &[StorageKey] = {
36 use StorageKey::*;
37 &[VmSpec, VideoMode, AudioMode, StoredPages, IncomingServiceMessages, OutgoingServiceMessages]
38};
39
40const DO_NOT_REMOVE_ON_RESET_KEYS: &[StorageKey] = {
42 use StorageKey::*;
43 &[Gas, ExecEnvRef, StateHash, Owner]
44};
45
46const _: () = assert!(
47 REMOVE_ON_RESET_KEYS.len() + DO_NOT_REMOVE_ON_RESET_KEYS.len() ==
48 StorageKey::COUNT_WITHOUT_PARAMS
49);
50
51pub trait AccumulateOps {
55 type Error: core::fmt::Debug;
57
58 fn get(&self, id: ServiceId, key: &StorageKey) -> Option<Cow<'_, [u8]>>;
60
61 fn set(&mut self, key: StorageKey, value: Cow<'_, [u8]>) -> Result<(), Self::Error>;
63
64 fn remove(&mut self, key: &StorageKey) -> bool;
68
69 fn set_typed(&mut self, key: StorageKey, value: &impl Encode) -> Result<(), Self::Error> {
71 value.using_encoded(|bytes| self.set(key, bytes.into()))
72 }
73
74 fn get_typed<T: Decode>(
76 &self,
77 id: ServiceId,
78 key: &StorageKey,
79 ) -> Result<Option<T>, codec::Error> {
80 self.get(id, key).map(|bytes| T::decode_all(&mut bytes.as_ref())).transpose()
81 }
82
83 fn min_memo_gas(&self, service_id: ServiceId) -> Option<UnsignedGas>;
85
86 fn transfer(
88 &self,
89 destination: ServiceId,
90 amount: Balance,
91 gas_limit: UnsignedGas,
92 memo: &Memo,
93 ) -> Result<(), Self::Error>;
94
95 fn transfer_typed(
99 &self,
100 destination: ServiceId,
101 amount: Balance,
102 gas_limit: UnsignedGas,
103 memo: &impl Encode,
104 ) -> Result<(), Self::Error> {
105 let mut raw_memo = Memo::zero();
106 memo.encode_to(&mut SliceOutput(&mut raw_memo[..]));
107 self.transfer(destination, amount, gas_limit, &raw_memo)
108 }
109
110 fn bless<'a>(
112 &mut self,
113 manager: ServiceId,
114 assigner: ServiceId,
115 designator: ServiceId,
116 registrar: ServiceId,
117 always_acc: impl IntoIterator<Item = &'a (ServiceId, UnsignedGas)>,
118 );
119
120 fn zombify(&mut self, ejector: ServiceId);
122}
123
124impl<A: AccumulateOps + ?Sized> AccumulateOps for &mut A {
125 type Error = A::Error;
126
127 fn get(&self, service_id: ServiceId, key: &StorageKey) -> Option<Cow<'_, [u8]>> {
128 AccumulateOps::get(*self, service_id, key)
129 }
130
131 fn set(&mut self, key: StorageKey, value: Cow<'_, [u8]>) -> Result<(), Self::Error> {
132 AccumulateOps::set(*self, key, value)
133 }
134
135 fn remove(&mut self, key: &StorageKey) -> bool {
136 AccumulateOps::remove(*self, key)
137 }
138
139 fn min_memo_gas(&self, service_id: ServiceId) -> Option<UnsignedGas> {
140 AccumulateOps::min_memo_gas(*self, service_id)
141 }
142
143 fn transfer(
144 &self,
145 destination: ServiceId,
146 amount: Balance,
147 gas_limit: UnsignedGas,
148 memo: &Memo,
149 ) -> Result<(), Self::Error> {
150 AccumulateOps::transfer(*self, destination, amount, gas_limit, memo)
151 }
152
153 fn bless<'a>(
154 &mut self,
155 manager: ServiceId,
156 assigner: ServiceId,
157 designator: ServiceId,
158 registrar: ServiceId,
159 always_acc: impl IntoIterator<Item = &'a (ServiceId, UnsignedGas)>,
160 ) {
161 AccumulateOps::bless(*self, manager, assigner, designator, registrar, always_acc);
162 }
163
164 fn zombify(&mut self, ejector: ServiceId) {
165 AccumulateOps::zombify(*self, ejector);
166 }
167}
168
169pub struct AccumulateEngine<A: AccumulateOps> {
171 ops: A,
172 slot: Slot,
174 service_id: ServiceId,
176}
177
178impl<A: AccumulateOps> AccumulateEngine<A> {
179 pub fn new(ops: A, slot: Slot, service_id: ServiceId) -> Self {
181 Self { ops, slot, service_id }
182 }
183
184 pub fn run(
188 &mut self,
189 items: &[AccumulateItem],
190 ) -> Vec<Result<(), AccumulationError<A::Error>>> {
191 items
192 .iter()
193 .map(|item| match item {
194 AccumulateItem::WorkItem(item) =>
195 self.accumulate(item.result.as_ref(), item.exports_root),
196 AccumulateItem::Transfer(item) => self.transfer(item.source, item.memo),
197 })
198 .collect()
199 }
200
201 pub fn into_inner(self) -> A {
203 self.ops
204 }
205
206 fn accumulate(
207 &mut self,
208 result: Result<&WorkOutput, &WorkError>,
209 exports_root: SegmentTreeRoot,
210 ) -> Result<(), AccumulationError<A::Error>> {
211 use AccumulationError::*;
212 let output = result.map_err(|e| Work(e.clone()))?;
213 let output = CoreVmOutput::decode_all(&mut &output[..])?;
214
215 if self.get_typed::<fs::BlockRef>(&StorageKey::ExecEnvRef)? != Some(output.exec_ref) {
216 return Err(ExecEnvRefMismatch);
217 }
218
219 if self.get_typed::<Hash>(&StorageKey::StateHash)? != Some(output.old_hash) {
221 return Err(PriorStateMismatch);
222 }
223 self.set_typed(StorageKey::StateHash, &output.new_hash)?;
224 let mut stored_pages =
225 self.get_typed::<RangeSet>(&StorageKey::StoredPages)?.unwrap_or_default();
226 for (page, our_hash) in output.touched_imported_pages.iter() {
228 let key = StorageKey::PageInfo(*page);
229 let their_hash = self.get_typed::<PageInfo>(&key)?.ok_or(NoSuchPage(*page))?.hash;
230 if &their_hash != our_hash {
231 return Err(PageHashMismatch);
232 }
233 }
234 let mut export_index = 0;
236 for (page, hash) in output.updated_pages.iter() {
237 let key = StorageKey::PageInfo(*page);
238 if hash == &[0; 32] {
239 self.get_typed::<PageInfo>(&key)?.ok_or(NoSuchPage(*page))?;
241 stored_pages.remove(&Range::new(page.0, page.0 + 1));
242 self.ops.remove(&key);
243 } else {
244 stored_pages.insert(Range::new(page.0, page.0 + 1));
245 let info = PageInfo { hash: *hash, exports_root, export_index };
246 self.set_typed(key, &info)?;
247 export_index += 1;
248 }
249 }
250 self.set_typed(StorageKey::StoredPages, &stored_pages)?;
251 if let Some(ref mode) = output.vm_state.video {
253 self.set_typed(StorageKey::VideoMode, mode)?;
254 }
255 if let Some(ref mode) = output.vm_state.audio {
257 self.set_typed(StorageKey::AudioMode, mode)?;
258 }
259 self.remove_processed_incoming_service_messages(&output.processed_service_messages)?;
260 self.send_new_outgoing_service_messages_and_remove_old(output.outgoing_messages)?;
261 let spec = VmSpec { exports_root, output: output.vm_output, state: output.vm_state };
263 self.set_typed(StorageKey::VmSpec, &spec)?;
264
265 Ok(())
266 }
267
268 fn transfer(
269 &mut self,
270 source: ServiceId,
271 memo: Memo,
272 ) -> Result<(), AccumulationError<A::Error>> {
273 use AccumulationError::*;
274 let instr = CoreVmInstruction::decode(&mut &memo[..])?;
275 let owner = self.get_typed::<ServiceId>(&StorageKey::Owner)?;
276 if let Some(owner) = owner {
277 if owner != source {
278 return Err(NotTheOwner(owner, source));
279 }
280 }
281 match instr {
282 CoreVmInstruction::Reset { gas, exec_ref } => {
283 if owner.is_none() {
284 debug!("Setting owner to {source:x}");
285 self.set_typed(StorageKey::Owner, &source)?;
286 }
287 self.reset(gas, exec_ref)?;
288 },
289 CoreVmInstruction::SetOwner(owner) => {
290 debug!("Setting owner to {owner:x}");
291 self.set_typed(StorageKey::Owner, &owner)?;
292 },
293 CoreVmInstruction::PushServiceMessage(message) => {
294 self.push_incoming_service_message(message)?;
295 },
296 CoreVmInstruction::Destroy { ejector } => {
297 self.destroy()?;
298 self.ops.bless(ejector, ejector, ejector, ejector, []);
299 self.ops.zombify(ejector);
300 },
301 }
302 Ok(())
303 }
304
305 fn reset(
309 &mut self,
310 gas: SignedGas,
311 exec_ref: fs::BlockRef,
312 ) -> Result<(), AccumulationError<A::Error>> {
313 debug!("Resetting VM: gas = {gas}, exec = {exec_ref}");
314 let state_hash = hash_encoded(VmState::initial());
315 self.set_typed(StorageKey::Gas, &gas)?;
316 self.set_typed(StorageKey::ExecEnvRef, &exec_ref)?;
317 self.set_typed(StorageKey::StateHash, &state_hash)?;
318 self.remove_memory_pages()?;
319 self.remove_all_incoming_service_messages()?;
320 self.remove_all_outgoing_service_messages()?;
321 for key in REMOVE_ON_RESET_KEYS {
324 self.ops.remove(key);
325 }
326 Ok(())
327 }
328
329 fn destroy(&mut self) -> Result<(), AccumulationError<A::Error>> {
333 self.remove_memory_pages()?;
334 self.remove_all_incoming_service_messages()?;
335 self.remove_all_outgoing_service_messages()?;
336 for key in ALL_STORAGE_KEYS_WITHOUT_PARAMS.iter() {
337 self.ops.remove(key);
338 }
339 Ok(())
340 }
341
342 fn remove_memory_pages(&mut self) -> Result<(), AccumulationError<A::Error>> {
343 let stored_pages =
344 self.get_typed::<RangeSet>(&StorageKey::StoredPages)?.unwrap_or_default();
345 for range in stored_pages.as_slice().iter() {
346 for page in range.start..range.end {
347 self.ops.remove(&StorageKey::PageInfo(PageNum(page)));
348 }
349 }
350 Ok(())
351 }
352
353 fn set_typed(
354 &mut self,
355 key: StorageKey,
356 value: &impl Encode,
357 ) -> Result<(), AccumulationError<A::Error>> {
358 self.ops.set_typed(key, value).map_err(AccumulationError::Api)
359 }
360
361 fn get_typed<T: Decode>(&self, key: &StorageKey) -> Result<Option<T>, codec::Error> {
362 self.ops.get_typed::<T>(self.service_id, key)
363 }
364}
365
366#[derive(Debug, thiserror::Error)]
368pub enum AccumulationError<E: core::fmt::Debug> {
369 #[error("Accumulation API error: {0:?}")]
370 Api(E),
371 #[error("Work error: {0:?}")]
372 Work(WorkError),
373 #[error("JAM codec error: {0:?}")]
374 Codec(codec::Error),
375 #[error("Prior VM state mismatch")]
376 PriorStateMismatch,
377 #[error("Page {0} not found in storage")]
378 NoSuchPage(PageNum),
379 #[error("Page hash mismatch")]
380 PageHashMismatch,
381 #[error("ExecEnvRef mismatch")]
382 ExecEnvRefMismatch,
383 #[error("Processed message doesn't match the message in the queue")]
384 MessageMismatch,
385 #[error("Invalid message")]
386 InvalidMessage,
387 #[error("Unknown service {0:x}")]
388 UnknownService(ServiceId),
389 #[error("Transfer source is not the owner of the service: actual owner {0:x}, transfer source {1:x}")]
390 NotTheOwner(ServiceId, ServiceId),
391}
392
393impl<E: core::fmt::Debug> From<codec::Error> for AccumulationError<E> {
394 fn from(e: codec::Error) -> Self {
395 Self::Codec(e)
396 }
397}
398
399struct SliceOutput<'a>(&'a mut [u8]);
400
401impl codec::Output for SliceOutput<'_> {
402 fn write(&mut self, src: &[u8]) {
403 let (dst, rest) = core::mem::take(&mut self.0).split_at_mut(src.len());
404 dst.copy_from_slice(src);
405 self.0 = rest;
406 }
407}