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