diff --git a/src/containers/anchors.rs b/src/containers/anchors.rs index d9beb866..18c0b094 100644 --- a/src/containers/anchors.rs +++ b/src/containers/anchors.rs @@ -20,18 +20,36 @@ // limitations under the License. use std::cmp::Ordering; +use std::vec; use amplify::ByteArray; use bp::dbc::opret::OpretProof; use bp::dbc::tapret::TapretProof; use bp::dbc::{anchor, Anchor}; -use bp::{Tx, Txid}; +use bp::{dbc, Tx, Txid}; use commit_verify::mpc; -use rgb::validation::DbcProof; -use rgb::{BundleId, DiscloseHash, TransitionBundle, XChain, XWitnessId}; +use rgb::validation::{DbcProof, EAnchor}; +use rgb::{ + BundleId, DiscloseHash, OpId, Operation, Transition, TransitionBundle, XChain, XGraphSeal, + XWitnessId, +}; use strict_encoding::StrictDumb; -use crate::{MergeReveal, MergeRevealError, LIB_NAME_RGB_STD}; +use crate::containers::Dichotomy; +use crate::{MergeReveal, MergeRevealError, TypedAssignsExt, LIB_NAME_RGB_STD}; + +#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] +#[display("state transition {0} is not a part of the bundle.")] +pub struct UnrelatedTransition(OpId, Transition); + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error)] +#[display(doc_comments)] +pub enum AnchoredBundleMismatch { + /// witness bundle for witness id {0} already has both opret and tapret information. + AlreadyDouble(XWitnessId), + /// the combined anchored bundles for witness id {0} are of the same type. + SameBundleType(XWitnessId), +} #[derive(Clone, Eq, PartialEq, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] @@ -159,36 +177,264 @@ impl PubWitness { )] #[derive(CommitEncode)] #[commit_encode(strategy = strict, id = DiscloseHash)] -pub struct WitnessBundle { +pub struct WitnessBundle { pub pub_witness: XPubWitness, - pub anchor: Anchor, - pub bundle: TransitionBundle, + pub anchored_bundles: AnchoredBundles, } -impl PartialEq for WitnessBundle

{ +impl PartialEq for WitnessBundle { fn eq(&self, other: &Self) -> bool { self.pub_witness == other.pub_witness } } -impl Ord for WitnessBundle

{ +impl Ord for WitnessBundle { fn cmp(&self, other: &Self) -> Ordering { self.pub_witness.cmp(&other.pub_witness) } } -impl PartialOrd for WitnessBundle

{ +impl PartialOrd for WitnessBundle { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl WitnessBundle { +impl WitnessBundle { + #[inline] + pub fn with(pub_witness: XPubWitness, anchored_bundle: AnchoredBundle) -> Self { + Self { + pub_witness, + anchored_bundles: AnchoredBundles::from(anchored_bundle), + } + } + + pub fn into_double(mut self, other: AnchoredBundle) -> Result { + match (self.anchored_bundles, other.anchor.dbc_proof) { + (AnchoredBundles::Double { .. }, _) => { + return Err(AnchoredBundleMismatch::AlreadyDouble( + self.pub_witness.to_witness_id(), + )); + } + (AnchoredBundles::Opret(opret), DbcProof::Tapret(tapret)) => { + let anchor = Anchor::new(other.anchor.mpc_proof, tapret); + self.anchored_bundles = AnchoredBundles::Double { + tapret: AnchoredBundle::new(anchor, other.bundle), + opret, + } + } + (AnchoredBundles::Tapret(tapret), DbcProof::Opret(opret)) => { + let anchor = Anchor::new(other.anchor.mpc_proof, opret); + self.anchored_bundles = AnchoredBundles::Double { + opret: AnchoredBundle::new(anchor, other.bundle), + tapret, + } + } + _ => { + return Err(AnchoredBundleMismatch::SameBundleType( + self.pub_witness.to_witness_id(), + )); + } + } + Ok(self) + } + pub fn witness_id(&self) -> XWitnessId { self.pub_witness.to_witness_id() } + + pub fn reveal_seal(&mut self, bundle_id: BundleId, seal: XGraphSeal) -> bool { + let bundle = match &mut self.anchored_bundles { + AnchoredBundles::Tapret(tapret) | AnchoredBundles::Double { tapret, .. } + if tapret.bundle.bundle_id() == bundle_id => + { + Some(&mut tapret.bundle) + } + AnchoredBundles::Opret(opret) | AnchoredBundles::Double { opret, .. } + if opret.bundle.bundle_id() == bundle_id => + { + Some(&mut opret.bundle) + } + _ => None, + }; + let Some(bundle) = bundle else { + return false; + }; + bundle + .known_transitions + .values_mut() + .flat_map(|t| t.assignments.values_mut()) + .for_each(|a| a.reveal_seal(seal)); + + true + } + + pub fn anchored_bundles(&self) -> impl Iterator { + self.anchored_bundles.iter() + } + + #[inline] + pub fn known_transitions(&self) -> impl Iterator { + self.anchored_bundles + .bundles() + .flat_map(|bundle| bundle.known_transitions.values()) + } } -impl WitnessBundle { - pub fn merge_reveal(mut self, other: Self) -> Result { - self.pub_witness = self.pub_witness.merge_reveal(other.pub_witness)?; - if self.anchor != other.anchor { - return Err(MergeRevealError::AnchorsNonEqual(self.bundle.bundle_id())); +/// Struct ensuring invariant between anchor and transition bundle methods. +#[derive(Clone, PartialEq, Eq, 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 AnchoredBundle { + anchor: Anchor, + bundle: TransitionBundle, +} + +impl AnchoredBundle { + /// # Panics + /// + /// Panics if anchor and bundle have different closing methods + pub fn new(anchor: Anchor, bundle: TransitionBundle) -> Self { + assert_eq!(anchor.method, bundle.close_method); + Self { anchor, bundle } + } + + #[inline] + pub fn bundle_id(&self) -> BundleId { self.bundle.bundle_id() } + + pub fn reveal_transition( + &mut self, + transition: Transition, + ) -> Result { + let opid = transition.id(); + if self.bundle.input_map.values().all(|id| *id != opid) { + return Err(UnrelatedTransition(opid, transition)); } - self.bundle = self.bundle.merge_reveal(other.bundle)?; - Ok(self) + if self.bundle.known_transitions.contains_key(&opid) { + return Ok(false); + } + self.bundle + .known_transitions + .insert(opid, transition) + .expect("same size as input map"); + Ok(true) + } +} + +#[derive(Clone, PartialEq, Eq, Debug)] +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_STD, tags = custom)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub enum AnchoredBundles { + #[strict_type(tag = 0x01)] + Tapret(AnchoredBundle), + #[strict_type(tag = 0x02)] + Opret(AnchoredBundle), + #[strict_type(tag = 0x03)] + Double { + tapret: AnchoredBundle, + opret: AnchoredBundle, + }, +} + +impl StrictDumb for AnchoredBundles { + fn strict_dumb() -> Self { Self::Opret(strict_dumb!()) } +} + +impl From for AnchoredBundles { + fn from(ab: AnchoredBundle) -> Self { + match ab.anchor.dbc_proof { + DbcProof::Opret(proof) => Self::Opret(AnchoredBundle::::new( + Anchor::new(ab.anchor.mpc_proof, proof), + ab.bundle, + )), + DbcProof::Tapret(proof) => Self::Tapret(AnchoredBundle::::new( + Anchor::new(ab.anchor.mpc_proof, proof), + ab.bundle, + )), + } + } +} + +impl AnchoredBundles { + pub fn bundles(&self) -> impl Iterator { + match self { + AnchoredBundles::Tapret(tapret) => Dichotomy::single(&tapret.bundle), + AnchoredBundles::Opret(opret) => Dichotomy::single(&opret.bundle), + AnchoredBundles::Double { tapret, opret } => { + Dichotomy::double(&tapret.bundle, &opret.bundle) + } + } + .into_iter() + } + + pub fn into_bundles(self) -> impl Iterator { + match self { + AnchoredBundles::Tapret(tapret) => Dichotomy::single(tapret.bundle), + AnchoredBundles::Opret(opret) => Dichotomy::single(opret.bundle), + AnchoredBundles::Double { tapret, opret } => { + Dichotomy::double(tapret.bundle, opret.bundle) + } + } + .into_iter() + } + + pub fn iter(&self) -> impl Iterator { + match self { + AnchoredBundles::Tapret(tapret) => { + let anchor = EAnchor::new( + tapret.anchor.mpc_proof.clone(), + tapret.anchor.dbc_proof.clone().into(), + ); + Dichotomy::single((anchor, &tapret.bundle)) + } + AnchoredBundles::Opret(opret) => { + let anchor = EAnchor::new( + opret.anchor.mpc_proof.clone(), + opret.anchor.dbc_proof.clone().into(), + ); + Dichotomy::single((anchor, &opret.bundle)) + } + AnchoredBundles::Double { tapret, opret } => { + let tapret_anchor = EAnchor::new( + tapret.anchor.mpc_proof.clone(), + tapret.anchor.dbc_proof.clone().into(), + ); + let opret_anchor = EAnchor::new( + opret.anchor.mpc_proof.clone(), + opret.anchor.dbc_proof.clone().into(), + ); + Dichotomy::double((tapret_anchor, &tapret.bundle), (opret_anchor, &opret.bundle)) + } + } + .into_iter() + } +} + +impl IntoIterator for AnchoredBundles { + type Item = (EAnchor, TransitionBundle); + type IntoIter = vec::IntoIter<(EAnchor, TransitionBundle)>; + + fn into_iter(self) -> Self::IntoIter { + match self { + AnchoredBundles::Tapret(tapret) => { + let anchor = EAnchor::new(tapret.anchor.mpc_proof, tapret.anchor.dbc_proof.into()); + Dichotomy::single((anchor, tapret.bundle)) + } + AnchoredBundles::Opret(opret) => { + let anchor = EAnchor::new(opret.anchor.mpc_proof, opret.anchor.dbc_proof.into()); + Dichotomy::single((anchor, opret.bundle)) + } + AnchoredBundles::Double { tapret, opret } => { + let tapret_anchor = + EAnchor::new(tapret.anchor.mpc_proof, tapret.anchor.dbc_proof.into()); + let opret_anchor = + EAnchor::new(opret.anchor.mpc_proof, opret.anchor.dbc_proof.into()); + Dichotomy::double((tapret_anchor, tapret.bundle), (opret_anchor, opret.bundle)) + } + } + .into_iter() } } diff --git a/src/containers/consignment.rs b/src/containers/consignment.rs index cd91798f..e49df199 100644 --- a/src/containers/consignment.rs +++ b/src/containers/consignment.rs @@ -50,7 +50,7 @@ use super::{ use crate::interface::{Iface, IfaceImpl}; use crate::persistence::{MemContract, MemContractState}; use crate::resolvers::ConsignmentResolver; -use crate::{BundleExt, SecretSeal, LIB_NAME_RGB_STD}; +use crate::{SecretSeal, LIB_NAME_RGB_STD}; pub type Transfer = Consignment; pub type Contract = Consignment; @@ -296,8 +296,7 @@ impl Consignment { for mut witness_bundle in self.bundles { for (bundle_id, secret) in &self.terminals { if let Some(seal) = f(*secret)? { - if witness_bundle.bundle.bundle_id() == *bundle_id { - witness_bundle.bundle.reveal_seal(seal); + if witness_bundle.reveal_seal(*bundle_id, seal) { break; } } diff --git a/src/containers/indexed.rs b/src/containers/indexed.rs index a8e332a5..355d8c28 100644 --- a/src/containers/indexed.rs +++ b/src/containers/indexed.rs @@ -36,7 +36,7 @@ use crate::containers::anchors::ToWitnessId; pub struct IndexedConsignment<'c, const TRANSFER: bool> { consignment: &'c Consignment, scripts: Scripts, - anchor_idx: BTreeMap, + anchor_idx: BTreeMap, bundle_idx: BTreeMap, op_witness_idx: BTreeMap, op_bundle_idx: BTreeMap, @@ -61,14 +61,15 @@ impl<'c, const TRANSFER: bool> IndexedConsignment<'c, TRANSFER> { for witness_bundle in &consignment.bundles { witness_idx .insert(witness_bundle.pub_witness.to_witness_id(), &witness_bundle.pub_witness); - let bundle = &witness_bundle.bundle; - let bundle_id = bundle.bundle_id(); let witness_id = witness_bundle.pub_witness.to_witness_id(); - bundle_idx.insert(bundle_id, bundle); - anchor_idx.insert(bundle_id, (witness_id, &witness_bundle.anchor)); - for opid in witness_bundle.bundle.known_transitions.keys() { - op_witness_idx.insert(*opid, witness_id); - op_bundle_idx.insert(*opid, bundle_id); + for (anchor, bundle) in witness_bundle.anchored_bundles() { + let bundle_id = bundle.bundle_id(); + bundle_idx.insert(bundle_id, bundle); + anchor_idx.insert(bundle_id, (witness_id, anchor)); + for opid in bundle.known_transitions.keys() { + op_witness_idx.insert(*opid, witness_id); + op_bundle_idx.insert(*opid, bundle_id); + } } } for extension in &consignment.extensions { @@ -137,7 +138,7 @@ impl<'c, const TRANSFER: bool> ConsignmentApi for IndexedConsignment<'c, TRANSFE } fn anchor(&self, bundle_id: BundleId) -> Option<(XWitnessId, &EAnchor)> { - self.anchor_idx.get(&bundle_id).map(|(id, set)| (*id, *set)) + self.anchor_idx.get(&bundle_id).map(|(id, set)| (*id, set)) } fn op_witness_id(&self, opid: OpId) -> Option { diff --git a/src/containers/mod.rs b/src/containers/mod.rs index 277138e2..35b0fd83 100644 --- a/src/containers/mod.rs +++ b/src/containers/mod.rs @@ -38,7 +38,10 @@ mod file; mod kit; mod suppl; -pub use anchors::{AnchorSet, PubWitness, SealWitness, ToWitnessId, WitnessBundle, XPubWitness}; +pub use anchors::{ + AnchorSet, AnchoredBundle, AnchoredBundleMismatch, AnchoredBundles, PubWitness, SealWitness, + ToWitnessId, UnrelatedTransition, WitnessBundle, XPubWitness, +}; pub use consignment::{ Consignment, ConsignmentExt, ConsignmentId, ConsignmentParseError, Contract, Transfer, ValidConsignment, ValidContract, ValidTransfer, diff --git a/src/containers/partials.rs b/src/containers/partials.rs index 28e9ca61..c46ccb5b 100644 --- a/src/containers/partials.rs +++ b/src/containers/partials.rs @@ -31,7 +31,10 @@ use rgb::{ ContractId, OpId, Operation, Transition, TransitionBundle, TxoSeal, XOutpoint, XOutputSeal, XWitnessId, }; -use strict_encoding::{StrictDecode, StrictDeserialize, StrictDumb, StrictEncode, StrictSerialize}; +use strict_encoding::{ + DecodeError, ReadStruct, StrictDecode, StrictDeserialize, StrictDumb, StrictEncode, + StrictProduct, StrictSerialize, StrictStruct, StrictType, TypedRead, TypedWrite, WriteStruct, +}; use crate::containers::{AnchorSet, XPubWitness}; use crate::LIB_NAME_RGB_STD; @@ -247,20 +250,54 @@ impl Batch { pub type BundleDichotomy = Dichotomy; pub type TransitionDichotomy = Dichotomy; +// TODO: Move to amplify #[derive(Clone, PartialEq, Eq, Hash, 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 Dichotomy { +pub struct Dichotomy { pub first: T, pub second: Option, } -impl FromIterator for Dichotomy { +impl StrictType for Dichotomy { + const STRICT_LIB_NAME: &'static str = LIB_NAME_RGB_STD; +} +impl StrictProduct for Dichotomy {} +impl StrictStruct for Dichotomy { + const ALL_FIELDS: &'static [&'static str] = &["first", "second"]; +} +impl StrictEncode for Dichotomy { + fn strict_encode(&self, writer: W) -> std::io::Result { + writer.write_struct::(|w| { + Ok(w.write_field(fname!("first"), &self.first)? + .write_field(fname!("second"), &self.second)? + .complete()) + }) + } +} +impl StrictDecode for Dichotomy { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + reader.read_struct(|r| { + Ok(Self { + first: r.read_field(fname!("first"))?, + second: r.read_field(fname!("second"))?, + }) + }) + } +} +impl StrictDumb for Dichotomy { + fn strict_dumb() -> Self { + Self { + first: T::strict_dumb(), + second: None, + } + } +} + +impl FromIterator for Dichotomy { fn from_iter>(iter: I) -> Self { let mut iter = iter.into_iter(); let first = iter.next().expect("iterator must have at least one item"); @@ -270,7 +307,7 @@ impl FromIterator for Dichotomy< } } -impl IntoIterator for Dichotomy { +impl IntoIterator for Dichotomy { type Item = T; type IntoIter = vec::IntoIter; @@ -284,7 +321,21 @@ impl IntoIterator for Dichotomy } } -impl Dichotomy { +impl Dichotomy { + pub fn single(first: T) -> Self { + Self { + first, + second: None, + } + } + + pub fn double(first: T, second: T) -> Self { + Self { + first, + second: Some(second), + } + } + pub fn with(first: T, second: Option) -> Self { Self { first, second } } pub fn iter(&self) -> vec::IntoIter<&T> { diff --git a/src/contract/bundle.rs b/src/contract/bundle.rs deleted file mode 100644 index ef9788ca..00000000 --- a/src/contract/bundle.rs +++ /dev/null @@ -1,68 +0,0 @@ -// RGB standard library for working with smart contracts on Bitcoin & Lightning -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2024 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2024 LNP/BP Standards Association. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use rgb::{GraphSeal, OpId, Operation, Transition, TransitionBundle, XChain}; - -use crate::contract::TypedAssignsExt; - -#[derive(Clone, Eq, PartialEq, Debug, Display, Error)] -#[display(doc_comments)] -pub enum RevealError { - /// the provided state transition is not a part of the bundle - UnrelatedTransition(OpId, Transition), -} - -pub trait BundleExt { - /// Ensures that the seal is revealed inside the bundle. - fn reveal_seal(&mut self, seal: XChain); - - /// Ensures that the transition is revealed inside the bundle. - /// - /// # Returns - /// - /// `true` if the transition was previously concealed; `false` if it was - /// already revealed; error if the transition is unrelated to the bundle. - fn reveal_transition(&mut self, transition: Transition) -> Result; -} - -impl BundleExt for TransitionBundle { - fn reveal_seal(&mut self, seal: XChain) { - for (_, transition) in self.known_transitions.keyed_values_mut() { - for (_, assign) in transition.assignments.keyed_values_mut() { - assign.reveal_seal(seal) - } - } - } - - fn reveal_transition(&mut self, transition: Transition) -> Result { - let opid = transition.id(); - if self.input_map.values().all(|id| id != &opid) { - return Err(RevealError::UnrelatedTransition(opid, transition)); - } - if self.known_transitions.contains_key(&opid) { - return Ok(false); - } - self.known_transitions - .insert(opid, transition) - .expect("same size as input map"); - Ok(true) - } -} diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 9322b78e..624830ed 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -20,11 +20,9 @@ // limitations under the License. mod assignments; -mod bundle; mod merge_reveal; pub use assignments::{KnownState, OutputAssignment, TypedAssignsExt, WitnessInfo}; -pub use bundle::{BundleExt, RevealError}; pub use merge_reveal::{MergeReveal, MergeRevealError}; use rgb::vm::OrdOpRef; use rgb::{ExtensionType, OpId, TransitionType, XWitnessId}; diff --git a/src/lib.rs b/src/lib.rs index b2531ffd..d428aa06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,8 +47,7 @@ pub mod info; pub use bp::{Outpoint, Txid}; pub use contract::{ - BundleExt, KnownState, MergeReveal, MergeRevealError, OutputAssignment, RevealError, - TypedAssignsExt, WitnessInfo, + KnownState, MergeReveal, MergeRevealError, OutputAssignment, TypedAssignsExt, WitnessInfo, }; pub use invoice::{Allocation, Amount, CoinAmount, OwnedFraction, Precision, TokenIndex}; pub use rgb::prelude::*; diff --git a/src/persistence/index.rs b/src/persistence/index.rs index a047d7d0..b6b71207 100644 --- a/src/persistence/index.rs +++ b/src/persistence/index.rs @@ -171,12 +171,13 @@ impl Index

{ } for WitnessBundle { pub_witness, - bundle, - anchor: _, + anchored_bundles, } in consignment.bundled_witnesses() { let witness_id = pub_witness.to_witness_id(); - self.index_bundle(contract_id, bundle, witness_id)?; + for bundle in anchored_bundles.bundles() { + self.index_bundle(contract_id, bundle, witness_id)?; + } } Ok(()) @@ -334,7 +335,7 @@ impl Index

{ pub(super) fn bundle_info( &self, bundle_id: BundleId, - ) -> Result<(impl Iterator + '_, ContractId), IndexError

> { + ) -> Result<(XWitnessId, ContractId), IndexError

> { Ok(self.provider.bundle_info(bundle_id)?) } } @@ -391,7 +392,7 @@ pub trait IndexReadProvider { fn bundle_info( &self, bundle_id: BundleId, - ) -> Result<(impl Iterator, ContractId), IndexReadError>; + ) -> Result<(XWitnessId, ContractId), IndexReadError>; } pub trait IndexWriteProvider: StoreTransaction { diff --git a/src/persistence/memory.rs b/src/persistence/memory.rs index ab08b0ac..696ca292 100644 --- a/src/persistence/memory.rs +++ b/src/persistence/memory.rs @@ -580,7 +580,7 @@ impl StateReadProvider for MemState { let ord = self .witnesses .get(&witness_id) - .ok_or(StateInconsistency::AbsentValidWitness)?; + .ok_or(StateInconsistency::AbsentWitness)?; Ok(ord.is_valid()) } } @@ -1210,7 +1210,7 @@ pub struct MemIndex { op_bundle_index: MediumOrdMap, bundle_contract_index: MediumOrdMap, - bundle_witness_index: MediumOrdMap>, + bundle_witness_index: MediumOrdMap, contract_index: TinyOrdMap, terminal_index: MediumOrdMap, TinyOrdSet>, } @@ -1344,8 +1344,8 @@ impl IndexReadProvider for MemIndex { fn bundle_info( &self, bundle_id: BundleId, - ) -> Result<(impl Iterator, ContractId), IndexReadError> { - let witness_ids = self + ) -> Result<(XWitnessId, ContractId), IndexReadError> { + let witness_id = self .bundle_witness_index .get(&bundle_id) .ok_or(IndexInconsistency::BundleWitnessUnknown(bundle_id))?; @@ -1353,7 +1353,7 @@ impl IndexReadProvider for MemIndex { .bundle_contract_index .get(&bundle_id) .ok_or(IndexInconsistency::BundleContractUnknown(bundle_id))?; - Ok((witness_ids.iter().copied(), *contract_id)) + Ok((*witness_id, *contract_id)) } } @@ -1387,12 +1387,7 @@ impl IndexWriteProvider for MemIndex { } .into()); } - let mut set = self - .bundle_witness_index - .remove(&bundle_id)? - .unwrap_or_default(); - set.push(witness_id)?; - self.bundle_witness_index.insert(bundle_id, set)?; + self.bundle_witness_index.insert(bundle_id, witness_id)?; let present2 = self .bundle_contract_index .insert(bundle_id, contract_id)? diff --git a/src/persistence/stash.rs b/src/persistence/stash.rs index 0924fdd0..056c79d3 100644 --- a/src/persistence/stash.rs +++ b/src/persistence/stash.rs @@ -513,8 +513,8 @@ impl Stash

{ .map_err(StashError::WriteProvider)?; } - for bw in consignment.bundles { - self.consume_bundled_witness(contract_id, bw)?; + for witness_bundles in consignment.bundles { + self.consume_witness_bundle(contract_id, witness_bundles)?; } for (id, attach) in consignment.attachments { @@ -545,28 +545,62 @@ impl Stash

{ }) } - fn consume_bundled_witness( + fn consume_witness_bundle( &mut self, contract_id: ContractId, - bundled_witness: WitnessBundle, + witness_bundle: WitnessBundle, ) -> Result<(), StashError

> { let WitnessBundle { pub_witness, - bundle, - anchor, - } = bundled_witness; + anchored_bundles, + } = witness_bundle; - // TODO: Save pub witness transaction and SPVs + // TODO: Save pub witness transaction SPVs - let bundle_id = bundle.bundle_id(); - self.consume_bundle(bundle)?; + let mut anchors = Vec::with_capacity(2); + for (anchor, bundle) in anchored_bundles.into_iter() { + let bundle_id = bundle.bundle_id(); + self.consume_bundle(bundle)?; - let proto = mpc::ProtocolId::from_byte_array(contract_id.to_byte_array()); - let msg = mpc::Message::from_byte_array(bundle_id.to_byte_array()); - let merkle_block = MerkleBlock::with(&anchor.mpc_proof, proto, msg)?; - let anchors = match anchor.dbc_proof { - DbcProof::Tapret(tapret) => AnchorSet::Tapret(Anchor::new(merkle_block, tapret)), - DbcProof::Opret(opret) => AnchorSet::Opret(Anchor::new(merkle_block, opret)), + let proto = mpc::ProtocolId::from_byte_array(contract_id.to_byte_array()); + let msg = mpc::Message::from_byte_array(bundle_id.to_byte_array()); + let merkle_block = MerkleBlock::with(&anchor.mpc_proof, proto, msg)?; + anchors.push(Anchor::new(merkle_block, anchor.dbc_proof)); + } + + let anchors = match (anchors.pop().unwrap(), anchors.pop()) { + ( + Anchor { + dbc_proof: DbcProof::Opret(opret), + mpc_proof, + .. + }, + None, + ) => AnchorSet::Opret(Anchor::new(mpc_proof, opret)), + ( + Anchor { + dbc_proof: DbcProof::Tapret(tapret), + mpc_proof, + .. + }, + None, + ) => AnchorSet::Tapret(Anchor::new(mpc_proof, tapret)), + ( + Anchor { + dbc_proof: DbcProof::Tapret(tapret), + mpc_proof: mpc_proof_tapret, + .. + }, + Some(Anchor { + dbc_proof: DbcProof::Opret(opret), + mpc_proof: mpc_proof_opret, + .. + }), + ) => AnchorSet::Double { + tapret: Anchor::new(mpc_proof_tapret, tapret), + opret: Anchor::new(mpc_proof_opret, opret), + }, + _ => unreachable!(), }; let witness = SealWitness { public: pub_witness.clone(), diff --git a/src/persistence/state.rs b/src/persistence/state.rs index 91e5356d..8c161bbd 100644 --- a/src/persistence/state.rs +++ b/src/persistence/state.rs @@ -19,7 +19,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::borrow::Borrow; use std::collections::BTreeMap; use std::error::Error; use std::fmt::Debug; @@ -66,6 +65,8 @@ pub enum StateError { pub enum StateInconsistency { /// contract state {0} is not known. UnknownContract(ContractId), + /// a witness is absent in the list of witnesses for a state transition bundle. + AbsentWitness, /// valid (non-archived) witness is absent in the list of witnesses for a /// state transition bundle. AbsentValidWitness, @@ -134,21 +135,10 @@ impl State

{ .map_err(StateError::ReadProvider) } - pub fn select_valid_witness( - &self, - witness_ids: impl IntoIterator>, - ) -> Result> { - for witness_id in witness_ids { - let witness_id = *witness_id.borrow(); - if self - .provider - .is_valid_witness(witness_id) - .map_err(StateError::ReadProvider)? - { - return Ok(witness_id); - } - } - Err(StateError::Inconsistency(StateInconsistency::AbsentValidWitness)) + pub fn is_valid_witness(&self, witness_id: XWitnessId) -> Result> { + self.provider + .is_valid_witness(witness_id) + .map_err(StateError::ReadProvider) } pub fn update_from_bundle( @@ -190,7 +180,7 @@ impl State

{ .collect::>(); let mut ordered_extensions = BTreeMap::new(); for witness_bundle in consignment.bundled_witnesses() { - for transition in witness_bundle.bundle.known_transitions.values() { + for transition in witness_bundle.known_transitions() { let witness_id = witness_bundle.pub_witness.to_witness_id(); let witness_ord = resolver .resolve_pub_witness_ord(witness_id) diff --git a/src/persistence/stock.rs b/src/persistence/stock.rs index ebc366a0..aa60e52b 100644 --- a/src/persistence/stock.rs +++ b/src/persistence/stock.rs @@ -49,17 +49,18 @@ use super::{ StateWriteProvider, StoreTransaction, }; use crate::containers::{ - AnchorSet, Batch, BuilderSeal, Consignment, ContainerVer, ContentId, ContentRef, Contract, - Fascia, Kit, SealWitness, SupplItem, SupplSub, Transfer, TransitionDichotomy, TransitionInfo, - TransitionInfoError, ValidConsignment, ValidContract, ValidKit, ValidTransfer, VelocityHint, - WitnessBundle, SUPPL_ANNOT_VELOCITY, + AnchorSet, AnchoredBundle, AnchoredBundleMismatch, Batch, BuilderSeal, Consignment, + ContainerVer, ContentId, ContentRef, Contract, Fascia, Kit, SealWitness, SupplItem, SupplSub, + Transfer, TransitionDichotomy, TransitionInfo, TransitionInfoError, UnrelatedTransition, + ValidConsignment, ValidContract, ValidKit, ValidTransfer, VelocityHint, WitnessBundle, + SUPPL_ANNOT_VELOCITY, }; use crate::info::{ContractInfo, IfaceInfo, SchemaInfo}; use crate::interface::{ BuilderError, ContractBuilder, ContractIface, Iface, IfaceClass, IfaceId, IfaceRef, IfaceWrapper, TransitionBuilder, }; -use crate::{BundleExt, MergeRevealError, RevealError}; +use crate::MergeRevealError; pub type ContractAssignments = HashMap>; @@ -169,7 +170,11 @@ pub enum ConsignError { #[from] #[display(inner)] - Reveal(RevealError), + Transition(UnrelatedTransition), + + #[from] + #[display(inner)] + AnchoredBundle(AnchoredBundleMismatch), /// the spent state from transition {1} inside bundle {0} is concealed. Concealed(BundleId, OpId), @@ -187,10 +192,16 @@ impl From Self { Self::InvalidInput(err.into()) } } -impl From +impl From + for StockError +{ + fn from(err: UnrelatedTransition) -> Self { Self::InvalidInput(err.into()) } +} + +impl From for StockError { - fn from(err: RevealError) -> Self { Self::InvalidInput(err.into()) } + fn from(err: AnchoredBundleMismatch) -> Self { Self::InvalidInput(err.into()) } } #[derive(Clone, PartialEq, Eq, Debug, Display, Error, From)] @@ -751,7 +762,7 @@ impl Stock { opouts.extend(self.index.opouts_by_terminals(secret_seal.into_iter())?); // 1.3. Collect all state transitions assigning state to the provided outpoints - let mut witness_bundles = BTreeMap::::new(); + let mut anchored_bundles = BTreeMap::::new(); let mut transitions = BTreeMap::::new(); let mut terminals = BTreeMap::>::new(); for opout in opouts { @@ -774,9 +785,9 @@ impl Stock { } } - if let Entry::Vacant(entry) = witness_bundles.entry(bundle_id) { - let bw = self.witness_bundle(bundle_id)?; - entry.insert(bw); + if let Entry::Vacant(entry) = anchored_bundles.entry(bundle_id) { + let ab = self.anchored_bundle(bundle_id)?; + entry.insert(ab); } } @@ -793,10 +804,9 @@ impl Stock { ids.extend(transition.inputs().iter().map(|input| input.prev_out.op)); transitions.insert(id, transition.clone()); let bundle_id = self.index.bundle_id_for_op(transition.id())?; - witness_bundles + anchored_bundles .entry(bundle_id) - .or_insert(self.witness_bundle(bundle_id)?.clone()) - .bundle + .or_insert(self.anchored_bundle(bundle_id)?.clone()) .reveal_transition(transition.clone())?; } @@ -842,16 +852,15 @@ impl Stock { let ifaces = Confined::from_checked(ifaces); let mut bundles = BTreeMap::::new(); - for witness_bundle in witness_bundles.into_values() { - let witness_id = witness_bundle.witness_id(); - match bundles.get_mut(&witness_id) { - Some(prev) => { - *prev = prev.clone().merge_reveal(witness_bundle)?; - } - None => { - bundles.insert(witness_id, witness_bundle); - } - } + for anchored_bundle in anchored_bundles.into_values() { + let witness_id = self.index.bundle_info(anchored_bundle.bundle_id())?.0; + let pub_witness = self.stash.witness(witness_id)?.public.clone(); + let wb = match bundles.remove(&witness_id) { + Some(bundle) => bundle.into_double(anchored_bundle)?, + None => WitnessBundle::with(pub_witness, anchored_bundle), + }; + let res = bundles.insert(witness_id, wb); + debug_assert!(res.is_none()); } let bundles = Confined::try_from_iter(bundles.into_values()) .map_err(|_| ConsignError::TooManyBundles)?; @@ -1346,11 +1355,13 @@ impl Stock { .ok_or(ConsignError::Concealed(bundle_id, opid).into()) } - fn witness_bundle(&self, bundle_id: BundleId) -> Result> { - let (witness_ids, contract_id) = self.index.bundle_info(bundle_id)?; + fn anchored_bundle(&self, bundle_id: BundleId) -> Result> { + let (witness_id, contract_id) = self.index.bundle_info(bundle_id)?; let bundle = self.stash.bundle(bundle_id)?.clone(); - let witness_id = self.state.select_valid_witness(witness_ids)?; + if !self.state.is_valid_witness(witness_id)? { + return Err(StateInconsistency::AbsentValidWitness.into()); + } let witness = self.stash.witness(witness_id)?; let (merkle_block, dbc) = match (bundle.close_method, &witness.anchors) { ( @@ -1380,11 +1391,7 @@ impl Stock { // TODO: Conceal all transitions except the one we need - Ok(WitnessBundle { - pub_witness: witness.public.clone(), - bundle, - anchor, - }) + Ok(AnchoredBundle::new(anchor, bundle)) } pub fn store_secret_seal( diff --git a/src/stl/stl.rs b/src/stl/stl.rs index 2aeb050b..3bea1e91 100644 --- a/src/stl/stl.rs +++ b/src/stl/stl.rs @@ -41,7 +41,7 @@ use crate::LIB_NAME_RGB_STD; /// Strict types id for the library providing standard data types which may be /// used in RGB smart contracts. pub const LIB_ID_RGB_STORAGE: &str = - "stl:YBgLtVmT-FuzNBPe-1fxIqay-JW4Evd5-Za7fm9w-GLF6JAg#transit-xray-giraffe"; + "stl:wQMBk8bx-tp7Jvse-Xz67y3R-PXPA7Wh-dGSsW2n-XNT0UbA#roman-mile-trumpet"; /// Strict types id for the library providing standard data types which may be /// used in RGB smart contracts. @@ -50,7 +50,7 @@ pub const LIB_ID_RGB_CONTRACT: &str = /// Strict types id for the library representing of RGB StdLib data types. pub const LIB_ID_RGB_STD: &str = - "stl:H1HLPfyC-5YLlAk!-KWhcUo1-0vtex!9-ODxSmIA-zj1J$qc#western-craft-bogart"; + "stl:iyjljt$D-YKLhmJg-eUncrj8-shw0TzX-etVz7sR-lnXk8q0#grace-atlas-method"; fn _rgb_std_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_STD), tiny_bset! {