Skip to content

Commit

Permalink
fix mismatch between anchored bundles dichotomies in persistence and …
Browse files Browse the repository at this point in the history
…containers
  • Loading branch information
dr-orlovsky committed Oct 12, 2024
1 parent 54f19cd commit 8d0bc85
Show file tree
Hide file tree
Showing 21 changed files with 1,956 additions and 857 deletions.
274 changes: 256 additions & 18 deletions src/containers/anchors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -159,36 +177,256 @@ impl PubWitness {
)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict, id = DiscloseHash)]
pub struct WitnessBundle<P: mpc::Proof + StrictDumb = mpc::MerkleProof> {
pub struct WitnessBundle {
pub pub_witness: XPubWitness,
pub anchor: Anchor<P, DbcProof>,
pub bundle: TransitionBundle,
pub anchored_bundles: AnchoredBundles,
}

impl<P: mpc::Proof + StrictDumb> PartialEq for WitnessBundle<P> {
impl PartialEq for WitnessBundle {
fn eq(&self, other: &Self) -> bool { self.pub_witness == other.pub_witness }
}

impl<P: mpc::Proof + StrictDumb> Ord for WitnessBundle<P> {
impl Ord for WitnessBundle {
fn cmp(&self, other: &Self) -> Ordering { self.pub_witness.cmp(&other.pub_witness) }
}

impl<P: mpc::Proof + StrictDumb> PartialOrd for WitnessBundle<P> {
impl PartialOrd for WitnessBundle {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}

impl WitnessBundle<mpc::MerkleProof> {
impl WitnessBundle {
#[inline]
pub fn with(pub_witness: XPubWitness, anchored_bundle: ClientBundle) -> Self {
Self {
pub_witness,
anchored_bundles: AnchoredBundles::from(anchored_bundle),
}
}

pub fn into_double(mut self, other: ClientBundle) -> Result<Self, AnchoredBundleMismatch> {
match (self.anchored_bundles, other.dbc_proof) {
(AnchoredBundles::Double { .. }, _) => {
return Err(AnchoredBundleMismatch::AlreadyDouble(
self.pub_witness.to_witness_id(),
));
}
(AnchoredBundles::Opret(opret), DbcProof::Tapret(tapret)) => {
self.anchored_bundles = AnchoredBundles::Double {
tapret: ClientBundle::new(other.mpc_proof, tapret, other.bundle),
opret,
}
}
(AnchoredBundles::Tapret(tapret), DbcProof::Opret(opret)) => {
self.anchored_bundles = AnchoredBundles::Double {
opret: ClientBundle::new(other.mpc_proof, opret, 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<Item = (EAnchor, &TransitionBundle)> {
self.anchored_bundles.iter()
}

#[inline]
pub fn known_transitions(&self) -> impl Iterator<Item = &Transition> {
self.anchored_bundles
.bundles()
.flat_map(|bundle| bundle.known_transitions.values())
}
}

impl WitnessBundle {
pub fn merge_reveal(mut self, other: Self) -> Result<Self, MergeRevealError> {
self.pub_witness = self.pub_witness.merge_reveal(other.pub_witness)?;
if self.anchor != other.anchor {
return Err(MergeRevealError::AnchorsNonEqual(self.bundle.bundle_id()));
/// Keeps client-side data - a combination of client-side witness (anchor) and state (transition
/// bundle). Ensures that transition bundle uses the same DBC close method as used by the
/// client-side witness (anchor).
#[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 ClientBundle<D: dbc::Proof = DbcProof> {
mpc_proof: mpc::MerkleProof,
dbc_proof: D,
bundle: TransitionBundle,
}

impl<D: dbc::Proof> ClientBundle<D> {
/// # Panics
///
/// Panics if DBC proof and bundle have different closing methods
pub fn new(mpc_proof: mpc::MerkleProof, dbc_proof: D, bundle: TransitionBundle) -> Self {
assert_eq!(D::METHOD, bundle.close_method);
Self {
mpc_proof,
dbc_proof,
bundle,
}
self.bundle = self.bundle.merge_reveal(other.bundle)?;
Ok(self)
}

#[inline]
pub fn bundle_id(&self) -> BundleId { self.bundle.bundle_id() }

pub fn reveal_transition(
&mut self,
transition: Transition,
) -> Result<bool, UnrelatedTransition> {
let opid = transition.id();
if self.bundle.input_map.values().all(|id| *id != opid) {
return Err(UnrelatedTransition(opid, transition));
}
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(ClientBundle<TapretProof>),
#[strict_type(tag = 0x02)]
Opret(ClientBundle<OpretProof>),
#[strict_type(tag = 0x03)]
Double {
tapret: ClientBundle<TapretProof>,
opret: ClientBundle<OpretProof>,
},
}

impl StrictDumb for AnchoredBundles {
fn strict_dumb() -> Self { Self::Opret(strict_dumb!()) }
}

impl From<ClientBundle> for AnchoredBundles {
fn from(ab: ClientBundle) -> Self {
match ab.dbc_proof {
DbcProof::Opret(proof) => {
Self::Opret(ClientBundle::<OpretProof>::new(ab.mpc_proof, proof, ab.bundle))
}
DbcProof::Tapret(proof) => {
Self::Tapret(ClientBundle::<TapretProof>::new(ab.mpc_proof, proof, ab.bundle))
}
}
}
}

impl AnchoredBundles {
pub fn bundles(&self) -> impl Iterator<Item = &TransitionBundle> {
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<Item = TransitionBundle> {
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<Item = (EAnchor, &TransitionBundle)> {
match self {
AnchoredBundles::Tapret(tapret) => {
let anchor =
EAnchor::new(tapret.mpc_proof.clone(), tapret.dbc_proof.clone().into());
Dichotomy::single((anchor, &tapret.bundle))
}
AnchoredBundles::Opret(opret) => {
let anchor = EAnchor::new(opret.mpc_proof.clone(), opret.dbc_proof.clone().into());
Dichotomy::single((anchor, &opret.bundle))
}
AnchoredBundles::Double { tapret, opret } => {
let tapret_anchor =
EAnchor::new(tapret.mpc_proof.clone(), tapret.dbc_proof.clone().into());
let opret_anchor =
EAnchor::new(opret.mpc_proof.clone(), opret.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.mpc_proof, tapret.dbc_proof.into());
Dichotomy::single((anchor, tapret.bundle))
}
AnchoredBundles::Opret(opret) => {
let anchor = EAnchor::new(opret.mpc_proof, opret.dbc_proof.into());
Dichotomy::single((anchor, opret.bundle))
}
AnchoredBundles::Double { tapret, opret } => {
let tapret_anchor = EAnchor::new(tapret.mpc_proof, tapret.dbc_proof.into());
let opret_anchor = EAnchor::new(opret.mpc_proof, opret.dbc_proof.into());
Dichotomy::double((tapret_anchor, tapret.bundle), (opret_anchor, opret.bundle))
}
}
.into_iter()
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/containers/consignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<true>;
pub type Contract = Consignment<false>;
Expand Down Expand Up @@ -296,8 +296,7 @@ impl<const TRANSFER: bool> Consignment<TRANSFER> {
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;
}
}
Expand Down
19 changes: 10 additions & 9 deletions src/containers/indexed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ use crate::containers::anchors::ToWitnessId;
pub struct IndexedConsignment<'c, const TRANSFER: bool> {
consignment: &'c Consignment<TRANSFER>,
scripts: Scripts,
anchor_idx: BTreeMap<BundleId, (XWitnessId, &'c EAnchor)>,
anchor_idx: BTreeMap<BundleId, (XWitnessId, EAnchor)>,
bundle_idx: BTreeMap<BundleId, &'c TransitionBundle>,
op_witness_idx: BTreeMap<OpId, XWitnessId>,
op_bundle_idx: BTreeMap<OpId, BundleId>,
Expand All @@ -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 {
Expand Down Expand Up @@ -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<XWitnessId> {
Expand Down
5 changes: 4 additions & 1 deletion src/containers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ mod file;
mod kit;
mod suppl;

pub use anchors::{AnchorSet, PubWitness, SealWitness, ToWitnessId, WitnessBundle, XPubWitness};
pub use anchors::{
AnchorSet, AnchoredBundleMismatch, AnchoredBundles, ClientBundle, PubWitness, SealWitness,
ToWitnessId, UnrelatedTransition, WitnessBundle, XPubWitness,
};
pub use consignment::{
Consignment, ConsignmentExt, ConsignmentId, ConsignmentParseError, Contract, Transfer,
ValidConsignment, ValidContract, ValidTransfer,
Expand Down
Loading

0 comments on commit 8d0bc85

Please sign in to comment.