From 1d68668c330c757731407fe7acc67b8eeea0a4ed Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 8 Apr 2023 13:05:49 +0200 Subject: [PATCH 01/13] contract: fix ContractId display format --- Cargo.lock | 19 +++++++++++++++---- Cargo.toml | 2 +- src/contract/operations.rs | 6 +++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14e3ebe4..f44ca4e5 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", @@ -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..fef3890c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/operations.rs b/src/contract/operations.rs index 36736ae5..44450406 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -73,7 +73,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( @@ -99,6 +99,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) } From 0eef1bf8271504d863c3b3c5835000928f2fde49 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 8 Apr 2023 13:10:59 +0200 Subject: [PATCH 02/13] schema: fix SchemaId display format --- src/schema/schema.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) 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"); } } From 8fd77d0cd15205fa5470afb514f921d39b2afd9b Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sat, 8 Apr 2023 14:03:55 +0200 Subject: [PATCH 03/13] contract: implement Display for RevealedData as hex string --- src/contract/data.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/contract/data.rs b/src/contract/data.rs index 42afdbb6..65672ce0 100644 --- a/src/contract/data.rs +++ b/src/contract/data.rs @@ -20,9 +20,10 @@ // 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::hex::ToHex; use amplify::Bytes32; use commit_verify::{CommitStrategy, CommitVerify, Conceal, StrictEncodedProtocol}; use strict_encoding::{StrictSerialize, StrictType}; @@ -79,6 +80,10 @@ impl CommitStrategy for RevealedData { impl StrictSerialize for RevealedData {} +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`]. From 9ccdf04c2d7884476ec7d6063eabb4afb91a64e7 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 10:58:47 +0200 Subject: [PATCH 04/13] contract: refactor Debug impl for RevealedData --- src/contract/data.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/contract/data.rs b/src/contract/data.rs index 65672ce0..86b1fe02 100644 --- a/src/contract/data.rs +++ b/src/contract/data.rs @@ -24,7 +24,7 @@ use core::fmt::{self, Debug, Display, Formatter}; use amplify::confinement::SmallVec; use amplify::hex::ToHex; -use amplify::Bytes32; +use amplify::{Bytes32, Wrapper}; use commit_verify::{CommitStrategy, CommitVerify, Conceal, StrictEncodedProtocol}; use strict_encoding::{StrictSerialize, StrictType}; @@ -58,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"))] @@ -80,6 +80,17 @@ 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()) } } From 483a81da47a9a06fc36c1440bd3dfac06ea10264 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 13:28:13 +0200 Subject: [PATCH 05/13] contract: add Eq between ContractId and OpId --- src/contract/operations.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/contract/operations.rs b/src/contract/operations.rs index 44450406..6fa6379b 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -87,6 +87,10 @@ pub struct ContractId( Bytes32, ); +impl PartialEq for ContractId { + fn eq(&self, other: &OpId) -> 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) From 8700f94c1a08eacfe5a68e300f90b746b03406a4 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 13:29:32 +0200 Subject: [PATCH 06/13] contract: add Eq between ContractId and OpId (2) --- src/contract/operations.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/contract/operations.rs b/src/contract/operations.rs index 6fa6379b..bb30694b 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -90,6 +90,9 @@ pub struct ContractId( 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 { From ad147d108e8b630fad13d6567e9170f809910318 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 14:38:06 +0200 Subject: [PATCH 07/13] validation: refactor TxResolverError --- src/validation/status.rs | 10 +++++----- src/validation/validator.rs | 10 +++++++--- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/validation/status.rs b/src/validation/status.rs index 33989fe3..63570a80 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 failures:\n")?; for warn in &self.warnings { writeln!(f, "- {warn}")?; } } if !self.info.is_empty() { - f.write_str("Validation failures:")?; + f.write_str("Validation failures:\n")?; for info in &self.info { writeln!(f, "- {info}")?; } diff --git a/src/validation/validator.rs b/src/validation/validator.rs index 6054580f..3ef75da3 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; From 71a0c800bb1a47e89b538222f22bf83eaf37af89 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 14:44:48 +0200 Subject: [PATCH 08/13] valudation: improve status formatting --- src/validation/status.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/validation/status.rs b/src/validation/status.rs index 63570a80..68a2d32e 100644 --- a/src/validation/status.rs +++ b/src/validation/status.rs @@ -90,14 +90,14 @@ impl Display for Status { } if !self.warnings.is_empty() { - f.write_str("Validation failures:\n")?; + f.write_str("Validation warnings:\n")?; for warn in &self.warnings { writeln!(f, "- {warn}")?; } } if !self.info.is_empty() { - f.write_str("Validation failures:\n")?; + f.write_str("Validation info:\n")?; for info in &self.info { writeln!(f, "- {info}")?; } From e39f49d1cb91fefaa85f6ad49436f06dd8710d9f Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 15:01:35 +0200 Subject: [PATCH 09/13] validation: fix failure validating last element in confined collection --- src/validation/model.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/validation/model.rs b/src/validation/model.rs index 234faea3..c5ec903b 100644 --- a/src/validation/model.rs +++ b/src/validation/model.rs @@ -473,53 +473,53 @@ 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)); } } - 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)); } } - 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)); } } - 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)); From 9c9afc5262596ebe6f25c404cde99c55b63a2b6d Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 16:06:05 +0200 Subject: [PATCH 10/13] contract: add DerefMut to Assignments --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/contract/assignments.rs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f44ca4e5..e757ca1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index fef3890c..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"] } 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( From 514ece03de425f6b0dad88a6e3f48a4031042b49 Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 16:49:11 +0200 Subject: [PATCH 11/13] contract: commit to ContractId in state transitions. Closes #148 --- src/contract/operations.rs | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/contract/operations.rs b/src/contract/operations.rs index bb30694b..158fdd1b 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -143,6 +143,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; @@ -200,8 +203,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, @@ -222,6 +225,7 @@ impl StrictDeserialize for Extension {} )] pub struct Transition { pub ffv: Ffv, + pub contract_id: ContractId, pub transition_type: TransitionType, pub metadata: SmallBlob, pub globals: GlobalState, @@ -269,11 +273,6 @@ 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 @@ -282,9 +281,6 @@ impl Transition { } 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 @@ -303,6 +299,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 } @@ -342,6 +341,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 } @@ -381,6 +383,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) } @@ -442,6 +447,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(), From b361a6ed332d7dfaf5f3baa938e9895313e2066e Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 16:55:27 +0200 Subject: [PATCH 12/13] contract: add reserved field to transition inputs. Closes #149 --- src/contract/contract.rs | 10 +++++----- src/contract/mod.rs | 3 +-- src/contract/operations.rs | 38 +++++++++++++++++++++++++------------ src/validation/model.rs | 16 ++++++++-------- src/validation/validator.rs | 14 +++++++------- 5 files changed, 47 insertions(+), 34 deletions(-) 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/mod.rs b/src/contract/mod.rs index 941d7c7f..4b62b644 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -48,8 +48,7 @@ pub use fungible::{ }; pub use global::{GlobalState, GlobalValues}; pub use operations::{ - ContractId, Extension, Genesis, OpId, OpRef, Operation, PrevOuts, Redeemed, Transition, - Valencies, + ContractId, Extension, Genesis, Inputs, OpId, OpRef, Operation, Redeemed, Transition, Valencies, }; pub use seal::{ExposedSeal, GenesisSeal, GraphSeal, SealWitness, SecretSeal, TxoSeal}; pub use state::{ConfidentialState, ExposedState, StateCommitment, StateData, StateType}; diff --git a/src/contract/operations.rs b/src/contract/operations.rs index 158fdd1b..b50b120c 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -38,9 +38,23 @@ use crate::{ }; 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, + pub reserved: u8, +} + /// Unique operation (genesis, extensions & state transition) identifier /// equivalent to the commitment hash #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)] @@ -169,7 +183,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)] @@ -229,7 +243,7 @@ pub struct Transition { pub transition_type: TransitionType, pub metadata: SmallBlob, pub globals: GlobalState, - pub inputs: PrevOuts, + pub inputs: Inputs, pub assignments: Assignments, pub valencies: Valencies, } @@ -275,9 +289,9 @@ impl CommitmentId for Extension { 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 { @@ -328,7 +342,7 @@ impl Operation for Genesis { } #[inline] - fn prev_outs(&self) -> TinyOrdSet { empty!() } + fn inputs(&self) -> Inputs { empty!() } } impl Operation for Extension { @@ -370,7 +384,7 @@ impl Operation for Extension { } #[inline] - fn prev_outs(&self) -> TinyOrdSet { empty!() } + fn inputs(&self) -> Inputs { empty!() } } impl Operation for Transition { @@ -409,7 +423,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)] @@ -511,11 +525,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/validation/model.rs b/src/validation/model.rs index c5ec903b..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 => { @@ -483,7 +483,7 @@ fn extract_prev_state( 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(prev_assignments)) => { @@ -496,7 +496,7 @@ fn extract_prev_state( 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(prev_assignments)) => { @@ -509,7 +509,7 @@ fn extract_prev_state( 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(prev_assignments)) => { @@ -522,7 +522,7 @@ fn extract_prev_state( 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/validator.rs b/src/validation/validator.rs index 3ef75da3..98d34fa3 100644 --- a/src/validation/validator.rs +++ b/src/validation/validator.rs @@ -309,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 }) }); @@ -390,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. @@ -407,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) From 40d4af5ecde086ed6eb6e6f1ca1c28d2599b48de Mon Sep 17 00:00:00 2001 From: Dr Maxim Orlovsky Date: Sun, 9 Apr 2023 17:01:33 +0200 Subject: [PATCH 13/13] contract: improve reserved value in operation input --- src/contract/mod.rs | 3 ++- src/contract/operations.rs | 13 +++++++++++-- src/lib.rs | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/contract/mod.rs b/src/contract/mod.rs index 4b62b644..1fedf6c0 100644 --- a/src/contract/mod.rs +++ b/src/contract/mod.rs @@ -48,7 +48,8 @@ pub use fungible::{ }; pub use global::{GlobalState, GlobalValues}; pub use operations::{ - ContractId, Extension, Genesis, Inputs, OpId, OpRef, Operation, Redeemed, Transition, Valencies, + ContractId, Extension, Genesis, Input, Inputs, OpId, OpRef, Operation, Redeemed, Transition, + Valencies, }; pub use seal::{ExposedSeal, GenesisSeal, GraphSeal, SealWitness, SecretSeal, TxoSeal}; pub use state::{ConfidentialState, ExposedState, StateCommitment, StateData, StateType}; diff --git a/src/contract/operations.rs b/src/contract/operations.rs index b50b120c..be6efd38 100644 --- a/src/contract/operations.rs +++ b/src/contract/operations.rs @@ -34,7 +34,7 @@ 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; @@ -52,7 +52,16 @@ pub type Redeemed = TinyOrdMap; #[display("{prev_out}")] pub struct Input { pub prev_out: Opout, - pub reserved: u8, + reserved: ReservedByte, +} + +impl Input { + pub fn with(prev_out: Opout) -> Input { + Input { + prev_out, + reserved: default!(), + } + } } /// Unique operation (genesis, extensions & state transition) identifier 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}")]