rgbstd/containers/
consignment.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::{iter, slice};
24
25use amplify::confinement::{LargeVec, MediumBlob, SmallOrdMap, TinyOrdMap, TinyOrdSet};
26use commit_verify::Conceal;
27use rgb::validation::{AnchoredBundle, ConsignmentApi};
28use rgb::{
29    validation, AttachId, BundleId, ContractHistory, ContractId, Extension, Genesis, GraphSeal,
30    OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, Transition, TransitionBundle,
31    WitnessAnchor,
32};
33use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize};
34
35use super::{ContainerVer, ContentId, ContentSigs, Terminal};
36use crate::accessors::BundleExt;
37use crate::interface::{ContractSuppl, IfaceId, IfacePair};
38use crate::resolvers::ResolveHeight;
39use crate::LIB_NAME_RGB_STD;
40
41pub type Transfer = Consignment<true>;
42pub type Contract = Consignment<false>;
43
44/// Consignment represents contract-specific data, always starting with genesis,
45/// which must be valid under client-side-validation rules (i.e. internally
46/// consistent and properly committed into the commitment layer, like bitcoin
47/// blockchain or current state of the lightning channel).
48///
49/// All consignments-related procedures, including validation or merging
50/// consignments data into stash or schema-specific data storage, must start
51/// with `endpoints` and process up to the genesis. If any of the nodes within
52/// the consignments are not part of the paths connecting endpoints with the
53/// genesis, consignments validation will return
54/// [`validation::Warning::ExcessiveNode`] warning.
55#[derive(Clone, Debug)]
56#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
57#[strict_type(lib = LIB_NAME_RGB_STD)]
58#[cfg_attr(
59    feature = "serde",
60    derive(Serialize, Deserialize),
61    serde(crate = "serde_crate", rename_all = "camelCase")
62)]
63pub struct Consignment<const TYPE: bool> {
64    /// Status of the latest validation.
65    ///
66    /// The value is not saved and when the structure is read from a disk or
67    /// network is left uninitialized. Thus, only locally-run verification by
68    /// this library is trusted.
69    #[strict_type(skip, dumb = None)]
70    #[cfg_attr(feature = "serde", serde(skip))]
71    pub(super) validation_status: Option<validation::Status>,
72
73    /// Version.
74    pub version: ContainerVer,
75
76    /// Specifies whether the consignment contains information about state
77    /// transfer (true), or it is just a consignment with an information about a
78    /// contract.
79    pub transfer: bool,
80
81    /// Schema (plus root schema, if any) under which contract is issued.
82    pub schema: SubSchema,
83
84    /// Interfaces supported by the contract.
85    pub ifaces: TinyOrdMap<IfaceId, IfacePair>,
86
87    /// Known supplements.
88    pub supplements: TinyOrdSet<ContractSuppl>,
89
90    /// Genesis data.
91    pub genesis: Genesis,
92
93    /// Set of seals which are history terminals.
94    pub terminals: SmallOrdMap<BundleId, Terminal>,
95
96    /// Data on all anchored state transitions contained in the consignments.
97    pub bundles: LargeVec<AnchoredBundle>,
98
99    /// Data on all state extensions contained in the consignments.
100    pub extensions: LargeVec<Extension>,
101
102    /// Data containers coming with this consignment. For the purposes of
103    /// in-memory consignments we are restricting the size of the containers to
104    /// 24 bit value (RGB allows containers up to 32-bit values in size).
105    pub attachments: SmallOrdMap<AttachId, MediumBlob>,
106
107    /// Signatures on the pieces of content which are the part of the
108    /// consignment.
109    pub signatures: TinyOrdMap<ContentId, ContentSigs>,
110}
111
112impl<const TYPE: bool> StrictSerialize for Consignment<TYPE> {}
113impl<const TYPE: bool> StrictDeserialize for Consignment<TYPE> {}
114
115impl<const TYPE: bool> Consignment<TYPE> {
116    /// # Panics
117    ///
118    /// If the provided schema is not the one which is used by genesis.
119    pub fn new(schema: SubSchema, genesis: Genesis) -> Self {
120        assert_eq!(schema.schema_id(), genesis.schema_id);
121        Consignment {
122            validation_status: None,
123            version: ContainerVer::V1,
124            transfer: TYPE,
125            schema,
126            ifaces: none!(),
127            supplements: none!(),
128            genesis,
129            terminals: none!(),
130            bundles: none!(),
131            extensions: none!(),
132            attachments: none!(),
133            signatures: none!(),
134        }
135    }
136
137    #[inline]
138    pub fn schema_id(&self) -> SchemaId { self.schema.schema_id() }
139
140    #[inline]
141    pub fn root_schema_id(&self) -> Option<SchemaId> {
142        self.schema.subset_of.as_ref().map(Schema::schema_id)
143    }
144
145    #[inline]
146    pub fn contract_id(&self) -> ContractId { self.genesis.contract_id() }
147
148    pub fn anchored_bundle(&self, bundle_id: BundleId) -> Option<&AnchoredBundle> {
149        self.bundles
150            .iter()
151            .find(|anchored_bundle| anchored_bundle.bundle.bundle_id() == bundle_id)
152    }
153
154    pub fn validation_status(&self) -> Option<&validation::Status> {
155        self.validation_status.as_ref()
156    }
157
158    pub fn into_validation_status(self) -> Option<validation::Status> { self.validation_status }
159
160    pub fn update_history<R: ResolveHeight>(
161        &self,
162        history: Option<&ContractHistory>,
163        resolver: &mut R,
164    ) -> Result<ContractHistory, R::Error> {
165        let mut history = history.cloned().unwrap_or_else(|| {
166            ContractHistory::with(
167                self.schema_id(),
168                self.root_schema_id(),
169                self.contract_id(),
170                &self.genesis,
171            )
172        });
173
174        let mut extension_idx = self
175            .extensions
176            .iter()
177            .map(Extension::id)
178            .zip(iter::repeat(false))
179            .collect::<BTreeMap<_, _>>();
180        let mut ordered_extensions = BTreeMap::new();
181        for anchored_bundle in &self.bundles {
182            for item in anchored_bundle.bundle.values() {
183                if let Some(transition) = &item.transition {
184                    let txid = anchored_bundle.anchor.txid;
185                    let height = resolver.resolve_height(txid)?;
186                    let ord_txid = WitnessAnchor::new(height, txid);
187                    history.add_transition(transition, ord_txid);
188                    for (id, used) in &mut extension_idx {
189                        if *used {
190                            continue;
191                        }
192                        for input in &transition.inputs {
193                            if input.prev_out.op == *id {
194                                *used = true;
195                                if let Some(ord) = ordered_extensions.get_mut(id) {
196                                    if *ord > ord_txid {
197                                        *ord = ord_txid;
198                                    }
199                                } else {
200                                    ordered_extensions.insert(*id, ord_txid);
201                                }
202                            }
203                        }
204                    }
205                }
206            }
207        }
208        for extension in &self.extensions {
209            if let Some(ord_txid) = ordered_extensions.get(&extension.id()) {
210                history.add_extension(extension, *ord_txid);
211            }
212        }
213
214        Ok(history)
215    }
216
217    pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: GraphSeal) {
218        for anchored_bundle in &mut self.bundles {
219            if anchored_bundle.bundle.bundle_id() == bundle_id {
220                anchored_bundle.bundle.reveal_seal(revealed);
221            }
222        }
223    }
224
225    pub fn into_contract(self) -> Contract {
226        Contract {
227            validation_status: self.validation_status,
228            version: self.version,
229            transfer: false,
230            schema: self.schema,
231            ifaces: self.ifaces,
232            supplements: self.supplements,
233            genesis: self.genesis,
234            terminals: self.terminals,
235            bundles: self.bundles,
236            extensions: self.extensions,
237            attachments: self.attachments,
238            signatures: self.signatures,
239        }
240    }
241}
242
243impl<const TYPE: bool> ConsignmentApi for Consignment<TYPE> {
244    type BundleIter<'container>
245    = slice::Iter<'container, AnchoredBundle> where Self: 'container;
246
247    fn schema(&self) -> &SubSchema { &self.schema }
248
249    fn operation(&self, opid: OpId) -> Option<OpRef> {
250        if opid == self.genesis.id() {
251            return Some(OpRef::Genesis(&self.genesis));
252        }
253        self.transition(opid)
254            .map(OpRef::from)
255            .or_else(|| self.extension(opid).map(OpRef::from))
256    }
257
258    fn genesis(&self) -> &Genesis { &self.genesis }
259
260    fn transition(&self, opid: OpId) -> Option<&Transition> {
261        for anchored_bundle in &self.bundles {
262            for (id, item) in anchored_bundle.bundle.iter() {
263                if *id == opid {
264                    return item.transition.as_ref();
265                }
266            }
267        }
268        None
269    }
270
271    fn extension(&self, opid: OpId) -> Option<&Extension> {
272        self.extensions
273            .iter()
274            .find(|&extension| extension.id() == opid)
275    }
276
277    fn terminals(&self) -> BTreeSet<(BundleId, SecretSeal)> {
278        self.terminals
279            .iter()
280            .flat_map(|(bundle_id, terminal)| {
281                terminal
282                    .seals
283                    .iter()
284                    .map(|seal| (*bundle_id, seal.conceal()))
285            })
286            .collect()
287    }
288
289    fn anchored_bundles(&self) -> Self::BundleIter<'_> { self.bundles.iter() }
290
291    fn bundle_by_id(&self, bundle_id: BundleId) -> Option<&TransitionBundle> {
292        self.anchored_bundle(bundle_id).map(|ab| &ab.bundle)
293    }
294
295    fn op_ids_except(&self, ids: &BTreeSet<OpId>) -> BTreeSet<OpId> {
296        let mut exceptions = BTreeSet::new();
297        for anchored_bundle in &self.bundles {
298            for item in anchored_bundle.bundle.values() {
299                if let Some(id) = item.transition.as_ref().map(Transition::id) {
300                    if !ids.contains(&id) {
301                        exceptions.insert(id);
302                    }
303                }
304            }
305        }
306        exceptions
307    }
308
309    fn has_operation(&self, opid: OpId) -> bool { self.operation(opid).is_some() }
310
311    fn known_transitions_by_bundle_id(&self, bundle_id: BundleId) -> Option<Vec<&Transition>> {
312        self.bundle_by_id(bundle_id).map(|bundle| {
313            bundle
314                .values()
315                .filter_map(|item| item.transition.as_ref())
316                .collect()
317        })
318    }
319}