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::{
13	accumulate::*,
14	refine::{export_slice, import},
15	*,
16};
17use jam_types::*;
18
19const BENCHMARK_CYCLES: u32 = 1_000;
20
21#[allow(dead_code)]
22struct Service;
23jam_pvm_common::declare_service!(Service);
24
25impl jam_pvm_common::Service for Service {
26	fn refine(
27		id: ServiceId,
28		payload: WorkPayload,
29		_package_hash: WorkPackageHash,
30		_context: RefineContext,
31		_auth_code_hash: CodeHash,
32	) -> WorkOutput {
33		info!(target = "boot", "Bootstrap Service Refine, {id:x}h");
34
35		//let test_hash = [1u8; 32];
36		//let mut target = vec![0; 100];
37		//let preimage = lookup(test_hash);
38		//assert_eq!(&preimage.unwrap(), &[1, 2, 3]);
39		let mut cursor = &payload[..];
40		let mut out = vec![];
41		while !cursor.is_empty() {
42			match Instruction::decode(&mut cursor).unwrap() {
43				Instruction::Lookup { service, hash } => {
44					info!(target = "boot", "Looking up...");
45					let maybe_data = foreign_lookup(service, &hash);
46					info!(target = "boot", "Got {:?}", maybe_data);
47					if let Some(data) = maybe_data {
48						out.push(Instruction::LookedUp { data });
49					}
50				},
51				Instruction::RandomStorageRefine(input) =>
52					out.push(Instruction::RandomStorageAccumulate(
53						jam_bootstrap_service_common::test_key_vals::generate_payload(input),
54					)),
55				Instruction::Export { data } => {
56					for d in &data {
57						let r = export_slice(d);
58						info!(target = "boot", "Exported slice: {d:?} -> {r:?}");
59					}
60					out.push(Instruction::Exported { count: data.len() as _ })
61				},
62				Instruction::Import { items } => {
63					let data = items
64						.into_iter()
65						.map(|(index, len)| {
66							import(index as usize).unwrap().truncate_into_vec(len as usize)
67						})
68						.collect();
69					out.push(Instruction::Imported { data });
70				},
71				x => out.push(x),
72			};
73		}
74		debug!(target = "boot", "Returning {:?} into accumulate", out);
75		out.encode().into()
76	}
77
78	fn accumulate(now: Slot, id: ServiceId, results: Vec<AccumulateItem>) -> Option<Hash> {
79		info!(
80			target = "boot",
81			"Bootstrap Service Accumulate, {id:x}h @{now} ${}",
82			my_info().balance
83		);
84		for raw_instructions in results.into_iter().filter_map(|x| x.result.ok()) {
85			for inst in Vec::<Instruction>::decode(&mut &raw_instructions[..]).unwrap() {
86				debug!(target = "boot", "Decoded instruction: {:?}", inst);
87				match inst {
88					Instruction::CreateService {
89						code_hash,
90						code_len,
91						min_item_gas,
92						min_memo_gas,
93						endowment,
94						memo,
95						registration,
96					} => {
97						let id = create_service(
98							&code_hash,
99							code_len as usize,
100							min_item_gas,
101							min_memo_gas,
102						);
103						if let Ok(id) = id {
104							info!(target = "boot", "Created service {id:x}h");
105							set(b"created", id).expect("balance?");
106
107							if let Some(code) = lookup(&code_hash) {
108								// Can provide the code right away.
109								let e = provide(id, &code);
110								info!(target = "boot", "Code provision resulted in {e:?}");
111							}
112
113							info!(target = "boot", "Attemting transfer, gas={}", gas());
114							let e = transfer(id, endowment, min_memo_gas, &memo);
115							info!(
116								target = "boot",
117								"Transfer of {endowment} with {min_memo_gas} gas resulted in {e:?}"
118							);
119							if let Some(registration) = registration {
120								let mut registry: ServiceRegistry =
121									get(SERVICE_REGISTRY_KEY).unwrap_or_default();
122								registry.update(registration, id, code_hash);
123								set(SERVICE_REGISTRY_KEY, &registry).expect("balance?");
124							}
125						} else {
126							error!("Failed to create!");
127						}
128					},
129					Instruction::Upgrade { code_hash, min_item_gas, min_memo_gas } => {
130						upgrade(&code_hash, min_item_gas, min_memo_gas);
131						info!(target = "boot", "Upgraded!");
132					},
133					Instruction::Transfer { destination, amount, gas_limit, memo } => {
134						info!(target = "boot", "Gas remaining: {}", gas());
135						info!(
136							"Attempting transfer: {} {} {} {:?}",
137							destination, amount, gas_limit, memo
138						);
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::LookedUp { data } => {
182						set_storage(b"looked_up", &data[..]).expect("balance?");
183					},
184					Instruction::Imported { data } => {
185						info!(
186							target = "boot",
187							"Imported data {:?}",
188							data.iter().cloned().map(AnyVec)
189						);
190						set_storage(b"imported", &(data.len() as u32).encode()).expect("balance?");
191						for (i, d) in data.into_iter().enumerate() {
192							set_storage(alloc::format!("import-{i}").as_bytes(), &d[..])
193								.expect("balance?");
194						}
195					},
196					Instruction::Exported { count } => {
197						info!(target = "boot", "Exported {count} items");
198						set_storage(b"exported", &count.encode()).expect("balance?");
199					},
200					Instruction::Solicit { hash, len } => {
201						solicit(&hash, len as usize).unwrap();
202						info!(target = "boot", "Solicited {hash} of length {len}");
203						set_storage(b"requested", &hash[..]).expect("balance?");
204					},
205					Instruction::Forget { hash, len } => {
206						let q = query(&hash, len as usize).unwrap();
207						info!(
208							target = "boot",
209							"Query result: {:?} (fi: {:?})",
210							q,
211							q.forget_implication(now)
212						);
213						forget(&hash, len as usize).unwrap();
214						set_storage(b"unrequested", &hash[..]).expect("balance?");
215					},
216					Instruction::Assign { core, queue } => {
217						info!(target = "boot", "Assigning core {:?} to queue {:?}", core, queue);
218						match assign(core, &queue) {
219							Ok(_) => info!(target = "boot", "Assigned!"),
220							Err(_) => error!("Failed to assign!"),
221						}
222					},
223					Instruction::Bless { manager: b, assign: a, designate: d, auto_acc } => {
224						bless(b, a, d, &auto_acc);
225						info!(
226							"Blessed services m: #{}, a: #{}. v: #{}. aa: {:?}",
227							b, a, d, auto_acc
228						);
229					},
230					Instruction::Designate { keys } => {
231						designate(&keys);
232						info!(target = "boot", "Designated keys {:?}", keys);
233					},
234					Instruction::Yield { hash } => {
235						yield_hash(&hash);
236						info!(target = "boot", "Yielded hash {:?}", hash);
237					},
238					Instruction::Provide { service_id, preimage } => {
239						let r = provide(service_id, &preimage);
240						info!(target = "boot", "Provided preimage to {service_id}: {:?}", r);
241					},
242					Instruction::Checkpoint => {
243						checkpoint();
244						info!(target = "boot", "Checkpointed!");
245					},
246					Instruction::Panic => {
247						panic!("Panic instruction executed!");
248					},
249					Instruction::RandomStorageAccumulate(result_refine) => {
250						if let Ok(keys) = result_refine {
251							let mut count: u64 = get_storage(b"count_random_storage")
252								.map(|v| u64::decode(&mut v.as_slice()).unwrap())
253								.unwrap_or(0);
254							// Warning this instruction is for testing. Most of the work is done in
255							// accumulate which is bad design.
256							for item in keys.items.into_iter() {
257								set_storage(&item.key[..], &item.key[..]).expect("balance?");
258								count += 1;
259							}
260							set_storage(b"count_random_storage", &count.encode()[..])
261								.expect("balance?");
262						}
263					},
264					Instruction::Benchmark =>
265						for i in 0..BENCHMARK_CYCLES {
266							set_storage(
267								format!("item-{now}-{i}").as_bytes(),
268								format!("{i}").as_bytes(),
269							)
270							.expect("balance low");
271							checkpoint();
272						},
273					i => {
274						info!(target = "boot", "Instruction not handled: {:?}", i);
275					},
276				}
277			}
278		}
279		None
280	}
281
282	fn on_transfer(_slot: Slot, _id: ServiceId, items: Vec<TransferRecord>) {
283		for TransferRecord { source, amount, memo, .. } in items.into_iter() {
284			let count = get::<u32>(b"transfer-count").unwrap_or(0);
285			set(b"transfer-count", count + 1).expect("balance?");
286			info!(
287				target = "boot",
288				"Received transfer from {source} of {amount} with memo {}",
289				alloc::string::String::from_utf8(memo.as_ref().to_vec())
290					.unwrap_or("???".to_string())
291			);
292			set_storage(
293				alloc::format!("transfer{count}").as_bytes(),
294				&(source, amount, memo).encode()[..],
295			)
296			.expect("balance?");
297		}
298	}
299}