diff --git a/src/contract/attachment.rs b/src/contract/attachment.rs index 1506355e..f36f7685 100644 --- a/src/contract/attachment.rs +++ b/src/contract/attachment.rs @@ -22,7 +22,6 @@ use std::str::FromStr; -use amplify::confinement::TinyString; use amplify::{Bytes32, RawArray}; use baid58::{Baid58ParseError, FromBaid58, ToBaid58}; use bp::secp256k1::rand::{thread_rng, RngCore}; @@ -30,7 +29,7 @@ use commit_verify::{CommitStrategy, CommitVerify, Conceal, StrictEncodedProtocol use strict_encoding::StrictEncode; use super::{ConfidentialState, ExposedState}; -use crate::{StateCommitment, StateData, StateType, LIB_NAME_RGB}; +use crate::{MediaType, StateCommitment, StateData, StateType, LIB_NAME_RGB}; /// Unique data attachment identifier #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)] @@ -72,17 +71,17 @@ pub struct Revealed { pub id: AttachId, /// We do not enforce a MIME standard since non-standard types can be also /// used - pub media_type: TinyString, + pub media_type: MediaType, pub salt: u64, } impl Revealed { /// Creates new revealed attachment for the attachment id and MIME type. /// Uses `thread_rng` to initialize [`Revealed::salt`]. - pub fn new(id: AttachId, media_type: impl Into) -> Self { + pub fn new(id: AttachId, media_type: MediaType) -> Self { Self { id, - media_type: media_type.into(), + media_type, salt: thread_rng().next_u64(), } } diff --git a/src/contract/state.rs b/src/contract/state.rs index 5e05b4fc..b82a5480 100644 --- a/src/contract/state.rs +++ b/src/contract/state.rs @@ -54,12 +54,13 @@ pub trait ExposedState: } /// Categories of the state -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] +#[display(lowercase)] pub enum StateType { /// No state data Void, diff --git a/src/schema/mod.rs b/src/schema/mod.rs index e3d87e36..fa3ef65b 100644 --- a/src/schema/mod.rs +++ b/src/schema/mod.rs @@ -34,7 +34,7 @@ pub use operations::{ }; pub use schema::{ ExtensionType, GlobalStateType, RootSchema, Schema, SchemaId, SchemaRoot, SchemaTypeIndex, - SubSchema, TransitionType, + SubSchema, TransitionType, BLANK_TRANSITION_ID, }; pub use script::{Script, VmType}; -pub use state::{FungibleType, GlobalStateSchema, StateSchema}; +pub use state::{FungibleType, GlobalStateSchema, MediaType, StateSchema}; diff --git a/src/schema/occurrences.rs b/src/schema/occurrences.rs index 51ebb110..4f7a2a0a 100644 --- a/src/schema/occurrences.rs +++ b/src/schema/occurrences.rs @@ -154,7 +154,7 @@ impl StrictDecode for Occurrences { #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Display)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] -#[display(Debug)] +#[display("expected from {min} to {max} elements, while {found} were provided")] pub struct OccurrencesMismatch { pub min: u16, pub max: u16, diff --git a/src/schema/operations.rs b/src/schema/operations.rs index 87a7cfc0..14c74e64 100644 --- a/src/schema/operations.rs +++ b/src/schema/operations.rs @@ -64,15 +64,23 @@ pub enum OpType { /// Aggregated type used to supply full contract operation type and /// transition/state extension type information -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] pub enum OpFullType { /// Genesis operation (no subtypes) + #[display("genesis")] Genesis, /// State transition contract operation, subtyped by transition type + #[display("state transition #{0}")] StateTransition(TransitionType), /// State extension contract operation, subtyped by extension type + #[display("state extension #{0}")] StateExtension(ExtensionType), } @@ -84,6 +92,10 @@ impl OpFullType { OpFullType::StateExtension(ty) => ty, } } + + pub fn is_transition(self) -> bool { matches!(self, Self::StateTransition(_)) } + + pub fn is_extension(self) -> bool { matches!(self, Self::StateExtension(_)) } } /// Trait defining common API for all operation type schemata diff --git a/src/schema/schema.rs b/src/schema/schema.rs index d5643bc7..d42cffcf 100644 --- a/src/schema/schema.rs +++ b/src/schema/schema.rs @@ -34,7 +34,7 @@ use super::{ AssignmentType, ExtensionSchema, GenesisSchema, Script, StateSchema, TransitionSchema, ValencyType, }; -use crate::{Ffv, GlobalStateSchema, LIB_NAME_RGB}; +use crate::{Ffv, GlobalStateSchema, Occurrences, LIB_NAME_RGB}; pub trait SchemaTypeIndex: Copy + Eq + Ord + Default + StrictType + StrictEncode + StrictDecode @@ -45,6 +45,7 @@ impl SchemaTypeIndex for u16 {} pub type GlobalStateType = u16; pub type ExtensionType = u16; pub type TransitionType = u16; +pub const BLANK_TRANSITION_ID: u16 = TransitionType::MAX; /// Schema identifier. /// @@ -134,6 +135,15 @@ impl StrictDeserialize for Schema {} impl Schema { #[inline] pub fn schema_id(&self) -> SchemaId { self.commitment_id() } + + pub fn blank_transition(&self) -> TransitionSchema { + let mut schema = TransitionSchema::default(); + for id in self.owned_types.keys() { + schema.inputs.insert(*id, Occurrences::NoneOrMore).ok(); + schema.assignments.insert(*id, Occurrences::NoneOrMore).ok(); + } + schema + } } #[cfg(test)] diff --git a/src/schema/state.rs b/src/schema/state.rs index fa1584e0..9f8183d6 100644 --- a/src/schema/state.rs +++ b/src/schema/state.rs @@ -25,6 +25,31 @@ use strict_types::SemId; use crate::{StateType, LIB_NAME_RGB}; +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Display)] +#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB, tags = repr, into_u8, try_from_u8)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", rename_all = "camelCase") +)] +#[non_exhaustive] +#[repr(u8)] +pub enum MediaType { + #[display("*/*")] + #[strict_type(dumb)] + Any = 0xFF, + // TODO: Complete MIME type implementation +} + +impl MediaType { + pub fn conforms(&self, other: &MediaType) -> bool { + match (self, other) { + (MediaType::Any, MediaType::Any) => true, + } + } +} + #[derive(Copy, Clone, PartialEq, Eq, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB, tags = order)] @@ -38,8 +63,7 @@ pub enum StateSchema { Declarative, Fungible(FungibleType), Structured(SemId), - // TODO: Add MIME type requirements - Attachment, + Attachment(MediaType), } impl StateSchema { @@ -48,7 +72,7 @@ impl StateSchema { StateSchema::Declarative => StateType::Void, StateSchema::Fungible(_) => StateType::Fungible, StateSchema::Structured(_) => StateType::Structured, - StateSchema::Attachment => StateType::Attachment, + StateSchema::Attachment(_) => StateType::Attachment, } } } @@ -60,7 +84,7 @@ impl StateSchema { /// future we plan to support more types. We reserve this possibility by /// internally encoding [`ConfidentialFormat`] with the same type specification /// details as used for [`DateFormat`] -#[derive(Copy, Clone, PartialEq, Eq, Debug, Default)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Display)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB, tags = repr, into_u8, try_from_u8)] #[cfg_attr( @@ -71,6 +95,7 @@ impl StateSchema { #[repr(u8)] pub enum FungibleType { #[default] + #[display("64bit")] Unsigned64Bit = U64.into_code(), } diff --git a/src/validation/mod.rs b/src/validation/mod.rs index ce7d1022..7f4408ba 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -21,7 +21,7 @@ // limitations under the License. mod script; -mod subschema; +mod schema; mod model; mod state; mod validator; diff --git a/src/validation/model.rs b/src/validation/model.rs index a4078329..cba21a4d 100644 --- a/src/validation/model.rs +++ b/src/validation/model.rs @@ -24,13 +24,14 @@ use std::collections::BTreeSet; use amplify::confinement::{Confined, SmallBlob}; use amplify::Wrapper; +use strict_types::SemId; use crate::schema::{AssignmentsSchema, GlobalSchema, ValencySchema}; use crate::validation::{ConsignmentApi, VirtualMachine}; use crate::{ - validation, Assignments, AssignmentsRef, ExposedSeal, GlobalState, GlobalValues, GraphSeal, - OpFullType, OpId, OpRef, Operation, Opout, PrevOuts, Redeemed, Schema, SchemaRoot, - TypedAssigns, Valencies, + validation, Assignments, AssignmentsRef, ExposedSeal, GlobalState, GlobalStateSchema, + GlobalValues, GraphSeal, OpFullType, OpId, OpRef, Operation, Opout, PrevOuts, Redeemed, Schema, + SchemaRoot, TypedAssigns, Valencies, BLANK_TRANSITION_ID, }; impl Schema { @@ -44,90 +45,99 @@ impl Schema { let empty_assign_schema = AssignmentsSchema::default(); let empty_valency_schema = ValencySchema::default(); - let (global_schema, owned_schema, redeem_schema, assign_schema, valency_schema) = - match (op.transition_type(), op.extension_type()) { - (None, None) => { - // Right now we do not have actions to implement; but later - // we may have embedded procedures which must be verified - // here - /* - if let Some(procedure) = self.genesis.abi.get(&GenesisAction::NoOp) { + let blank_transition = self.blank_transition(); + let ( + metadata_schema, + global_schema, + owned_schema, + redeem_schema, + assign_schema, + valency_schema, + ) = match (op.transition_type(), op.extension_type()) { + (None, None) => { + // Right now we do not have actions to implement; but later + // we may have embedded procedures which must be verified + // here + /* + if let Some(procedure) = self.genesis.abi.get(&GenesisAction::NoOp) { - } - */ - - ( - &self.genesis.globals, - &empty_assign_schema, - &empty_valency_schema, - &self.genesis.assignments, - &self.genesis.valencies, - ) } - (Some(transition_type), None) => { - // Right now we do not have actions to implement; but later - // we may have embedded procedures which must be verified - // here - /* - if let Some(procedure) = transition_type.abi.get(&TransitionAction::NoOp) { + */ + + ( + &self.genesis.metadata, + &self.genesis.globals, + &empty_assign_schema, + &empty_valency_schema, + &self.genesis.assignments, + &self.genesis.valencies, + ) + } + (Some(transition_type), None) => { + // Right now we do not have actions to implement; but later + // we may have embedded procedures which must be verified + // here + /* + if let Some(procedure) = transition_type.abi.get(&TransitionAction::NoOp) { - } - */ - - let transition_type = match self.transitions.get(&transition_type) { - None => { - return validation::Status::with_failure( - validation::Failure::SchemaUnknownTransitionType( - id, - transition_type, - ), - ); - } - Some(transition_type) => transition_type, - }; - - ( - &transition_type.globals, - &transition_type.inputs, - &empty_valency_schema, - &transition_type.assignments, - &transition_type.valencies, - ) } - (None, Some(extension_type)) => { - // Right now we do not have actions to implement; but later - // we may have embedded procedures which must be verified - // here - /* - if let Some(procedure) = extension_type.abi.get(&ExtensionAction::NoOp) { + */ + let transition_schema = match self.transitions.get(&transition_type) { + None if transition_type == BLANK_TRANSITION_ID => &blank_transition, + None => { + return validation::Status::with_failure( + validation::Failure::SchemaUnknownTransitionType(id, transition_type), + ); } - */ - - let extension_type = match self.extensions.get(&extension_type) { - None => { - return validation::Status::with_failure( - validation::Failure::SchemaUnknownExtensionType(id, extension_type), - ); - } - Some(extension_type) => extension_type, - }; - - ( - &extension_type.globals, - &empty_assign_schema, - &extension_type.redeems, - &extension_type.assignments, - &extension_type.redeems, - ) + Some(transition_schema) => transition_schema, + }; + + ( + &transition_schema.metadata, + &transition_schema.globals, + &transition_schema.inputs, + &empty_valency_schema, + &transition_schema.assignments, + &transition_schema.valencies, + ) + } + (None, Some(extension_type)) => { + // Right now we do not have actions to implement; but later + // we may have embedded procedures which must be verified + // here + /* + if let Some(procedure) = extension_type.abi.get(&ExtensionAction::NoOp) { + } - _ => unreachable!("Node can't be extension and state transition at the same time"), - }; + */ + + let extension_schema = match self.extensions.get(&extension_type) { + None => { + return validation::Status::with_failure( + validation::Failure::SchemaUnknownExtensionType(id, extension_type), + ); + } + Some(extension_schema) => extension_schema, + }; + + ( + &extension_schema.metadata, + &extension_schema.globals, + &empty_assign_schema, + &extension_schema.redeems, + &extension_schema.assignments, + &extension_schema.redeems, + ) + } + _ => unreachable!("Node can't be extension and state transition at the same time"), + }; let mut status = validation::Status::new(); // Validate type system status += self.validate_type_system(); + status += self.validate_metadata(id, *metadata_schema, op.metadata()); status += self.validate_global_state(id, op.globals(), global_schema); let prev_state = if let OpRef::Transition(ref transition) = op { let prev_state = extract_prev_state(consignment, id, &transition.inputs, &mut status); @@ -174,6 +184,25 @@ impl Schema { }*/ } + fn validate_metadata( + &self, + opid: OpId, + sem_id: SemId, + metadata: &SmallBlob, + ) -> validation::Status { + let mut status = validation::Status::new(); + + if self + .type_system + .strict_deserialize_type(sem_id, metadata.as_ref()) + .is_err() + { + status.add_failure(validation::Failure::SchemaInvalidMetadata(opid, sem_id)); + }; + + status + } + fn validate_global_state( &self, opid: OpId, @@ -187,38 +216,48 @@ impl Schema { .collect::>() .difference(&global_schema.keys().collect()) .for_each(|field_id| { - status.add_failure(validation::Failure::SchemaUnknownFieldType(opid, **field_id)); + status.add_failure(validation::Failure::SchemaUnknownGlobalStateType( + opid, **field_id, + )); }); - for (global_id, occ) in global_schema { + for (type_id, occ) in global_schema { let set = global - .get(global_id) + .get(type_id) .cloned() .map(GlobalValues::into_inner) .map(Confined::unbox) .unwrap_or_default(); - // Checking number of field occurrences - if let Err(err) = occ.check(set.len() as u16) { - status.add_failure(validation::Failure::SchemaMetaOccurrencesError( - opid, *global_id, err, - )); - } - - let _field = self.global_types.get(global_id).expect( - "If the field were absent, the schema would not be able to pass the internal \ + let GlobalStateSchema { sem_id, max_items } = self.global_types.get(type_id).expect( + "if the field were absent, the schema would not be able to pass the internal \ validation and we would not reach this point", ); - for _data in set { - // TODO: #137 Run strict type validation - /* - let schema_type = data.schema_type(); - status.add_failure(validation::Failure::SchemaMismatchedDataType( - *field_type_id, + // Checking number of field occurrences + let count = set.len() as u16; + if let Err(err) = occ.check(count) { + status.add_failure(validation::Failure::SchemaGlobalStateOccurrences( + opid, *type_id, err, )); - status += field.verify(&self.type_system, opid, *field_type_id, &data); - */ + } + if count >= *max_items { + status.add_failure(validation::Failure::SchemaGlobalStateLimit( + opid, *type_id, count, *max_items, + )); + } + + // Validating data types + for data in set { + if self + .type_system + .strict_deserialize_type(*sem_id, data.as_ref()) + .is_err() + { + status.add_failure(validation::Failure::SchemaInvalidGlobalValue( + opid, *type_id, *sem_id, + )); + }; } } @@ -238,7 +277,7 @@ impl Schema { .collect::>() .difference(&assign_schema.keys().collect()) .for_each(|owned_type_id| { - status.add_failure(validation::Failure::SchemaUnknownOwnedRightType( + status.add_failure(validation::Failure::SchemaUnknownAssignmentType( id, **owned_type_id, )); @@ -252,7 +291,7 @@ impl Schema { // Checking number of ancestor's assignment occurrences if let Err(err) = occ.check(len as u16) { - status.add_failure(validation::Failure::SchemaParentOwnedRightOccurrencesError( + status.add_failure(validation::Failure::SchemaInputOccurrences( id, *owned_type_id, err, @@ -274,7 +313,7 @@ impl Schema { valencies .difference(valency_schema) .for_each(|public_type_id| { - status.add_failure(validation::Failure::SchemaUnknownPublicRightType( + status.add_failure(validation::Failure::SchemaUnknownValencyType( id, *public_type_id, )); @@ -296,7 +335,7 @@ impl Schema { .collect::>() .difference(&assign_schema.keys().collect()) .for_each(|assignment_type_id| { - status.add_failure(validation::Failure::SchemaUnknownOwnedRightType( + status.add_failure(validation::Failure::SchemaUnknownAssignmentType( id, **assignment_type_id, )); @@ -310,7 +349,7 @@ impl Schema { // Checking number of assignment occurrences if let Err(err) = occ.check(len as u16) { - status.add_failure(validation::Failure::SchemaOwnedRightOccurrencesError( + status.add_failure(validation::Failure::SchemaAssignmentOccurrences( id, *state_id, err, )); } @@ -322,18 +361,18 @@ impl Schema { match owned_state.get(state_id) { None => {} - Some(TypedAssigns::Declarative(set)) => set - .iter() - .for_each(|data| status += assignment.validate(&id, *state_id, data)), - Some(TypedAssigns::Fungible(set)) => set - .iter() - .for_each(|data| status += assignment.validate(&id, *state_id, data)), - Some(TypedAssigns::Structured(set)) => set - .iter() - .for_each(|data| status += assignment.validate(&id, *state_id, data)), - Some(TypedAssigns::Attachment(set)) => set - .iter() - .for_each(|data| status += assignment.validate(&id, *state_id, data)), + Some(TypedAssigns::Declarative(set)) => set.iter().for_each(|data| { + status += assignment.validate(&self.type_system, &id, *state_id, data) + }), + Some(TypedAssigns::Fungible(set)) => set.iter().for_each(|data| { + status += assignment.validate(&self.type_system, &id, *state_id, data) + }), + Some(TypedAssigns::Structured(set)) => set.iter().for_each(|data| { + status += assignment.validate(&self.type_system, &id, *state_id, data) + }), + Some(TypedAssigns::Attachment(set)) => set.iter().for_each(|data| { + status += assignment.validate(&self.type_system, &id, *state_id, data) + }), }; } @@ -351,7 +390,7 @@ impl Schema { valencies .difference(valency_schema) .for_each(|public_type_id| { - status.add_failure(validation::Failure::SchemaUnknownPublicRightType( + status.add_failure(validation::Failure::SchemaUnknownValencyType( id, *public_type_id, )); diff --git a/src/validation/schema.rs b/src/validation/schema.rs new file mode 100644 index 00000000..a21788a7 --- /dev/null +++ b/src/validation/schema.rs @@ -0,0 +1,265 @@ +// RGB Core Library: consensus layer for RGB smart contracts. +// +// SPDX-License-Identifier: Apache-2.0 +// +// Written in 2019-2023 by +// Dr Maxim Orlovsky +// +// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. +// Copyright (C) 2019-2023 Dr Maxim Orlovsky. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::validation::Status; +use crate::{ + validation, OpFullType, OpSchema, Schema, StateSchema, SubSchema, BLANK_TRANSITION_ID, +}; + +impl SubSchema { + pub fn verify(&self) -> validation::Status { + let mut status = validation::Status::new(); + + if let Some(ref root) = self.subset_of { + status += self.verify_subschema(root); + } + + // Validate internal schema consistency + status += self.verify_consistency(); + + status + } + + fn verify_consistency(&self) -> validation::Status { + let mut status = validation::Status::new(); + + status += self.verify_operation(OpFullType::Genesis, &self.genesis); + for (type_id, schema) in &self.transitions { + status += self.verify_operation(OpFullType::StateTransition(*type_id), schema); + } + for (type_id, schema) in &self.extensions { + status += self.verify_operation(OpFullType::StateExtension(*type_id), schema); + } + // Check that the schema doesn't contain reserved type ids + if self.transitions.contains_key(&BLANK_TRANSITION_ID) { + status.add_failure(validation::Failure::SchemaBlankTransitionRedefined); + } + + for (type_id, schema) in &self.global_types { + if !self.type_system.contains_key(&schema.sem_id) { + status.add_failure(validation::Failure::SchemaGlobalSemIdUnknown( + *type_id, + schema.sem_id, + )); + } + } + + for (type_id, schema) in &self.owned_types { + if let StateSchema::Structured(sem_id) = schema { + if !self.type_system.contains_key(sem_id) { + status.add_failure(validation::Failure::SchemaOwnedSemIdUnknown( + *type_id, *sem_id, + )); + } + } + } + + status + } + + fn verify_operation(&self, op_type: OpFullType, schema: &impl OpSchema) -> Status { + let mut status = validation::Status::new(); + + if !self.type_system.contains_key(&schema.metadata()) { + status.add_failure(validation::Failure::SchemaOpMetaSemIdUnknown( + op_type, + schema.metadata(), + )); + } + if matches!(schema.inputs(), Some(inputs) if inputs.is_empty()) { + status.add_failure(validation::Failure::SchemaOpEmptyInputs(op_type)); + } + if matches!(schema.redeems(), Some(inputs) if inputs.is_empty()) { + status.add_failure(validation::Failure::SchemaOpEmptyInputs(op_type)); + } + for type_id in schema.globals().keys() { + if !self.global_types.contains_key(type_id) { + status + .add_failure(validation::Failure::SchemaOpGlobalTypeUnknown(op_type, *type_id)); + } + } + for type_id in schema.assignments().keys() { + if !self.owned_types.contains_key(type_id) { + status.add_failure(validation::Failure::SchemaOpAssignmentTypeUnknown( + op_type, *type_id, + )); + } + } + for type_id in schema.valencies() { + if !self.valency_types.contains(type_id) { + status.add_failure(validation::Failure::SchemaOpValencyTypeUnknown( + op_type, *type_id, + )); + } + } + + status + } + + fn verify_subschema(&self, root: &Schema<()>) -> validation::Status { + let mut status = validation::Status::new(); + + if self.subset_of.as_ref() != Some(root) { + panic!("SubSchema::schema_verify called with a root schema not matching subset_of"); + } + + for (global_type, data_format) in &self.global_types { + match root.global_types.get(global_type) { + None => status + .add_failure(validation::Failure::SubschemaGlobalStateMismatch(*global_type)), + Some(root_data_format) if root_data_format != data_format => status + .add_failure(validation::Failure::SubschemaGlobalStateMismatch(*global_type)), + _ => &status, + }; + } + + for (assignments_type, state_schema) in &self.owned_types { + match root.owned_types.get(assignments_type) { + None => status.add_failure(validation::Failure::SubschemaAssignmentTypeMismatch( + *assignments_type, + )), + Some(root_state_schema) if root_state_schema != state_schema => status.add_failure( + validation::Failure::SubschemaAssignmentTypeMismatch(*assignments_type), + ), + _ => &status, + }; + } + + for valencies_type in &self.valency_types { + match root.valency_types.contains(valencies_type) { + false => status.add_failure(validation::Failure::SubschemaValencyTypeMismatch( + *valencies_type, + )), + _ => &status, + }; + } + + status += self + .genesis + .verify_subschema(OpFullType::Genesis, &root.genesis); + + for (type_id, transition_schema) in &self.transitions { + if let Some(root_transition_schema) = root.transitions.get(type_id) { + status += transition_schema.verify_subschema( + OpFullType::StateTransition(*type_id), + root_transition_schema, + ); + } else { + status.add_failure(validation::Failure::SubschemaTransitionTypeMismatch(*type_id)); + } + } + for (type_id, extension_schema) in &self.extensions { + if let Some(root_extension_schema) = root.extensions.get(type_id) { + status += extension_schema + .verify_subschema(OpFullType::StateExtension(*type_id), root_extension_schema); + } else { + status.add_failure(validation::Failure::SubschemaExtensionTypeMismatch(*type_id)); + } + } + + status + } +} + +/// Trait used for internal schema validation against some root schema +pub(crate) trait SchemaVerify { + type Root; + fn verify_subschema(&self, op_type: OpFullType, root: &Self::Root) -> validation::Status; +} + +impl SchemaVerify for T +where T: OpSchema +{ + type Root = T; + + fn verify_subschema(&self, op_type: OpFullType, root: &Self) -> validation::Status { + let mut status = validation::Status::new(); + + if self.metadata() != root.metadata() { + status.add_failure(validation::Failure::SubschemaOpMetaMismatch { + op_type, + expected: root.metadata(), + actual: self.metadata(), + }); + } + + for (type_id, occ) in self.globals() { + match root.globals().get(type_id) { + None => status.add_failure(validation::Failure::SubschemaOpGlobalStateMismatch( + op_type, *type_id, + )), + Some(root_occ) if occ != root_occ => status.add_failure( + validation::Failure::SubschemaOpGlobalStateMismatch(op_type, *type_id), + ), + _ => &status, + }; + } + + if let Some(inputs) = self.inputs() { + let root_inputs = root.inputs().expect("generic guarantees"); + for (type_id, occ) in inputs { + match root_inputs.get(type_id) { + None => status.add_failure(validation::Failure::SubschemaOpInputMismatch( + op_type, *type_id, + )), + Some(root_occ) if occ != root_occ => status.add_failure( + validation::Failure::SubschemaOpInputMismatch(op_type, *type_id), + ), + _ => &status, + }; + } + } + + for (type_id, occ) in self.assignments() { + match root.assignments().get(type_id) { + None => status.add_failure(validation::Failure::SubschemaOpAssignmentsMismatch( + op_type, *type_id, + )), + Some(root_occ) if occ != root_occ => status.add_failure( + validation::Failure::SubschemaOpAssignmentsMismatch(op_type, *type_id), + ), + _ => &status, + }; + } + + if let Some(redeems) = self.redeems() { + let root_redeems = root.redeems().expect("generic guarantees"); + for type_id in redeems { + if !root_redeems.contains(type_id) { + status.add_failure(validation::Failure::SubschemaOpRedeemMismatch( + op_type, *type_id, + )); + } + } + } + + for type_id in self.valencies() { + if !root.valencies().contains(type_id) { + status.add_failure(validation::Failure::SubschemaOpValencyMismatch( + op_type, *type_id, + )); + } + } + + status + } +} diff --git a/src/validation/state.rs b/src/validation/state.rs index 1b4f6f50..11efeccf 100644 --- a/src/validation/state.rs +++ b/src/validation/state.rs @@ -20,6 +20,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use strict_types::TypeSystem; + use crate::schema::AssignmentType; use crate::{ validation, Assign, ConfidentialState, ExposedSeal, ExposedState, OpId, StateCommitment, @@ -29,7 +31,7 @@ use crate::{ impl StateSchema { pub fn validate( &self, - // type_system: &TypeSystem, + type_system: &TypeSystem, opid: &OpId, state_type: AssignmentType, data: &Assign, @@ -54,7 +56,7 @@ impl StateSchema { *opid, state_type, )); } - (StateSchema::Attachment, StateCommitment::Attachment(_)) => { + (StateSchema::Attachment(_), StateCommitment::Attachment(_)) => { status.add_info(validation::Info::UncheckableConfidentialState( *opid, state_type, )); @@ -73,8 +75,15 @@ impl StateSchema { Assign::Revealed { state, .. } | Assign::ConfidentialSeal { state, .. } => { match (self, state.state_data()) { (StateSchema::Declarative, StateData::Void) => {} - (StateSchema::Attachment, StateData::Attachment(_)) => { - // TODO: Check against MIME type + (StateSchema::Attachment(media_type), StateData::Attachment(attach)) + if !attach.media_type.conforms(media_type) => + { + status.add_failure(validation::Failure::MediaTypeMismatch { + opid: *opid, + state_type, + expected: media_type.clone(), + found: attach.media_type, + }); } (StateSchema::Fungible(schema), StateData::Fungible(v)) if v.value.fungible_type() != *schema => @@ -87,8 +96,15 @@ impl StateSchema { }); } (StateSchema::Fungible(_), StateData::Fungible(_)) => {} - (StateSchema::Structured(_sem_id), StateData::Structured(_data)) => { - // TODO #137: Run strict type validation + (StateSchema::Structured(sem_id), StateData::Structured(data)) => { + if type_system + .strict_deserialize_type(*sem_id, data.as_ref()) + .is_err() + { + status.add_failure(validation::Failure::SchemaInvalidOwnedValue( + *opid, state_type, *sem_id, + )); + }; } // all other options are mismatches (state_schema, found) => { diff --git a/src/validation/status.rs b/src/validation/status.rs index 9a5b3470..ad531690 100644 --- a/src/validation/status.rs +++ b/src/validation/status.rs @@ -25,10 +25,13 @@ use core::ops::AddAssign; use bp::dbc::anchor; use bp::{seals, Txid}; +use strict_types::SemId; use crate::contract::Opout; -use crate::schema::{self, OpType, SchemaId}; -use crate::{data, BundleId, OccurrencesMismatch, OpId, SecretSeal, StateType}; +use crate::schema::{self, SchemaId}; +use crate::{ + AssignmentType, BundleId, OccurrencesMismatch, OpFullType, OpId, SecretSeal, StateType, +}; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Display)] #[display(Debug)] @@ -130,66 +133,117 @@ impl Status { } #[derive(Clone, PartialEq, Eq, Debug, Display, From)] -//#[derive(StrictEncode, StrictDecode)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -// TODO #44: (v0.3) convert to detailed error description using doc_comments -#[display(Debug)] +#[display(doc_comments)] pub enum Failure { - SchemaUnknown(SchemaId), - /// schema is a subschema, so root schema {0} must be provided for the - /// validation - SchemaRootRequired(SchemaId), - SchemaRootNoFieldTypeMatch(schema::GlobalStateType), - SchemaRootNoOwnedRightTypeMatch(schema::AssignmentType), - SchemaRootNoPublicRightTypeMatch(schema::ValencyType), - SchemaRootNoTransitionTypeMatch(schema::TransitionType), - SchemaRootNoExtensionTypeMatch(schema::ExtensionType), - - SchemaRootNoMetadataMatch(OpType, schema::GlobalStateType), - SchemaRootNoParentOwnedRightsMatch(OpType, schema::AssignmentType), - SchemaRootNoParentPublicRightsMatch(OpType, schema::ValencyType), - SchemaRootNoOwnedRightsMatch(OpType, schema::AssignmentType), - SchemaRootNoPublicRightsMatch(OpType, schema::ValencyType), - - SchemaUnknownExtensionType(OpId, schema::ExtensionType), - SchemaUnknownTransitionType(OpId, schema::TransitionType), - SchemaUnknownFieldType(OpId, schema::GlobalStateType), - SchemaUnknownOwnedRightType(OpId, schema::AssignmentType), - SchemaUnknownPublicRightType(OpId, schema::ValencyType), - - SchemaDeniedScriptExtension(OpId), - - SchemaMetaValueTooSmall(schema::GlobalStateType), - SchemaMetaValueTooLarge(schema::GlobalStateType), - SchemaStateValueTooSmall(schema::AssignmentType), - SchemaStateValueTooLarge(schema::AssignmentType), - - SchemaWrongEnumValue { - field_or_state_type: u16, - unexpected: u8, + /// schema {actual} provided for the consignment validation doesn't match + /// schema {expected} used by the contract. This means that the consignment + /// is invalid. + SchemaMismatch { + /// Expected schema id required by the contracts genesis. + expected: SchemaId, + /// Actual schema id provided by the consignment. + actual: SchemaId, }, - SchemaWrongDataLength { - field_or_state_type: u16, - max_expected: u16, - found: usize, + /// schema uses reserved type for the blank state transition. + SchemaBlankTransitionRedefined, + + /// schema global state #{0} uses semantic data type absent in type library + /// ({1}). + SchemaGlobalSemIdUnknown(schema::GlobalStateType, SemId), + /// schema owned state #{0} uses semantic data type absent in type library + /// ({1}). + SchemaOwnedSemIdUnknown(schema::AssignmentType, SemId), + /// schema metadata in {0} uses semantic data type absent in type library + /// ({1}). + SchemaOpMetaSemIdUnknown(OpFullType, SemId), + + /// schema for {0} has zero inputs. + SchemaOpEmptyInputs(OpFullType), + /// schema for {0} references undeclared global state type {1}. + SchemaOpGlobalTypeUnknown(OpFullType, schema::GlobalStateType), + /// schema for {0} references undeclared owned state type {1}. + SchemaOpAssignmentTypeUnknown(OpFullType, schema::AssignmentType), + /// schema for {0} references undeclared valency type {1}. + SchemaOpValencyTypeUnknown(OpFullType, schema::ValencyType), + + /// invalid schema - no match with root schema requirements for global state + /// type #{0}. + SubschemaGlobalStateMismatch(schema::GlobalStateType), + /// invalid schema - no match with root schema requirements for assignment + /// type #{0}. + SubschemaAssignmentTypeMismatch(schema::AssignmentType), + /// invalid schema - no match with root schema requirements for valency + /// type #{0}. + SubschemaValencyTypeMismatch(schema::ValencyType), + /// invalid schema - no match with root schema requirements for transition + /// type #{0}. + SubschemaTransitionTypeMismatch(schema::TransitionType), + /// invalid schema - no match with root schema requirements for extension + /// type #{0}. + SubschemaExtensionTypeMismatch(schema::ExtensionType), + + /// invalid schema - no match with root schema requirements for metadata + /// type (required {expected}, found {actual}). + SubschemaOpMetaMismatch { + op_type: OpFullType, + expected: SemId, + actual: SemId, }, - SchemaMismatchedDataType(u16), - - SchemaMetaOccurrencesError(OpId, schema::GlobalStateType, OccurrencesMismatch), - SchemaParentOwnedRightOccurrencesError(OpId, schema::AssignmentType, OccurrencesMismatch), - SchemaOwnedRightOccurrencesError(OpId, schema::AssignmentType, OccurrencesMismatch), - - SchemaScriptOverrideDenied, - SchemaScriptVmChangeDenied, - - SchemaTypeSystem(/* TODO: use error from strict types */), + /// invalid schema - no match with root schema requirements for global state + /// type #{1} used in {0}. + SubschemaOpGlobalStateMismatch(OpFullType, schema::GlobalStateType), + /// invalid schema - no match with root schema requirements for input + /// type #{1} used in {0}. + SubschemaOpInputMismatch(OpFullType, schema::AssignmentType), + /// invalid schema - no match with root schema requirements for redeem + /// type #{1} used in {0}. + SubschemaOpRedeemMismatch(OpFullType, schema::ValencyType), + /// invalid schema - no match with root schema requirements for assignment + /// type #{1} used in {0}. + SubschemaOpAssignmentsMismatch(OpFullType, schema::AssignmentType), + /// invalid schema - no match with root schema requirements for valency + /// type #{1} used in {0}. + SubschemaOpValencyMismatch(OpFullType, schema::ValencyType), + + /// operation {0} uses invalid state extension type {1}. + SchemaUnknownExtensionType(OpId, schema::ExtensionType), + /// operation {0} uses invalid state transition type {1}. + SchemaUnknownTransitionType(OpId, schema::TransitionType), + /// operation {0} uses invalid global state type {1}. + SchemaUnknownGlobalStateType(OpId, schema::GlobalStateType), + /// operation {0} uses invalid assignment type {1}. + SchemaUnknownAssignmentType(OpId, schema::AssignmentType), + /// operation {0} uses invalid valency type {1}. + SchemaUnknownValencyType(OpId, schema::ValencyType), + + /// invalid number of global state entries of type {1} in operation {0} - + /// {2} + SchemaGlobalStateOccurrences(OpId, schema::GlobalStateType, OccurrencesMismatch), + /// number of global state entries of type {1} in operation {0} exceeds + /// schema-defined maximum for that global state type ({2} vs {3}). + SchemaGlobalStateLimit(OpId, schema::GlobalStateType, u16, u16), + /// invalid metadata in operation {0} not matching semantic type id {1}. + SchemaInvalidMetadata(OpId, SemId), + /// invalid global state value in operation {0}, state type #{1} which does + /// not match semantic type id {2}. + SchemaInvalidGlobalValue(OpId, schema::GlobalStateType, SemId), + /// invalid owned state value in operation {0}, state type #{1} which does + /// not match semantic type id {2}. + SchemaInvalidOwnedValue(OpId, schema::AssignmentType, SemId), + /// invalid number of input entries of type {1} in operation {0} - {2} + SchemaInputOccurrences(OpId, schema::AssignmentType, OccurrencesMismatch), + /// invalid number of assignment entries of type {1} in operation {0} - {2} + SchemaAssignmentOccurrences(OpId, schema::AssignmentType, OccurrencesMismatch), // Consignment consistency errors + /// operation {0} is absent from the consignment. OperationAbsent(OpId), + /// state transition {0} is absent from the consignment. TransitionAbsent(OpId), /// bundle with id {0} is invalid. BundleInvalid(BundleId), @@ -199,8 +253,8 @@ pub enum Failure { NotAnchored(OpId), /// anchor for transition {0} doesn't commit to the actual transition data. NotInAnchor(OpId, Txid), - /// transition {op} references state type {ty} absent in the outputs of - /// previous state transition {prev_id}. + /// transition {opid} references state type {state_type} absent in the + /// outputs of previous state transition {prev_id}. NoPrevState { opid: OpId, prev_id: OpId, @@ -249,15 +303,21 @@ pub enum Failure { }, /// state in {opid}/{state_type} is of {found} type, while schema requires /// it to be {expected}. + MediaTypeMismatch { + opid: OpId, + state_type: schema::AssignmentType, + expected: schema::MediaType, + found: schema::MediaType, + }, + /// state in {opid}/{state_type} is of {found} type, while schema requires + /// it to be {expected}. FungibleTypeMismatch { opid: OpId, state_type: schema::AssignmentType, expected: schema::FungibleType, found: schema::FungibleType, }, - InvalidStateDataType(OpId, u16, /* TODO: Use strict type */ data::Revealed), - InvalidStateDataValue(OpId, u16, /* TODO: Use strict type */ Vec), - /// invalid bulletproofs in {0}:{1}: {3} + /// invalid bulletproofs in {0}:{1}: {2} BulletproofsInvalid(OpId, u16, String), /// operation {0} is invalid: {1} ScriptFailure(OpId, String), @@ -268,19 +328,23 @@ pub enum Failure { } #[derive(Clone, PartialEq, Eq, Debug, Display, From)] -//#[derive(StrictEncode, StrictDecode)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -// TODO #44: (v0.3) convert to detailed descriptions using doc_comments -#[display(Debug)] +#[display(doc_comments)] pub enum Warning { - EndpointDuplication(OpId, SecretSeal), - EndpointTransitionSealNotFound(OpId, SecretSeal), - ExcessiveNode(OpId), - EndpointTransactionMissed(Txid), + /// duplicated terminal seal {1} in operation {0}. + TerminalDuplication(OpId, SecretSeal), + /// terminal seal {1} referencing operation {0} is not present in operation + /// assignments. + TerminalSealAbsent(OpId, SecretSeal), + /// operation {0} present in the consignment is excessive and not a part of + /// the validated contract history. + ExcessiveOperation(OpId), + /// terminal witness transaction {0} is not yet mined. + TerminalWitnessNotMined(Txid), /// Custom warning by external services on top of RGB Core. #[display(inner)] @@ -288,16 +352,16 @@ pub enum Warning { } #[derive(Clone, PartialEq, Eq, Debug, Display, From)] -//#[derive(StrictEncode, StrictDecode)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -// TODO #44: (v0.3) convert to detailed descriptions using doc_comments -#[display(Debug)] +#[display(doc_comments)] pub enum Info { - UncheckableConfidentialState(OpId, u16), + /// operation {0} contains state in assignment {1} which is confidential and + /// thus was not validated. + UncheckableConfidentialState(OpId, AssignmentType), /// Custom info by external services on top of RGB Core. #[display(inner)] diff --git a/src/validation/subschema.rs b/src/validation/subschema.rs deleted file mode 100644 index f066e7b8..00000000 --- a/src/validation/subschema.rs +++ /dev/null @@ -1,192 +0,0 @@ -// RGB Core Library: consensus layer for RGB smart contracts. -// -// SPDX-License-Identifier: Apache-2.0 -// -// Written in 2019-2023 by -// Dr Maxim Orlovsky -// -// Copyright (C) 2019-2023 LNP/BP Standards Association. All rights reserved. -// Copyright (C) 2019-2023 Dr Maxim Orlovsky. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{validation, OpSchema, Schema, SubSchema}; - -impl SubSchema { - pub fn verify(&self) -> validation::Status { - let mut status = validation::Status::new(); - - if let Some(ref root) = self.subset_of { - status += self.schema_verify(root); - } - - // TODO: Verify internal schema consistency - - status - } -} - -/// Trait used for internal schema validation against some root schema -pub(crate) trait SchemaVerify { - type Root; - fn schema_verify(&self, root: &Self::Root) -> validation::Status; -} - -impl SchemaVerify for SubSchema { - type Root = Schema<()>; - - fn schema_verify(&self, root: &Schema<()>) -> validation::Status { - let mut status = validation::Status::new(); - - if self.subset_of.as_ref() != Some(root) { - panic!("SubSchema::schema_verify called with a root schema not matching subset_of"); - } - - for (field_type, data_format) in &self.global_types { - match root.global_types.get(field_type) { - None => { - status.add_failure(validation::Failure::SchemaRootNoFieldTypeMatch(*field_type)) - } - Some(root_data_format) if root_data_format != data_format => { - status.add_failure(validation::Failure::SchemaRootNoFieldTypeMatch(*field_type)) - } - _ => &status, - }; - } - - for (assignments_type, state_schema) in &self.owned_types { - match root.owned_types.get(assignments_type) { - None => status.add_failure(validation::Failure::SchemaRootNoOwnedRightTypeMatch( - *assignments_type, - )), - Some(root_state_schema) if root_state_schema != state_schema => status.add_failure( - validation::Failure::SchemaRootNoOwnedRightTypeMatch(*assignments_type), - ), - _ => &status, - }; - } - - for valencies_type in &self.valency_types { - match root.valency_types.contains(valencies_type) { - false => status.add_failure(validation::Failure::SchemaRootNoPublicRightTypeMatch( - *valencies_type, - )), - _ => &status, - }; - } - - status += self.genesis.schema_verify(&root.genesis); - - for (transition_type, transition_schema) in &self.transitions { - if let Some(root_transition_schema) = root.transitions.get(transition_type) { - status += transition_schema.schema_verify(root_transition_schema); - } else { - status.add_failure(validation::Failure::SchemaRootNoTransitionTypeMatch( - *transition_type, - )); - } - } - for (extension_type, extension_schema) in &self.extensions { - if let Some(root_extension_schema) = root.extensions.get(extension_type) { - status += extension_schema.schema_verify(root_extension_schema); - } else { - status.add_failure(validation::Failure::SchemaRootNoExtensionTypeMatch( - *extension_type, - )); - } - } - - status - } -} - -impl SchemaVerify for T -where T: OpSchema -{ - type Root = T; - - fn schema_verify(&self, root: &Self) -> validation::Status { - let mut status = validation::Status::new(); - let op_type = self.op_type(); - - for (field_type, occ) in self.globals() { - match root.globals().get(field_type) { - None => status.add_failure(validation::Failure::SchemaRootNoMetadataMatch( - op_type, - *field_type, - )), - Some(root_occ) if occ != root_occ => status.add_failure( - validation::Failure::SchemaRootNoMetadataMatch(op_type, *field_type), - ), - _ => &status, - }; - } - - if let Some(inputs) = self.inputs() { - let root_inputs = root.inputs().expect("generic guarantees"); - for (assignments_type, occ) in inputs { - match root_inputs.get(assignments_type) { - None => { - status.add_failure(validation::Failure::SchemaRootNoParentOwnedRightsMatch( - op_type, - *assignments_type, - )) - } - Some(root_occ) if occ != root_occ => { - status.add_failure(validation::Failure::SchemaRootNoParentOwnedRightsMatch( - op_type, - *assignments_type, - )) - } - _ => &status, - }; - } - } - - for (assignments_type, occ) in self.assignments() { - match root.assignments().get(assignments_type) { - None => status.add_failure(validation::Failure::SchemaRootNoOwnedRightsMatch( - op_type, - *assignments_type, - )), - Some(root_occ) if occ != root_occ => status.add_failure( - validation::Failure::SchemaRootNoOwnedRightsMatch(op_type, *assignments_type), - ), - _ => &status, - }; - } - - if let Some(redeems) = self.redeems() { - let root_redeems = root.redeems().expect("generic guarantees"); - for valencies_type in redeems { - if !root_redeems.contains(valencies_type) { - status.add_failure(validation::Failure::SchemaRootNoParentPublicRightsMatch( - op_type, - *valencies_type, - )); - } - } - } - - for valencies_type in self.valencies() { - if !root.valencies().contains(valencies_type) { - status.add_failure(validation::Failure::SchemaRootNoPublicRightsMatch( - op_type, - *valencies_type, - )); - } - } - - status - } -} diff --git a/src/validation/validator.rs b/src/validation/validator.rs index 48ee064b..e494bafe 100644 --- a/src/validation/validator.rs +++ b/src/validation/validator.rs @@ -29,7 +29,6 @@ use commit_verify::mpc; use single_use_seals::SealWitness; use super::status::{Failure, Warning}; -use super::subschema::SchemaVerify; use super::{ConsignmentApi, Status, Validity, VirtualMachine}; use crate::contract::Opout; use crate::validation::AnchoredBundle; @@ -115,8 +114,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> { // We generate just a warning here because it's up to a user to decide whether // to accept consignment with wrong endpoint list - status - .add_warning(Warning::EndpointTransitionSealNotFound(opid, seal_endpoint)); + status.add_warning(Warning::TerminalSealAbsent(opid, seal_endpoint)); } if end_transitions .iter() @@ -124,7 +122,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> .count() > 0 { - status.add_warning(Warning::EndpointDuplication(opid, seal_endpoint)); + status.add_warning(Warning::TerminalDuplication(opid, seal_endpoint)); } else { end_transitions.push((transition, bundle_id)); } @@ -186,19 +184,16 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> validator.status } - fn validate_schema(&mut self, schema: &SubSchema) { - // Validating schema against root schema - if let Some(ref root) = schema.subset_of { - self.status += schema.schema_verify(root); - } - } + fn validate_schema(&mut self, schema: &SubSchema) { self.status += schema.verify(); } fn validate_contract(&mut self, schema: &Schema) { // [VALIDATION]: Making sure that we were supplied with the schema // that corresponds to the schema of the contract genesis if schema.schema_id() != self.schema_id { - self.status - .add_failure(Failure::SchemaUnknown(self.schema_id)); + self.status.add_failure(Failure::SchemaMismatch { + expected: self.schema_id, + actual: schema.schema_id(), + }); // Unlike other failures, here we return immediatelly, since there is no point // to validate all consignment data against an invalid schema: it will result in // a plenty of meaningless errors @@ -238,7 +233,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> self.status.unmined_endpoint_txids.push(anchor.txid); self.status .warnings - .push(Warning::EndpointTransactionMissed(anchor.txid)); + .push(Warning::TerminalWitnessNotMined(anchor.txid)); } } } @@ -247,7 +242,7 @@ impl<'consignment, 'resolver, C: ConsignmentApi, R: ResolveTx> // excessive (i.e. not part of validation_index). Nothing critical, but still // good to report the user that the consignment is not perfect for opid in self.consignment.op_ids_except(&self.validation_index) { - self.status.add_warning(Warning::ExcessiveNode(opid)); + self.status.add_warning(Warning::ExcessiveOperation(opid)); } }