1use std::collections::{BTreeMap, BTreeSet};
23use std::error::Error;
24use std::ops::Deref;
25
26use amplify::confinement::{self, Confined};
27use bp::seals::txout::blind::SingleBlindSeal;
28use bp::Txid;
29use commit_verify::mpc;
30use rgb::{
31 validation, Anchor, AnchoredBundle, BundleId, ContractId, ExposedSeal, GraphSeal, OpId,
32 Operation, Opout, SchemaId, SecretSeal, SubSchema, Transition, TransitionBundle,
33};
34use strict_encoding::TypeName;
35
36use crate::accessors::{BundleExt, MergeRevealError, RevealError};
37use crate::containers::{
38 Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Terminal, Transfer,
39};
40use crate::interface::{
41 ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, TransitionBuilder, TypedState,
42};
43use crate::persistence::hoard::ConsumeError;
44use crate::persistence::stash::StashInconsistency;
45use crate::persistence::{Stash, StashError};
46use crate::resolvers::ResolveHeight;
47use crate::Outpoint;
48
49#[derive(Debug, Display, Error, From)]
50#[display(doc_comments)]
51pub enum ConsignerError<E1: Error, E2: Error> {
52 TooManyTerminals,
54
55 TooManyBundles,
58
59 ConcealedPublicState(Opout),
61
62 #[display(inner)]
63 #[from]
64 Reveal(RevealError),
65
66 #[display(inner)]
67 #[from]
68 #[from(InventoryInconsistency)]
69 InventoryError(InventoryError<E1>),
70
71 #[display(inner)]
72 #[from]
73 #[from(StashInconsistency)]
74 StashError(StashError<E2>),
75}
76
77#[derive(Debug, Display, Error, From)]
78#[display(inner)]
79pub enum InventoryError<E: Error> {
80 Connectivity(E),
82
83 #[from]
86 Consume(ConsumeError),
87
88 #[from]
90 #[from(confinement::Error)]
91 DataError(DataError),
92
93 #[from]
96 #[from(mpc::LeafNotKnown)]
97 #[from(mpc::InvalidProof)]
98 #[from(RevealError)]
99 #[from(StashInconsistency)]
100 InternalInconsistency(InventoryInconsistency),
101}
102
103impl<E1: Error, E2: Error> From<StashError<E1>> for InventoryError<E2>
104where E2: From<E1>
105{
106 fn from(err: StashError<E1>) -> Self {
107 match err {
108 StashError::Connectivity(err) => Self::Connectivity(err.into()),
109 StashError::InternalInconsistency(e) => {
110 Self::InternalInconsistency(InventoryInconsistency::Stash(e))
111 }
112 }
113 }
114}
115
116#[derive(Debug, Display, Error, From)]
117#[display(inner)]
118pub enum InventoryDataError<E: Error> {
119 Connectivity(E),
121
122 #[from]
124 #[from(validation::Status)]
125 #[from(confinement::Error)]
126 #[from(IfaceImplError)]
127 #[from(RevealError)]
128 #[from(MergeRevealError)]
129 DataError(DataError),
130}
131
132impl<E: Error> From<InventoryDataError<E>> for InventoryError<E> {
133 fn from(err: InventoryDataError<E>) -> Self {
134 match err {
135 InventoryDataError::Connectivity(e) => InventoryError::Connectivity(e),
136 InventoryDataError::DataError(e) => InventoryError::DataError(e),
137 }
138 }
139}
140
141#[derive(Debug, Display, Error, From)]
142#[display(inner)]
143pub enum DataError {
144 NotValidated,
147
148 #[from]
150 Invalid(validation::Status),
151
152 UnresolvedTransactions,
156
157 TerminalsUnmined,
160
161 #[display(inner)]
162 #[from]
163 Reveal(RevealError),
164
165 #[from]
166 #[display(inner)]
167 Merge(MergeRevealError),
168
169 OutpointUnknown(Outpoint, ContractId),
171
172 #[from]
173 Confinement(confinement::Error),
174
175 #[from]
176 IfaceImpl(IfaceImplError),
177
178 NoIfaceImpl(SchemaId, IfaceId),
180
181 #[from]
182 HeightResolver(Box<dyn Error>),
183
184 Concealed,
186}
187
188#[derive(Clone, Debug, Display, Error, From)]
189#[display(doc_comments)]
190pub enum IfaceImplError {
191 UnknownSchema(SchemaId),
193
194 UnknownIface(IfaceId),
196}
197
198#[derive(Debug, Display, Error, From)]
202#[display(doc_comments)]
203pub enum InventoryInconsistency {
204 StateAbsent(ContractId),
206
207 DisclosureAbsent(Txid),
212
213 BundleAbsent(OpId),
218
219 NoBundleAnchor(BundleId),
224
225 #[from(mpc::LeafNotKnown)]
230 #[from(mpc::InvalidProof)]
231 UnrelatedAnchor,
232
233 #[from]
238 BundleReveal(RevealError),
239
240 OutsizedBundle,
245
246 #[from]
247 #[display(inner)]
248 Stash(StashInconsistency),
249}
250
251#[allow(clippy::result_large_err)]
252pub trait Inventory: Deref<Target = Self::Stash> {
253 type Stash: Stash;
254 type Error: Error;
256
257 fn stash(&self) -> &Self::Stash;
258
259 fn import_sigs<I>(
260 &mut self,
261 content_id: ContentId,
262 sigs: I,
263 ) -> Result<(), InventoryDataError<Self::Error>>
264 where
265 I: IntoIterator<Item = Cert>,
266 I::IntoIter: ExactSizeIterator<Item = Cert>;
267
268 fn import_schema(
269 &mut self,
270 schema: impl Into<Bindle<SubSchema>>,
271 ) -> Result<validation::Status, InventoryDataError<Self::Error>>;
272
273 fn import_iface(
274 &mut self,
275 iface: impl Into<Bindle<Iface>>,
276 ) -> Result<validation::Status, InventoryDataError<Self::Error>>;
277
278 fn import_iface_impl(
279 &mut self,
280 iimpl: impl Into<Bindle<IfaceImpl>>,
281 ) -> Result<validation::Status, InventoryDataError<Self::Error>>;
282
283 fn import_contract<R: ResolveHeight>(
284 &mut self,
285 contract: Contract,
286 resolver: &mut R,
287 ) -> Result<validation::Status, InventoryError<Self::Error>>
288 where
289 R::Error: 'static;
290
291 fn accept_transfer<R: ResolveHeight>(
292 &mut self,
293 transfer: Transfer,
294 resolver: &mut R,
295 force: bool,
296 ) -> Result<validation::Status, InventoryError<Self::Error>>
297 where
298 R::Error: 'static;
299
300 fn consume_anchor(
306 &mut self,
307 anchor: Anchor<mpc::MerkleBlock>,
308 ) -> Result<(), InventoryError<Self::Error>>;
309
310 fn consume_bundle(
316 &mut self,
317 contract_id: ContractId,
318 bundle: TransitionBundle,
319 witness_txid: Txid,
320 ) -> Result<(), InventoryError<Self::Error>>;
321
322 unsafe fn import_contract_force<R: ResolveHeight>(
327 &mut self,
328 contract: Contract,
329 resolver: &mut R,
330 ) -> Result<validation::Status, InventoryError<Self::Error>>
331 where
332 R::Error: 'static;
333
334 fn contracts_with_iface(
335 &mut self,
336 iface: impl Into<TypeName>,
337 ) -> Result<Vec<ContractIface>, InventoryError<Self::Error>>
338 where
339 Self::Error: From<<Self::Stash as Stash>::Error>,
340 InventoryError<Self::Error>: From<<Self::Stash as Stash>::Error>,
341 {
342 let iface = iface.into();
343 let iface_id = self.iface_by_name(&iface)?.iface_id();
344 self.contract_ids_by_iface(&iface)?
345 .into_iter()
346 .map(|id| self.contract_iface(id, iface_id))
347 .collect()
348 }
349
350 fn contract_iface_named(
351 &mut self,
352 contract_id: ContractId,
353 iface: impl Into<TypeName>,
354 ) -> Result<ContractIface, InventoryError<Self::Error>>
355 where
356 Self::Error: From<<Self::Stash as Stash>::Error>,
357 InventoryError<Self::Error>: From<<Self::Stash as Stash>::Error>,
358 {
359 let iface = iface.into();
360 let iface_id = self.iface_by_name(&iface)?.iface_id();
361 self.contract_iface(contract_id, iface_id)
362 }
363
364 fn contract_iface(
365 &mut self,
366 contract_id: ContractId,
367 iface_id: IfaceId,
368 ) -> Result<ContractIface, InventoryError<Self::Error>>;
369
370 fn anchored_bundle(&self, opid: OpId) -> Result<AnchoredBundle, InventoryError<Self::Error>>;
371
372 fn transition_builder(
373 &mut self,
374 contract_id: ContractId,
375 iface: impl Into<TypeName>,
376 transition_name: Option<impl Into<TypeName>>,
377 ) -> Result<TransitionBuilder, InventoryError<Self::Error>>
378 where
379 Self::Error: From<<Self::Stash as Stash>::Error>,
380 {
381 let schema_ifaces = self.contract_schema(contract_id)?;
382 let iface = self.iface_by_name(&iface.into())?;
383 let schema = &schema_ifaces.schema;
384 let iimpl = schema_ifaces
385 .iimpls
386 .get(&iface.iface_id())
387 .ok_or(DataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?;
388 let builder = if let Some(transition_name) = transition_name {
389 TransitionBuilder::named_transition(
390 iface.clone(),
391 schema.clone(),
392 iimpl.clone(),
393 transition_name.into(),
394 )
395 } else {
396 TransitionBuilder::default_transition(iface.clone(), schema.clone(), iimpl.clone())
397 }
398 .expect("internal inconsistency");
399 Ok(builder)
400 }
401
402 fn blank_builder(
403 &mut self,
404 contract_id: ContractId,
405 iface: impl Into<TypeName>,
406 ) -> Result<TransitionBuilder, InventoryError<Self::Error>>
407 where
408 Self::Error: From<<Self::Stash as Stash>::Error>,
409 {
410 let schema_ifaces = self.contract_schema(contract_id)?;
411 let iface = self.iface_by_name(&iface.into())?;
412 let schema = &schema_ifaces.schema;
413 let iimpl = schema_ifaces
414 .iimpls
415 .get(&iface.iface_id())
416 .ok_or(DataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?;
417 let builder =
418 TransitionBuilder::blank_transition(iface.clone(), schema.clone(), iimpl.clone())
419 .expect("internal inconsistency");
420 Ok(builder)
421 }
422
423 fn transition(&self, opid: OpId) -> Result<&Transition, InventoryError<Self::Error>>;
424
425 fn contracts_by_outpoints(
426 &mut self,
427 outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
428 ) -> Result<BTreeSet<ContractId>, InventoryError<Self::Error>>;
429
430 fn public_opouts(
431 &mut self,
432 contract_id: ContractId,
433 ) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>>;
434
435 fn opouts_by_outpoints(
436 &mut self,
437 contract_id: ContractId,
438 outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
439 ) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>>;
440
441 fn opouts_by_terminals(
442 &mut self,
443 terminals: impl IntoIterator<Item = SecretSeal>,
444 ) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>>;
445
446 fn state_for_outpoints(
447 &mut self,
448 contract_id: ContractId,
449 outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
450 ) -> Result<BTreeMap<Opout, TypedState>, InventoryError<Self::Error>>;
451
452 fn store_seal_secret(&mut self, seal: GraphSeal) -> Result<(), InventoryError<Self::Error>>;
453 fn seal_secrets(&mut self) -> Result<BTreeSet<GraphSeal>, InventoryError<Self::Error>>;
454
455 #[allow(clippy::type_complexity)]
456 fn export_contract(
457 &mut self,
458 contract_id: ContractId,
459 ) -> Result<
460 Bindle<Contract>,
461 ConsignerError<Self::Error, <<Self as Deref>::Target as Stash>::Error>,
462 > {
463 let mut consignment =
464 self.consign::<GraphSeal, false>(contract_id, [] as [GraphSeal; 0])?;
465 consignment.transfer = false;
466 Ok(consignment.into())
467 }
469
470 #[allow(clippy::type_complexity)]
471 fn transfer(
472 &mut self,
473 contract_id: ContractId,
474 seals: impl IntoIterator<Item = impl Into<BuilderSeal<SingleBlindSeal>>>,
475 ) -> Result<
476 Bindle<Transfer>,
477 ConsignerError<Self::Error, <<Self as Deref>::Target as Stash>::Error>,
478 > {
479 let mut consignment = self.consign(contract_id, seals)?;
480 consignment.transfer = true;
481 Ok(consignment.into())
482 }
484
485 fn consign<Seal: ExposedSeal, const TYPE: bool>(
486 &mut self,
487 contract_id: ContractId,
488 seals: impl IntoIterator<Item = impl Into<BuilderSeal<Seal>>>,
489 ) -> Result<
490 Consignment<TYPE>,
491 ConsignerError<Self::Error, <<Self as Deref>::Target as Stash>::Error>,
492 > {
493 let mut opouts = self.public_opouts(contract_id)?;
495 let (outpoint_seals, terminal_seals) = seals
496 .into_iter()
497 .map(|seal| match seal.into() {
498 BuilderSeal::Revealed(seal) => (seal.outpoint(), seal.conceal()),
499 BuilderSeal::Concealed(seal) => (None, seal),
500 })
501 .unzip::<_, _, Vec<_>, Vec<_>>();
502 opouts.extend(self.opouts_by_outpoints(contract_id, outpoint_seals.into_iter().flatten())?);
503 opouts.extend(self.opouts_by_terminals(terminal_seals.iter().copied())?);
504
505 let mut anchored_bundles = BTreeMap::<OpId, AnchoredBundle>::new();
509 let mut transitions = BTreeMap::<OpId, Transition>::new();
510 let mut terminals = BTreeMap::<BundleId, Terminal>::new();
511 for opout in opouts {
512 if opout.op == contract_id {
513 continue; }
515 let transition = self.transition(opout.op)?;
516 transitions.insert(opout.op, transition.clone());
517 let anchored_bundle = self.anchored_bundle(opout.op)?;
518
519 let bundle_id = anchored_bundle.bundle.bundle_id();
522 for (type_id, typed_assignments) in transition.assignments.iter() {
523 for index in 0..typed_assignments.len_u16() {
524 let seal = typed_assignments.to_confidential_seals()[index as usize];
525 if terminal_seals.contains(&seal) {
526 terminals.insert(bundle_id, Terminal::new(seal.into()));
527 } else if opout.no == index && opout.ty == *type_id {
528 if let Some(seal) = typed_assignments
529 .revealed_seal_at(index)
530 .expect("index exists")
531 {
532 terminals.insert(bundle_id, Terminal::new(seal.into()));
533 } else {
534 return Err(ConsignerError::ConcealedPublicState(opout));
535 }
536 }
537 }
538 }
539
540 anchored_bundles.insert(opout.op, anchored_bundle.clone());
541 }
542
543 let mut ids = vec![];
545 for transition in transitions.values() {
546 ids.extend(transition.inputs().iter().map(|input| input.prev_out.op));
547 }
548 while let Some(id) = ids.pop() {
549 if id == contract_id {
550 continue; }
552 let transition = self.transition(id)?;
553 ids.extend(transition.inputs().iter().map(|input| input.prev_out.op));
554 transitions.insert(id, transition.clone());
555 anchored_bundles
556 .entry(id)
557 .or_insert(self.anchored_bundle(id)?.clone())
558 .bundle
559 .reveal_transition(transition)?;
560 }
561
562 let genesis = self.genesis(contract_id)?;
563 let schema_ifaces = self.schema(genesis.schema_id)?;
564 let mut consignment = Consignment::new(schema_ifaces.schema.clone(), genesis.clone());
565 for (iface_id, iimpl) in &schema_ifaces.iimpls {
566 let iface = self.iface_by_id(*iface_id)?;
567 consignment
568 .ifaces
569 .insert(*iface_id, IfacePair::with(iface.clone(), iimpl.clone()))
570 .expect("same collection size");
571 }
572 consignment.bundles = Confined::try_from_iter(anchored_bundles.into_values())
573 .map_err(|_| ConsignerError::TooManyBundles)?;
574 consignment.terminals =
575 Confined::try_from(terminals).map_err(|_| ConsignerError::TooManyTerminals)?;
576
577 Ok(consignment)
581 }
582}