use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::convert::Infallible;
use std::error::Error;
use std::fmt::Debug;
use std::num::NonZeroU32;
use amplify::confinement::{Confined, LargeOrdSet};
use nonasync::persistence::{CloneNoPersistence, PersistenceError, PersistenceProvider};
use rgb::bitcoin::{OutPoint as Outpoint, Txid};
use rgb::dbc::{Anchor, Proof};
use rgb::validation::{
OpoutsDagData, OpoutsDagInfo, ResolveWitness, UnsafeHistoryMap, WitnessOrdProvider,
WitnessResolverError,
};
use rgb::vm::WitnessOrd;
use rgb::{
validation, AssignmentType, BundleId, ChainNet, ContractId, Genesis, GraphSeal, Identity,
KnownTransition, OpId, Operation, Opout, OutputSeal, Schema, SchemaId, SecretSeal, Transition,
TransitionType, TxoSeal, UnrelatedTransition,
};
use strict_types::FieldName;
use super::{
ContractStateRead, Index, IndexError, IndexInconsistency, IndexProvider, IndexReadProvider,
IndexWriteProvider, MemIndex, MemStash, MemState, Stash, StashDataError, StashError,
StashInconsistency, StashProvider, StashReadProvider, StashWriteProvider, State, StateError,
StateInconsistency, StateProvider, StateReadProvider, StateWriteProvider, StoreTransaction,
};
use crate::containers::{
Consignment, ContainerVer, Contract, Fascia, Kit, SealWitness, SecretSeals, Transfer,
ValidConsignment, ValidContract, ValidKit, ValidTransfer, WitnessBundle,
};
use crate::contract::{
AllocatedState, BuilderError, ContractBuilder, ContractData, IssuerWrapper, LinkError,
LinkableIssuerWrapper, LinkableSchemaWrapper, SchemaWrapper, TransitionBuilder,
};
use crate::info::{ContractInfo, SchemaInfo};
use crate::MergeRevealError;
pub type ContractAssignments = HashMap<OutputSeal, HashMap<Opout, AllocatedState>>;
type SortedBundlesWithDag = (Vec<WitnessBundle>, Option<OpoutsDagData>);
type ConsignmentWithOptDag<const TRANSFER: bool> = (Consignment<TRANSFER>, Option<OpoutsDagData>);
pub type ConsignmentWithDag<const TRANSFER: bool> = (Consignment<TRANSFER>, OpoutsDagData);
#[derive(Debug, Display, Error, From)]
#[display(inner)]
pub enum StockError<
S: StashProvider = MemStash,
H: StateProvider = MemState,
P: IndexProvider = MemIndex,
E: Error = Infallible,
> {
InvalidInput(E),
Resolver(String),
StashRead(<S as StashReadProvider>::Error),
StashWrite(<S as StashWriteProvider>::Error),
IndexRead(<P as IndexReadProvider>::Error),
IndexWrite(<P as IndexWriteProvider>::Error),
StateRead(<H as StateReadProvider>::Error),
StateWrite(<H as StateWriteProvider>::Error),
#[from]
#[display(doc_comments)]
StashInconsistency(StashInconsistency),
#[from]
#[display(doc_comments)]
StateInconsistency(StateInconsistency),
#[from]
#[display(doc_comments)]
IndexInconsistency(IndexInconsistency),
#[from]
StashData(StashDataError),
AbsentValidWitness,
BundlesInconsistency,
WitnessUnresolved(Txid, WitnessResolverError),
#[from]
ContractLinkError(LinkError),
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider, E: Error> From<StashError<S>>
for StockError<S, H, P, E>
{
fn from(err: StashError<S>) -> Self {
match err {
StashError::ReadProvider(err) => Self::StashRead(err),
StashError::WriteProvider(err) => Self::StashWrite(err),
StashError::Data(e) => Self::StashData(e),
StashError::Inconsistency(e) => Self::StashInconsistency(e),
}
}
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider, E: Error> From<StateError<H>>
for StockError<S, H, P, E>
{
fn from(err: StateError<H>) -> Self {
match err {
StateError::ReadProvider(err) => Self::StateRead(err),
StateError::WriteProvider(err) => Self::StateWrite(err),
StateError::Inconsistency(e) => Self::StateInconsistency(e),
StateError::Resolver(id, e) => Self::WitnessUnresolved(id, e),
StateError::AbsentValidWitness => Self::AbsentValidWitness,
}
}
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider, E: Error> From<IndexError<P>>
for StockError<S, H, P, E>
{
fn from(err: IndexError<P>) -> Self {
match err {
IndexError::ReadProvider(err) => Self::IndexRead(err),
IndexError::WriteProvider(err) => Self::IndexWrite(err),
IndexError::Inconsistency(e) => Self::IndexInconsistency(e),
}
}
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum ConsignError {
TooManyTerminals,
InvalidSecretSealsNumber,
TooManyBundles,
#[from]
#[display(inner)]
MergeReveal(MergeRevealError),
#[from]
#[display(inner)]
Transition(UnrelatedTransition),
Concealed(BundleId, OpId),
UnrelatedContract(ContractId),
ConcealedTransition(BundleId, OpId),
UnorderedTransition(BundleId, OpId),
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<ConsignError>
for StockError<S, H, P, ConsignError>
{
fn from(err: ConsignError) -> Self { Self::InvalidInput(err) }
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<MergeRevealError>
for StockError<S, H, P, ConsignError>
{
fn from(err: MergeRevealError) -> Self { Self::InvalidInput(err.into()) }
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<UnrelatedTransition>
for StockError<S, H, P, ConsignError>
{
fn from(err: UnrelatedTransition) -> Self { Self::InvalidInput(err.into()) }
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum ComposeError {
NoExtraOrChange(AssignmentType),
NoBeneficiaryOutput,
BeneficiaryVout,
InvoiceExpired,
NoContract,
InsufficientState,
TooManyInputs,
TooManyExtras,
#[from]
#[display(inner)]
Builder(BuilderError),
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<ComposeError>
for StockError<S, H, P, ComposeError>
{
fn from(err: ComposeError) -> Self { Self::InvalidInput(err) }
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<BuilderError>
for StockError<S, H, P, ComposeError>
{
fn from(err: BuilderError) -> Self { Self::InvalidInput(err.into()) }
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum FasciaError {
InvalidBundle(ContractId, BundleId),
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<FasciaError>
for StockError<S, H, P, FasciaError>
{
fn from(err: FasciaError) -> Self { Self::InvalidInput(err) }
}
#[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)]
#[display(inner)]
pub enum InputError {
#[from]
Compose(ComposeError),
#[from]
Consign(ConsignError),
#[from]
Fascia(FasciaError),
}
macro_rules! stock_err_conv {
($err1:ty, $err2:ty) => {
impl<S: StashProvider, H: StateProvider, P: IndexProvider> From<StockError<S, H, P, $err1>>
for StockError<S, H, P, $err2>
{
fn from(err: StockError<S, H, P, $err1>) -> Self {
match err {
StockError::InvalidInput(e) => StockError::InvalidInput(e.into()),
StockError::Resolver(e) => StockError::Resolver(e),
StockError::StashRead(e) => StockError::StashRead(e),
StockError::StashWrite(e) => StockError::StashWrite(e),
StockError::IndexRead(e) => StockError::IndexRead(e),
StockError::IndexWrite(e) => StockError::IndexWrite(e),
StockError::StateRead(e) => StockError::StateRead(e),
StockError::StateWrite(e) => StockError::StateWrite(e),
StockError::AbsentValidWitness => StockError::AbsentValidWitness,
StockError::BundlesInconsistency => StockError::BundlesInconsistency,
StockError::StashData(e) => StockError::StashData(e),
StockError::StashInconsistency(e) => StockError::StashInconsistency(e),
StockError::StateInconsistency(e) => StockError::StateInconsistency(e),
StockError::IndexInconsistency(e) => StockError::IndexInconsistency(e),
StockError::WitnessUnresolved(id, e) => StockError::WitnessUnresolved(id, e),
StockError::ContractLinkError(e) => StockError::ContractLinkError(e),
}
}
}
};
}
impl From<Infallible> for InputError {
fn from(_: Infallible) -> Self { unreachable!() }
}
impl From<Infallible> for ComposeError {
fn from(_: Infallible) -> Self { unreachable!() }
}
impl From<Infallible> for ConsignError {
fn from(_: Infallible) -> Self { unreachable!() }
}
impl From<Infallible> for FasciaError {
fn from(_: Infallible) -> Self { unreachable!() }
}
stock_err_conv!(Infallible, ComposeError);
stock_err_conv!(Infallible, ConsignError);
stock_err_conv!(Infallible, FasciaError);
stock_err_conv!(Infallible, InputError);
stock_err_conv!(ComposeError, InputError);
stock_err_conv!(ConsignError, InputError);
stock_err_conv!(FasciaError, InputError);
pub type StockErrorMem<E = Infallible> = StockError<MemStash, MemState, MemIndex, E>;
pub type StockErrorAll<S = MemStash, H = MemState, P = MemIndex> = StockError<S, H, P, InputError>;
#[derive(Debug)]
pub struct Stock<
S: StashProvider = MemStash,
H: StateProvider = MemState,
P: IndexProvider = MemIndex,
> {
stash: Stash<S>,
state: State<H>,
index: Index<P>,
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> CloneNoPersistence for Stock<S, H, P> {
fn clone_no_persistence(&self) -> Self {
Self {
stash: self.stash.clone_no_persistence(),
state: self.state.clone_no_persistence(),
index: self.index.clone_no_persistence(),
}
}
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> Default for Stock<S, H, P>
where
S: Default,
H: Default,
P: Default,
{
fn default() -> Self {
Self {
stash: default!(),
state: default!(),
index: default!(),
}
}
}
impl Stock {
#[inline]
pub fn in_memory() -> Self {
Self::with(MemStash::in_memory(), MemState::in_memory(), MemIndex::in_memory())
}
}
impl<S: StashProvider, H: StateProvider, I: IndexProvider> Stock<S, H, I> {
pub fn load<P>(provider: P, autosave: bool) -> Result<Self, PersistenceError>
where P: Clone
+ PersistenceProvider<S>
+ PersistenceProvider<H>
+ PersistenceProvider<I>
+ 'static {
let stash = S::load(provider.clone(), autosave)?;
let state = H::load(provider.clone(), autosave)?;
let index = I::load(provider, autosave)?;
Ok(Self::with(stash, state, index))
}
pub fn make_persistent<P>(
&mut self,
provider: P,
autosave: bool,
) -> Result<bool, PersistenceError>
where
P: Clone
+ PersistenceProvider<S>
+ PersistenceProvider<H>
+ PersistenceProvider<I>
+ 'static,
{
let a = self
.as_stash_provider_mut()
.make_persistent(provider.clone(), autosave)?;
let b = self
.as_state_provider_mut()
.make_persistent(provider.clone(), autosave)?;
let c = self
.as_index_provider_mut()
.make_persistent(provider, autosave)?;
Ok(a && b && c)
}
pub fn store(&mut self) -> Result<(), PersistenceError> {
self.as_stash_provider_mut().store()?;
self.as_state_provider_mut().store()?;
self.as_index_provider_mut().store()?;
Ok(())
}
}
impl<S: StashProvider, H: StateProvider, P: IndexProvider> Stock<S, H, P> {
pub fn with(stash_provider: S, state_provider: H, index_provider: P) -> Self {
Stock {
stash: Stash::new(stash_provider),
state: State::new(state_provider),
index: Index::new(index_provider),
}
}
#[doc(hidden)]
pub fn as_stash_provider(&self) -> &S { self.stash.as_provider() }
#[doc(hidden)]
pub fn as_state_provider(&self) -> &H { self.state.as_provider() }
#[doc(hidden)]
pub fn as_index_provider(&self) -> &P { self.index.as_provider() }
#[doc(hidden)]
pub fn as_stash_provider_mut(&mut self) -> &mut S { self.stash.as_provider_mut() }
#[doc(hidden)]
pub fn as_state_provider_mut(&mut self) -> &mut H { self.state.as_provider_mut() }
#[doc(hidden)]
pub fn as_index_provider_mut(&mut self) -> &mut P { self.index.as_provider_mut() }
pub fn schemata(&self) -> Result<impl Iterator<Item = SchemaInfo> + '_, StockError<S, H, P>> {
Ok(self.stash.schemata()?.map(SchemaInfo::with))
}
pub fn schema(&self, schema_id: SchemaId) -> Result<&Schema, StockError<S, H, P>> {
Ok(self.stash.schema(schema_id)?)
}
pub fn contracts(
&self,
) -> Result<impl Iterator<Item = ContractInfo> + '_, StockError<S, H, P>> {
Ok(self.stash.geneses()?.map(ContractInfo::with))
}
pub fn contracts_assigning(
&self,
outputs: impl IntoIterator<Item = impl Into<Outpoint>>,
) -> Result<impl Iterator<Item = ContractId> + '_, StockError<S, H, P>> {
let outputs = outputs
.into_iter()
.map(|o| o.into())
.collect::<BTreeSet<_>>();
Ok(self.index.contracts_assigning(outputs)?)
}
#[allow(clippy::type_complexity)]
fn contract_raw(
&self,
contract_id: ContractId,
) -> Result<(&Schema, H::ContractRead<'_>, ContractInfo), StockError<S, H, P>> {
let state = self.state.contract_state(contract_id)?;
let schema_id = state.schema_id();
let schema = self.stash.schema(schema_id)?;
Ok((schema, state, self.contract_info(contract_id)?))
}
pub fn contract_info(
&self,
contract_id: ContractId,
) -> Result<ContractInfo, StockError<S, H, P>> {
Ok(ContractInfo::with(self.stash.genesis(contract_id)?))
}
pub fn contract_state(
&self,
contract_id: ContractId,
) -> Result<H::ContractRead<'_>, StockError<S, H, P>> {
self.state
.contract_state(contract_id)
.map_err(StockError::from)
}
pub fn contract_wrapper<C: IssuerWrapper>(
&self,
contract_id: ContractId,
) -> Result<C::Wrapper<H::ContractRead<'_>>, StockError<S, H, P>> {
self.schema_wrapper::<C::Wrapper<_>>(contract_id)
}
fn schema_wrapper<'a, C: SchemaWrapper<H::ContractRead<'a>>>(
&'a self,
contract_id: ContractId,
) -> Result<C, StockError<S, H, P>> {
let contract_data = self.contract_data(contract_id)?;
Ok(C::with(contract_data))
}
pub fn contract_data(
&self,
contract_id: ContractId,
) -> Result<ContractData<H::ContractRead<'_>>, StockError<S, H, P>> {
let (schema, state, info) = self.contract_raw(contract_id)?;
let (types, _) = self.stash.extract(schema)?;
Ok(ContractData {
state,
schema: schema.clone(),
types,
info,
})
}
pub fn contract_assignments_for(
&self,
contract_id: ContractId,
outpoints: impl IntoIterator<Item = impl Into<Outpoint>>,
) -> Result<ContractAssignments, StockError<S, H, P>> {
let outputs: BTreeSet<Outpoint> = outpoints.into_iter().map(|o| o.into()).collect();
let state = self.contract_state(contract_id)?;
let mut res =
HashMap::<OutputSeal, HashMap<Opout, AllocatedState>>::with_capacity(outputs.len());
for item in state.fungible_all() {
let outpoint = item.seal.into();
if outputs.contains::<Outpoint>(&outpoint) {
res.entry(item.seal)
.or_default()
.insert(item.opout, AllocatedState::Amount(item.state));
}
}
for item in state.data_all() {
let outpoint = item.seal.into();
if outputs.contains::<Outpoint>(&outpoint) {
res.entry(item.seal)
.or_default()
.insert(item.opout, AllocatedState::Data(item.state.clone()));
}
}
for item in state.rights_all() {
let outpoint = item.seal.into();
if outputs.contains::<Outpoint>(&outpoint) {
res.entry(item.seal)
.or_default()
.insert(item.opout, AllocatedState::Void);
}
}
Ok(res)
}
pub fn contract_builder(
&self,
issuer: impl Into<Identity>,
schema_id: SchemaId,
chain_net: ChainNet,
) -> Result<ContractBuilder, StockError<S, H, P>> {
Ok(self
.stash
.contract_builder(issuer.into(), schema_id, chain_net)?)
}
pub fn transition_builder(
&self,
contract_id: ContractId,
transition_name: impl Into<FieldName>,
) -> Result<TransitionBuilder, StockError<S, H, P>> {
Ok(self
.stash
.transition_builder(contract_id, transition_name)?)
}
pub fn transition_builder_raw(
&self,
contract_id: ContractId,
transition_type: TransitionType,
) -> Result<TransitionBuilder, StockError<S, H, P>> {
Ok(self
.stash
.transition_builder_raw(contract_id, transition_type)?)
}
pub fn export_schema(&self, schema_id: SchemaId) -> Result<ValidKit, StockError<S, H, P>> {
let mut kit = Kit::default();
let schema = self.schema(schema_id)?;
kit.schemata.push(schema.clone()).expect("single item");
let (types, scripts) = self.stash.extract(schema)?;
kit.scripts
.extend(scripts.into_values())
.expect("type guarantees");
kit.types = types;
Ok(kit.validate().expect("stock produced invalid kit"))
}
pub fn export_contract(
&self,
contract_id: ContractId,
) -> Result<Contract, StockError<S, H, P, ConsignError>> {
self.consign::<false>(contract_id, [], vec![], [], None, false)
.map(|(c, _)| c)
}
pub fn transfer(
&self,
contract_id: ContractId,
outputs: impl AsRef<[OutputSeal]>,
secret_seals: impl AsRef<[SecretSeal]>,
opids: impl IntoIterator<Item = OpId>,
witness_id: Option<Txid>,
) -> Result<Transfer, StockError<S, H, P, ConsignError>> {
self.consign(contract_id, outputs, secret_seals, opids, witness_id, false)
.map(|(c, _)| c)
}
pub fn transfer_with_dag(
&self,
contract_id: ContractId,
outputs: impl AsRef<[OutputSeal]>,
secret_seals: impl AsRef<[SecretSeal]>,
opids: impl IntoIterator<Item = OpId>,
witness_id: Option<Txid>,
) -> Result<ConsignmentWithDag<true>, StockError<S, H, P, ConsignError>> {
self.consign(contract_id, outputs, secret_seals, opids, witness_id, true)
.map(|(c, d)| (c, d.unwrap()))
}
fn sort_bundles(
&self,
bundles: BTreeMap<BundleId, (WitnessBundle, u32)>,
contract_id: ContractId,
build_opouts_dag: bool,
genesis: &Genesis,
) -> Result<SortedBundlesWithDag, StockError<S, H, P, ConsignError>> {
let mut dag_info = None;
if build_opouts_dag {
dag_info = Some(OpoutsDagInfo::new());
}
if let Some(ref mut dag_info) = dag_info {
dag_info.register_outputs(genesis, &genesis.id());
}
let bundles_len = bundles.len();
if bundles_len <= 1 {
let bundles = bundles.into_values().map(|(b, _)| b).collect::<Vec<_>>();
if let Some(ref mut dag_info) = dag_info {
dag_info.build_dag(
&bundles
.iter()
.flat_map(|wb| wb.bundle.known_transitions.iter())
.collect::<Vec<_>>(),
);
}
return Ok((bundles, dag_info.map(|d| d.to_opouts_dag_data())));
}
let mut bundles_with_height = bundles.into_iter().collect::<Vec<_>>();
bundles_with_height.sort_by_key(|(_, (_, num))| *num);
let mut needs_reordering = false;
let bundle_positions = bundles_with_height
.iter()
.enumerate()
.map(|(i, (bundle_id, (_, _)))| (*bundle_id, i))
.collect::<HashMap<_, _>>();
'outer: for (i, (_, (witness_bundle, _))) in bundles_with_height.iter().enumerate() {
for KnownTransition { transition, opid } in &witness_bundle.bundle.known_transitions {
if let Some(ref mut dag_info) = dag_info {
dag_info.register_outputs(transition, opid);
}
for input in &transition.inputs {
if let Some(ref mut dag_info) = dag_info {
dag_info.connect_input_to_outputs_by_opid(input, opid);
}
if input.op != contract_id {
let input_bundle_id = self.index.bundle_id_for_op(input.op)?;
if let Some(&input_pos) = bundle_positions.get(&input_bundle_id) {
if input_pos > i {
needs_reordering = true;
break 'outer;
}
}
}
}
}
}
if !needs_reordering {
let bundles = bundles_with_height
.into_iter()
.map(|(_, (wb, _))| wb)
.collect::<Vec<_>>();
return Ok((bundles, dag_info.map(|d| d.to_opouts_dag_data())));
}
let mut known_bundle_dependencies: HashMap<BundleId, HashSet<BundleId>> =
HashMap::with_capacity(bundles_len);
for (bundle_id, (witness_bundle, _)) in &bundles_with_height {
for KnownTransition { transition, opid } in &witness_bundle.bundle.known_transitions {
if let Some(ref mut dag_info) = dag_info {
dag_info.register_outputs(transition, opid);
}
for input in &transition.inputs {
if let Some(ref mut dag_info) = dag_info {
dag_info.connect_input_to_outputs_by_opid(input, opid);
}
if input.op != contract_id {
let input_bundle_id = self.index.bundle_id_for_op(input.op)?;
if bundle_positions.contains_key(&input_bundle_id)
&& input_bundle_id != *bundle_id
{
known_bundle_dependencies
.entry(*bundle_id)
.or_default()
.insert(input_bundle_id);
}
}
}
}
}
let mut sorted_bundles: Vec<WitnessBundle> = Vec::with_capacity(bundles_len);
let mut remaining = bundles_with_height
.into_iter()
.map(|(id, (wb, _))| (id, wb))
.collect::<Vec<_>>();
while !remaining.is_empty() {
let processed_ids = sorted_bundles
.iter()
.map(|wb| wb.bundle.bundle_id())
.collect::<HashSet<_>>();
let mut found = false;
let mut i = 0;
while i < remaining.len() {
let (bundle_id, _) = &remaining[i];
let dependencies = known_bundle_dependencies
.get(bundle_id)
.cloned()
.unwrap_or_default();
if dependencies.is_subset(&processed_ids) {
let (_, witness_bundle) = remaining.remove(i);
sorted_bundles.push(witness_bundle);
found = true;
break;
}
i += 1;
}
if !found {
return Err(StockError::BundlesInconsistency);
}
}
Ok((sorted_bundles, dag_info.map(|d| d.to_opouts_dag_data())))
}
fn consign<const TRANSFER: bool>(
&self,
contract_id: ContractId,
outputs: impl AsRef<[OutputSeal]>,
secret_seals: impl AsRef<[SecretSeal]>,
opids: impl IntoIterator<Item = OpId>,
witness_id: Option<Txid>,
build_opouts_dag: bool,
) -> Result<ConsignmentWithOptDag<TRANSFER>, StockError<S, H, P, ConsignError>> {
let outputs = outputs.as_ref();
let secret_seals = secret_seals.as_ref();
let mut opids = opids.into_iter().collect::<HashSet<_>>();
opids.extend(
self.index
.public_opouts(contract_id)?
.into_iter()
.chain(
self.index
.opouts_by_outputs(contract_id, outputs.iter().copied())?,
)
.chain(
self.index
.opouts_by_terminals(secret_seals.iter().copied())?,
)
.map(|opout| opout.op),
);
self.consign_operations(contract_id, opids, secret_seals, witness_id, build_opouts_dag)
}
fn consign_operations<const TRANSFER: bool>(
&self,
contract_id: ContractId,
opids: impl IntoIterator<Item = OpId>,
secret_seals: &[SecretSeal],
witness_id: Option<Txid>,
build_opouts_dag: bool,
) -> Result<ConsignmentWithOptDag<TRANSFER>, StockError<S, H, P, ConsignError>> {
let mut bundles = BTreeMap::<BundleId, (WitnessBundle, u32)>::new();
let mut parent_opids = Vec::<OpId>::new();
let mut bundle_sec_seals: BTreeMap<BundleId, BTreeSet<SecretSeal>> = BTreeMap::new();
for opid in opids {
if opid == contract_id {
continue; }
let transition = self.transition(opid)?;
let bundle_id = self.index.bundle_id_for_op(transition.id())?;
if let Some(witness_id) = witness_id {
let (mut witness_ids, _) = self.index.bundle_info(bundle_id)?;
if !witness_ids.any(|w| w == witness_id) {
continue;
}
}
parent_opids.extend(transition.inputs().iter().map(|input| input.op));
for typed_assignments in transition.assignments.values() {
for seal in typed_assignments.to_confidential_seals() {
if secret_seals.contains(&seal) {
bundle_sec_seals.entry(bundle_id).or_default().insert(seal);
}
}
}
if let Some((wbundle, _)) = bundles.get_mut(&bundle_id) {
wbundle.bundle.reveal_transition(transition.clone())?;
} else {
bundles.insert(bundle_id, self.witness_bundle(bundle_id, opid)?);
};
}
self.consign_bundles(contract_id, bundles, parent_opids, bundle_sec_seals, build_opouts_dag)
}
fn consign_bundles<const TRANSFER: bool>(
&self,
contract_id: ContractId,
mut bundles: BTreeMap<BundleId, (WitnessBundle, u32)>,
mut parent_opids: Vec<OpId>,
bundle_sec_seals: BTreeMap<BundleId, BTreeSet<SecretSeal>>,
build_opouts_dag: bool,
) -> Result<ConsignmentWithOptDag<TRANSFER>, StockError<S, H, P, ConsignError>> {
let mut seen_ids = HashSet::new();
while let Some(id) = parent_opids.pop() {
if id == contract_id {
continue; }
if !seen_ids.insert(id) {
continue; }
let transition = self.transition(id)?;
parent_opids.extend(transition.inputs().iter().map(|input| input.op));
let bundle_id = self.index.bundle_id_for_op(transition.id())?;
if let Some((wbundle, _)) = bundles.get_mut(&bundle_id) {
wbundle.bundle.reveal_transition(transition.clone())?;
} else {
bundles.insert(bundle_id, self.witness_bundle(bundle_id, id)?);
};
}
let genesis = self.stash.genesis(contract_id)?.clone();
let schema = self.stash.schema(genesis.schema_id)?.clone();
let (sorted_bundles, dag) =
self.sort_bundles(bundles, contract_id, build_opouts_dag, &genesis)?;
let bundles =
Confined::try_from_iter(sorted_bundles).map_err(|_| ConsignError::TooManyBundles)?;
let terminals = Confined::try_from(
bundle_sec_seals
.into_iter()
.map(|(bundle_id, seals)| {
Confined::try_from(seals)
.map(|confined| (bundle_id, SecretSeals::from(confined)))
.map_err(|_| ConsignError::InvalidSecretSealsNumber)
})
.collect::<Result<BTreeMap<_, _>, _>>()?,
)
.map_err(|_| ConsignError::TooManyTerminals)?;
let (types, scripts) = self.stash.extract(&schema)?;
let scripts = Confined::from_iter_checked(scripts.into_values());
let consignment = Consignment {
version: ContainerVer::V0,
transfer: TRANSFER,
schema,
genesis,
terminals,
bundles,
types,
scripts,
};
Ok((consignment, dag))
}
pub fn transfer_from_fascia(
&self,
contract_id: ContractId,
outputs: impl AsRef<[OutputSeal]>,
secret_seals: impl AsRef<[SecretSeal]>,
opids: impl IntoIterator<Item = OpId>,
fascia: &Fascia,
) -> Result<Consignment<true>, StockError<S, H, P, ConsignError>> {
self.consign_from_fascia(contract_id, outputs, secret_seals, opids, fascia, false)
.map(|(c, _)| c)
}
pub fn transfer_from_fascia_with_dag(
&self,
contract_id: ContractId,
outputs: impl AsRef<[OutputSeal]>,
secret_seals: impl AsRef<[SecretSeal]>,
opids: impl IntoIterator<Item = OpId>,
fascia: &Fascia,
) -> Result<ConsignmentWithDag<true>, StockError<S, H, P, ConsignError>> {
self.consign_from_fascia(contract_id, outputs, secret_seals, opids, fascia, true)
.map(|(c, d)| (c, d.expect("build_opouts_dag=true")))
}
fn consign_from_fascia(
&self,
contract_id: ContractId,
outputs: impl AsRef<[OutputSeal]>,
secret_seals: impl AsRef<[SecretSeal]>,
opids: impl IntoIterator<Item = OpId>,
fascia: &Fascia,
build_opouts_dag: bool,
) -> Result<ConsignmentWithOptDag<true>, StockError<S, H, P, ConsignError>> {
let mut contract_bundle = fascia
.bundles()
.get(&contract_id)
.ok_or(ConsignError::UnrelatedContract(contract_id))?
.clone();
let bundle_id = contract_bundle.bundle_id();
let all_bundle_opids = contract_bundle.input_map_opids();
let bundle_revealed_opids = contract_bundle.known_transitions_opids();
let opids = opids.into_iter().collect::<HashSet<_>>();
let secret_seals = secret_seals
.as_ref()
.iter()
.cloned()
.collect::<BTreeSet<_>>();
let outputs = outputs.as_ref().iter().collect::<HashSet<_>>();
let witness_id = fascia.witness_id();
let is_requested_transition = |kt: &KnownTransition| {
if opids.contains(&kt.opid) {
return true; }
for typed_assigns in kt.transition.assignments.values() {
for index in 0..typed_assigns.len_u16() {
match typed_assigns
.revealed_seal_at(index)
.expect("cycling indexes")
{
Some(s) => {
if outputs.contains(&OutputSeal::with(witness_id, s.vout())) {
return true; }
}
None => {
if secret_seals.contains(
&typed_assigns
.confidential_seal_at(index)
.expect("cycling indexes"),
) {
return true; }
}
}
}
}
false
};
let mut required_opids = bset![];
let mut rev_bundle_transitions = vec![];
for known_transition in contract_bundle.known_transitions.into_iter().rev() {
if required_opids.contains(&known_transition.opid)
|| is_requested_transition(&known_transition)
{
required_opids.remove(&known_transition.opid);
required_opids.extend(known_transition.transition.inputs.iter().map(|o| o.op));
rev_bundle_transitions.push(known_transition);
}
}
if let Some(opid) = required_opids.intersection(&all_bundle_opids).next() {
if let Some(opid) = required_opids.intersection(&bundle_revealed_opids).next() {
return Err(ConsignError::ConcealedTransition(bundle_id, *opid).into());
}
return Err(ConsignError::UnorderedTransition(bundle_id, *opid).into());
}
rev_bundle_transitions.reverse();
contract_bundle.known_transitions = Confined::from_checked(rev_bundle_transitions);
let SealWitness {
public: pub_witness,
merkle_block,
dbc_proof,
} = fascia.seal_witness().clone();
let anchor = Anchor::new(
merkle_block
.into_merkle_proof(contract_id.into())
.map_err(|_| ConsignError::UnrelatedContract(contract_id))?,
dbc_proof,
);
let bundle_sec_seals = if !secret_seals.is_empty() {
bmap! {bundle_id => secret_seals}
} else {
bmap! {}
};
self.consign_bundles(
contract_id,
bmap! {bundle_id => (WitnessBundle::with(pub_witness, anchor, contract_bundle), u32::MAX)},
required_opids.into_iter().collect::<Vec<_>>(),
bundle_sec_seals,
build_opouts_dag,
)
}
fn store_transaction<E: Error>(
&mut self,
f: impl FnOnce(
&mut Stash<S>,
&mut State<H>,
&mut Index<P>,
) -> Result<(), StockError<S, H, P, E>>,
) -> Result<(), StockError<S, H, P, E>> {
self.state.begin_transaction()?;
self.stash
.begin_transaction()
.inspect_err(|_| self.stash.rollback_transaction())?;
self.index.begin_transaction().inspect_err(|_| {
self.state.rollback_transaction();
self.stash.rollback_transaction();
})?;
f(&mut self.stash, &mut self.state, &mut self.index)?;
self.index
.commit_transaction()
.map_err(StockError::from)
.and_then(|_| self.state.commit_transaction().map_err(StockError::from))
.and_then(|_| self.stash.commit_transaction().map_err(StockError::from))
.inspect_err(|_| {
self.state.rollback_transaction();
self.stash.rollback_transaction();
self.index.rollback_transaction();
})
}
pub fn import_kit(&mut self, kit: ValidKit) -> Result<validation::Status, StockError<S, H, P>> {
let (kit, status) = kit.split();
self.stash.begin_transaction()?;
self.stash.consume_kit(kit)?;
self.stash.commit_transaction()?;
Ok(status)
}
pub fn import_contract<R: ResolveWitness>(
&mut self,
contract: ValidContract,
resolver: R,
) -> Result<(), StockError<S, H, P>> {
self.consume_consignment(contract, resolver)
}
pub fn accept_transfer<R: ResolveWitness>(
&mut self,
contract: ValidTransfer,
resolver: R,
) -> Result<(), StockError<S, H, P>> {
self.consume_consignment(contract, resolver)
}
fn consume_consignment<R: ResolveWitness, const TRANSFER: bool>(
&mut self,
consignment: ValidConsignment<TRANSFER>,
resolver: R,
) -> Result<(), StockError<S, H, P>> {
let consignment = self.stash.resolve_secrets(consignment.into_consignment())?;
self.store_transaction(move |stash, state, index| {
state.update_from_consignment(&consignment, &resolver)?;
index.index_consignment(&consignment)?;
stash.consume_consignment(consignment)?;
Ok(())
})?;
Ok(())
}
pub fn consume_fascia<WP: WitnessOrdProvider>(
&mut self,
fascia: Fascia,
witness_ord_provider: WP,
) -> Result<(), StockError<S, H, P, FasciaError>> {
self.store_transaction(move |stash, state, index| {
let witness_id = fascia.witness_id();
stash.consume_witness(fascia.seal_witness())?;
for (contract_id, bundle) in fascia.into_bundles() {
bundle
.check_opid_commitments()
.map_err(|_| FasciaError::InvalidBundle(contract_id, bundle.bundle_id()))?;
index.index_bundle(contract_id, &bundle, witness_id)?;
state.update_from_bundle(
contract_id,
&bundle,
witness_id,
&witness_ord_provider,
)?;
stash.consume_bundle(bundle)?;
}
Ok(())
})
}
fn transition(&self, opid: OpId) -> Result<&Transition, StockError<S, H, P, ConsignError>> {
let bundle_id = self.index.bundle_id_for_op(opid)?;
let bundle = self.stash.bundle(bundle_id)?;
bundle
.get_transition(opid)
.ok_or(ConsignError::Concealed(bundle_id, opid).into())
}
fn witness_bundle(
&self,
bundle_id: BundleId,
opid: OpId,
) -> Result<(WitnessBundle, u32), StockError<S, H, P, ConsignError>> {
let (witness_ids, contract_id) = self.index.bundle_info(bundle_id)?;
let bundle = self
.stash
.bundle(bundle_id)?
.to_concealed_except(opid)
.map_err(|e| StockError::from(ConsignError::Transition(e)))?;
let (witness_id, witness_ord) = self.state.select_valid_witness(witness_ids)?;
let witness = self.stash.witness(witness_id)?;
let pub_witness = witness.public.clone();
let Ok(mpc_proof) = witness.merkle_block.to_merkle_proof(contract_id.into()) else {
return Err(StashInconsistency::WitnessMissesContract(
witness_id,
bundle_id,
contract_id,
witness.dbc_proof.method(),
)
.into());
};
let anchor = Anchor::new(mpc_proof, witness.dbc_proof.clone());
let height = match witness_ord {
WitnessOrd::Mined(pos) => pos.height().into(),
WitnessOrd::Tentative => u32::MAX - 1,
WitnessOrd::Ignored => u32::MAX,
WitnessOrd::Archived => unreachable!("select_valid_witness prevents this"),
};
Ok((WitnessBundle::with(pub_witness, anchor, bundle), height))
}
pub fn store_secret_seal(&mut self, seal: GraphSeal) -> Result<bool, StockError<S, H, P>> {
Ok(self.stash.store_secret_seal(seal)?)
}
fn set_bundles_as_invalid(&mut self, bundle_id: &BundleId) -> Result<(), StockError<S, H, P>> {
self.state.update_bundle(*bundle_id, false)?;
let bundle = self.stash.bundle(*bundle_id)?.clone();
for opid in bundle.known_transitions_opids() {
let children_bundle_ids = match self.index.bundle_ids_children_of_op(opid) {
Ok(bundle_ids) => bundle_ids,
Err(IndexError::Inconsistency(IndexInconsistency::BundleAbsent(_))) => {
return Ok(());
}
Err(e) => return Err(e.into()),
};
for child_bundle_id in children_bundle_ids {
self.set_bundles_as_invalid(&child_bundle_id)?;
}
}
Ok(())
}
fn maybe_update_bundles_as_valid(
&mut self,
bundle_id: &BundleId,
invalid_bundles: &mut LargeOrdSet<BundleId>,
maybe_became_valid_bundle_ids: &mut BTreeSet<BundleId>,
) -> Result<bool, StockError<S, H, P>> {
let bundle = self.stash.bundle(*bundle_id)?.clone();
let mut valid = true;
for KnownTransition { transition, .. } in &bundle.known_transitions {
for input in &transition.inputs {
let input_opid = input.op;
let input_bundle_id = match self.index.bundle_id_for_op(input_opid) {
Ok(id) => Some(id),
Err(IndexError::Inconsistency(IndexInconsistency::BundleAbsent(_))) => {
None
}
Err(e) => return Err(e.into()),
};
if let Some(input_bundle_id) = input_bundle_id {
if maybe_became_valid_bundle_ids.contains(&input_bundle_id) {
valid = self.maybe_update_bundles_as_valid(
&input_bundle_id,
invalid_bundles,
maybe_became_valid_bundle_ids,
)?;
} else if invalid_bundles.contains(&input_bundle_id) {
valid = false;
break;
}
}
}
}
maybe_became_valid_bundle_ids.remove(bundle_id);
if valid {
self.state.update_bundle(*bundle_id, true)?;
invalid_bundles.remove(bundle_id).unwrap();
for KnownTransition { opid, .. } in bundle.known_transitions {
let children_bundle_ids = match self.index.bundle_ids_children_of_op(opid) {
Ok(bundle_ids) => bundle_ids,
Err(IndexError::Inconsistency(IndexInconsistency::BundleAbsent(_))) => {
small_bset![]
}
Err(e) => return Err(e.into()),
};
for child_bundle_id in children_bundle_ids {
self.maybe_update_bundles_as_valid(
&child_bundle_id,
invalid_bundles,
maybe_became_valid_bundle_ids,
)?;
}
}
}
Ok(valid)
}
fn update_witness_ord(
&mut self,
resolver: impl ResolveWitness,
id: &Txid,
ord: &mut WitnessOrd,
became_invalid_witnesses: &mut BTreeMap<Txid, BTreeSet<BundleId>>,
became_valid_witnesses: &mut BTreeMap<Txid, BTreeSet<BundleId>>,
) -> Result<(), StockError<S, H, P>> {
let new = resolver
.resolve_witness(*id)
.map_err(|e| StockError::WitnessUnresolved(*id, e))?
.witness_ord();
let changed = *ord != new;
if changed {
let bundle_valid = match (*ord, new) {
(WitnessOrd::Archived, _) => Some(true),
(_, WitnessOrd::Archived) => Some(false),
_ => None,
};
if let Some(valid) = bundle_valid {
let seal_witness = self.stash.witness(*id)?;
let bundle_ids: BTreeSet<_> = seal_witness.known_bundle_ids().collect();
if valid {
became_valid_witnesses.insert(*id, bundle_ids);
} else {
became_invalid_witnesses.insert(*id, bundle_ids);
}
}
self.state.upsert_witness(*id, new)?;
*ord = new
}
Ok(())
}
pub fn update_witnesses(
&mut self,
resolver: impl ResolveWitness,
after_height: u32,
force_witnesses: Vec<Txid>,
) -> Result<UpdateRes, StockError<S, H, P>> {
let after_height = NonZeroU32::new(after_height).unwrap_or(NonZeroU32::MIN);
let mut succeeded = 0;
let mut failed = map![];
self.state.begin_transaction()?;
let witnesses = self.as_state_provider().witnesses();
let mut witnesses = witnesses.release();
let mut became_invalid_witnesses = bmap!();
let mut became_valid_witnesses = bmap!();
for (id, ord) in &mut witnesses {
if matches!(ord, WitnessOrd::Ignored) && !force_witnesses.contains(id) {
continue;
}
if matches!(ord, WitnessOrd::Mined(pos) if pos.height() < after_height) {
continue;
}
match self.update_witness_ord(
&resolver,
id,
ord,
&mut became_invalid_witnesses,
&mut became_valid_witnesses,
) {
Ok(()) => {
succeeded += 1;
}
Err(err) => {
failed.insert(*id, err.to_string());
}
}
}
for bundle_ids in became_invalid_witnesses.values() {
for bundle_id in bundle_ids {
let bundle_witness_ids: BTreeSet<Txid> =
self.index.bundle_info(*bundle_id)?.0.collect();
if bundle_witness_ids
.iter()
.all(|id| !witnesses.get(id).unwrap().is_valid())
{
self.set_bundles_as_invalid(bundle_id)?;
}
}
}
let mut maybe_became_valid_bundle_ids = bset!();
let mut invalid_bundles_pre = self.as_state_provider().invalid_bundles();
for bundle_ids in became_valid_witnesses.values() {
maybe_became_valid_bundle_ids.extend(bundle_ids);
}
for bundle_ids in became_valid_witnesses.values() {
for bundle_id in bundle_ids {
self.maybe_update_bundles_as_valid(
bundle_id,
&mut invalid_bundles_pre,
&mut maybe_became_valid_bundle_ids,
)?;
}
}
self.state.commit_transaction()?;
Ok(UpdateRes { succeeded, failed })
}
pub fn upsert_witness(
&mut self,
witness_id: Txid,
witness_ord: WitnessOrd,
) -> Result<(), StockError<S, H, P>> {
self.store_transaction(move |_stash, state, _index| {
Ok(state.upsert_witness(witness_id, witness_ord)?)
})
}
fn _check_bundle_history(
&self,
bundle_id: &BundleId,
safe_height: NonZeroU32,
contract_history: &mut HashMap<ContractId, HashMap<u32, HashSet<Txid>>>,
) -> Result<(), StockError<S, H, P>> {
let (bundle_witness_ids, contract_id) = self.index.bundle_info(*bundle_id)?;
let (witness_id, ord) = self.state.select_valid_witness(bundle_witness_ids)?;
match ord {
WitnessOrd::Mined(witness_pos) => {
let witness_height = witness_pos.height();
if witness_height > safe_height {
contract_history
.entry(contract_id)
.or_default()
.entry(witness_height.into())
.or_default()
.insert(witness_id);
}
}
WitnessOrd::Tentative | WitnessOrd::Ignored | WitnessOrd::Archived => {
contract_history
.entry(contract_id)
.or_default()
.entry(0)
.or_default()
.insert(witness_id);
}
}
let bundle = self.stash.bundle(*bundle_id)?.clone();
for KnownTransition { transition, .. } in bundle.known_transitions {
for input in &transition.inputs {
let input_opid = input.op;
let input_bundle_id = match self.index.bundle_id_for_op(input_opid) {
Ok(id) => Some(id),
Err(IndexError::Inconsistency(IndexInconsistency::BundleAbsent(_))) => {
None
}
Err(e) => return Err(e.into()),
};
if let Some(input_bundle_id) = input_bundle_id {
self._check_bundle_history(&input_bundle_id, safe_height, contract_history)?;
}
}
}
Ok(())
}
pub fn get_outpoint_unsafe_history(
&self,
outpoint: Outpoint,
safe_height: NonZeroU32,
) -> Result<HashMap<ContractId, UnsafeHistoryMap>, StockError<S, H, P>> {
let mut contract_history: HashMap<ContractId, HashMap<u32, HashSet<Txid>>> = HashMap::new();
for id in self.contracts_assigning([outpoint])? {
let state = self.contract_assignments_for(id, [outpoint])?;
for opid in state
.values()
.flat_map(|assigns| assigns.keys().map(|opout| opout.op))
{
let bundle_id = self.index.bundle_id_for_op(opid)?;
self._check_bundle_history(&bundle_id, safe_height, &mut contract_history)?;
}
}
Ok(contract_history)
}
pub fn validate_contracts_link<Parent: LinkableIssuerWrapper, Child: LinkableIssuerWrapper>(
&self,
parent_contract_id: ContractId,
child_contract_id: ContractId,
) -> Result<(), StockError<S, H, P>> {
let parent_links_to_child = self
.schema_wrapper::<<Parent as LinkableIssuerWrapper>::Wrapper<_>>(parent_contract_id)?
.link_to()?
.ok_or(LinkError::NoValue)?
== child_contract_id;
let child_links_to_parent = self
.schema_wrapper::<<Child as LinkableIssuerWrapper>::Wrapper<_>>(child_contract_id)?
.link_from()?
.ok_or(LinkError::NoValue)?
== parent_contract_id;
if parent_links_to_child && child_links_to_parent {
Ok(())
} else {
Err(LinkError::ValueMismatch.into())
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct UpdateRes {
pub succeeded: usize,
pub failed: HashMap<Txid, String>,
}
#[cfg(test)]
mod test {
use baid64::FromBaid64Str;
use rgb::commit_verify::{Conceal, DigestExt, Sha256};
use rgb::Vout;
use super::*;
use crate::containers::ConsignmentExt;
#[test]
fn test_consign() {
let mut stock = Stock::in_memory();
let seal = GraphSeal::new_random_vout(Vout::from_u32(0));
let secret_seal = seal.conceal();
stock.store_secret_seal(seal).unwrap();
let contract_id =
ContractId::from_baid64_str("rgb:qFuT6DN8-9AuO95M-7R8R8Mc-AZvs7zG-obum1Va-BRnweKk")
.unwrap();
if let Ok(transfer) =
stock.consign::<true>(contract_id, [], vec![secret_seal], [], None, false)
{
println!("{transfer:?}")
}
}
#[test]
fn test_export_contract() {
let stock = Stock::in_memory();
let contract_id =
ContractId::from_baid64_str("rgb:qFuT6DN8-9AuO95M-7R8R8Mc-AZvs7zG-obum1Va-BRnweKk")
.unwrap();
if let Ok(contract) = stock.export_contract(contract_id) {
println!("{:?}", contract.contract_id())
}
}
#[test]
fn test_export_schema() {
let stock = Stock::in_memory();
let hasher = Sha256::default();
let schema_id = SchemaId::from(hasher);
if let Ok(schema) = stock.export_schema(schema_id) {
println!("{:?}", schema.kit_id())
}
}
#[test]
fn test_transition_builder() {
let stock = Stock::in_memory();
let hasher = Sha256::default();
let bytes_hash = hasher.finish();
let contract_id = ContractId::copy_from_slice(bytes_hash).unwrap();
if let Ok(builder) = stock.transition_builder(contract_id, "transfer") {
println!("{:?}", builder.transition_type())
}
}
}