use std::collections::{BTreeMap, BTreeSet};
use std::{iter, slice};
use amplify::confinement::{LargeVec, MediumBlob, SmallOrdMap, TinyOrdMap, TinyOrdSet};
use commit_verify::Conceal;
use rgb::validation::{AnchoredBundle, ConsignmentApi};
use rgb::{
validation, AttachId, BundleId, ContractHistory, ContractId, Extension, Genesis, GraphSeal,
OpId, OpRef, Operation, Schema, SchemaId, SecretSeal, SubSchema, Transition, TransitionBundle,
WitnessAnchor,
};
use strict_encoding::{StrictDeserialize, StrictDumb, StrictSerialize};
use super::{ContainerVer, ContentId, ContentSigs, Terminal};
use crate::accessors::BundleExt;
use crate::interface::{ContractSuppl, IfaceId, IfacePair};
use crate::resolvers::ResolveHeight;
use crate::LIB_NAME_RGB_STD;
pub type Transfer = Consignment<true>;
pub type Contract = Consignment<false>;
#[derive(Clone, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB_STD)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct Consignment<const TYPE: bool> {
#[strict_type(skip, dumb = None)]
#[cfg_attr(feature = "serde", serde(skip))]
pub(super) validation_status: Option<validation::Status>,
pub version: ContainerVer,
pub transfer: bool,
pub schema: SubSchema,
pub ifaces: TinyOrdMap<IfaceId, IfacePair>,
pub supplements: TinyOrdSet<ContractSuppl>,
pub genesis: Genesis,
pub terminals: SmallOrdMap<BundleId, Terminal>,
pub bundles: LargeVec<AnchoredBundle>,
pub extensions: LargeVec<Extension>,
pub attachments: SmallOrdMap<AttachId, MediumBlob>,
pub signatures: TinyOrdMap<ContentId, ContentSigs>,
}
impl<const TYPE: bool> StrictSerialize for Consignment<TYPE> {}
impl<const TYPE: bool> StrictDeserialize for Consignment<TYPE> {}
impl<const TYPE: bool> Consignment<TYPE> {
pub fn new(schema: SubSchema, genesis: Genesis) -> Self {
assert_eq!(schema.schema_id(), genesis.schema_id);
Consignment {
validation_status: None,
version: ContainerVer::V1,
transfer: TYPE,
schema,
ifaces: none!(),
supplements: none!(),
genesis,
terminals: none!(),
bundles: none!(),
extensions: none!(),
attachments: none!(),
signatures: none!(),
}
}
#[inline]
pub fn schema_id(&self) -> SchemaId { self.schema.schema_id() }
#[inline]
pub fn root_schema_id(&self) -> Option<SchemaId> {
self.schema.subset_of.as_ref().map(Schema::schema_id)
}
#[inline]
pub fn contract_id(&self) -> ContractId { self.genesis.contract_id() }
pub fn anchored_bundle(&self, bundle_id: BundleId) -> Option<&AnchoredBundle> {
self.bundles
.iter()
.find(|anchored_bundle| anchored_bundle.bundle.bundle_id() == bundle_id)
}
pub fn validation_status(&self) -> Option<&validation::Status> {
self.validation_status.as_ref()
}
pub fn into_validation_status(self) -> Option<validation::Status> { self.validation_status }
pub fn update_history<R: ResolveHeight>(
&self,
history: Option<&ContractHistory>,
resolver: &mut R,
) -> Result<ContractHistory, R::Error> {
let mut history = history.cloned().unwrap_or_else(|| {
ContractHistory::with(
self.schema_id(),
self.root_schema_id(),
self.contract_id(),
&self.genesis,
)
});
let mut extension_idx = self
.extensions
.iter()
.map(Extension::id)
.zip(iter::repeat(false))
.collect::<BTreeMap<_, _>>();
let mut ordered_extensions = BTreeMap::new();
for anchored_bundle in &self.bundles {
for item in anchored_bundle.bundle.values() {
if let Some(transition) = &item.transition {
let txid = anchored_bundle.anchor.txid;
let height = resolver.resolve_height(txid)?;
let ord_txid = WitnessAnchor::new(height, txid);
history.add_transition(transition, ord_txid);
for (id, used) in &mut extension_idx {
if *used {
continue;
}
for input in &transition.inputs {
if input.prev_out.op == *id {
*used = true;
if let Some(ord) = ordered_extensions.get_mut(id) {
if *ord > ord_txid {
*ord = ord_txid;
}
} else {
ordered_extensions.insert(*id, ord_txid);
}
}
}
}
}
}
}
for extension in &self.extensions {
if let Some(ord_txid) = ordered_extensions.get(&extension.id()) {
history.add_extension(extension, *ord_txid);
}
}
Ok(history)
}
pub fn reveal_bundle_seal(&mut self, bundle_id: BundleId, revealed: GraphSeal) {
for anchored_bundle in &mut self.bundles {
if anchored_bundle.bundle.bundle_id() == bundle_id {
anchored_bundle.bundle.reveal_seal(revealed);
}
}
}
pub fn into_contract(self) -> Contract {
Contract {
validation_status: self.validation_status,
version: self.version,
transfer: false,
schema: self.schema,
ifaces: self.ifaces,
supplements: self.supplements,
genesis: self.genesis,
terminals: self.terminals,
bundles: self.bundles,
extensions: self.extensions,
attachments: self.attachments,
signatures: self.signatures,
}
}
}
impl<const TYPE: bool> ConsignmentApi for Consignment<TYPE> {
type BundleIter<'container>
= slice::Iter<'container, AnchoredBundle> where Self: 'container;
fn schema(&self) -> &SubSchema { &self.schema }
fn operation(&self, opid: OpId) -> Option<OpRef> {
if opid == self.genesis.id() {
return Some(OpRef::Genesis(&self.genesis));
}
self.transition(opid)
.map(OpRef::from)
.or_else(|| self.extension(opid).map(OpRef::from))
}
fn genesis(&self) -> &Genesis { &self.genesis }
fn transition(&self, opid: OpId) -> Option<&Transition> {
for anchored_bundle in &self.bundles {
for (id, item) in anchored_bundle.bundle.iter() {
if *id == opid {
return item.transition.as_ref();
}
}
}
None
}
fn extension(&self, opid: OpId) -> Option<&Extension> {
self.extensions
.iter()
.find(|&extension| extension.id() == opid)
}
fn terminals(&self) -> BTreeSet<(BundleId, SecretSeal)> {
self.terminals
.iter()
.flat_map(|(bundle_id, terminal)| {
terminal
.seals
.iter()
.map(|seal| (*bundle_id, seal.conceal()))
})
.collect()
}
fn anchored_bundles(&self) -> Self::BundleIter<'_> { self.bundles.iter() }
fn bundle_by_id(&self, bundle_id: BundleId) -> Option<&TransitionBundle> {
self.anchored_bundle(bundle_id).map(|ab| &ab.bundle)
}
fn op_ids_except(&self, ids: &BTreeSet<OpId>) -> BTreeSet<OpId> {
let mut exceptions = BTreeSet::new();
for anchored_bundle in &self.bundles {
for item in anchored_bundle.bundle.values() {
if let Some(id) = item.transition.as_ref().map(Transition::id) {
if !ids.contains(&id) {
exceptions.insert(id);
}
}
}
}
exceptions
}
fn has_operation(&self, opid: OpId) -> bool { self.operation(opid).is_some() }
fn known_transitions_by_bundle_id(&self, bundle_id: BundleId) -> Option<Vec<&Transition>> {
self.bundle_by_id(bundle_id).map(|bundle| {
bundle
.values()
.filter_map(|item| item.transition.as_ref())
.collect()
})
}
}