From bde1287ad02c035191a27349a4679381d7162ea4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 30 Mar 2024 13:25:24 +0100 Subject: [PATCH] RCP-240313A: support future SPV proofs by refactoring anchors All non-consensus data has been removed from anchors and consignment API into resolver API, which can now return SPV info etc without consensus-breaking changes --- Cargo.lock | 12 +++----- Cargo.toml | 6 ++++ src/contract/anchor.rs | 54 +---------------------------------- src/contract/mod.rs | 2 +- src/stl.rs | 6 ++-- src/validation/consignment.rs | 28 ++++++++++++------ src/validation/status.rs | 8 ++++-- src/validation/validator.rs | 53 ++++++++++++++++++---------------- 8 files changed, 67 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3c8c10c..140fb25d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,8 +202,7 @@ dependencies = [ [[package]] name = "bp-consensus" version = "0.11.0-beta.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "966395ea17fa99b33a9093355924b0f79312b410e2c8a85ca8ebb8333098fb9a" +source = "git+https://github.com/BP-WG/bp-core?branch=deanchor#c014bad013c99e4eb828eabc37c1c5ccc30acf75" dependencies = [ "amplify", "chrono", @@ -217,8 +216,7 @@ dependencies = [ [[package]] name = "bp-core" version = "0.11.0-beta.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27581dae64d76a00fe4015eb7d710d262192ca6f18c1e798f75c4f5477f52d3c" +source = "git+https://github.com/BP-WG/bp-core?branch=deanchor#c014bad013c99e4eb828eabc37c1c5ccc30acf75" dependencies = [ "amplify", "bp-consensus", @@ -236,8 +234,7 @@ dependencies = [ [[package]] name = "bp-dbc" version = "0.11.0-beta.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4d21f5af26b145f7f73c7a338dc7bfe44d2b0f943c3ec6e3447d11d1fc3d6e0" +source = "git+https://github.com/BP-WG/bp-core?branch=deanchor#c014bad013c99e4eb828eabc37c1c5ccc30acf75" dependencies = [ "amplify", "base85", @@ -251,8 +248,7 @@ dependencies = [ [[package]] name = "bp-seals" version = "0.11.0-beta.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131f371c9d605d5ed07cb0f5b07f3f261f6b3f6a146b6e890ecdb37802c51f6a" +source = "git+https://github.com/BP-WG/bp-core?branch=deanchor#c014bad013c99e4eb828eabc37c1c5ccc30acf75" dependencies = [ "amplify", "baid58", diff --git a/Cargo.toml b/Cargo.toml index 11e59f34..7a5e4c08 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/src/contract/anchor.rs b/src/contract/anchor.rs index 648598f4..cc9a48fc 100644 --- a/src/contract/anchor.rs +++ b/src/contract/anchor.rs @@ -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 { Some(self.cmp(other)) } -} +use crate::{BundleId, ContractId, WitnessId, WitnessOrd, XChain, LIB_NAME_RGB}; pub type XAnchor

= XChain>; -impl XAnchor

{ - #[inline] - pub fn witness_id(&self) -> Option { self.maybe_map_ref(|set| set.txid()) } - - #[inline] - pub fn witness_id_unchecked(&self) -> WitnessId { self.map_ref(|set| set.txid_unchecked()) } -} - impl XAnchor { pub fn known_bundle_ids(&self) -> impl Iterator + '_ { match self { @@ -129,23 +94,6 @@ pub enum AnchorSet { } impl AnchorSet

{ - pub fn txid(&self) -> Option { - 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>, opret: Option>, diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 70085d3b..b4e29423 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -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, diff --git a/src/stl.rs b/src/stl.rs index 0f21445f..bc028c3f 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -28,9 +28,7 @@ use strict_types::stl::{std_stl, strict_types_stl}; use strict_types::typelib::LibBuilder; use strict_types::{CompileError, TypeLib}; -use crate::{ - AnchoredBundle, ContractState, Extension, Genesis, OpCommitment, SubSchema, LIB_NAME_RGB, -}; +use crate::{ContractState, Extension, Genesis, OpCommitment, SubSchema, XAnchor, LIB_NAME_RGB}; /// Strict types id for the library providing data types for RGB consensus. pub const LIB_ID_RGB: &str = @@ -47,7 +45,7 @@ fn _rgb_core_stl() -> Result { }) .transpile::() .transpile::() - .transpile::() + .transpile::() .transpile::() .transpile::() .transpile::() diff --git a/src/validation/consignment.rs b/src/validation/consignment.rs index 83d53b7f..0053db6c 100644 --- a/src/validation/consignment.rs +++ b/src/validation/consignment.rs @@ -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); @@ -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> { + fn bundle(&self, bundle_id: BundleId) -> Option> { 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> { self.0.anchor(bundle_id) } + + fn bundle_witness_id(&self, bundle_id: BundleId) -> Option { + self.0.bundle_witness_id(bundle_id) } fn op_witness_id(&self, opid: OpId) -> Option { self.0.op_witness_id(opid) } @@ -81,7 +87,7 @@ pub trait ConsignmentApi { /// Asset tags uses in the confidential asset validation. fn asset_tags(&self) -> &BTreeMap; - /// 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; @@ -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>; + /// Returns reference to a bundle given a bundle id. + fn bundle(&self, bundle_id: BundleId) -> Option>; + + /// Returns reference to an anchor given a bundle id. + fn anchor(&self, bundle_id: BundleId) -> Option>; + + /// Returns witness id for a given transition bundle. + fn bundle_witness_id(&self, bundle_id: BundleId) -> Option; /// Returns witness id for a given operation. fn op_witness_id(&self, opid: OpId) -> Option; diff --git a/src/validation/status.rs b/src/validation/status.rs index 123aa642..c5698855 100644 --- a/src/validation/status.rs +++ b/src/validation/status.rs @@ -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), @@ -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), diff --git a/src/validation/validator.rs b/src/validation/validator.rs index 5e07e962..2b835232 100644 --- a/src/validation/validator.rs +++ b/src/validation/validator.rs @@ -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, @@ -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 @@ -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); } } @@ -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); } } @@ -367,15 +373,12 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness> seals: impl AsRef<[XOutputSeal]>, bundle_id: BundleId, anchors: &XAnchor, + witness_id: WitnessId, ) -> Option { - 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. @@ -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)); @@ -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)); @@ -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,