diff --git a/Cargo.lock b/Cargo.lock index 14e3ebe4..e757ca1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c8b31faf1f2667ba122c49112d3c643588d11e7a999585192c2d492d697bb46" dependencies = [ "amplify", - "baid58", + "baid58 0.2.0", "bitcoin_hashes", "blake3", "half", @@ -20,9 +20,9 @@ dependencies = [ [[package]] name = "amplify" -version = "4.0.0-beta.20" +version = "4.0.0-beta.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da67d736b18002e654099c6b393ebb3058825513baefec514cf7725c2088ced7" +checksum = "63ee47f02478dc742201353a504cba65c7ca0959b4830c9324e7e95c8f98b3fa" dependencies = [ "amplify_apfloat", "amplify_derive", @@ -126,6 +126,17 @@ dependencies = [ "mnemonic", ] +[[package]] +name = "baid58" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe675ca8398044a1076a8237e60540182931807beec840ddfa89f9df9f8c4d9f" +dependencies = [ + "base58", + "blake3", + "mnemonic", +] + [[package]] name = "base58" version = "0.2.0" @@ -232,7 +243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24f0598dcb7c3c8c8b983eca9ef0dbfdc86503e5b258918122ac90d88030dcea" dependencies = [ "amplify", - "baid58", + "baid58 0.2.0", "bp-dbc", "bp-primitives", "commit_verify", @@ -684,7 +695,7 @@ version = "0.10.0-rc.2" dependencies = [ "aluvm", "amplify", - "baid58", + "baid58 0.3.0", "bp-core", "commit_verify", "getrandom", @@ -911,7 +922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "371701e525f77661673ef2dca749793000162c28eb9745e69499653f61f6a9e2" dependencies = [ "amplify", - "baid58", + "baid58 0.2.0", "blake3", "half", "indexmap", diff --git a/Cargo.toml b/Cargo.toml index d6b621f7..05c0da72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ name = "rgb" crate-type = ["cdylib", "rlib"] # We need this for WASM [dependencies] -amplify = "~4.0.0-beta.20" +amplify = "~4.0.0-beta.22" strict_encoding = "~2.0.1" strict_types = "~1.0.0-rc.1" aluvm = { version = "~0.10.0-rc.1", features = ["std"] } @@ -26,7 +26,7 @@ commit_verify = { version = "~0.10.0-rc.3", features = ["rand"] } single_use_seals = "~0.10.0-rc.2" bp-core = { version = "~0.10.0-rc.2" } secp256k1-zkp = { version = "~0.7.0", features = ["use-rand", "rand-std", "global-context"] } -baid58 = "~0.2.0" +baid58 = "~0.3.0" mime = "~0.3.16" serde_crate = { package = "serde", version = "1", features = ["derive"], optional = true } diff --git a/src/contract/assignments.rs b/src/contract/assignments.rs index 9425d131..f3582477 100644 --- a/src/contract/assignments.rs +++ b/src/contract/assignments.rs @@ -561,8 +561,9 @@ impl TypedAssigns { } } -#[derive(Wrapper, Clone, PartialEq, Eq, Hash, Debug, From)] +#[derive(Wrapper, WrapperMut, Clone, PartialEq, Eq, Hash, Debug, From)] #[wrapper(Deref)] +#[wrapper_mut(DerefMut)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB)] #[cfg_attr( diff --git a/src/contract/contract.rs b/src/contract/contract.rs index 2f64068e..caf07347 100644 --- a/src/contract/contract.rs +++ b/src/contract/contract.rs @@ -385,26 +385,26 @@ impl ContractHistory { } // Remove invalidated state - for output in op.prev_outs() { - if let Some(o) = self.rights.iter().find(|r| r.opout == output) { + for input in op.inputs() { + if let Some(o) = self.rights.iter().find(|r| r.opout == input.prev_out) { let o = o.clone(); // need this b/c of borrow checker self.rights .remove(&o) .expect("collection allows zero elements"); } - if let Some(o) = self.fungibles.iter().find(|r| r.opout == output) { + if let Some(o) = self.fungibles.iter().find(|r| r.opout == input.prev_out) { let o = o.clone(); self.fungibles .remove(&o) .expect("collection allows zero elements"); } - if let Some(o) = self.data.iter().find(|r| r.opout == output) { + if let Some(o) = self.data.iter().find(|r| r.opout == input.prev_out) { let o = o.clone(); self.data .remove(&o) .expect("collection allows zero elements"); } - if let Some(o) = self.attach.iter().find(|r| r.opout == output) { + if let Some(o) = self.attach.iter().find(|r| r.opout == input.prev_out) { let o = o.clone(); self.attach .remove(&o) diff --git a/src/contract/data.rs b/src/contract/data.rs index 42afdbb6..86b1fe02 100644 --- a/src/contract/data.rs +++ b/src/contract/data.rs @@ -20,10 +20,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::fmt::Debug; +use core::fmt::{self, Debug, Display, Formatter}; use amplify::confinement::SmallVec; -use amplify::Bytes32; +use amplify::hex::ToHex; +use amplify::{Bytes32, Wrapper}; use commit_verify::{CommitStrategy, CommitVerify, Conceal, StrictEncodedProtocol}; use strict_encoding::{StrictSerialize, StrictType}; @@ -57,7 +58,7 @@ impl CommitStrategy for VoidState { type Strategy = commit_verify::strategies::Strict; } -#[derive(Wrapper, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, From)] +#[derive(Wrapper, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, From)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] @@ -79,6 +80,21 @@ impl CommitStrategy for RevealedData { impl StrictSerialize for RevealedData {} +impl Debug for RevealedData { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let val = match String::from_utf8(self.0.to_inner()) { + Ok(s) => s, + Err(_) => self.0.to_hex(), + }; + + f.debug_tuple("RevealedData").field(&val).finish() + } +} + +impl Display for RevealedData { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(&self.as_ref().to_hex()) } +} + /// Confidential version of an structured state data. /// /// See also revealed version [`RevealedData`]. diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 941d7c7f..1fedf6c0 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -48,7 +48,7 @@ pub use fungible::{ }; pub use global::{GlobalState, GlobalValues}; pub use operations::{ - ContractId, Extension, Genesis, OpId, OpRef, Operation, PrevOuts, Redeemed, Transition, + ContractId, Extension, Genesis, Input, Inputs, OpId, OpRef, Operation, Redeemed, Transition, Valencies, }; pub use seal::{ExposedSeal, GenesisSeal, GraphSeal, SealWitness, SecretSeal, TxoSeal}; diff --git a/src/contract/operations.rs b/src/contract/operations.rs index 36736ae5..be6efd38 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -34,13 +34,36 @@ use strict_encoding::{StrictDeserialize, StrictEncode, StrictSerialize}; use crate::schema::{self, ExtensionType, OpFullType, OpType, SchemaId, TransitionType}; use crate::{ AssignmentType, Assignments, AssignmentsRef, Ffv, GenesisSeal, GlobalState, GraphSeal, Opout, - TypedAssigns, LIB_NAME_RGB, + ReservedByte, TypedAssigns, LIB_NAME_RGB, }; pub type Valencies = TinyOrdSet; -pub type PrevOuts = TinyOrdSet; +pub type Inputs = TinyOrdSet; pub type Redeemed = TinyOrdMap; +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[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") +)] +#[display("{prev_out}")] +pub struct Input { + pub prev_out: Opout, + reserved: ReservedByte, +} + +impl Input { + pub fn with(prev_out: Opout) -> Input { + Input { + prev_out, + reserved: default!(), + } + } +} + /// Unique operation (genesis, extensions & state transition) identifier /// equivalent to the commitment hash #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)] @@ -73,7 +96,7 @@ impl OpId { /// Unique contract identifier equivalent to the contract genesis commitment #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)] #[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)] -#[display(Self::to_baid58)] +#[display(Self::to_baid58_string)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB)] #[cfg_attr( @@ -87,6 +110,13 @@ pub struct ContractId( Bytes32, ); +impl PartialEq for ContractId { + fn eq(&self, other: &OpId) -> bool { self.to_raw_array() == other.to_raw_array() } +} +impl PartialEq for OpId { + fn eq(&self, other: &ContractId) -> bool { self.to_raw_array() == other.to_raw_array() } +} + impl ContractId { pub fn from_slice(slice: impl AsRef<[u8]>) -> Option { Bytes32::from_slice(slice).map(Self) @@ -99,6 +129,10 @@ impl ToBaid58<32> for ContractId { } impl FromBaid58<32> for ContractId {} +impl ContractId { + fn to_baid58_string(&self) -> String { format!("{:0}", self.to_baid58()) } +} + impl FromStr for ContractId { type Err = Baid58ParseError; fn from_str(s: &str) -> Result { Self::from_baid58_str(s) } @@ -132,6 +166,9 @@ pub trait Operation { /// serialization fn id(&self) -> OpId; + /// Returns [`ContractId`] this operation belongs to. + fn contract_id(&self) -> ContractId; + /// Returns [`Option::Some`]`(`[`TransitionType`]`)` for transitions or /// [`Option::None`] for genesis and extension operation types fn transition_type(&self) -> Option; @@ -155,7 +192,7 @@ pub trait Operation { /// For genesis and public state extensions always returns an empty list. /// While public state extension do have parent nodes, they do not contain /// indexed rights. - fn prev_outs(&self) -> TinyOrdSet; + fn inputs(&self) -> Inputs; } #[derive(Clone, PartialEq, Eq, Hash, Debug)] @@ -189,8 +226,8 @@ impl StrictDeserialize for Genesis {} )] pub struct Extension { pub ffv: Ffv, - pub extension_type: ExtensionType, pub contract_id: ContractId, + pub extension_type: ExtensionType, pub metadata: SmallBlob, pub globals: GlobalState, pub assignments: Assignments, @@ -211,10 +248,11 @@ impl StrictDeserialize for Extension {} )] pub struct Transition { pub ffv: Ffv, + pub contract_id: ContractId, pub transition_type: TransitionType, pub metadata: SmallBlob, pub globals: GlobalState, - pub inputs: PrevOuts, + pub inputs: Inputs, pub assignments: Assignments, pub valencies: Valencies, } @@ -258,22 +296,14 @@ impl CommitmentId for Extension { type Id = OpId; } -impl Genesis { - #[inline] - pub fn contract_id(&self) -> ContractId { ContractId::from_inner(self.id().into_inner()) } -} - impl Transition { /// Returns reference to information about the owned rights in form of - /// [`PrevOuts`] wrapper structure which this operation updates with + /// [`Inputs`] wrapper structure which this operation updates with /// state transition ("parent owned rights"). - pub fn prev_state(&self) -> &PrevOuts { &self.inputs } + pub fn prev_state(&self) -> &Inputs { &self.inputs } } impl Extension { - #[inline] - pub fn contract_id(&self) -> ContractId { self.contract_id } - /// Returns reference to information about the public rights (in form of /// [`Redeemed`] wrapper structure), defined with "parent" state /// extensions (i.e. those finalized with the current state transition) or @@ -292,6 +322,9 @@ impl Operation for Genesis { #[inline] fn id(&self) -> OpId { OpId(self.commitment_id().into_inner()) } + #[inline] + fn contract_id(&self) -> ContractId { ContractId::from_inner(self.id().into_inner()) } + #[inline] fn transition_type(&self) -> Option { None } @@ -318,7 +351,7 @@ impl Operation for Genesis { } #[inline] - fn prev_outs(&self) -> TinyOrdSet { empty!() } + fn inputs(&self) -> Inputs { empty!() } } impl Operation for Extension { @@ -331,6 +364,9 @@ impl Operation for Extension { #[inline] fn id(&self) -> OpId { self.commitment_id() } + #[inline] + fn contract_id(&self) -> ContractId { self.contract_id } + #[inline] fn transition_type(&self) -> Option { None } @@ -357,7 +393,7 @@ impl Operation for Extension { } #[inline] - fn prev_outs(&self) -> TinyOrdSet { empty!() } + fn inputs(&self) -> Inputs { empty!() } } impl Operation for Transition { @@ -370,6 +406,9 @@ impl Operation for Transition { #[inline] fn id(&self) -> OpId { self.commitment_id() } + #[inline] + fn contract_id(&self) -> ContractId { self.contract_id } + #[inline] fn transition_type(&self) -> Option { Some(self.transition_type) } @@ -393,7 +432,7 @@ impl Operation for Transition { self.assignments.get(&t).cloned() } - fn prev_outs(&self) -> TinyOrdSet { self.inputs.clone() } + fn inputs(&self) -> Inputs { self.inputs.clone() } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, From)] @@ -431,6 +470,14 @@ impl<'op> Operation for OpRef<'op> { } } + fn contract_id(&self) -> ContractId { + match self { + OpRef::Genesis(op) => op.contract_id(), + OpRef::Transition(op) => op.contract_id(), + OpRef::Extension(op) => op.contract_id(), + } + } + fn transition_type(&self) -> Option { match self { OpRef::Genesis(op) => op.transition_type(), @@ -487,11 +534,11 @@ impl<'op> Operation for OpRef<'op> { } } - fn prev_outs(&self) -> TinyOrdSet { + fn inputs(&self) -> Inputs { match self { - OpRef::Genesis(op) => op.prev_outs(), - OpRef::Transition(op) => op.prev_outs(), - OpRef::Extension(op) => op.prev_outs(), + OpRef::Genesis(op) => op.inputs(), + OpRef::Transition(op) => op.inputs(), + OpRef::Extension(op) => op.inputs(), } } } diff --git a/src/lib.rs b/src/lib.rs index 5be333fd..3646dd0b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,39 @@ pub mod prelude { pub use prelude::*; +/// Reserved byte. +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, Display)] +#[display("reserved")] +#[derive(StrictType, StrictEncode)] +#[strict_type(lib = LIB_NAME_RGB)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +pub struct ReservedByte(u8); + +mod _reserved { + use strict_encoding::{DecodeError, ReadTuple, StrictDecode, TypedRead}; + + use crate::ReservedByte; + + impl StrictDecode for ReservedByte { + fn strict_decode(reader: &mut impl TypedRead) -> Result { + let reserved = reader.read_tuple(|r| r.read_field().map(Self))?; + if reserved != ReservedByte::default() { + Err(DecodeError::DataIntegrityError(format!( + "unsupported reserved byte value indicating a future RGB version. Please \ + update your software, or, if the problem persists, contact your vendor \ + providing the following version information: {reserved}" + ))) + } else { + Ok(reserved) + } + } + } +} + /// Fast-forward version code #[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, Display)] #[display("v0.10.0+{0}")] diff --git a/src/schema/schema.rs b/src/schema/schema.rs index d42cffcf..e0a698bf 100644 --- a/src/schema/schema.rs +++ b/src/schema/schema.rs @@ -52,7 +52,7 @@ pub const BLANK_TRANSITION_ID: u16 = TransitionType::MAX; /// Schema identifier commits to all of the schema data. #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)] #[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)] -#[display(Self::to_baid58)] +#[display(Self::to_baid58_string)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB)] #[cfg_attr( @@ -72,6 +72,14 @@ impl ToBaid58<32> for SchemaId { } impl FromBaid58<32> for SchemaId {} +impl SchemaId { + fn to_baid58_string(&self) -> String { format!("{}", self.to_baid58()) } + pub fn mnemonic_checksum(&self) -> String { + self.to_baid58() + .mnemonic_with_case(baid58::MnemonicCase::Kebab) + } +} + impl FromStr for SchemaId { type Err = Baid58ParseError; fn from_str(s: &str) -> Result { Self::from_baid58_str(s) } @@ -156,16 +164,12 @@ mod test { fn display() { let dumb = SchemaId::strict_dumb(); assert_eq!(dumb.to_string(), "11111111111111111111111111111111"); - assert_eq!( - &format!("{dumb::^#}"), - "rgb-sch:11111111111111111111111111111111#sweden-gate-virgo" - ); + assert_eq!(&format!("{dumb::^#}"), "11111111111111111111111111111111"); + assert_eq!(dumb.mnemonic_checksum(), "sweden-gate-virgo"); let less_dumb = SchemaId::from_raw_array(*b"EV4350-'4vwj'4;v-w94w'e'vFVVDhpq"); assert_eq!(less_dumb.to_string(), "5ffNUkMTVSnWquPLT6xKb7VmAxUbw8CUNqCkUWsZfkwz"); - assert_eq!( - &format!("{less_dumb::^#}"), - "rgb-sch:5ffNUkMTVSnWquPLT6xKb7VmAxUbw8CUNqCkUWsZfkwz#salami-comedy-cello" - ); + assert_eq!(&format!("{less_dumb::^#}"), "5ffNUkMTVSnWquPLT6xKb7VmAxUbw8CUNqCkUWsZfkwz"); + assert_eq!(less_dumb.mnemonic_checksum(), "salami-comedy-cello"); } } diff --git a/src/validation/model.rs b/src/validation/model.rs index 234faea3..0b075657 100644 --- a/src/validation/model.rs +++ b/src/validation/model.rs @@ -30,7 +30,7 @@ use crate::schema::{AssignmentsSchema, GlobalSchema, ValencySchema}; use crate::validation::{ConsignmentApi, VirtualMachine}; use crate::{ validation, Assignments, AssignmentsRef, ExposedSeal, GlobalState, GlobalStateSchema, - GlobalValues, GraphSeal, OpFullType, OpId, OpRef, Operation, Opout, PrevOuts, Redeemed, Schema, + GlobalValues, GraphSeal, Inputs, OpFullType, OpId, OpRef, Operation, Opout, Redeemed, Schema, SchemaRoot, TypedAssigns, Valencies, BLANK_TRANSITION_ID, }; @@ -456,12 +456,12 @@ impl<'op> OpInfo<'op> { fn extract_prev_state( consignment: &C, opid: OpId, - prev_state: &PrevOuts, + inputs: &Inputs, status: &mut validation::Status, ) -> Assignments { let mut assignments = bmap! {}; - for opout in prev_state { - let Opout { op, ty, no } = *opout; + for input in inputs { + let Opout { op, ty, no } = input.prev_out; let prev_op = match consignment.operation(op) { None => { @@ -473,56 +473,56 @@ fn extract_prev_state( let no = no as usize; match prev_op.assignments_by_type(ty) { - Some(TypedAssigns::Declarative(mut prev_assignments)) => { - if let Ok(prev_assign) = prev_assignments.remove(no) { + Some(TypedAssigns::Declarative(prev_assignments)) => { + if let Some(prev_assign) = prev_assignments.get(no) { if let Some(typed_assigns) = assignments .entry(ty) .or_insert_with(|| TypedAssigns::Declarative(Default::default())) .as_declarative_mut() { - typed_assigns.push(prev_assign).expect("same size"); + typed_assigns.push(prev_assign.clone()).expect("same size"); } } else { - status.add_failure(validation::Failure::NoPrevOut(opid, *opout)); + status.add_failure(validation::Failure::NoPrevOut(opid, input.prev_out)); } } - Some(TypedAssigns::Fungible(mut prev_assignments)) => { - if let Ok(prev_assign) = prev_assignments.remove(no) { + Some(TypedAssigns::Fungible(prev_assignments)) => { + if let Some(prev_assign) = prev_assignments.get(no) { if let Some(typed_assigns) = assignments .entry(ty) .or_insert_with(|| TypedAssigns::Fungible(Default::default())) .as_fungible_mut() { - typed_assigns.push(prev_assign).expect("same size"); + typed_assigns.push(prev_assign.clone()).expect("same size"); } } else { - status.add_failure(validation::Failure::NoPrevOut(opid, *opout)); + status.add_failure(validation::Failure::NoPrevOut(opid, input.prev_out)); } } - Some(TypedAssigns::Structured(mut prev_assignments)) => { - if let Ok(prev_assign) = prev_assignments.remove(no) { + Some(TypedAssigns::Structured(prev_assignments)) => { + if let Some(prev_assign) = prev_assignments.get(no) { if let Some(typed_assigns) = assignments .entry(ty) .or_insert_with(|| TypedAssigns::Structured(Default::default())) .as_structured_mut() { - typed_assigns.push(prev_assign).expect("same size"); + typed_assigns.push(prev_assign.clone()).expect("same size"); } } else { - status.add_failure(validation::Failure::NoPrevOut(opid, *opout)); + status.add_failure(validation::Failure::NoPrevOut(opid, input.prev_out)); } } - Some(TypedAssigns::Attachment(mut prev_assignments)) => { - if let Ok(prev_assign) = prev_assignments.remove(no) { + Some(TypedAssigns::Attachment(prev_assignments)) => { + if let Some(prev_assign) = prev_assignments.get(no) { if let Some(typed_assigns) = assignments .entry(ty) .or_insert_with(|| TypedAssigns::Attachment(Default::default())) .as_attachment_mut() { - typed_assigns.push(prev_assign).expect("same size"); + typed_assigns.push(prev_assign.clone()).expect("same size"); } } else { - status.add_failure(validation::Failure::NoPrevOut(opid, *opout)); + status.add_failure(validation::Failure::NoPrevOut(opid, input.prev_out)); } } None => { diff --git a/src/validation/status.rs b/src/validation/status.rs index 33989fe3..68a2d32e 100644 --- a/src/validation/status.rs +++ b/src/validation/status.rs @@ -69,35 +69,35 @@ impl Display for Status { writeln!(f, "Consignment {}", self.validity())?; if !self.unresolved_txids.is_empty() { - f.write_str("Unknown witness transactions:")?; + f.write_str("Unknown witness transactions:\n")?; for txid in &self.unresolved_txids { writeln!(f, "- {txid}")?; } } if !self.unmined_terminals.is_empty() { - f.write_str("Non-mined terminals:")?; + 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:")?; + f.write_str("Validation failures:\n")?; for fail in &self.failures { writeln!(f, "- {fail}")?; } } if !self.warnings.is_empty() { - f.write_str("Validation failures:")?; + f.write_str("Validation warnings:\n")?; for warn in &self.warnings { writeln!(f, "- {warn}")?; } } if !self.info.is_empty() { - f.write_str("Validation failures:")?; + f.write_str("Validation info:\n")?; for info in &self.info { writeln!(f, "- {info}")?; } diff --git a/src/validation/validator.rs b/src/validation/validator.rs index 6054580f..98d34fa3 100644 --- a/src/validation/validator.rs +++ b/src/validation/validator.rs @@ -38,10 +38,14 @@ use crate::{ Transition, TransitionBundle, TypedAssigns, }; -#[derive(Debug, Display, Error)] +#[derive(Clone, Debug, Display, Error, From)] #[display(doc_comments)] -/// transaction {0} is not mined -pub struct TxResolverError(Txid); +pub enum TxResolverError { + /// transaction {0} is not mined + Unknown(Txid), + /// unable to retriev transaction {0}, {1} + Other(Txid, String), +} pub trait ResolveTx { fn resolve_tx(&self, txid: Txid) -> Result; @@ -305,13 +309,13 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> } // Now, we must collect all parent nodes and add them to the verification queue - let parent_nodes = transition.inputs.iter().filter_map(|prevout| { - self.consignment.operation(prevout.op).or_else(|| { + let parent_nodes = transition.inputs.iter().filter_map(|input| { + self.consignment.operation(input.prev_out.op).or_else(|| { // This will not actually happen since we already checked that each // ancestor reference has a corresponding operation in the code above. // But lets double-check :) self.status - .add_failure(Failure::TransitionAbsent(prevout.op)); + .add_failure(Failure::TransitionAbsent(input.prev_out.op)); None }) }); @@ -386,8 +390,8 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> // Checking that witness transaction closes seals defined by transition previous // outputs. let mut seals = vec![]; - for prev_out in &transition.inputs { - let Opout { op, ty, no } = *prev_out; + for input in &transition.inputs { + let Opout { op, ty, no } = input.prev_out; let Some(prev_op) = self.consignment.operation(op) else { // Node, referenced as the ancestor, was not found in the consignment. @@ -403,14 +407,14 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> }; let Ok(seal) = variant.revealed_seal_at(no) else { - self.status.add_failure(Failure::NoPrevOut(opid,*prev_out)); + self.status.add_failure(Failure::NoPrevOut(opid,input.prev_out)); continue }; let Some(seal) = seal else { // Everything is ok, but we have incomplete data (confidential), thus can't do a // full verification and have to report the failure self.status - .add_failure(Failure::ConfidentialSeal(*prev_out)); + .add_failure(Failure::ConfidentialSeal(input.prev_out)); continue }; seals.push(seal)