Skip to main content

jam_bootstrap_service/
lib.rs

1//! JAM Bootstrap Service
2//!
3//! Use by concatenating one or more encoded `Instruction`s into a work item's payload.
4
5#![cfg_attr(any(target_arch = "riscv32", target_arch = "riscv64"), no_std)]
6#![allow(clippy::unwrap_used)]
7
8extern crate alloc;
9
10use alloc::{format, string::ToString, vec, vec::Vec};
11use jam_bootstrap_service_common::{Instruction, ServiceRegistry, SERVICE_REGISTRY_KEY};
12use jam_pvm_common::*;
13use jam_types::*;
14
15const BENCHMARK_CYCLES: u32 = 1_000;
16
17pub struct Service;
18jam_pvm_common::declare_service!(Service);
19
20impl jam_pvm_common::Service for Service {
21	fn refine(
22		_core_index: CoreIndex,
23		_item_index: usize,
24		service_id: ServiceId,
25		payload: WorkPayload,
26		_package_hash: WorkPackageHash,
27	) -> WorkOutput {
28		use refine::*;
29		info!(target = "boot", "Bootstrap Service Refine, {service_id:x}h");
30
31		let mut cursor = &payload[..];
32		let mut out = vec![];
33		while !cursor.is_empty() {
34			match Instruction::decode(&mut cursor).unwrap() {
35				Instruction::Lookup { service, hash, eager } if eager => {
36					let maybe_data = foreign_lookup(service, &hash);
37					info!(target = "boot", "Eager lookup, got {:?}", maybe_data);
38					out.push(Instruction::LookedUp { data: maybe_data });
39				},
40				Instruction::RandomStorageRefine(input) =>
41					out.push(Instruction::RandomStorageAccumulate(
42						jam_bootstrap_service_common::test_key_vals::generate_payload(input),
43					)),
44				Instruction::Export { data } => {
45					for d in &data {
46						let r = export_slice(d);
47						info!(target = "boot", "Exported slice: {d:?} -> {r:?}");
48					}
49					out.push(Instruction::Exported { count: data.len() as _ })
50				},
51				Instruction::Import { items } => {
52					let data = items
53						.into_iter()
54						.map(|(index, len)| {
55							import(index as usize).unwrap().truncate_into_vec(len as usize)
56						})
57						.collect();
58					out.push(Instruction::Imported { data });
59				},
60				x => out.push(x),
61			};
62		}
63		debug!(target = "boot", "Returning {:?} into accumulate", out);
64		out.encode().into()
65	}
66
67	fn accumulate(now: Slot, id: ServiceId, _item_count: usize) -> Option<Hash> {
68		use accumulate::*;
69		info!(
70			target = "boot",
71			"Bootstrap Service Accumulate, {id:x}h @{now} ${}",
72			my_info().balance
73		);
74		for item in accumulate::accumulate_items() {
75			match item {
76				AccumulateItem::WorkItem(r) => on_work_item(r, now),
77				AccumulateItem::Transfer(t) => on_transfer(t),
78			}
79		}
80		None
81	}
82}
83
84fn on_work_item(item: WorkItemRecord, now: Slot) {
85	use accumulate::*;
86
87	let Ok(raw_instructions) = item.result else { return };
88
89	for inst in Vec::<Instruction>::decode(&mut &raw_instructions[..]).unwrap() {
90		debug!(target = "boot", "Decoded instruction: {:?}", inst);
91		match inst {
92			Instruction::CreateService {
93				code_hash,
94				code_len,
95				min_item_gas,
96				min_memo_gas,
97				endowment,
98				memo,
99				registration,
100			} => {
101				let id = create_service(&code_hash, code_len as usize, min_item_gas, min_memo_gas);
102				if let Ok(id) = id {
103					info!(
104						target = "boot",
105						"Created service {id:x}h with code_hash {}",
106						AnyHash(*code_hash)
107					);
108					set(b"created", id).expect("balance?");
109
110					if let Some(code) = lookup(&code_hash) {
111						// Can provide the code right away.
112						let e = provide(id, &code);
113						info!(target = "boot", "Code provision resulted in {e:?}");
114					}
115
116					info!(target = "boot", "Attempting transfer, gas={}", gas());
117					let e = transfer(id, endowment, min_memo_gas, &memo);
118					info!(
119						target = "boot",
120						"Transfer of {endowment} with {min_memo_gas} gas resulted in {e:?}"
121					);
122					if let Some(registration) = registration {
123						let mut registry: ServiceRegistry =
124							get(SERVICE_REGISTRY_KEY).unwrap_or_default();
125						registry.update(registration, id, code_hash);
126						set(SERVICE_REGISTRY_KEY, &registry).expect("balance?");
127					}
128				} else {
129					error!("Failed to create!");
130				}
131			},
132			Instruction::Upgrade { code_hash, min_item_gas, min_memo_gas } => {
133				upgrade(&code_hash, min_item_gas, min_memo_gas);
134				info!(target = "boot", "Upgraded!");
135			},
136			Instruction::Transfer { destination, amount, gas_limit, memo } => {
137				info!(target = "boot", "Gas remaining: {}", gas());
138				info!("Attempting transfer: {} {} {} {:?}", destination, amount, gas_limit, memo);
139				let e = transfer(destination, amount, gas_limit, &memo);
140				info!(target = "boot", "Result: {:?}", e);
141				(destination, amount, memo)
142					.using_encoded(|d| set_storage(b"transferred", d).expect("balance?"));
143			},
144			Instruction::Zombify { ejector } => {
145				info!(target = "boot", "Zombifying service. Ejector: #{:x}", ejector);
146				let info = my_info();
147				if info.bytes >= 81 && info.items == 2 {
148					if forget(&info.code_hash, info.bytes as usize - 81).is_ok() {
149						zombify(ejector);
150						info!(target = "boot", "Zombified");
151					} else {
152						error!("Failed to zombify - invalid code_hash?");
153					}
154				} else {
155					error!("Failed to zombify - laggards in storage/lookup?");
156				}
157			},
158			Instruction::Eject { target, code_hash } => {
159				info!(
160					target = "boot",
161					"Ejecting service #{:x} with code_hash {:?}", target, code_hash
162				);
163				let e = eject(target, &code_hash);
164				info!(target = "boot", "Result: {:?}", e);
165			},
166			Instruction::DeleteItems { storage_items } => {
167				let mut fail = 0;
168				for i in &storage_items {
169					info!(target = "boot", "Deleting item: {:?}", i);
170					if remove_storage(i).is_none() {
171						error!("Failed to remove item: {:?}", i);
172						fail += 1;
173					}
174				}
175				info!(
176					"{} items deleted successfully, {} keys not found",
177					storage_items.len() - fail,
178					fail
179				);
180			},
181			Instruction::Imported { data } => {
182				info!(target = "boot", "Imported data {:?}", data.iter().cloned().map(AnyVec));
183				set_storage(b"imported", &(data.len() as u32).encode()).expect("balance?");
184				for (i, d) in data.into_iter().enumerate() {
185					set_storage(alloc::format!("import-{i}").as_bytes(), &d[..]).expect("balance?");
186				}
187			},
188			Instruction::Exported { count } => {
189				info!(target = "boot", "Exported {count} items");
190				set_storage(b"exported", &count.encode()).expect("balance?");
191			},
192			Instruction::Solicit { hash, len } => {
193				solicit(&hash, len as usize).unwrap();
194				info!(target = "boot", "Solicited {hash} of length {len}");
195				set_storage(b"requested", &hash[..]).expect("balance?");
196			},
197			Instruction::Forget { hash, len } => {
198				let q = query(&hash, len as usize).unwrap();
199				info!(
200					target = "boot",
201					"Query result: {:?} (fi: {:?})",
202					q,
203					q.forget_implication(now)
204				);
205				forget(&hash, len as usize).unwrap();
206				set_storage(b"unrequested", &hash[..]).expect("balance?");
207			},
208			Instruction::Lookup { service, hash, .. } => {
209				let maybe_data = foreign_lookup(service, &hash);
210				info!(target = "boot", "Lookup, got {:?}", maybe_data);
211				if let Some(data) = maybe_data {
212					set_storage(b"looked_up", &data[..]).expect("balance?");
213				} else {
214					remove_storage(b"looked_up");
215				}
216			},
217			Instruction::LookedUp { data } =>
218				if let Some(data) = data {
219					set_storage(b"looked_up", &data[..]).expect("balance?");
220				} else {
221					remove_storage(b"looked_up");
222				},
223			Instruction::Assign { core, queue, assigner } => {
224				info!(target = "boot", "Assigning core {:?} to queue {:?}", core, queue);
225				match assign(core, &queue, assigner) {
226					Ok(_) => info!(target = "boot", "Assigned!"),
227					Err(_) => error!("Failed to assign!"),
228				}
229			},
230			Instruction::Bless { manager, assign, designate, register, auto_acc } => {
231				bless(manager, assign, designate, register, &auto_acc);
232				info!("Blessed services m: #{manager}, a: #{assign}, v: #{designate}, r: #{register}, aa: {auto_acc:?}");
233			},
234			Instruction::Designate { keys } => match designate(&keys) {
235				Ok(_) => info!(target = "boot", "Designated keys {:?}", keys),
236				Err(_) => error!("Failed to designate"),
237			},
238			Instruction::Yield { hash } => {
239				yield_hash(&hash);
240				info!(target = "boot", "Yielded hash {:?}", hash);
241			},
242			Instruction::Provide { service_id, preimage } => {
243				let r = provide(service_id, &preimage);
244				info!(target = "boot", "Provided preimage to {service_id}: {:?}", r);
245			},
246			Instruction::Checkpoint => {
247				checkpoint();
248				info!(target = "boot", "Checkpointed!");
249			},
250			Instruction::Panic => {
251				panic!("Panic instruction executed!");
252			},
253			Instruction::RandomStorageAccumulate(result_refine) => {
254				if let Ok(keys) = result_refine {
255					let mut count: u64 = get_storage(b"count_random_storage")
256						.map(|v| u64::decode(&mut v.as_slice()).unwrap())
257						.unwrap_or(0);
258					// Warning this instruction is for testing. Most of the work is done in
259					// accumulate which is bad design.
260					for item in keys.items.into_iter() {
261						set_storage(&item.key[..], &item.key[..]).expect("balance?");
262						count += 1;
263					}
264					set_storage(b"count_random_storage", &count.encode()[..]).expect("balance?");
265				}
266			},
267			Instruction::Benchmark =>
268				for i in 0..BENCHMARK_CYCLES {
269					set_storage(format!("item-{now}-{i}").as_bytes(), format!("{i}").as_bytes())
270						.expect("balance low");
271					checkpoint();
272				},
273			i => {
274				info!(target = "boot", "Instruction not handled: {:?}", i);
275			},
276		}
277	}
278}
279
280fn on_transfer(item: TransferRecord) {
281	use accumulate::*;
282	let TransferRecord { source, amount, memo, .. } = item;
283	let count = get::<u32>(b"transfer-count").unwrap_or(0);
284	set(b"transfer-count", count + 1).expect("balance?");
285	info!(
286		target = "boot",
287		"Received transfer from {source} of {amount} with memo {}",
288		alloc::string::String::from_utf8(memo.as_ref().to_vec()).unwrap_or("???".to_string())
289	);
290	set_storage(alloc::format!("transfer{count}").as_bytes(), &(source, amount, memo).encode()[..])
291		.expect("balance?");
292}
293
294pub const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");