diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 5e284b9e..981411ae 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -68,6 +68,6 @@ pub use seal::{ }; pub use state::{ConcealedState, ConfidentialState, ExposedState, RevealedState, StateType}; pub use xchain::{ - AltLayer1, AltLayer1Set, XChain, XChainParseError, XOutpoint, XCHAIN_BITCOIN_PREFIX, - XCHAIN_LIQUID_PREFIX, Impossible + AltLayer1, AltLayer1Set, Impossible, XChain, XChainParseError, XOutpoint, + XCHAIN_BITCOIN_PREFIX, XCHAIN_LIQUID_PREFIX, }; diff --git a/src/validation/state.rs b/src/validation/state.rs index 22b9f085..3f2cbf2c 100644 --- a/src/validation/state.rs +++ b/src/validation/state.rs @@ -52,12 +52,12 @@ impl OwnedStateSchema { } } (OwnedStateSchema::Structured(_), ConcealedState::Structured(_)) => { - status.add_info(validation::Info::UncheckableConfidentialState( + status.add_warning(validation::Warning::UncheckableConfidentialState( opid, state_type, )); } (OwnedStateSchema::Attachment(_), ConcealedState::Attachment(_)) => { - status.add_info(validation::Info::UncheckableConfidentialState( + status.add_warning(validation::Warning::UncheckableConfidentialState( opid, state_type, )); } diff --git a/src/validation/status.rs b/src/validation/status.rs index 13f18a19..6f1f8254 100644 --- a/src/validation/status.rs +++ b/src/validation/status.rs @@ -23,14 +23,13 @@ use core::ops::AddAssign; use std::fmt::{self, Display, Formatter}; -use bp::Txid; use commit_verify::mpc::InvalidProof; use strict_types::SemId; use crate::contract::Opout; use crate::schema::{self, SchemaId}; use crate::{ - AssignmentType, BundleId, ContractId, Layer1, OccurrencesMismatch, OpFullType, OpId, + AssignmentType, BundleId, ContractId, Layer1, OccurrencesMismatch, OpFullType, OpId, OpType, SecretSeal, StateType, Vin, XChain, XGraphSeal, XOutputSeal, XWitnessId, }; @@ -40,11 +39,8 @@ pub enum Validity { #[display("is valid")] Valid, - #[display("has non-mined terminal(s)")] - UnminedTerminals, - - #[display("contains unknown witness transactions")] - UnresolvedTransactions, + #[display("valid, with warnings")] + Warnings, #[display("is NOT valid")] Invalid, @@ -57,8 +53,6 @@ pub enum Validity { serde(crate = "serde_crate", rename_all = "camelCase") )] pub struct Status { - pub absent_pub_witnesses: Vec, - pub unmined_terminals: Vec, pub failures: Vec, pub warnings: Vec, pub info: Vec, @@ -70,20 +64,6 @@ impl Display for Status { writeln!(f, "Consignment {}", self.validity())?; } - if !self.absent_pub_witnesses.is_empty() { - f.write_str("Unknown public witnesses:\n")?; - for txid in &self.absent_pub_witnesses { - writeln!(f, "- {txid}")?; - } - } - - if !self.unmined_terminals.is_empty() { - f.write_str("Non-mined terminals:\n")?; - for txid in &self.unmined_terminals { - writeln!(f, "- {txid}")?; - } - } - if !self.failures.is_empty() { f.write_str("Validation failures:\n")?; for fail in &self.failures { @@ -111,8 +91,6 @@ impl Display for Status { impl AddAssign for Status { fn add_assign(&mut self, rhs: Self) { - self.absent_pub_witnesses.extend(rhs.absent_pub_witnesses); - self.unmined_terminals.extend(rhs.unmined_terminals); self.failures.extend(rhs.failures); self.warnings.extend(rhs.warnings); self.info.extend(rhs.info); @@ -122,8 +100,6 @@ impl AddAssign for Status { impl Status { pub fn from_error(v: Failure) -> Self { Status { - absent_pub_witnesses: vec![], - unmined_terminals: vec![], failures: vec![v], warnings: vec![], info: vec![], @@ -166,16 +142,12 @@ impl Status { } pub fn validity(&self) -> Validity { - if self.failures.is_empty() { - if self.unmined_terminals.is_empty() { - Validity::Valid - } else { - Validity::UnminedTerminals - } - } else if self.absent_pub_witnesses.is_empty() { + if !self.failures.is_empty() { Validity::Invalid + } else if !self.warnings.is_empty() { + Validity::Warnings } else { - Validity::UnresolvedTransactions + Validity::Valid } } } @@ -265,6 +237,12 @@ pub enum Failure { CyclicGraph(OpId), /// operation {0} is absent from the consignment. OperationAbsent(OpId), + /// {ty} data doesn't match operation id {expected} (actual id is {actual}). + OperationIdMismatch { + ty: OpType, + expected: OpId, + actual: OpId, + }, /// transition bundle {0} referenced in consignment terminals is absent from /// the consignment. TerminalBundleAbsent(BundleId), @@ -308,8 +286,8 @@ pub enum Failure { /// seal defined in the history as a part of operation output {0} is /// confidential and can't be validated. ConfidentialSeal(Opout), - /// witness {0} is not known to the transaction resolver. - SealNoWitnessTx(XWitnessId), + /// public witness {0} is not known to the resolver. + SealNoPubWitness(XWitnessId), /// witness layer 1 {anchor} doesn't match seal definition {seal}. SealWitnessLayer1Mismatch { seal: Layer1, anchor: Layer1 }, /// seal {1} is defined on {0} which is not in the set of layers allowed @@ -390,12 +368,12 @@ pub enum Failure { )] #[display(doc_comments)] pub enum Warning { - // TODO: Replace debug with display /// terminal seal {1:?} referencing operation {0} is not present in /// operation assignments. TerminalSealAbsent(OpId, XChain), - /// terminal witness transaction {0} is not yet mined. - TerminalWitnessNotMined(Txid), + /// operation {0} contains state in assignment {1} which is confidential and + /// thus was not validated. + UncheckableConfidentialState(OpId, schema::AssignmentType), /// Custom warning by external services on top of RGB Core. #[display(inner)] @@ -410,10 +388,6 @@ pub enum Warning { )] #[display(doc_comments)] pub enum Info { - /// operation {0} contains state in assignment {1} which is confidential and - /// thus was not validated. - UncheckableConfidentialState(OpId, schema::AssignmentType), - /// Custom info by external services on top of RGB Core. #[display(inner)] Custom(String), diff --git a/src/validation/validator.rs b/src/validation/validator.rs index b4159a55..def5fc32 100644 --- a/src/validation/validator.rs +++ b/src/validation/validator.rs @@ -230,10 +230,13 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness> fn validate_logic_on_route(&self, opid: OpId) { let schema = self.consignment.schema(); let Some(OpRef::Transition(transition)) = self.consignment.operation(opid) else { - panic!("provided {opid} is absent"); + self.status + .borrow_mut() + .add_failure(Failure::OperationAbsent(opid)); + return; }; - let mut queue: VecDeque = VecDeque::new(); + let mut queue: VecDeque<(OpId, OpRef)> = VecDeque::new(); // Instead of constructing complex graph structures or using a recursions we // utilize queue to keep the track of the upstream (ancestor) nodes and make @@ -244,9 +247,18 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness> // change to a given operation is valid against the schema + committed // into bitcoin transaction graph with proper anchor. That is what we are // checking in the code below: - queue.push_back(OpRef::Transition(transition)); - while let Some(operation) = queue.pop_front() { - let opid = operation.id(); + queue.push_back((opid, OpRef::Transition(transition))); + while let Some((opid, operation)) = queue.pop_front() { + let actual_opid = operation.id(); + if opid != actual_opid { + self.status + .borrow_mut() + .add_failure(Failure::OperationIdMismatch { + ty: operation.op_type(), + expected: opid, + actual: actual_opid, + }); + } if operation.contract_id() != self.contract_id { self.status @@ -274,12 +286,15 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness> OpRef::Transition(transition) => { // Now, we must collect all parent nodes and add them to the verification queue let parent_nodes = transition.inputs.iter().filter_map(|input| { - self.consignment.operation(input.prev_out.op).or_else(|| { - self.status - .borrow_mut() - .add_failure(Failure::OperationAbsent(input.prev_out.op)); - None - }) + self.consignment + .operation(input.prev_out.op) + .map(|op| (input.prev_out.op, op)) + .or_else(|| { + self.status + .borrow_mut() + .add_failure(Failure::OperationAbsent(input.prev_out.op)); + None + }) }); queue.extend(parent_nodes); @@ -308,7 +323,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness> continue; } - queue.push_back(prev_op); + queue.push_back((*prev_id, prev_op)); } } } @@ -407,19 +422,14 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveWitness> // Reporting this incident and continuing further. Why this happens? No // connection to Bitcoin Core, Electrum or other backend etc. So this is not a // failure in a strict sense, however we can't be sure that the consignment is - // valid. That's why we keep the track of such information in a separate place - // (`unresolved_txids` field of the validation status object). - self.status - .borrow_mut() - .absent_pub_witnesses - .push(witness_id); + // valid. // This also can mean that there is no known transaction with the id provided by // the anchor, i.e. consignment is invalid. We are proceeding with further // validation in order to detect the rest of problems (and reporting the // failure!) self.status .borrow_mut() - .add_failure(Failure::SealNoWitnessTx(witness_id)); + .add_failure(Failure::SealNoPubWitness(witness_id)); None } Ok(pub_witness) => {