1use borderless::{
2 common::{Description, Metadata, Revocation},
3 contracts::Info,
4 BorderlessId, ContractId,
5 __private::storage_keys::*,
6 events::Sink,
7 hash::Hash256,
8 http::{AgentInfo, ContractInfo},
9 pkg::{Source, SourceFlattened, WasmPkg, WasmPkgNoSource},
10 prelude::Id,
11 AgentId, TxIdentifier,
12};
13use borderless_kv_store::*;
14use serde::de::DeserializeOwned;
15
16use super::{
17 action_log::{ActionLog, ActionRecord, RelTxAction},
18 logger::Logger,
19};
20use crate::{Result, ACTION_TX_REL_SUB_DB, AGENT_SUB_DB, CONTRACT_SUB_DB};
21
22pub struct Controller<'a, S: Db> {
24 db: &'a S,
25}
26
27impl<'a, S: Db> Controller<'a, S> {
28 pub fn new(db: &'a S) -> Self {
29 Self { db }
30 }
31
32 pub fn actions(&self, cid: ContractId) -> ActionLog<'a, S> {
34 ActionLog::new(self.db, cid)
35 }
36
37 pub fn logs(&self, id: impl Into<Id>) -> Logger<'a, S> {
39 Logger::new(self.db, id)
40 }
41
42 pub fn contract_participants(&self, cid: &ContractId) -> Result<Option<Vec<BorderlessId>>> {
44 self.read_value(
45 &Id::contract(*cid),
46 BASE_KEY_METADATA,
47 META_SUB_KEY_PARTICIPANTS,
48 )
49 }
50
51 pub fn contract_exists(&self, cid: &ContractId) -> Result<bool> {
53 Ok(self
54 .read_value::<ContractId>(
55 &Id::contract(*cid),
56 BASE_KEY_METADATA,
57 META_SUB_KEY_CONTRACT_ID,
58 )?
59 .is_some())
60 }
61
62 pub fn agent_exists(&self, aid: &AgentId) -> Result<bool> {
64 Ok(self
65 .read_value::<AgentId>(
66 &Id::agent(*aid),
67 BASE_KEY_METADATA,
68 META_SUB_KEY_CONTRACT_ID,
69 )?
70 .is_some())
71 }
72
73 pub fn contract_revoked(&self, cid: &ContractId) -> Result<bool> {
75 Ok(self.contract_revoked_ts(cid)?.is_some())
76 }
77
78 pub fn agent_revoked(&self, aid: &AgentId) -> Result<bool> {
80 Ok(self.agent_revoked_ts(aid)?.is_some())
81 }
82
83 pub fn contract_revoked_ts(&self, cid: &ContractId) -> Result<Option<u64>> {
85 self.read_value::<u64>(
86 &Id::contract(*cid),
87 BASE_KEY_METADATA,
88 META_SUB_KEY_REVOKED_TS,
89 )
90 }
91
92 pub fn agent_revoked_ts(&self, aid: &AgentId) -> Result<Option<u64>> {
94 self.read_value::<u64>(&Id::agent(*aid), BASE_KEY_METADATA, META_SUB_KEY_REVOKED_TS)
95 }
96
97 pub fn contract_last_tx_hash(&self, cid: &ContractId) -> Result<Option<Hash256>> {
99 let actions = ActionLog::new(self.db, *cid);
100 if let Some(action) = actions.last()? {
101 return Ok(Some(action.tx_ctx.tx_id.hash));
102 }
103 match self.contract_meta(cid)? {
106 Some(meta) => Ok(meta.tx_ctx_introduction.map(|t| t.tx_id.hash)),
107 None => Ok(None),
108 }
109 }
110
111 pub fn contract_info(&self, cid: &ContractId) -> Result<Option<Info>> {
113 let id = Id::contract(*cid);
114 let participants = self.read_value(&id, BASE_KEY_METADATA, META_SUB_KEY_PARTICIPANTS)?;
115 let roles = self.read_value(&id, BASE_KEY_METADATA, META_SUB_KEY_ROLES)?;
116 let sinks = self.read_value(&id, BASE_KEY_METADATA, META_SUB_KEY_SINKS)?;
117 match (participants, roles, sinks) {
118 (Some(participants), Some(roles), Some(sinks)) => Ok(Some(Info {
119 contract_id: *cid,
120 participants,
121 roles,
122 sinks,
123 })),
124 _ => Ok(None),
125 }
126 }
127
128 pub fn agent_sinks(&self, aid: &AgentId) -> Result<Option<Vec<Sink>>> {
133 let aid = Id::agent(*aid);
134 self.read_value(&aid, BASE_KEY_METADATA, META_SUB_KEY_SINKS)
135 }
136
137 pub fn contract_desc(&self, cid: &ContractId) -> Result<Option<Description>> {
139 self.read_value(&Id::contract(*cid), BASE_KEY_METADATA, META_SUB_KEY_DESC)
140 }
141
142 pub fn agent_desc(&self, aid: &AgentId) -> Result<Option<Description>> {
144 self.read_value(&Id::agent(*aid), BASE_KEY_METADATA, META_SUB_KEY_DESC)
145 }
146
147 pub fn contract_meta(&self, cid: &ContractId) -> Result<Option<Metadata>> {
149 self.read_value(&Id::contract(*cid), BASE_KEY_METADATA, META_SUB_KEY_META)
150 }
151
152 pub fn agent_meta(&self, aid: &AgentId) -> Result<Option<Metadata>> {
154 self.read_value(&Id::agent(*aid), BASE_KEY_METADATA, META_SUB_KEY_META)
155 }
156
157 pub fn contract_full(&self, cid: &ContractId) -> Result<Option<ContractInfo>> {
159 let info = self.contract_info(cid)?;
160 let desc = self.contract_desc(cid)?;
161 let meta = self.contract_meta(cid)?;
162 Ok(Some(ContractInfo { info, desc, meta }))
163 }
164
165 pub fn agent_full(&self, aid: &AgentId) -> Result<Option<AgentInfo>> {
167 let sinks = self.agent_sinks(aid)?.unwrap_or_default();
168 let desc = self.agent_desc(aid)?;
169 let meta = self.agent_meta(aid)?;
170 Ok(Some(AgentInfo {
171 agent_id: *aid,
172 sinks,
173 desc,
174 meta,
175 }))
176 }
177
178 pub fn contract_revocation(&self, cid: &ContractId) -> Result<Option<Revocation>> {
180 self.read_value(
181 &Id::contract(*cid),
182 BASE_KEY_METADATA,
183 META_SUB_KEY_REVOCATION,
184 )
185 }
186
187 pub fn agent_revocation(&self, aid: &AgentId) -> Result<Option<Revocation>> {
189 self.read_value(&Id::agent(*aid), BASE_KEY_METADATA, META_SUB_KEY_REVOCATION)
190 }
191
192 pub fn query_action(&self, tx_id: &TxIdentifier) -> Result<Option<ActionRecord>> {
194 let tx_id_bytes = tx_id.to_bytes();
195 let relation = {
196 let rel_db = self.db.create_sub_db(ACTION_TX_REL_SUB_DB)?;
197 let txn = self.db.begin_ro_txn()?;
198 match txn.read(&rel_db, &tx_id_bytes)? {
199 Some(bytes) => RelTxAction::from_bytes(&bytes),
200 None => return Ok(None),
201 }
202 };
203 match self
205 .actions(relation.cid)
206 .get(relation.action_idx as usize)?
207 {
208 Some(record) => {
209 debug_assert!(record.tx_ctx.tx_id == *tx_id, "tx-id must match");
210 Ok(Some(record))
211 }
212 None => Ok(None),
213 }
214 }
215
216 pub fn agent_pkg_def(&self, aid: &AgentId) -> Result<Option<WasmPkgNoSource>> {
218 self.read_value(
219 &Id::agent(*aid),
220 BASE_KEY_METADATA,
221 META_SUB_KEY_PACKAGE_DEF,
222 )
223 }
224
225 pub fn agent_pkg_source(&self, aid: &AgentId) -> Result<Option<Source>> {
227 let source: Option<SourceFlattened> = self.read_value(
228 &Id::agent(*aid),
229 BASE_KEY_METADATA,
230 META_SUB_KEY_PACKAGE_SOURCE,
231 )?;
232 Ok(source.map(|s| s.unflatten()))
233 }
234
235 pub fn agent_pkg_full(&self, aid: &AgentId) -> Result<Option<WasmPkg>> {
237 let pkg_def = self.agent_pkg_def(aid)?;
238 let source = self.agent_pkg_source(aid)?;
239 match (pkg_def, source) {
240 (Some(pkg), Some(source)) => Ok(Some(WasmPkg::from_def_and_source(pkg, source))),
241 _ => Ok(None),
242 }
243 }
244
245 pub fn contract_pkg_def(&self, aid: &ContractId) -> Result<Option<WasmPkgNoSource>> {
247 self.read_value(
248 &Id::contract(*aid),
249 BASE_KEY_METADATA,
250 META_SUB_KEY_PACKAGE_DEF,
251 )
252 }
253
254 pub fn contract_pkg_source(&self, aid: &ContractId) -> Result<Option<Source>> {
256 let source: Option<SourceFlattened> = self.read_value(
258 &Id::contract(*aid),
259 BASE_KEY_METADATA,
260 META_SUB_KEY_PACKAGE_SOURCE,
261 )?;
262 Ok(source.map(|s| s.unflatten()))
263 }
264
265 pub fn contract_pkg_full(&self, aid: &ContractId) -> Result<Option<WasmPkg>> {
267 let pkg_def = self.contract_pkg_def(aid)?;
268 let source = self.contract_pkg_source(aid)?;
269 match (pkg_def, source) {
270 (Some(pkg), Some(source)) => Ok(Some(WasmPkg::from_def_and_source(pkg, source))),
271 _ => Ok(None),
272 }
273 }
274
275 fn read_value<D: DeserializeOwned>(
276 &self,
277 id: &Id,
278 base_key: u64,
279 sub_key: u64,
280 ) -> Result<Option<D>> {
281 let db_ptr = match id {
283 Id::Contract { .. } => self.db.open_sub_db(CONTRACT_SUB_DB)?,
284 Id::Agent { .. } => self.db.open_sub_db(AGENT_SUB_DB)?,
285 };
286 let txn = self.db.begin_ro_txn()?;
287 let key = StorageKey::system_key(id, base_key, sub_key);
288 let bytes = txn.read(&db_ptr, &key)?;
289 let result = match bytes {
290 Some(val) => Some(postcard::from_bytes(val)?),
291 None => None,
292 };
293 txn.commit()?;
294 Ok(result)
295 }
296}
297
298#[cfg(any(feature = "contracts", feature = "agents"))]
300pub(crate) fn write_system_value<S: Db, D: serde::Serialize, ID: AsRef<[u8; 16]>>(
301 db_ptr: &S::Handle,
302 txn: &mut <S as Db>::RwTx<'_>,
303 uid: ID,
304 base_key: u64,
305 sub_key: u64,
306 data: &D,
307) -> Result<()> {
308 let key = StorageKey::system_key(uid, base_key, sub_key);
309 let bytes = postcard::to_allocvec(data)?;
310 txn.write(db_ptr, &key, &bytes)?;
311 Ok(())
312}
313
314#[cfg(any(feature = "contracts", feature = "agents"))]
316pub(crate) fn read_system_value<S: Db, D: DeserializeOwned, ID: AsRef<[u8; 16]>>(
317 db_ptr: &S::Handle,
318 txn: &<S as Db>::RwTx<'_>,
319 cid: ID,
320 base_key: u64,
321 sub_key: u64,
322) -> Result<Option<D>> {
323 let key = StorageKey::system_key(cid, base_key, sub_key);
324 let bytes = txn.read(db_ptr, &key)?;
325 match bytes {
326 Some(val) => {
327 let out = postcard::from_bytes(val)?;
328 Ok(Some(out))
329 }
330 None => Ok(None),
331 }
332}
333
334#[cfg(any(feature = "contracts", feature = "agents"))]
335pub(crate) fn write_introduction<S: Db>(
336 db_ptr: &S::Handle,
337 txn: &mut <S as Db>::RwTx<'_>,
338 introduction: borderless::common::Introduction,
339) -> Result<()> {
340 use borderless::__private::storage_keys::*;
341
342 use crate::error::ErrorKind;
343 let cid = introduction.id;
344
345 let check_id = read_system_value::<S, Id, _>(
347 db_ptr,
348 txn,
349 &cid,
350 BASE_KEY_METADATA,
351 META_SUB_KEY_CONTRACT_ID,
352 )?;
353 if check_id.is_some() {
354 return Err(ErrorKind::DoubleIntroduction.into());
355 }
356
357 write_system_value::<S, _, _>(
359 db_ptr,
360 txn,
361 &cid,
362 BASE_KEY_METADATA,
363 META_SUB_KEY_CONTRACT_ID,
364 &introduction.id,
365 )?;
366
367 write_system_value::<S, _, _>(
369 db_ptr,
370 txn,
371 &cid,
372 BASE_KEY_METADATA,
373 META_SUB_KEY_PARTICIPANTS,
374 &introduction.participants,
375 )?;
376
377 write_system_value::<S, _, _>(
379 db_ptr,
380 txn,
381 &cid,
382 BASE_KEY_METADATA,
383 META_SUB_KEY_ROLES,
384 &introduction.roles,
385 )?;
386
387 write_system_value::<S, _, _>(
389 db_ptr,
390 txn,
391 &cid,
392 BASE_KEY_METADATA,
393 META_SUB_KEY_SINKS,
394 &introduction.sinks,
395 )?;
396
397 write_system_value::<S, _, _>(
399 db_ptr,
400 txn,
401 &cid,
402 BASE_KEY_METADATA,
403 META_SUB_KEY_DESC,
404 &introduction.desc,
405 )?;
406
407 write_system_value::<S, _, _>(
409 db_ptr,
410 txn,
411 &cid,
412 BASE_KEY_METADATA,
413 META_SUB_KEY_META,
414 &introduction.meta,
415 )?;
416
417 write_system_value::<S, _, _>(
419 db_ptr,
420 txn,
421 &cid,
422 BASE_KEY_METADATA,
423 META_SUB_KEY_INIT_STATE,
424 &introduction.initial_state,
425 )?;
426
427 let (pkg_def, pkg_source) = introduction.package.into_def_and_source();
429 let pkg_source = pkg_source.flatten();
430
431 write_system_value::<S, _, _>(
433 db_ptr,
434 txn,
435 &cid,
436 BASE_KEY_METADATA,
437 META_SUB_KEY_PACKAGE_DEF,
438 &pkg_def,
439 )?;
440
441 write_system_value::<S, _, _>(
443 db_ptr,
444 txn,
445 &cid,
446 BASE_KEY_METADATA,
447 META_SUB_KEY_PACKAGE_SOURCE,
448 &pkg_source,
449 )?;
450
451 Ok(())
452}
453
454#[cfg(any(feature = "contracts", feature = "agents"))]
455pub(crate) fn write_revocation<S: Db>(
456 db_ptr: &S::Handle,
457 txn: &mut <S as Db>::RwTx<'_>,
458 revocation: &Revocation,
459 tx_ctx: borderless::contracts::TxCtx,
460 timestamp: u64,
461) -> Result<()> {
462 let cid = revocation.id;
463 let meta: Option<Metadata> =
465 read_system_value::<S, _, _>(db_ptr, txn, &cid, BASE_KEY_METADATA, META_SUB_KEY_META)?;
466 let mut meta = meta.unwrap();
467
468 meta.inactive_since = timestamp;
469 meta.tx_ctx_revocation = Some(tx_ctx);
470
471 write_system_value::<S, _, _>(
472 db_ptr,
473 txn,
474 &cid,
475 BASE_KEY_METADATA,
476 META_SUB_KEY_META,
477 &meta,
478 )?;
479 write_system_value::<S, _, _>(
480 db_ptr,
481 txn,
482 &cid,
483 BASE_KEY_METADATA,
484 META_SUB_KEY_REVOKED_TS,
485 ×tamp,
486 )?;
487 write_system_value::<S, _, _>(
488 db_ptr,
489 txn,
490 &cid,
491 BASE_KEY_METADATA,
492 META_SUB_KEY_REVOCATION,
493 &revocation,
494 )?;
495 Ok(())
496}