Skip to content

RCP-240313A: support future SPV proofs by refactoring anchors #228

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ wasm-bindgen-test = "0.3"

[package.metadata.docs.rs]
features = ["all"]

[patch.crates-io]
bp-consensus = { git = "https://github.com/BP-WG/bp-core", branch = "deanchor" }
bp-dbc = { git = "https://github.com/BP-WG/bp-core", branch = "deanchor" }
bp-seals = { git = "https://github.com/BP-WG/bp-core", branch = "deanchor" }
bp-core = { git = "https://github.com/BP-WG/bp-core", branch = "deanchor" }
8 changes: 4 additions & 4 deletions src/bin/rgbcore-stl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,11 @@ Transition vesper lexicon=types+commitments
let tt = sys.type_tree("RGB.Transition").unwrap();
writeln!(file, "{tt}").unwrap();

let mut file = fs::File::create(format!("{dir}/AnchoredBundle.vesper")).unwrap();
let mut file = fs::File::create(format!("{dir}/TransitionBundle.vesper")).unwrap();
writeln!(
file,
"{{-
Description: RGB Anchored Bundle
Description: RGB Transition Bundle
Author: Dr Maxim Orlovsky <[email protected]>
Copyright (C) 2024 LNP/BP Standards Association. All rights reserved.
License: Apache-2.0
Expand All @@ -135,8 +135,8 @@ Bundles vesper lexicon=types+commitments
.unwrap();
let layout = TransitionBundle::commitment_layout();
writeln!(file, "{layout}").unwrap();
let tt = sys.type_tree("RGB.InputMap").unwrap();
let tt = sys.type_tree("RGB.XChainAnchorSet").unwrap();
writeln!(file, "{tt}").unwrap();
let tt = sys.type_tree("RGB.AnchoredBundle").unwrap();
let tt = sys.type_tree("RGB.TransitionBundle").unwrap();
writeln!(file, "{tt}").unwrap();
}
54 changes: 1 addition & 53 deletions src/contract/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,48 +25,13 @@ use std::cmp::Ordering;
use bp::dbc::opret::OpretProof;
use bp::dbc::tapret::TapretProof;
use bp::dbc::Anchor;
use bp::Txid;
use commit_verify::mpc;
use strict_encoding::StrictDumb;

use crate::{BundleId, ContractId, TransitionBundle, WitnessId, WitnessOrd, XChain, LIB_NAME_RGB};

#[derive(Clone, Eq, PartialEq, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct AnchoredBundle {
pub anchor: XAnchor,
pub bundle: TransitionBundle,
}

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

impl Ord for AnchoredBundle {
fn cmp(&self, other: &Self) -> Ordering { self.bundle_id().cmp(&other.bundle_id()) }
}

impl PartialOrd for AnchoredBundle {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
use crate::{BundleId, ContractId, WitnessId, WitnessOrd, XChain, LIB_NAME_RGB};

pub type XAnchor<P = mpc::MerkleProof> = XChain<AnchorSet<P>>;

impl<P: mpc::Proof + StrictDumb> XAnchor<P> {
#[inline]
pub fn witness_id(&self) -> Option<WitnessId> { self.maybe_map_ref(|set| set.txid()) }

#[inline]
pub fn witness_id_unchecked(&self) -> WitnessId { self.map_ref(|set| set.txid_unchecked()) }
}

impl XAnchor<mpc::MerkleBlock> {
pub fn known_bundle_ids(&self) -> impl Iterator<Item = (BundleId, ContractId)> + '_ {
match self {
Expand Down Expand Up @@ -129,23 +94,6 @@ pub enum AnchorSet<P: mpc::Proof + StrictDumb = mpc::MerkleProof> {
}

impl<P: mpc::Proof + StrictDumb> AnchorSet<P> {
pub fn txid(&self) -> Option<Txid> {
match self {
AnchorSet::Tapret(a) => Some(a.txid),
AnchorSet::Opret(a) => Some(a.txid),
AnchorSet::Dual { tapret, opret } if tapret.txid == opret.txid => Some(tapret.txid),
_ => None,
}
}

pub fn txid_unchecked(&self) -> Txid {
match self {
AnchorSet::Tapret(a) => a.txid,
AnchorSet::Opret(a) => a.txid,
AnchorSet::Dual { tapret, opret: _ } => tapret.txid,
}
}

pub fn from_split(
tapret: Option<Anchor<P, TapretProof>>,
opret: Option<Anchor<P, OpretProof>>,
Expand Down
2 changes: 1 addition & 1 deletion src/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ mod contract;
mod xchain;
mod commit;

pub use anchor::{AnchorSet, AnchoredBundle, Layer1, WitnessAnchor, XAnchor};
pub use anchor::{AnchorSet, Layer1, WitnessAnchor, XAnchor};
pub use assignments::{
Assign, AssignAttach, AssignData, AssignFungible, AssignRights, Assignments, AssignmentsRef,
TypedAssigns,
Expand Down
8 changes: 5 additions & 3 deletions src/stl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ use strict_types::typelib::LibBuilder;
use strict_types::{CompileError, TypeLib};

use crate::{
AnchoredBundle, ContractState, Extension, Genesis, OpCommitment, SubSchema, LIB_NAME_RGB,
ContractState, Extension, Genesis, OpCommitment, SubSchema, TransitionBundle, XAnchor,
LIB_NAME_RGB,
};

/// Strict types id for the library providing data types for RGB consensus.
pub const LIB_ID_RGB: &str =
"urn:ubideco:stl:py61NAh7V4xHa7if2mF88KL3Z11rUruBNQEAsEqaf2Q#stock-sonata-carlo";
"urn:ubideco:stl:9B8mwkJrtk1nkptoEHaXWunxdRdR4S21DKD5F2quGJDG#doctor-society-gabriel";

fn _rgb_core_stl() -> Result<TypeLib, CompileError> {
LibBuilder::new(libname!(LIB_NAME_RGB), tiny_bset! {
Expand All @@ -47,7 +48,8 @@ fn _rgb_core_stl() -> Result<TypeLib, CompileError> {
})
.transpile::<SubSchema>()
.transpile::<Genesis>()
.transpile::<AnchoredBundle>()
.transpile::<XAnchor>()
.transpile::<TransitionBundle>()
.transpile::<Extension>()
.transpile::<ContractState>()
.transpile::<OpCommitment>()
Expand Down
28 changes: 20 additions & 8 deletions src/validation/consignment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;

use crate::{
AnchoredBundle, AssetTag, AssignmentType, BundleId, Genesis, OpId, OpRef, Operation,
SecretSeal, SubSchema, WitnessId, XChain,
AssetTag, AssignmentType, BundleId, Genesis, OpId, OpRef, Operation, SecretSeal, SubSchema,
TransitionBundle, WitnessId, XAnchor, XChain,
};

pub struct CheckedConsignment<'consignment, C: ConsignmentApi>(&'consignment C);
Expand All @@ -55,10 +55,16 @@ impl<'consignment, C: ConsignmentApi> ConsignmentApi for CheckedConsignment<'con

fn bundle_ids<'a>(&self) -> Self::Iter<'a> { self.0.bundle_ids() }

fn anchored_bundle(&self, bundle_id: BundleId) -> Option<Rc<AnchoredBundle>> {
fn bundle(&self, bundle_id: BundleId) -> Option<Rc<TransitionBundle>> {
self.0
.anchored_bundle(bundle_id)
.filter(|ab| ab.bundle.bundle_id() == bundle_id)
.bundle(bundle_id)
.filter(|b| b.bundle_id() == bundle_id)
}

fn anchor(&self, bundle_id: BundleId) -> Option<Rc<XAnchor>> { self.0.anchor(bundle_id) }

fn bundle_witness_id(&self, bundle_id: BundleId) -> Option<WitnessId> {
self.0.bundle_witness_id(bundle_id)
}

fn op_witness_id(&self, opid: OpId) -> Option<WitnessId> { self.0.op_witness_id(opid) }
Expand All @@ -81,7 +87,7 @@ pub trait ConsignmentApi {
/// Asset tags uses in the confidential asset validation.
fn asset_tags(&self) -> &BTreeMap<AssignmentType, AssetTag>;

/// Retrieves reference to a operation (genesis, state transition or state
/// Retrieves reference to an operation (genesis, state transition or state
/// extension) matching the provided id, or `None` otherwise
fn operation(&self, opid: OpId) -> Option<OpRef>;

Expand All @@ -102,8 +108,14 @@ pub trait ConsignmentApi {
/// Returns iterator over all bundle ids present in the consignment.
fn bundle_ids<'a>(&self) -> Self::Iter<'a>;

/// Returns reference to an anchored bundle given a bundle id.
fn anchored_bundle(&self, bundle_id: BundleId) -> Option<Rc<AnchoredBundle>>;
/// Returns reference to a bundle given a bundle id.
fn bundle(&self, bundle_id: BundleId) -> Option<Rc<TransitionBundle>>;

/// Returns reference to an anchor given a bundle id.
fn anchor(&self, bundle_id: BundleId) -> Option<Rc<XAnchor>>;

/// Returns witness id for a given transition bundle.
fn bundle_witness_id(&self, bundle_id: BundleId) -> Option<WitnessId>;

/// Returns witness id for a given operation.
fn op_witness_id(&self, opid: OpId) -> Option<WitnessId>;
Expand Down
8 changes: 5 additions & 3 deletions src/validation/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,12 @@ pub enum Failure {
/// transition bundle {0} referenced in consignment terminals is absent from
/// the consignment.
TerminalBundleAbsent(BundleId),
/// transition bundle {0} is absent from the consignment.
/// transition bundle {0} is absent in the consignment.
BundleAbsent(BundleId),
/// anchor for transitio bundle {0} is absent in the consignment.
AnchorAbsent(BundleId),
/// witness id for transition bundle {0} is absent in the consignment.
WitnessIdAbsent(BundleId),
/// operation {0} is under a different contract {1}.
ContractMismatch(OpId, ContractId),

Expand All @@ -327,8 +331,6 @@ pub enum Failure {
},
/// transition {0} references non-existing previous output {1}.
NoPrevOut(OpId, Opout),
/// anchors used inside bundle {0} reference different public witness ids.
AnchorSetInvalid(BundleId),
/// seal defined in the history as a part of operation output {0} is
/// confidential and can't be validated.
ConfidentialSeal(Opout),
Expand Down
53 changes: 28 additions & 25 deletions src/validation/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ use crate::{
#[derive(Clone, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum WitnessResolverError {
/// witness {0} does not exists.
/// witness {0} does not exist.
Unknown(WitnessId),
/// unable to retrieve witness {0}, {1}
Other(WitnessId, String),
}

pub trait ResolveWitness {
// TODO: Return with SPV proof data
fn resolve_pub_witness(
&self,
witness_id: WitnessId,
Expand Down Expand Up @@ -95,11 +96,11 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
// to detect any potential issues with the consignment structure and notify user
// about them (in form of generated warnings)
for (bundle_id, seal_endpoint) in consignment.terminals() {
let Some(anchored_bundle) = consignment.anchored_bundle(bundle_id) else {
let Some(bundle) = consignment.bundle(bundle_id) else {
status.add_failure(Failure::TerminalBundleAbsent(bundle_id));
continue;
};
for (opid, transition) in &anchored_bundle.bundle.known_transitions {
for (opid, transition) in &bundle.known_transitions {
// Checking for endpoint definition duplicates
if !transition
.assignments
Expand Down Expand Up @@ -209,12 +210,12 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
// treat it as a superposition of subgraphs, one for each endpoint; and validate
// them independently.
for (bundle_id, _) in self.consignment.terminals() {
let Some(anchored_bundle) = self.consignment.anchored_bundle(bundle_id) else {
let Some(bundle) = self.consignment.bundle(bundle_id) else {
// We already checked and errored here during the terminal validation, so just
// skipping.
continue;
};
for transition in anchored_bundle.bundle.known_transitions.values() {
for transition in bundle.known_transitions.values() {
self.validate_logic_on_route(schema, transition);
}
}
Expand Down Expand Up @@ -303,27 +304,32 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
// *** PART III: Validating single-use-seals
fn validate_commitments(&mut self) {
for bundle_id in self.consignment.bundle_ids() {
let Some(anchored_bundle) = self.consignment.anchored_bundle(bundle_id) else {
let Some(bundle) = self.consignment.bundle(bundle_id) else {
self.status.add_failure(Failure::BundleAbsent(bundle_id));
continue;
};

let layer1 = anchored_bundle.anchor.layer1();

let anchors = &anchored_bundle.anchor;
let bundle = &anchored_bundle.bundle;
let Some(anchor) = self.consignment.anchor(bundle_id) else {
self.status.add_failure(Failure::AnchorAbsent(bundle_id));
continue;
};
let Some(witness_id) = self.consignment.bundle_witness_id(bundle_id) else {
self.status.add_failure(Failure::WitnessIdAbsent(bundle_id));
continue;
};

// [VALIDATION]: We validate that the seals were properly defined on BP-type layers
let (seals, input_map) = self.validate_seal_definitions(layer1, bundle);
let (seals, input_map) =
self.validate_seal_definitions(witness_id.layer1(), bundle.as_ref());

// [VALIDATION]: We validate that the seals were properly closed on BP-type layers
let Some(witness_tx) = self.validate_seal_commitments(&seals, bundle_id, anchors)
let Some(witness_tx) =
self.validate_seal_commitments(&seals, bundle_id, anchor.as_ref(), witness_id)
else {
continue;
};

// [VALIDATION]: We validate bundle commitments to the input map
self.validate_bundle_commitments(bundle_id, bundle, witness_tx, input_map);
self.validate_bundle_commitments(bundle_id, bundle.as_ref(), witness_tx, input_map);
}
}

Expand Down Expand Up @@ -367,15 +373,12 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
seals: impl AsRef<[XOutputSeal]>,
bundle_id: BundleId,
anchors: &XAnchor,
witness_id: WitnessId,
) -> Option<XPubWitness> {
let Some(witness_id) = anchors.witness_id() else {
self.status
.add_failure(Failure::AnchorSetInvalid(bundle_id));
return None;
};

// Check that the anchor is committed into a transaction spending all of the
// Check that the anchor is committed into a transaction spending all the
// transition inputs.
// Here the method can do SPV proof instead of querying the indexer. The SPV
// proofs can be part of the consignments, but do not require .
match self.resolver.resolve_pub_witness(witness_id) {
Err(_) => {
// We wre unable to retrieve corresponding transaction, so can't check.
Expand Down Expand Up @@ -403,7 +406,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
if let Some(tapret) = tapret {
let witness = pub_witness
.clone()
.map(|tx| Witness::with(tx, tapret.clone()));
.map(|tx| Witness::with(tx, tapret.dbc_proof.clone()));
self.validate_seal_closing(tapret_seals, witness, bundle_id, tapret)
} else if tapret_seals.count() > 0 {
self.status.add_warning(Warning::UnclosedSeals(bundle_id));
Expand All @@ -416,7 +419,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
if let Some(opret) = opret {
let witness = pub_witness
.clone()
.map(|tx| Witness::with(tx, opret.clone()));
.map(|tx| Witness::with(tx, opret.dbc_proof));
self.validate_seal_closing(opret_seals, witness, bundle_id, opret)
} else if opret_seals.count() > 0 {
self.status.add_warning(Warning::UnclosedSeals(bundle_id));
Expand Down Expand Up @@ -530,8 +533,8 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness>
/// generic type `Dbc`) and extra-transaction data, which are taken from
/// anchors DBC proof.
///
/// Additionally checks that the provided message contains commitment to the
/// bundle under the current contract.
/// Additionally, checks that the provided message contains commitment to
/// the bundle under the current contract.
fn validate_seal_closing<'seal, 'temp, Seal: 'seal, Dbc: dbc::Proof>(
&mut self,
seals: impl IntoIterator<Item = &'seal Seal>,
Expand Down
Loading