use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;
use std::ops::Deref;
use amplify::confinement::{self, Confined};
use bp::seals::txout::blind::SingleBlindSeal;
use bp::Txid;
use commit_verify::mpc;
use rgb::{
validation, Anchor, AnchoredBundle, BundleId, ContractId, ExposedSeal, GraphSeal, OpId,
Operation, Opout, SchemaId, SecretSeal, SubSchema, Transition, TransitionBundle,
};
use strict_encoding::TypeName;
use crate::accessors::{BundleExt, MergeRevealError, RevealError};
use crate::containers::{
Bindle, BuilderSeal, Cert, Consignment, ContentId, Contract, Terminal, Transfer,
};
use crate::interface::{
ContractIface, Iface, IfaceId, IfaceImpl, IfacePair, TransitionBuilder, TypedState,
};
use crate::persistence::hoard::ConsumeError;
use crate::persistence::stash::StashInconsistency;
use crate::persistence::{Stash, StashError};
use crate::resolvers::ResolveHeight;
use crate::Outpoint;
#[derive(Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum ConsignerError<E1: Error, E2: Error> {
TooManyTerminals,
TooManyBundles,
ConcealedPublicState(Opout),
#[display(inner)]
#[from]
Reveal(RevealError),
#[display(inner)]
#[from]
#[from(InventoryInconsistency)]
InventoryError(InventoryError<E1>),
#[display(inner)]
#[from]
#[from(StashInconsistency)]
StashError(StashError<E2>),
}
#[derive(Debug, Display, Error, From)]
#[display(inner)]
pub enum InventoryError<E: Error> {
Connectivity(E),
#[from]
Consume(ConsumeError),
#[from]
#[from(confinement::Error)]
DataError(DataError),
#[from]
#[from(mpc::LeafNotKnown)]
#[from(mpc::InvalidProof)]
#[from(RevealError)]
#[from(StashInconsistency)]
InternalInconsistency(InventoryInconsistency),
}
impl<E1: Error, E2: Error> From<StashError<E1>> for InventoryError<E2>
where E2: From<E1>
{
fn from(err: StashError<E1>) -> Self {
match err {
StashError::Connectivity(err) => Self::Connectivity(err.into()),
StashError::InternalInconsistency(e) => {
Self::InternalInconsistency(InventoryInconsistency::Stash(e))
}
}
}
}
#[derive(Debug, Display, Error, From)]
#[display(inner)]
pub enum InventoryDataError<E: Error> {
Connectivity(E),
#[from]
#[from(validation::Status)]
#[from(confinement::Error)]
#[from(IfaceImplError)]
#[from(RevealError)]
#[from(MergeRevealError)]
DataError(DataError),
}
impl<E: Error> From<InventoryDataError<E>> for InventoryError<E> {
fn from(err: InventoryDataError<E>) -> Self {
match err {
InventoryDataError::Connectivity(e) => InventoryError::Connectivity(e),
InventoryDataError::DataError(e) => InventoryError::DataError(e),
}
}
}
#[derive(Debug, Display, Error, From)]
#[display(inner)]
pub enum DataError {
NotValidated,
#[from]
Invalid(validation::Status),
UnresolvedTransactions,
TerminalsUnmined,
#[display(inner)]
#[from]
Reveal(RevealError),
#[from]
#[display(inner)]
Merge(MergeRevealError),
OutpointUnknown(Outpoint, ContractId),
#[from]
Confinement(confinement::Error),
#[from]
IfaceImpl(IfaceImplError),
NoIfaceImpl(SchemaId, IfaceId),
#[from]
HeightResolver(Box<dyn Error>),
Concealed,
}
#[derive(Clone, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum IfaceImplError {
UnknownSchema(SchemaId),
UnknownIface(IfaceId),
}
#[derive(Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum InventoryInconsistency {
StateAbsent(ContractId),
DisclosureAbsent(Txid),
BundleAbsent(OpId),
NoBundleAnchor(BundleId),
#[from(mpc::LeafNotKnown)]
#[from(mpc::InvalidProof)]
UnrelatedAnchor,
#[from]
BundleReveal(RevealError),
OutsizedBundle,
#[from]
#[display(inner)]
Stash(StashInconsistency),
}
#[allow(clippy::result_large_err)]
pub trait Inventory: Deref<Target = Self::Stash> {
type Stash: Stash;
type Error: Error;
fn stash(&self) -> &Self::Stash;
fn import_sigs<I>(
&mut self,
content_id: ContentId,
sigs: I,
) -> Result<(), InventoryDataError<Self::Error>>
where
I: IntoIterator<Item = Cert>,
I::IntoIter: ExactSizeIterator<Item = Cert>;
fn import_schema(
&mut self,
schema: impl Into<Bindle<SubSchema>>,
) -> Result<validation::Status, InventoryDataError<Self::Error>>;
fn import_iface(
&mut self,
iface: impl Into<Bindle<Iface>>,
) -> Result<validation::Status, InventoryDataError<Self::Error>>;
fn import_iface_impl(
&mut self,
iimpl: impl Into<Bindle<IfaceImpl>>,
) -> Result<validation::Status, InventoryDataError<Self::Error>>;
fn import_contract<R: ResolveHeight>(
&mut self,
contract: Contract,
resolver: &mut R,
) -> Result<validation::Status, InventoryError<Self::Error>>
where
R::Error: 'static;
fn accept_transfer<R: ResolveHeight>(
&mut self,
transfer: Transfer,
resolver: &mut R,
force: bool,
) -> Result<validation::Status, InventoryError<Self::Error>>
where
R::Error: 'static;
fn consume_anchor(
&mut self,
anchor: Anchor<mpc::MerkleBlock>,
) -> Result<(), InventoryError<Self::Error>>;
fn consume_bundle(
&mut self,
contract_id: ContractId,
bundle: TransitionBundle,
witness_txid: Txid,
) -> Result<(), InventoryError<Self::Error>>;
unsafe fn import_contract_force<R: ResolveHeight>(
&mut self,
contract: Contract,
resolver: &mut R,
) -> Result<validation::Status, InventoryError<Self::Error>>
where
R::Error: 'static;
fn contracts_with_iface(
&mut self,
iface: impl Into<TypeName>,
) -> Result<Vec<ContractIface>, InventoryError<Self::Error>>
where
Self::Error: From<<Self::Stash as Stash>::Error>,
InventoryError<Self::Error>: From<<Self::Stash as Stash>::Error>,
{
let iface = iface.into();
let iface_id = self.iface_by_name(&iface)?.iface_id();
self.contract_ids_by_iface(&iface)?
.into_iter()
.map(|id| self.contract_iface(id, iface_id))
.collect()
}
fn contract_iface_named(
&mut self,
contract_id: ContractId,
iface: impl Into<TypeName>,
) -> Result<ContractIface, InventoryError<Self::Error>>
where
Self::Error: From<<Self::Stash as Stash>::Error>,
InventoryError<Self::Error>: From<<Self::Stash as Stash>::Error>,
{
let iface = iface.into();
let iface_id = self.iface_by_name(&iface)?.iface_id();
self.contract_iface(contract_id, iface_id)
}
fn contract_iface(
&mut self,
contract_id: ContractId,
iface_id: IfaceId,
) -> Result<ContractIface, InventoryError<Self::Error>>;
fn anchored_bundle(&self, opid: OpId) -> Result<AnchoredBundle, InventoryError<Self::Error>>;
fn transition_builder(
&mut self,
contract_id: ContractId,
iface: impl Into<TypeName>,
transition_name: Option<impl Into<TypeName>>,
) -> Result<TransitionBuilder, InventoryError<Self::Error>>
where
Self::Error: From<<Self::Stash as Stash>::Error>,
{
let schema_ifaces = self.contract_schema(contract_id)?;
let iface = self.iface_by_name(&iface.into())?;
let schema = &schema_ifaces.schema;
let iimpl = schema_ifaces
.iimpls
.get(&iface.iface_id())
.ok_or(DataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?;
let builder = if let Some(transition_name) = transition_name {
TransitionBuilder::named_transition(
iface.clone(),
schema.clone(),
iimpl.clone(),
transition_name.into(),
)
} else {
TransitionBuilder::default_transition(iface.clone(), schema.clone(), iimpl.clone())
}
.expect("internal inconsistency");
Ok(builder)
}
fn blank_builder(
&mut self,
contract_id: ContractId,
iface: impl Into<TypeName>,
) -> Result<TransitionBuilder, InventoryError<Self::Error>>
where
Self::Error: From<<Self::Stash as Stash>::Error>,
{
let schema_ifaces = self.contract_schema(contract_id)?;
let iface = self.iface_by_name(&iface.into())?;
let schema = &schema_ifaces.schema;
let iimpl = schema_ifaces
.iimpls
.get(&iface.iface_id())
.ok_or(DataError::NoIfaceImpl(schema.schema_id(), iface.iface_id()))?;
let builder =
TransitionBuilder::blank_transition(iface.clone(), schema.clone(), iimpl.clone())
.expect("internal inconsistency");
Ok(builder)
}
fn transition(&self, opid: OpId) -> Result<&Transition, InventoryError<Self::Error>>;
fn contracts_by_outpoints(
&mut self,
outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
) -> Result<BTreeSet<ContractId>, InventoryError<Self::Error>>;
fn public_opouts(
&mut self,
contract_id: ContractId,
) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>>;
fn opouts_by_outpoints(
&mut self,
contract_id: ContractId,
outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>>;
fn opouts_by_terminals(
&mut self,
terminals: impl IntoIterator<Item = SecretSeal>,
) -> Result<BTreeSet<Opout>, InventoryError<Self::Error>>;
fn state_for_outpoints(
&mut self,
contract_id: ContractId,
outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
) -> Result<BTreeMap<Opout, TypedState>, InventoryError<Self::Error>>;
fn store_seal_secret(&mut self, seal: GraphSeal) -> Result<(), InventoryError<Self::Error>>;
fn seal_secrets(&mut self) -> Result<BTreeSet<GraphSeal>, InventoryError<Self::Error>>;
#[allow(clippy::type_complexity)]
fn export_contract(
&mut self,
contract_id: ContractId,
) -> Result<
Bindle<Contract>,
ConsignerError<Self::Error, <<Self as Deref>::Target as Stash>::Error>,
> {
let mut consignment =
self.consign::<GraphSeal, false>(contract_id, [] as [GraphSeal; 0])?;
consignment.transfer = false;
Ok(consignment.into())
}
#[allow(clippy::type_complexity)]
fn transfer(
&mut self,
contract_id: ContractId,
seals: impl IntoIterator<Item = impl Into<BuilderSeal<SingleBlindSeal>>>,
) -> Result<
Bindle<Transfer>,
ConsignerError<Self::Error, <<Self as Deref>::Target as Stash>::Error>,
> {
let mut consignment = self.consign(contract_id, seals)?;
consignment.transfer = true;
Ok(consignment.into())
}
fn consign<Seal: ExposedSeal, const TYPE: bool>(
&mut self,
contract_id: ContractId,
seals: impl IntoIterator<Item = impl Into<BuilderSeal<Seal>>>,
) -> Result<
Consignment<TYPE>,
ConsignerError<Self::Error, <<Self as Deref>::Target as Stash>::Error>,
> {
let mut opouts = self.public_opouts(contract_id)?;
let (outpoint_seals, terminal_seals) = seals
.into_iter()
.map(|seal| match seal.into() {
BuilderSeal::Revealed(seal) => (seal.outpoint(), seal.conceal()),
BuilderSeal::Concealed(seal) => (None, seal),
})
.unzip::<_, _, Vec<_>, Vec<_>>();
opouts.extend(self.opouts_by_outpoints(contract_id, outpoint_seals.into_iter().flatten())?);
opouts.extend(self.opouts_by_terminals(terminal_seals.iter().copied())?);
let mut anchored_bundles = BTreeMap::<OpId, AnchoredBundle>::new();
let mut transitions = BTreeMap::<OpId, Transition>::new();
let mut terminals = BTreeMap::<BundleId, Terminal>::new();
for opout in opouts {
if opout.op == contract_id {
continue; }
let transition = self.transition(opout.op)?;
transitions.insert(opout.op, transition.clone());
let anchored_bundle = self.anchored_bundle(opout.op)?;
let bundle_id = anchored_bundle.bundle.bundle_id();
for (type_id, typed_assignments) in transition.assignments.iter() {
for index in 0..typed_assignments.len_u16() {
let seal = typed_assignments.to_confidential_seals()[index as usize];
if terminal_seals.contains(&seal) {
terminals.insert(bundle_id, Terminal::new(seal.into()));
} else if opout.no == index && opout.ty == *type_id {
if let Some(seal) = typed_assignments
.revealed_seal_at(index)
.expect("index exists")
{
terminals.insert(bundle_id, Terminal::new(seal.into()));
} else {
return Err(ConsignerError::ConcealedPublicState(opout));
}
}
}
}
anchored_bundles.insert(opout.op, anchored_bundle.clone());
}
let mut ids = vec![];
for transition in transitions.values() {
ids.extend(transition.inputs().iter().map(|input| input.prev_out.op));
}
while let Some(id) = ids.pop() {
if id == contract_id {
continue; }
let transition = self.transition(id)?;
ids.extend(transition.inputs().iter().map(|input| input.prev_out.op));
transitions.insert(id, transition.clone());
anchored_bundles
.entry(id)
.or_insert(self.anchored_bundle(id)?.clone())
.bundle
.reveal_transition(transition)?;
}
let genesis = self.genesis(contract_id)?;
let schema_ifaces = self.schema(genesis.schema_id)?;
let mut consignment = Consignment::new(schema_ifaces.schema.clone(), genesis.clone());
for (iface_id, iimpl) in &schema_ifaces.iimpls {
let iface = self.iface_by_id(*iface_id)?;
consignment
.ifaces
.insert(*iface_id, IfacePair::with(iface.clone(), iimpl.clone()))
.expect("same collection size");
}
consignment.bundles = Confined::try_from_iter(anchored_bundles.into_values())
.map_err(|_| ConsignerError::TooManyBundles)?;
consignment.terminals =
Confined::try_from(terminals).map_err(|_| ConsignerError::TooManyTerminals)?;
Ok(consignment)
}
}