rgbstd/persistence/
hoard.rs

1// RGB standard library for working with smart contracts on Bitcoin & Lightning
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Written in 2019-2023 by
6//     Dr Maxim Orlovsky <orlovsky@lnp-bp.org>
7//
8// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved.
9//
10// Licensed under the Apache License, Version 2.0 (the "License");
11// you may not use this file except in compliance with the License.
12// You may obtain a copy of the License at
13//
14//     http://www.apache.org/licenses/LICENSE-2.0
15//
16// Unless required by applicable law or agreed to in writing, software
17// distributed under the License is distributed on an "AS IS" BASIS,
18// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// See the License for the specific language governing permissions and
20// limitations under the License.
21
22use std::collections::{BTreeMap, BTreeSet};
23use std::convert::Infallible;
24
25use amplify::confinement;
26use amplify::confinement::{Confined, LargeOrdMap, SmallOrdMap, TinyOrdMap, TinyOrdSet};
27use bp::dbc::anchor::MergeError;
28use commit_verify::mpc;
29use commit_verify::mpc::MerkleBlock;
30use rgb::{
31    Anchor, AnchorId, AnchoredBundle, BundleId, ContractId, Extension, Genesis, OpId, Operation,
32    SchemaId, TransitionBundle,
33};
34use strict_encoding::TypeName;
35
36use crate::accessors::{MergeReveal, MergeRevealError};
37use crate::containers::{Cert, Consignment, ContentId, ContentSigs};
38use crate::interface::{rgb20, ContractSuppl, Iface, IfaceId, IfacePair, SchemaIfaces};
39use crate::persistence::{InventoryError, Stash, StashError, StashInconsistency};
40use crate::LIB_NAME_RGB_STD;
41
42#[derive(Clone, Eq, PartialEq, Debug, Display, Error, From)]
43#[display(inner)]
44pub enum ConsumeError {
45    #[from]
46    Confinement(confinement::Error),
47
48    #[from]
49    Anchor(mpc::InvalidProof),
50
51    #[from]
52    Merge(MergeError),
53
54    #[from]
55    MergeReveal(MergeRevealError),
56}
57
58impl From<Infallible> for InventoryError<Infallible> {
59    fn from(_: Infallible) -> Self { unreachable!() }
60}
61
62/// Hoard is an in-memory stash useful for WASM implementations.
63#[derive(Clone, Debug)]
64#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
65#[strict_type(lib = LIB_NAME_RGB_STD, dumb = Hoard::preset())]
66pub struct Hoard {
67    pub(super) schemata: TinyOrdMap<SchemaId, SchemaIfaces>,
68    pub(super) ifaces: TinyOrdMap<IfaceId, Iface>,
69    pub(super) geneses: TinyOrdMap<ContractId, Genesis>,
70    pub(super) suppl: TinyOrdMap<ContractId, TinyOrdSet<ContractSuppl>>,
71    pub(super) bundles: LargeOrdMap<BundleId, TransitionBundle>,
72    pub(super) extensions: LargeOrdMap<OpId, Extension>,
73    pub(super) anchors: LargeOrdMap<AnchorId, Anchor<mpc::MerkleBlock>>,
74    pub(super) sigs: SmallOrdMap<ContentId, ContentSigs>,
75}
76
77impl Hoard {
78    pub fn preset() -> Self {
79        let rgb20 = rgb20();
80        let rgb20_id = rgb20.iface_id();
81        Hoard {
82            schemata: none!(),
83            ifaces: tiny_bmap! {
84                rgb20_id => rgb20,
85            },
86            geneses: none!(),
87            suppl: none!(),
88            bundles: none!(),
89            extensions: none!(),
90            anchors: none!(),
91            sigs: none!(),
92        }
93    }
94
95    pub(super) fn import_sigs_internal<I>(
96        &mut self,
97        content_id: ContentId,
98        sigs: I,
99    ) -> Result<(), confinement::Error>
100    where
101        I: IntoIterator<Item = Cert>,
102        I::IntoIter: ExactSizeIterator<Item = Cert>,
103    {
104        let sigs = sigs.into_iter();
105        if sigs.len() > 0 {
106            if let Some(prev_sigs) = self.sigs.get_mut(&content_id) {
107                prev_sigs.extend(sigs)?;
108            } else {
109                let sigs = Confined::try_from_iter(sigs)?;
110                self.sigs.insert(content_id, ContentSigs::from(sigs)).ok();
111            }
112        }
113        Ok(())
114    }
115
116    // TODO: Move into Stash trait and re-implement using trait accessor methods
117    pub fn consume_consignment<const TYPE: bool>(
118        &mut self,
119        consignment: Consignment<TYPE>,
120    ) -> Result<(), ConsumeError> {
121        let contract_id = consignment.contract_id();
122        let schema_id = consignment.schema_id();
123
124        let iimpls = match self.schemata.get_mut(&schema_id) {
125            Some(si) => &mut si.iimpls,
126            None => {
127                self.schemata
128                    .insert(schema_id, SchemaIfaces::new(consignment.schema))?;
129                &mut self
130                    .schemata
131                    .get_mut(&schema_id)
132                    .expect("just inserted")
133                    .iimpls
134            }
135        };
136
137        for (iface_id, IfacePair { iface, iimpl }) in consignment.ifaces {
138            if !self.ifaces.contains_key(&iface_id) {
139                self.ifaces.insert(iface_id, iface)?;
140            };
141            // TODO: Update for newer implementations
142            if !iimpls.contains_key(&iface_id) {
143                iimpls.insert(iface_id, iimpl)?;
144            };
145        }
146
147        // TODO: filter most trusted signers
148        match self.suppl.get_mut(&contract_id) {
149            Some(entry) => {
150                entry.extend(consignment.supplements).ok();
151            }
152            None => {
153                self.suppl.insert(contract_id, consignment.supplements).ok();
154            }
155        }
156
157        match self.geneses.get_mut(&contract_id) {
158            Some(genesis) => *genesis = genesis.clone().merge_reveal(consignment.genesis)?,
159            None => {
160                self.geneses.insert(contract_id, consignment.genesis)?;
161            }
162        }
163
164        for extension in consignment.extensions {
165            let opid = extension.id();
166            match self.extensions.get_mut(&opid) {
167                Some(e) => *e = e.clone().merge_reveal(extension)?,
168                None => {
169                    self.extensions.insert(opid, extension)?;
170                }
171            }
172        }
173
174        for AnchoredBundle { anchor, bundle } in consignment.bundles {
175            let bundle_id = bundle.bundle_id();
176            let anchor = anchor.into_merkle_block(contract_id, bundle_id.into())?;
177            self.consume_anchor(anchor)?;
178            self.consume_bundle(bundle)?;
179        }
180
181        for (content_id, sigs) in consignment.signatures {
182            // Do not bother if we can't import all the sigs
183            self.import_sigs_internal(content_id, sigs).ok();
184        }
185
186        Ok(())
187    }
188
189    // TODO: Move into Stash trait and re-implement using trait accessor methods
190    pub fn consume_bundle(&mut self, bundle: TransitionBundle) -> Result<(), ConsumeError> {
191        let bundle_id = bundle.bundle_id();
192        match self.bundles.get_mut(&bundle_id) {
193            Some(b) => *b = b.clone().merge_reveal(bundle)?,
194            None => {
195                self.bundles.insert(bundle_id, bundle)?;
196            }
197        }
198        Ok(())
199    }
200
201    // TODO: Move into Stash trait and re-implement using trait accessor methods
202    pub fn consume_anchor(&mut self, anchor: Anchor<MerkleBlock>) -> Result<(), ConsumeError> {
203        let anchor_id = anchor.anchor_id();
204        match self.anchors.get_mut(&anchor_id) {
205            Some(a) => *a = a.clone().merge_reveal(anchor)?,
206            None => {
207                self.anchors.insert(anchor_id, anchor)?;
208            }
209        }
210        Ok(())
211    }
212}
213
214impl Stash for Hoard {
215    // With in-memory data we have no connectivity or I/O errors
216    type Error = Infallible;
217
218    fn ifaces(&self) -> Result<BTreeMap<IfaceId, TypeName>, Self::Error> {
219        Ok(self
220            .ifaces
221            .iter()
222            .map(|(id, iface)| (*id, iface.name.clone()))
223            .collect())
224    }
225
226    fn iface_by_name(&self, name: &TypeName) -> Result<&Iface, StashError<Self::Error>> {
227        self.ifaces
228            .values()
229            .find(|iface| &iface.name == name)
230            .ok_or_else(|| StashInconsistency::IfaceNameAbsent(name.clone()).into())
231    }
232    fn iface_by_id(&self, id: IfaceId) -> Result<&Iface, StashError<Self::Error>> {
233        self.ifaces
234            .get(&id)
235            .ok_or_else(|| StashInconsistency::IfaceAbsent(id).into())
236    }
237
238    fn schema_ids(&self) -> Result<BTreeSet<SchemaId>, Self::Error> {
239        Ok(self.schemata.keys().copied().collect())
240    }
241
242    fn schema(&self, schema_id: SchemaId) -> Result<&SchemaIfaces, StashError<Self::Error>> {
243        self.schemata
244            .get(&schema_id)
245            .ok_or_else(|| StashInconsistency::SchemaAbsent(schema_id).into())
246    }
247
248    fn contract_ids_by_iface(&self, name: &TypeName) -> Result<BTreeSet<ContractId>, Self::Error> {
249        let iface = self.iface_by_name(name).unwrap();
250        let iface_id = iface.iface_id();
251        let schemata = self
252            .schemata
253            .iter()
254            .filter(|(_, iface)| iface.iimpls.contains_key(&iface_id))
255            .map(|(schema_id, _)| schema_id)
256            .collect::<BTreeSet<_>>();
257        Ok(self
258            .geneses
259            .iter()
260            .filter(|(_, genesis)| schemata.contains(&genesis.schema_id))
261            .map(|(contract_id, _)| contract_id)
262            .copied()
263            .collect())
264    }
265
266    fn contract_ids(&self) -> Result<BTreeSet<ContractId>, Self::Error> {
267        Ok(self.geneses.keys().copied().collect())
268    }
269
270    fn contract_suppl(&self, contract_id: ContractId) -> Option<&TinyOrdSet<ContractSuppl>> {
271        self.suppl.get(&contract_id)
272    }
273
274    fn genesis(&self, contract_id: ContractId) -> Result<&Genesis, StashError<Self::Error>> {
275        self.geneses
276            .get(&contract_id)
277            .ok_or(StashInconsistency::ContractAbsent(contract_id).into())
278    }
279
280    fn bundle_ids(&self) -> Result<BTreeSet<BundleId>, Self::Error> {
281        Ok(self.bundles.keys().copied().collect())
282    }
283
284    fn bundle(&self, bundle_id: BundleId) -> Result<&TransitionBundle, StashError<Self::Error>> {
285        self.bundles
286            .get(&bundle_id)
287            .ok_or(StashInconsistency::BundleAbsent(bundle_id).into())
288    }
289
290    fn extension_ids(&self) -> Result<BTreeSet<OpId>, Self::Error> {
291        Ok(self.extensions.keys().copied().collect())
292    }
293
294    fn extension(&self, op_id: OpId) -> Result<&Extension, StashError<Self::Error>> {
295        self.extensions
296            .get(&op_id)
297            .ok_or(StashInconsistency::OperationAbsent(op_id).into())
298    }
299
300    fn anchor_ids(&self) -> Result<BTreeSet<AnchorId>, Self::Error> {
301        Ok(self.anchors.keys().copied().collect())
302    }
303
304    fn anchor(&self, anchor_id: AnchorId) -> Result<&Anchor<MerkleBlock>, StashError<Self::Error>> {
305        self.anchors
306            .get(&anchor_id)
307            .ok_or(StashInconsistency::AnchorAbsent(anchor_id).into())
308    }
309}