1use std::collections::{BTreeMap, BTreeSet};
23use std::convert::Infallible;
24use std::ops::{Deref, DerefMut};
25
26use amplify::confinement::{MediumOrdMap, MediumOrdSet, TinyOrdMap};
27use amplify::ByteArray;
28use bp::dbc::Anchor;
29use bp::Txid;
30use commit_verify::mpc::MerkleBlock;
31use rgb::validation::{Status, Validity, Warning};
32use rgb::{
33 validation, AnchorId, AnchoredBundle, Assign, AssignmentType, BundleId, ContractHistory,
34 ContractId, ContractState, ExposedState, Extension, Genesis, GenesisSeal, GraphSeal, OpId,
35 Operation, Opout, SecretSeal, SubSchema, Transition, TransitionBundle, TxoSeal, TypedAssigns,
36 WitnessAnchor,
37};
38use strict_encoding::{StrictDeserialize, StrictSerialize};
39
40use crate::containers::{Bindle, Cert, Consignment, ContentId, Contract, TerminalSeal, Transfer};
41use crate::interface::{
42 ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, SchemaIfaces, TypedState,
43};
44use crate::persistence::inventory::{DataError, IfaceImplError, InventoryInconsistency};
45use crate::persistence::{
46 Hoard, Inventory, InventoryDataError, InventoryError, Stash, StashInconsistency,
47};
48use crate::resolvers::ResolveHeight;
49use crate::{Outpoint, LIB_NAME_RGB_STD};
50
51#[derive(Clone, Eq, PartialEq, Debug)]
52#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
53#[strict_type(lib = LIB_NAME_RGB_STD)]
54pub struct IndexedBundle(ContractId, BundleId);
55
56#[derive(Clone, Debug, Default)]
57#[derive(StrictType, StrictEncode, StrictDecode)]
58#[strict_type(lib = LIB_NAME_RGB_STD)]
59pub struct ContractIndex {
60 public_opouts: MediumOrdSet<Opout>,
61 outpoint_opouts: MediumOrdMap<Outpoint, MediumOrdSet<Opout>>,
62}
63
64#[derive(Clone, Debug, Getters)]
69#[getter(prefix = "debug_")]
70#[derive(StrictType, StrictEncode, StrictDecode)]
71#[strict_type(lib = LIB_NAME_RGB_STD)]
72pub struct Stock {
73 hoard: Hoard,
75 history: TinyOrdMap<ContractId, ContractHistory>,
77 bundle_op_index: MediumOrdMap<OpId, IndexedBundle>,
79 anchor_bundle_index: MediumOrdMap<BundleId, AnchorId>,
80 contract_index: TinyOrdMap<ContractId, ContractIndex>,
81 terminal_index: MediumOrdMap<SecretSeal, Opout>,
82 seal_secrets: MediumOrdSet<GraphSeal>,
84}
85
86impl Default for Stock {
87 fn default() -> Self {
88 Stock {
89 hoard: Hoard::preset(),
90 history: empty!(),
91 bundle_op_index: empty!(),
92 anchor_bundle_index: empty!(),
93 contract_index: empty!(),
94 terminal_index: empty!(),
95 seal_secrets: empty!(),
96 }
97 }
98}
99
100impl StrictSerialize for Stock {}
101impl StrictDeserialize for Stock {}
102
103impl Deref for Stock {
104 type Target = Hoard;
105
106 fn deref(&self) -> &Self::Target { &self.hoard }
107}
108
109impl DerefMut for Stock {
110 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.hoard }
111}
112
113#[allow(clippy::result_large_err)]
114impl Stock {
115 fn consume_consignment<R: ResolveHeight, const TYPE: bool>(
116 &mut self,
117 mut consignment: Consignment<TYPE>,
118 resolver: &mut R,
119 force: bool,
120 ) -> Result<validation::Status, InventoryError<Infallible>>
121 where
122 R::Error: 'static,
123 {
124 let mut status = validation::Status::new();
125 match consignment.validation_status() {
126 None => return Err(DataError::NotValidated.into()),
127 Some(status) if status.validity() == Validity::Invalid => {
128 return Err(DataError::Invalid(status.clone()).into());
129 }
130 Some(status) if status.validity() == Validity::UnresolvedTransactions && !force => {
131 return Err(DataError::UnresolvedTransactions.into());
132 }
133 Some(status) if status.validity() == Validity::UnminedTerminals && !force => {
134 return Err(DataError::TerminalsUnmined.into());
135 }
136 Some(s) if s.validity() == Validity::UnresolvedTransactions && !force => {
137 status.add_warning(Warning::Custom(s!(
138 "contract contains unknown transactions and was forcefully imported"
139 )));
140 }
141 Some(s) if s.validity() == Validity::UnminedTerminals && !force => {
142 status.add_warning(Warning::Custom(s!("contract contains not yet mined final \
143 transactions and was forcefully imported")));
144 }
145 _ => {}
146 }
147
148 let id = consignment.contract_id();
149
150 self.import_schema(consignment.schema.clone())?;
151 for IfacePair { iface, iimpl } in consignment.ifaces.values() {
152 self.import_iface(iface.clone())?;
153 self.import_iface_impl(iimpl.clone())?;
154 }
155
156 for (bundle_id, terminal) in consignment.terminals.clone() {
158 for secret in terminal.seals.iter().filter_map(TerminalSeal::secret_seal) {
159 if let Some(seal) = self
160 .seal_secrets
161 .iter()
162 .find(|s| s.to_concealed_seal() == secret)
163 {
164 consignment.reveal_bundle_seal(bundle_id, *seal);
165 }
166 }
167 }
168
169 let history = consignment
171 .update_history(self.history.get(&id), resolver)
172 .map_err(|err| DataError::HeightResolver(Box::new(err)))?;
173 self.history.insert(id, history)?;
174
175 let contract_id = consignment.contract_id();
176 if !self.contract_index.contains_key(&contract_id) {
177 self.contract_index.insert(contract_id, empty!())?;
178 }
179 self.index_genesis(contract_id, &consignment.genesis)?;
180 for extension in &consignment.extensions {
181 self.index_extension(contract_id, extension)?;
182 }
183 for AnchoredBundle { anchor, bundle } in &mut consignment.bundles {
184 let bundle_id = bundle.bundle_id();
185 let anchor_id = anchor.anchor_id(contract_id, bundle_id.into())?;
186 self.anchor_bundle_index.insert(bundle_id, anchor_id)?;
187 self.index_bundle(contract_id, bundle, anchor.txid)?;
188 }
189
190 self.hoard.consume_consignment(consignment)?;
191
192 Ok(status)
193 }
194
195 fn index_genesis(
196 &mut self,
197 id: ContractId,
198 genesis: &Genesis,
199 ) -> Result<(), InventoryError<<Self as Inventory>::Error>> {
200 let opid = genesis.id();
201 for (type_id, assign) in genesis.assignments.iter() {
202 match assign {
203 TypedAssigns::Declarative(vec) => {
204 self.index_genesis_assignments(id, vec, opid, *type_id)?;
205 }
206 TypedAssigns::Fungible(vec) => {
207 self.index_genesis_assignments(id, vec, opid, *type_id)?;
208 }
209 TypedAssigns::Structured(vec) => {
210 self.index_genesis_assignments(id, vec, opid, *type_id)?;
211 }
212 TypedAssigns::Attachment(vec) => {
213 self.index_genesis_assignments(id, vec, opid, *type_id)?;
214 }
215 }
216 }
217 Ok(())
218 }
219
220 fn index_extension(
221 &mut self,
222 id: ContractId,
223 extension: &Extension,
224 ) -> Result<(), InventoryError<<Self as Inventory>::Error>> {
225 let opid = extension.id();
226 for (type_id, assign) in extension.assignments.iter() {
227 match assign {
228 TypedAssigns::Declarative(vec) => {
229 self.index_genesis_assignments(id, vec, opid, *type_id)?;
230 }
231 TypedAssigns::Fungible(vec) => {
232 self.index_genesis_assignments(id, vec, opid, *type_id)?;
233 }
234 TypedAssigns::Structured(vec) => {
235 self.index_genesis_assignments(id, vec, opid, *type_id)?;
236 }
237 TypedAssigns::Attachment(vec) => {
238 self.index_genesis_assignments(id, vec, opid, *type_id)?;
239 }
240 }
241 }
242 Ok(())
243 }
244
245 fn index_bundle(
246 &mut self,
247 id: ContractId,
248 bundle: &TransitionBundle,
249 witness_txid: Txid,
250 ) -> Result<(), InventoryError<<Self as Inventory>::Error>> {
251 let bundle_id = bundle.bundle_id();
252 for (opid, item) in bundle.iter() {
253 if let Some(transition) = &item.transition {
254 self.bundle_op_index
255 .insert(*opid, IndexedBundle(id, bundle_id))?;
256 for (type_id, assign) in transition.assignments.iter() {
257 match assign {
258 TypedAssigns::Declarative(vec) => {
259 self.index_transition_assignments(
260 id,
261 vec,
262 *opid,
263 *type_id,
264 witness_txid,
265 )?;
266 }
267 TypedAssigns::Fungible(vec) => {
268 self.index_transition_assignments(
269 id,
270 vec,
271 *opid,
272 *type_id,
273 witness_txid,
274 )?;
275 }
276 TypedAssigns::Structured(vec) => {
277 self.index_transition_assignments(
278 id,
279 vec,
280 *opid,
281 *type_id,
282 witness_txid,
283 )?;
284 }
285 TypedAssigns::Attachment(vec) => {
286 self.index_transition_assignments(
287 id,
288 vec,
289 *opid,
290 *type_id,
291 witness_txid,
292 )?;
293 }
294 }
295 }
296 }
297 }
298
299 Ok(())
300 }
301
302 fn index_genesis_assignments<State: ExposedState>(
303 &mut self,
304 contract_id: ContractId,
305 vec: &[Assign<State, GenesisSeal>],
306 opid: OpId,
307 type_id: AssignmentType,
308 ) -> Result<(), InventoryError<<Self as Inventory>::Error>> {
309 let index = self
310 .contract_index
311 .get_mut(&contract_id)
312 .ok_or(StashInconsistency::ContractAbsent(contract_id))?;
313
314 for (no, a) in vec.iter().enumerate() {
315 let opout = Opout::new(opid, type_id, no as u16);
316 if let Assign::ConfidentialState { seal, .. } | Assign::Revealed { seal, .. } = a {
317 let outpoint = seal.outpoint_or(seal.txid);
318 match index.outpoint_opouts.get_mut(&outpoint) {
319 Some(opouts) => {
320 opouts.push(opout)?;
321 }
322 None => {
323 index
324 .outpoint_opouts
325 .insert(outpoint, confined_bset!(opout))?;
326 }
327 }
328 }
329 if let Assign::Confidential { seal, .. } | Assign::ConfidentialSeal { seal, .. } = a {
330 self.terminal_index.insert(*seal, opout)?;
331 }
332 }
333 Ok(())
334 }
335
336 fn index_transition_assignments<State: ExposedState>(
337 &mut self,
338 contract_id: ContractId,
339 vec: &[Assign<State, GraphSeal>],
340 opid: OpId,
341 type_id: AssignmentType,
342 witness_txid: Txid,
343 ) -> Result<(), InventoryError<<Self as Inventory>::Error>> {
344 let index = self
345 .contract_index
346 .get_mut(&contract_id)
347 .ok_or(StashInconsistency::ContractAbsent(contract_id))?;
348
349 for (no, a) in vec.iter().enumerate() {
350 let opout = Opout::new(opid, type_id, no as u16);
351 if let Assign::ConfidentialState { seal, .. } | Assign::Revealed { seal, .. } = a {
352 let outpoint = seal.outpoint_or(witness_txid);
353 match index.outpoint_opouts.get_mut(&outpoint) {
354 Some(opouts) => {
355 opouts.push(opout)?;
356 }
357 None => {
358 index
359 .outpoint_opouts
360 .insert(outpoint, confined_bset!(opout))?;
361 }
362 }
363 }
364 if let Assign::Confidential { seal, .. } | Assign::ConfidentialSeal { seal, .. } = a {
365 self.terminal_index.insert(*seal, opout)?;
366 }
367 }
368 Ok(())
369 }
370}
371
372impl Inventory for Stock {
373 type Stash = Hoard;
374 type Error = Infallible;
376
377 fn stash(&self) -> &Self::Stash { self }
378
379 fn import_sigs<I>(
380 &mut self,
381 content_id: ContentId,
382 sigs: I,
383 ) -> Result<(), InventoryDataError<Self::Error>>
384 where
385 I: IntoIterator<Item = Cert>,
386 I::IntoIter: ExactSizeIterator<Item = Cert>,
387 {
388 self.import_sigs_internal(content_id, sigs)?;
389 Ok(())
390 }
391
392 fn import_schema(
393 &mut self,
394 schema: impl Into<Bindle<SubSchema>>,
395 ) -> Result<validation::Status, InventoryDataError<Self::Error>> {
396 let bindle = schema.into();
397 let (schema, sigs) = bindle.into_split();
398 let id = schema.schema_id();
399
400 let mut status = schema.verify();
401 if !status.failures.is_empty() {
402 return Err(status.into());
403 }
404 if self.schemata.contains_key(&id) {
405 status.add_warning(Warning::Custom(format!("schema {id::<0} is already known")));
406 } else {
407 let schema_ifaces = SchemaIfaces::new(schema);
408 self.schemata.insert(id, schema_ifaces)?;
409 }
410
411 let content_id = ContentId::Schema(id);
412 self.import_sigs_internal(content_id, sigs).ok();
414
415 Ok(status)
416 }
417
418 fn import_iface(
419 &mut self,
420 iface: impl Into<Bindle<Iface>>,
421 ) -> Result<validation::Status, InventoryDataError<Self::Error>> {
422 let bindle = iface.into();
423 let (iface, sigs) = bindle.into_split();
424 let id = iface.iface_id();
425
426 let mut status = validation::Status::new();
427
428 if self.ifaces.insert(id, iface)?.is_some() {
430 status.add_warning(Warning::Custom(format!("interface {id::<0} is already known")));
431 }
432
433 let content_id = ContentId::Iface(id);
434 self.import_sigs_internal(content_id, sigs).ok();
436
437 Ok(status)
438 }
439
440 fn import_iface_impl(
441 &mut self,
442 iimpl: impl Into<Bindle<IfaceImpl>>,
443 ) -> Result<validation::Status, InventoryDataError<Self::Error>> {
444 let bindle = iimpl.into();
445 let (iimpl, sigs) = bindle.into_split();
446 let iface_id = iimpl.iface_id;
447 let impl_id = iimpl.impl_id();
448
449 let mut status = validation::Status::new();
450
451 if !self.ifaces.contains_key(&iface_id) {
452 return Err(IfaceImplError::UnknownIface(iface_id).into());
453 }
454 let Some(schema_ifaces) = self.schemata.get_mut(&iimpl.schema_id) else {
455 return Err(IfaceImplError::UnknownSchema(iimpl.schema_id).into());
456 };
457 if schema_ifaces.iimpls.insert(iface_id, iimpl)?.is_some() {
459 status.add_warning(Warning::Custom(format!(
460 "interface implementation {impl_id::<0} is already known",
461 )));
462 }
463
464 let content_id = ContentId::IfaceImpl(impl_id);
465 self.import_sigs_internal(content_id, sigs).ok();
467
468 Ok(status)
469 }
470
471 fn import_contract<R: ResolveHeight>(
472 &mut self,
473 contract: Contract,
474 resolver: &mut R,
475 ) -> Result<validation::Status, InventoryError<Self::Error>>
476 where
477 R::Error: 'static,
478 {
479 self.consume_consignment(contract, resolver, false)
480 }
481
482 fn accept_transfer<R: ResolveHeight>(
483 &mut self,
484 transfer: Transfer,
485 resolver: &mut R,
486 force: bool,
487 ) -> Result<Status, InventoryError<Self::Error>>
488 where
489 R::Error: 'static,
490 {
491 self.consume_consignment(transfer, resolver, force)
492 }
493
494 fn consume_anchor(
495 &mut self,
496 anchor: Anchor<MerkleBlock>,
497 ) -> Result<(), InventoryError<Self::Error>> {
498 let anchor_id = anchor.anchor_id();
499 for (_, bundle_id) in anchor.mpc_proof.to_known_message_map() {
500 self.anchor_bundle_index
501 .insert(bundle_id.to_byte_array().into(), anchor_id)?;
502 }
503 self.hoard.consume_anchor(anchor)?;
504 Ok(())
505 }
506
507 fn consume_bundle(
508 &mut self,
509 contract_id: ContractId,
510 bundle: TransitionBundle,
511 witness_txid: Txid,
512 ) -> Result<(), InventoryError<<Self as Inventory>::Error>> {
513 self.index_bundle(contract_id, &bundle, witness_txid)?;
514 let history = self
515 .history
516 .get_mut(&contract_id)
517 .ok_or(InventoryInconsistency::StateAbsent(contract_id))?;
518 for item in bundle.values() {
519 if let Some(transition) = &item.transition {
520 let ord_txid = WitnessAnchor::from_mempool(witness_txid);
521 history.add_transition(transition, ord_txid);
522 }
523 }
524 self.hoard.consume_bundle(bundle)?;
525 Ok(())
526 }
527
528 unsafe fn import_contract_force<R: ResolveHeight>(
529 &mut self,
530 contract: Contract,
531 resolver: &mut R,
532 ) -> Result<validation::Status, InventoryError<Self::Error>>
533 where
534 R::Error: 'static,
535 {
536 self.consume_consignment(contract, resolver, true)
537 }
538
539 fn contract_iface(
540 &mut self,
541 contract_id: ContractId,
542 iface_id: IfaceId,
543 ) -> Result<ContractIface, InventoryError<Self::Error>> {
544 let history = self
545 .history
546 .get(&contract_id)
547 .ok_or(InventoryInconsistency::StateAbsent(contract_id))?
548 .clone();
549 let schema_id = history.schema_id();
550 let schema_ifaces = self
551 .schemata
552 .get(&schema_id)
553 .ok_or(StashInconsistency::SchemaAbsent(schema_id))?;
554 let state = ContractState {
555 schema: schema_ifaces.schema.clone(),
556 history,
557 };
558 let iimpl = schema_ifaces
559 .iimpls
560 .get(&iface_id)
561 .ok_or(StashInconsistency::IfaceImplAbsent(iface_id, schema_id))?
562 .clone();
563 Ok(ContractIface {
564 state,
565 iface: iimpl,
566 })
567 }
568
569 fn transition(&self, opid: OpId) -> Result<&Transition, InventoryError<Self::Error>> {
570 let IndexedBundle(_, bundle_id) = self
571 .bundle_op_index
572 .get(&opid)
573 .ok_or(InventoryInconsistency::BundleAbsent(opid))?;
574 let bundle = self.bundle(*bundle_id)?;
575 let item = bundle.get(&opid).ok_or(DataError::Concealed)?;
576 let transition = item.transition.as_ref().ok_or(DataError::Concealed)?;
577 Ok(transition)
578 }
579
580 fn anchored_bundle(&self, opid: OpId) -> Result<AnchoredBundle, InventoryError<Self::Error>> {
581 let IndexedBundle(contract_id, bundle_id) = self
582 .bundle_op_index
583 .get(&opid)
584 .ok_or(InventoryInconsistency::BundleAbsent(opid))?;
585
586 let anchor_id = self
587 .anchor_bundle_index
588 .get(bundle_id)
589 .ok_or(InventoryInconsistency::NoBundleAnchor(*bundle_id))?;
590
591 let bundle = self.bundle(*bundle_id)?.clone();
592 let anchor = self.anchor(*anchor_id)?;
593 let anchor = anchor.to_merkle_proof(*contract_id)?;
594 Ok(AnchoredBundle { anchor, bundle })
597 }
598
599 fn contracts_by_outpoints(
600 &mut self,
601 outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
602 ) -> Result<BTreeSet<ContractId>, InventoryError<Self::Error>> {
603 let outpoints = outpoints
604 .into_iter()
605 .map(|o| o.into())
606 .collect::<BTreeSet<_>>();
607 let mut selected = BTreeSet::new();
608 for (contract_id, index) in &self.contract_index {
609 for outpoint in &outpoints {
610 if index.outpoint_opouts.contains_key(outpoint) {
611 selected.insert(*contract_id);
612 }
613 }
614 }
615 Ok(selected)
616 }
617
618 fn public_opouts(
619 &mut self,
620 contract_id: ContractId,
621 ) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>> {
622 let index = self
623 .contract_index
624 .get(&contract_id)
625 .ok_or(StashInconsistency::ContractAbsent(contract_id))?;
626 Ok(index.public_opouts.to_inner())
627 }
628
629 fn opouts_by_outpoints(
630 &mut self,
631 contract_id: ContractId,
632 outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
633 ) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>> {
634 let index = self
635 .contract_index
636 .get(&contract_id)
637 .ok_or(StashInconsistency::ContractAbsent(contract_id))?;
638 let mut opouts = BTreeSet::new();
639 for outpoint in outpoints.into_iter().map(|o| o.into()) {
640 let set = index
641 .outpoint_opouts
642 .get(&outpoint)
643 .ok_or(DataError::OutpointUnknown(outpoint, contract_id))?;
644 opouts.extend(set)
645 }
646 Ok(opouts)
647 }
648
649 fn opouts_by_terminals(
650 &mut self,
651 terminals: impl IntoIterator<Item = SecretSeal>,
652 ) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>> {
653 let terminals = terminals.into_iter().collect::<BTreeSet<_>>();
654 Ok(self
655 .terminal_index
656 .iter()
657 .filter(|(seal, _)| terminals.contains(*seal))
658 .map(|(_, opout)| *opout)
659 .collect())
660 }
661
662 fn state_for_outpoints(
663 &mut self,
664 contract_id: ContractId,
665 outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
666 ) -> Result<BTreeMap<Opout, TypedState>, InventoryError<Self::Error>> {
667 let outpoints = outpoints
668 .into_iter()
669 .map(|o| o.into())
670 .collect::<BTreeSet<_>>();
671
672 let history = self
673 .history
674 .get(&contract_id)
675 .ok_or(StashInconsistency::ContractAbsent(contract_id))?;
676
677 let mut res = BTreeMap::new();
678
679 for output in history.fungibles() {
680 if outpoints.contains(&output.seal) {
681 res.insert(output.opout, TypedState::Amount(output.state.value.as_u64()));
682 }
683 }
684
685 for output in history.data() {
686 if outpoints.contains(&output.seal) {
687 res.insert(output.opout, TypedState::Data(output.state.clone()));
688 }
689 }
690
691 for output in history.rights() {
692 if outpoints.contains(&output.seal) {
693 res.insert(output.opout, TypedState::Void);
694 }
695 }
696
697 for output in history.attach() {
698 if outpoints.contains(&output.seal) {
699 res.insert(output.opout, TypedState::Attachment(output.state.clone().into()));
700 }
701 }
702
703 Ok(res)
704 }
705
706 fn store_seal_secret(&mut self, seal: GraphSeal) -> Result<(), InventoryError<Self::Error>> {
707 self.seal_secrets.push(seal)?;
708 Ok(())
709 }
710
711 fn seal_secrets(&mut self) -> Result<BTreeSet<GraphSeal>, InventoryError<Self::Error>> {
712 Ok(self.seal_secrets.to_inner())
713 }
714}