diff --git a/src/operation/commit.rs b/src/operation/commit.rs index 544996ac..14fdd85a 100644 --- a/src/operation/commit.rs +++ b/src/operation/commit.rs @@ -262,6 +262,7 @@ pub enum TypeCommitment { #[commit_encode(strategy = strict, id = OpId)] pub struct OpCommitment { pub ffv: Ffv, + pub nonce: u8, pub op_type: TypeCommitment, pub metadata: StrictHash, pub globals: MerkleHash, @@ -286,6 +287,7 @@ impl Genesis { }; OpCommitment { ffv: self.ffv, + nonce: u8::MAX, op_type: TypeCommitment::Genesis(base), metadata: self.metadata.commit_id(), globals: MerkleHash::merklize(&self.globals), @@ -305,6 +307,7 @@ impl Transition { pub fn commit(&self) -> OpCommitment { OpCommitment { ffv: self.ffv, + nonce: self.nonce, op_type: TypeCommitment::Transition(self.contract_id, self.transition_type), metadata: self.metadata.commit_id(), globals: MerkleHash::merklize(&self.globals), @@ -322,6 +325,7 @@ impl Extension { pub fn commit(&self) -> OpCommitment { OpCommitment { ffv: self.ffv, + nonce: self.nonce, op_type: TypeCommitment::Extension(self.contract_id, self.extension_type), metadata: self.metadata.commit_id(), globals: MerkleHash::merklize(&self.globals), diff --git a/src/operation/operations.rs b/src/operation/operations.rs index 63a4b02a..6745f36f 100644 --- a/src/operation/operations.rs +++ b/src/operation/operations.rs @@ -226,6 +226,10 @@ pub trait Operation { /// Returns [`ContractId`] this operation belongs to. fn contract_id(&self) -> ContractId; + /// Returns nonce used in consensus ordering of state transitions and + /// extensions. + fn nonce(&self) -> u8; + /// Returns [`Option::Some`]`(`[`TransitionType`]`)` for transitions or /// [`Option::None`] for genesis and extension operation types fn transition_type(&self) -> Option; @@ -378,6 +382,7 @@ impl StrictDeserialize for Genesis {} pub struct Extension { pub ffv: Ffv, pub contract_id: ContractId, + pub nonce: u8, pub extension_type: ExtensionType, pub metadata: Metadata, pub globals: GlobalState, @@ -410,6 +415,7 @@ impl PartialOrd for Extension { pub struct Transition { pub ffv: Ffv, pub contract_id: ContractId, + pub nonce: u8, pub transition_type: TransitionType, pub metadata: Metadata, pub globals: GlobalState, @@ -511,6 +517,9 @@ impl Operation for Genesis { #[inline] fn contract_id(&self) -> ContractId { ContractId::from_inner(self.id().into_inner()) } + #[inline] + fn nonce(&self) -> u8 { u8::MAX } + #[inline] fn transition_type(&self) -> Option { None } @@ -553,6 +562,9 @@ impl Operation for Extension { #[inline] fn contract_id(&self) -> ContractId { self.contract_id } + #[inline] + fn nonce(&self) -> u8 { self.nonce } + #[inline] fn transition_type(&self) -> Option { None } @@ -595,6 +607,9 @@ impl Operation for Transition { #[inline] fn contract_id(&self) -> ContractId { self.contract_id } + #[inline] + fn nonce(&self) -> u8 { self.nonce } + #[inline] fn transition_type(&self) -> Option { Some(self.transition_type) } diff --git a/src/stl.rs b/src/stl.rs index d7cc755a..747c81b0 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -37,10 +37,10 @@ use crate::{ /// Strict types id for the library providing data types for RGB consensus. pub const LIB_ID_RGB_COMMIT: &str = - "stl:tjFc6jD7-fe78CxG-WdJlH!l-uXlFfW0-XwG1!qV-MNdtNGE#orbit-airport-voice"; + "stl:4WY0kCd1-qKjYuh5-4GgHKme-XIsmv98-W5Z9$8D-vMjFt!Y#miranda-blue-promise"; /// Strict types id for the library providing data types for RGB consensus. pub const LIB_ID_RGB_LOGIC: &str = - "stl:pxDxFGo9-MbacU6J-Qug1G$1-6LsuROd-Um1H$hU-T6o2Lgk#lobster-dilemma-famous"; + "stl:Yd7koRpf-hs7nsKX-TOLAnZl-hIfJ9wQ-M8J58hj-n60RcaA#pioneer-gong-smoke"; fn _rgb_commit_stl() -> Result { LibBuilder::new(libname!(LIB_NAME_RGB_COMMIT), tiny_bset! { diff --git a/src/validation/consignment.rs b/src/validation/consignment.rs index 6dce37bb..edfd7af9 100644 --- a/src/validation/consignment.rs +++ b/src/validation/consignment.rs @@ -31,13 +31,133 @@ use amplify::confinement::Confined; use strict_types::TypeSystem; use super::EAnchor; -use crate::vm::{OpRef, XWitnessId}; -use crate::{BundleId, Genesis, OpId, Operation, Schema, TransitionBundle}; +use crate::vm::XWitnessId; +use crate::{ + AssignmentType, AssignmentsRef, BundleId, ContractId, Extension, ExtensionType, Genesis, + GlobalState, GraphSeal, Inputs, Metadata, OpFullType, OpId, OpType, Operation, Schema, + Transition, TransitionBundle, TransitionType, TypedAssigns, Valencies, +}; pub const CONSIGNMENT_MAX_LIBS: usize = 1024; pub type Scripts = Confined, 0, CONSIGNMENT_MAX_LIBS>; +#[derive(Copy, Clone, PartialEq, Eq, Debug, From)] +pub enum OpRef<'op> { + #[from] + Genesis(&'op Genesis), + #[from] + Transition(&'op Transition), + #[from] + Extension(&'op Extension), +} + +impl<'op> Operation for OpRef<'op> { + fn op_type(&self) -> OpType { + match self { + Self::Genesis(op) => op.op_type(), + Self::Transition(op) => op.op_type(), + Self::Extension(op) => op.op_type(), + } + } + + fn full_type(&self) -> OpFullType { + match self { + Self::Genesis(op) => op.full_type(), + Self::Transition(op) => op.full_type(), + Self::Extension(op) => op.full_type(), + } + } + + fn id(&self) -> OpId { + match self { + Self::Genesis(op) => op.id(), + Self::Transition(op) => op.id(), + Self::Extension(op) => op.id(), + } + } + + fn contract_id(&self) -> ContractId { + match self { + Self::Genesis(op) => op.contract_id(), + Self::Transition(op) => op.contract_id(), + Self::Extension(op) => op.contract_id(), + } + } + + fn nonce(&self) -> u8 { + match self { + Self::Genesis(op) => op.nonce(), + Self::Transition(op) => op.nonce(), + Self::Extension(op) => op.nonce(), + } + } + + fn transition_type(&self) -> Option { + match self { + Self::Genesis(op) => op.transition_type(), + Self::Transition(op) => op.transition_type(), + Self::Extension(op) => op.transition_type(), + } + } + + fn extension_type(&self) -> Option { + match self { + Self::Genesis(op) => op.extension_type(), + Self::Transition(op) => op.extension_type(), + Self::Extension(op) => op.extension_type(), + } + } + + fn metadata(&self) -> &Metadata { + match self { + Self::Genesis(op) => op.metadata(), + Self::Transition(op) => op.metadata(), + Self::Extension(op) => op.metadata(), + } + } + + fn globals(&self) -> &GlobalState { + match self { + Self::Genesis(op) => op.globals(), + Self::Transition(op) => op.globals(), + Self::Extension(op) => op.globals(), + } + } + + fn valencies(&self) -> &Valencies { + match self { + Self::Genesis(op) => op.valencies(), + Self::Transition(op) => op.valencies(), + Self::Extension(op) => op.valencies(), + } + } + + fn assignments(&self) -> AssignmentsRef<'op> { + match self { + Self::Genesis(op) => (&op.assignments).into(), + Self::Transition(op) => (&op.assignments).into(), + Self::Extension(op) => (&op.assignments).into(), + } + } + + fn assignments_by_type(&self, t: AssignmentType) -> Option> { + match self { + Self::Genesis(op) => op.assignments_by_type(t), + Self::Transition(op) => op.assignments_by_type(t), + Self::Extension(op) => op.assignments_by_type(t), + } + } + + fn inputs(&self) -> Inputs { + match self { + Self::Genesis(op) => op.inputs(), + Self::Transition(op) => op.inputs(), + Self::Extension(op) => op.inputs(), + } + } +} + pub struct CheckedConsignment<'consignment, C: ConsignmentApi>(&'consignment C); impl<'consignment, C: ConsignmentApi> CheckedConsignment<'consignment, C> { diff --git a/src/validation/logic.rs b/src/validation/logic.rs index ca214121..ed7c3732 100644 --- a/src/validation/logic.rs +++ b/src/validation/logic.rs @@ -34,7 +34,7 @@ use strict_types::TypeSystem; use crate::schema::{AssignmentsSchema, GlobalSchema, ValencySchema}; use crate::validation::{CheckedConsignment, ConsignmentApi}; -use crate::vm::{ContractStateAccess, ContractStateEvolve, OpInfo, OpRef, RgbIsa, VmContext}; +use crate::vm::{ContractStateAccess, ContractStateEvolve, OpInfo, OrdOpRef, RgbIsa, VmContext}; use crate::{ validation, Assign, AssignmentType, Assignments, AssignmentsRef, ConcealedState, ConfidentialState, ExposedSeal, ExposedState, Extension, GlobalState, GlobalStateSchema, @@ -43,7 +43,6 @@ use crate::{ }; impl Schema { - // TODO: Instead of returning status fail immediately pub fn validate_state< 'validator, C: ConsignmentApi, @@ -51,7 +50,7 @@ impl Schema { >( &'validator self, consignment: &'validator CheckedConsignment<'_, C>, - op: OpRef, + op: OrdOpRef, contract_state: Rc>, ) -> validation::Status { let opid = op.id(); @@ -70,7 +69,7 @@ impl Schema { validator, ty, ) = match op { - OpRef::Genesis(genesis) => { + OrdOpRef::Genesis(genesis) => { for id in genesis.asset_tags.keys() { if !matches!(self.owned_types.get(id), Some(OwnedStateSchema::Fungible(_))) { status.add_failure(validation::Failure::AssetTagNoState(*id)); @@ -95,9 +94,12 @@ impl Schema { None::, ) } - OpRef::Transition(Transition { - transition_type, .. - }) => { + OrdOpRef::Transition( + Transition { + transition_type, .. + }, + .., + ) => { // Right now we do not have actions to implement; but later // we may have embedded procedures which must be verified // here @@ -131,7 +133,7 @@ impl Schema { Some(transition_type.into_inner()), ) } - OpRef::Extension(Extension { extension_type, .. }) => { + OrdOpRef::Extension(Extension { extension_type, .. }, ..) => { // Right now we do not have actions to implement; but later // we may have embedded procedures which must be verified // here @@ -168,7 +170,7 @@ impl Schema { status += self.validate_metadata(opid, op.metadata(), metadata_schema, consignment.types()); status += self.validate_global_state(opid, op.globals(), global_schema, consignment.types()); - let prev_state = if let OpRef::Transition(transition) = op { + let prev_state = if let OrdOpRef::Transition(transition, ..) = op { let prev_state = extract_prev_state(consignment, opid, &transition.inputs, &mut status); status += self.validate_prev_state(opid, &prev_state, owned_schema); prev_state @@ -176,7 +178,7 @@ impl Schema { Assignments::default() }; let mut redeemed = Valencies::default(); - if let OpRef::Extension(extension) = op { + if let OrdOpRef::Extension(extension, ..) = op { for valency in extension.redeemed.keys() { redeemed.push(*valency).expect("same size"); } @@ -218,9 +220,15 @@ impl Schema { error_code.map(u8::from), None, )); + // We return here since all other validations will have no valid state to access + return status; } let contract_state = context.contract_state; - contract_state.borrow_mut().evolve_state(op); + if contract_state.borrow_mut().evolve_state(op).is_err() { + status.add_failure(validation::Failure::ContractStateFilled(opid)); + // We return here since all other validations will have no valid state to access + return status; + } } status } diff --git a/src/validation/mod.rs b/src/validation/mod.rs index 6b8fa165..c7c3eb09 100644 --- a/src/validation/mod.rs +++ b/src/validation/mod.rs @@ -28,6 +28,6 @@ mod status; mod commitments; pub use commitments::{DbcError, DbcProof, EAnchor}; -pub use consignment::{CheckedConsignment, ConsignmentApi, Scripts, CONSIGNMENT_MAX_LIBS}; +pub use consignment::{CheckedConsignment, ConsignmentApi, OpRef, Scripts, CONSIGNMENT_MAX_LIBS}; pub use status::{Failure, Info, Status, Validity, Warning}; pub use validator::{ResolveWitness, Validator, WitnessResolverError}; diff --git a/src/validation/status.rs b/src/validation/status.rs index 288c1589..f77c6a0a 100644 --- a/src/validation/status.rs +++ b/src/validation/status.rs @@ -352,6 +352,8 @@ pub enum Failure { /// evaluation of AluVM script for operation {0} has failed with the code /// {1:?} and message {2:?}. ScriptFailure(OpId, Option, Option), + /// contract state can't fit more data (at operation id {0}). + ContractStateFilled(OpId), /// Custom error by external services on top of RGB Core. #[display(inner)] diff --git a/src/validation/validator.rs b/src/validation/validator.rs index 69026463..0306cdcb 100644 --- a/src/validation/validator.rs +++ b/src/validation/validator.rs @@ -31,10 +31,9 @@ use commit_verify::mpc; use single_use_seals::SealWitness; use super::status::Failure; -use super::{CheckedConsignment, ConsignmentApi, DbcProof, EAnchor, Status, Validity}; +use super::{CheckedConsignment, ConsignmentApi, DbcProof, EAnchor, OpRef, Status, Validity}; use crate::vm::{ - ContractStateAccess, ContractStateEvolve, OpOrd, OpRef, TxOrd, WitnessOrd, XWitnessId, - XWitnessTx, + ContractStateAccess, ContractStateEvolve, OrdOpRef, WitnessOrd, XWitnessId, XWitnessTx, }; use crate::{ validation, AltLayer1, BundleId, ContractId, Layer1, OpId, OpType, Operation, Opout, Schema, @@ -70,7 +69,7 @@ pub trait ResolveWitness { fn resolve_pub_witness_ord( &self, witness_id: XWitnessId, - ) -> Result; + ) -> Result; } impl ResolveWitness for &T { @@ -84,7 +83,7 @@ impl ResolveWitness for &T { fn resolve_pub_witness_ord( &self, witness_id: XWitnessId, - ) -> Result { + ) -> Result { ResolveWitness::resolve_pub_witness_ord(*self, witness_id) } } @@ -117,7 +116,7 @@ impl ResolveWitness for CheckedWitnessResolver { fn resolve_pub_witness_ord( &self, witness_id: XWitnessId, - ) -> Result { + ) -> Result { self.inner.resolve_pub_witness_ord(witness_id) } } @@ -134,13 +133,11 @@ pub struct Validator< status: RefCell, schema_id: SchemaId, - genesis_id: OpId, contract_id: ContractId, layers1: BTreeSet, contract_state: Rc>, validated_op_seals: RefCell>, - validated_op_state: RefCell>, resolver: CheckedWitnessResolver<&'resolver R>, } @@ -161,14 +158,10 @@ impl< // Frequently used computation-heavy data let genesis = consignment.genesis(); - let genesis_id = genesis.id(); let contract_id = genesis.contract_id(); let schema_id = genesis.schema_id; - // Validation index is used to check that all transitions presented in the - // consignment were validated. Also, we use it to avoid double schema - // validations for transitions. - let validated_op_state = RefCell::new(BTreeSet::::new()); + // Prevent repeated validation of single-use seals let validated_op_seals = RefCell::new(BTreeSet::::new()); let mut layers1 = bset! { Layer1::Bitcoin }; @@ -178,10 +171,8 @@ impl< consignment, status: RefCell::new(status), schema_id, - genesis_id, contract_id, layers1, - validated_op_state, validated_op_seals, resolver: CheckedWitnessResolver::from(resolver), contract_state: Rc::new(RefCell::new(S::init(context))), @@ -261,14 +252,13 @@ impl< // [VALIDATION]: Validate genesis *self.status.borrow_mut() += schema.validate_state( &self.consignment, - OpRef::Genesis(self.consignment.genesis()), + OrdOpRef::Genesis(self.consignment.genesis()), self.contract_state.clone(), ); - self.validated_op_state.borrow_mut().insert(self.genesis_id); // [VALIDATION]: Iterating over all consignment operations, ordering them according to the // consensus ordering rules. - let mut ops = BTreeMap::::new(); + let mut ops = BTreeSet::::new(); for bundle_id in self.consignment.bundle_ids() { let bundle = self .consignment @@ -278,7 +268,7 @@ impl< .consignment .anchor(bundle_id) .expect("invalid checked consignment"); - let pub_ord = + let witness_ord = match self.resolver.resolve_pub_witness_ord(witness_id) { Ok(ord) => ord, Err(err) => { @@ -289,26 +279,42 @@ impl< return; } }; - for (opid, op) in &bundle.known_transitions { - ops.insert( - OpOrd { - witness_ord: WitnessOrd { - pub_ord, - witness_id, - }, - opid: *opid, - }, - OpRef::Transition(op), - ); + for op in bundle.known_transitions.values() { + ops.insert(OrdOpRef::Transition(op, witness_id, witness_ord)); + for input in &op.inputs { + // We will error in `validate_operations` below on the absent extension from the + // consignment. + if let Some(OpRef::Extension(extension)) = + self.consignment.operation(input.prev_out.op) + { + let ext = OrdOpRef::Extension(extension, witness_id, witness_ord); + // Account only for the first time when extension seal was closed + let prev = ops.iter().find(|r| matches!(r, OrdOpRef::Extension(ext, ..) if ext.id() == extension.id())).copied(); + match prev { + Some(old) if old > ext => { + ops.remove(&old); + ops.insert(ext) + } + None => ops.insert(ext), + _ => { + /* the extension is already present in the queue and properly + * ordered, so we have nothing to add or change */ + true + } + }; + } + } } } - // TODO: Check that we include all terminal transitions - for op in ops.into_values() { + for op in ops { + // We do not skip validating archive operations since after a re-org they may + // become valid and thus must be added to the contract state and validated + // beforehand. self.validate_operation(op); } } - fn validate_operation(&self, operation: OpRef<'consignment>) { + fn validate_operation(&self, operation: OrdOpRef<'consignment>) { let schema = self.consignment.schema(); let opid = operation.id(); @@ -326,16 +332,14 @@ impl< .add_failure(Failure::SealsUnvalidated(opid)); } // [VALIDATION]: Verify operation against the schema and scripts - if self.validated_op_state.borrow_mut().insert(opid) { - *self.status.borrow_mut() += - schema.validate_state(&self.consignment, operation, self.contract_state.clone()); - } + *self.status.borrow_mut() += + schema.validate_state(&self.consignment, operation, self.contract_state.clone()); match operation { - OpRef::Genesis(_) => { + OrdOpRef::Genesis(_) => { unreachable!("genesis is not a part of the operation history") } - OpRef::Transition(transition) => { + OrdOpRef::Transition(transition, ..) => { for input in &transition.inputs { if self.consignment.operation(input.prev_out.op).is_none() { self.status @@ -344,7 +348,7 @@ impl< } } } - OpRef::Extension(extension) => { + OrdOpRef::Extension(extension, ..) => { for (valency, prev_id) in &extension.redeemed { let Some(prev_op) = self.consignment.operation(*prev_id) else { self.status diff --git a/src/vm/contract.rs b/src/vm/contract.rs index a601e885..d08885fc 100644 --- a/src/vm/contract.rs +++ b/src/vm/contract.rs @@ -27,8 +27,8 @@ use std::fmt::Debug; use std::num::NonZeroU32; use std::rc::Rc; +use amplify::confinement; use amplify::num::u24; -use amplify::Bytes32; use bp::seals::txout::{CloseMethod, ExplicitSeal, VerifyError, Witness}; use bp::{dbc, Tx, Txid}; use commit_verify::mpc; @@ -135,110 +135,155 @@ impl XChain { } } +/// The type is used during validation and computing a contract state. It +/// combines both the operation with the information required for its ordering +/// in the contract history (via construction of [`OpOrd`]) according to the +/// consensus rules. #[derive(Copy, Clone, PartialEq, Eq, Debug, From)] -pub enum OpRef<'op> { +pub enum OrdOpRef<'op> { #[from] Genesis(&'op Genesis), - #[from] - Transition(&'op Transition), - #[from] - Extension(&'op Extension), + Transition(&'op Transition, XWitnessId, WitnessOrd), + Extension(&'op Extension, XWitnessId, WitnessOrd), +} + +impl<'op> PartialOrd for OrdOpRef<'op> { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} + +impl<'op> Ord for OrdOpRef<'op> { + fn cmp(&self, other: &Self) -> Ordering { self.op_ord().cmp(&other.op_ord()) } } -impl<'op> Operation for OpRef<'op> { +impl<'op> OrdOpRef<'op> { + pub fn witness_id(&self) -> Option { + match self { + OrdOpRef::Genesis(_) => None, + OrdOpRef::Transition(_, witness_id, ..) | OrdOpRef::Extension(_, witness_id, ..) => { + Some(*witness_id) + } + } + } + + pub fn op_ord(&self) -> OpOrd { + match self { + OrdOpRef::Genesis(_) => OpOrd::Genesis, + OrdOpRef::Transition(op, _, witness_ord) => OpOrd::Transition { + witness: *witness_ord, + nonce: op.nonce, + opid: op.id(), + }, + OrdOpRef::Extension(op, _, witness_ord) => OpOrd::Extension { + witness: *witness_ord, + nonce: op.nonce, + opid: op.id(), + }, + } + } +} + +impl<'op> Operation for OrdOpRef<'op> { fn op_type(&self) -> OpType { match self { - OpRef::Genesis(op) => op.op_type(), - OpRef::Transition(op) => op.op_type(), - OpRef::Extension(op) => op.op_type(), + OrdOpRef::Genesis(op) => op.op_type(), + OrdOpRef::Transition(op, ..) => op.op_type(), + OrdOpRef::Extension(op, ..) => op.op_type(), } } fn full_type(&self) -> OpFullType { match self { - OpRef::Genesis(op) => op.full_type(), - OpRef::Transition(op) => op.full_type(), - OpRef::Extension(op) => op.full_type(), + OrdOpRef::Genesis(op) => op.full_type(), + OrdOpRef::Transition(op, ..) => op.full_type(), + OrdOpRef::Extension(op, ..) => op.full_type(), } } fn id(&self) -> OpId { match self { - OpRef::Genesis(op) => op.id(), - OpRef::Transition(op) => op.id(), - OpRef::Extension(op) => op.id(), + OrdOpRef::Genesis(op) => op.id(), + OrdOpRef::Transition(op, ..) => op.id(), + OrdOpRef::Extension(op, ..) => op.id(), } } 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(), + OrdOpRef::Genesis(op) => op.contract_id(), + OrdOpRef::Transition(op, ..) => op.contract_id(), + OrdOpRef::Extension(op, ..) => op.contract_id(), + } + } + + fn nonce(&self) -> u8 { + match self { + OrdOpRef::Genesis(op) => op.nonce(), + OrdOpRef::Transition(op, ..) => op.nonce(), + OrdOpRef::Extension(op, ..) => op.nonce(), } } fn transition_type(&self) -> Option { match self { - OpRef::Genesis(op) => op.transition_type(), - OpRef::Transition(op) => op.transition_type(), - OpRef::Extension(op) => op.transition_type(), + OrdOpRef::Genesis(op) => op.transition_type(), + OrdOpRef::Transition(op, ..) => op.transition_type(), + OrdOpRef::Extension(op, ..) => op.transition_type(), } } fn extension_type(&self) -> Option { match self { - OpRef::Genesis(op) => op.extension_type(), - OpRef::Transition(op) => op.extension_type(), - OpRef::Extension(op) => op.extension_type(), + OrdOpRef::Genesis(op) => op.extension_type(), + OrdOpRef::Transition(op, ..) => op.extension_type(), + OrdOpRef::Extension(op, ..) => op.extension_type(), } } fn metadata(&self) -> &Metadata { match self { - OpRef::Genesis(op) => op.metadata(), - OpRef::Transition(op) => op.metadata(), - OpRef::Extension(op) => op.metadata(), + OrdOpRef::Genesis(op) => op.metadata(), + OrdOpRef::Transition(op, ..) => op.metadata(), + OrdOpRef::Extension(op, ..) => op.metadata(), } } fn globals(&self) -> &GlobalState { match self { - OpRef::Genesis(op) => op.globals(), - OpRef::Transition(op) => op.globals(), - OpRef::Extension(op) => op.globals(), + OrdOpRef::Genesis(op) => op.globals(), + OrdOpRef::Transition(op, ..) => op.globals(), + OrdOpRef::Extension(op, ..) => op.globals(), } } fn valencies(&self) -> &Valencies { match self { - OpRef::Genesis(op) => op.valencies(), - OpRef::Transition(op) => op.valencies(), - OpRef::Extension(op) => op.valencies(), + OrdOpRef::Genesis(op) => op.valencies(), + OrdOpRef::Transition(op, ..) => op.valencies(), + OrdOpRef::Extension(op, ..) => op.valencies(), } } fn assignments(&self) -> AssignmentsRef<'op> { match self { - OpRef::Genesis(op) => (&op.assignments).into(), - OpRef::Transition(op) => (&op.assignments).into(), - OpRef::Extension(op) => (&op.assignments).into(), + OrdOpRef::Genesis(op) => (&op.assignments).into(), + OrdOpRef::Transition(op, ..) => (&op.assignments).into(), + OrdOpRef::Extension(op, ..) => (&op.assignments).into(), } } fn assignments_by_type(&self, t: AssignmentType) -> Option> { match self { - OpRef::Genesis(op) => op.assignments_by_type(t), - OpRef::Transition(op) => op.assignments_by_type(t), - OpRef::Extension(op) => op.assignments_by_type(t), + OrdOpRef::Genesis(op) => op.assignments_by_type(t), + OrdOpRef::Transition(op, ..) => op.assignments_by_type(t), + OrdOpRef::Extension(op, ..) => op.assignments_by_type(t), } } fn inputs(&self) -> Inputs { match self { - OpRef::Genesis(op) => op.inputs(), - OpRef::Transition(op) => op.inputs(), - OpRef::Extension(op) => op.inputs(), + OrdOpRef::Genesis(op) => op.inputs(), + OrdOpRef::Transition(op, ..) => op.inputs(), + OrdOpRef::Extension(op, ..) => op.inputs(), } } } @@ -252,30 +297,30 @@ impl<'op> Operation for OpRef<'op> { serde(crate = "serde_crate", rename_all = "camelCase") )] #[display("{height}@{timestamp}")] -pub struct TxPos { +pub struct WitnessPos { height: u32, timestamp: i64, } -impl TxPos { +impl WitnessPos { pub fn new(height: u32, timestamp: i64) -> Option { if height == 0 || timestamp < 1231006505 { return None; } - Some(TxPos { height, timestamp }) + Some(WitnessPos { height, timestamp }) } pub fn height(&self) -> NonZeroU32 { NonZeroU32::new(self.height).expect("invariant") } } -impl PartialOrd for TxPos { +impl PartialOrd for WitnessPos { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } -impl Ord for TxPos { +impl Ord for WitnessPos { /// Since we support multiple layer 1, we have to order basing on the /// timestamp information and not height. The timestamp data are consistent - /// accross multiple blockchains, while height evolves with a different + /// across multiple blockchains, while height evolves with a different /// speed and can't be used in comparisons. fn cmp(&self, other: &Self) -> Ordering { assert!(self.timestamp > 0); @@ -290,14 +335,15 @@ impl Ord for TxPos { /// of the contract global state data, as they are presented to all contract /// users. #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash, Debug, Display, From)] +#[display(lowercase)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_LOGIC, tags = order)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase", untagged) + serde(crate = "serde_crate", rename_all = "camelCase") )] -pub enum TxOrd { +pub enum WitnessOrd { /// Witness transaction must be excluded from the state processing. /// /// Cases for the exclusion: @@ -308,7 +354,6 @@ pub enum TxOrd { /// - past state channel transactions once a new channel state is signed /// (and until they may become valid once again due to an uncooperative /// channel closing). - #[display("archived")] #[strict_type(dumb)] Archived, @@ -316,10 +361,10 @@ pub enum TxOrd { /// timestamp. /// /// NB: only timestamp is used in consensus ordering though, see - /// [`TxPos::ord`] for the details. + /// [`WitnessPos::cmp`] for the details. #[from] #[display(inner)] - OnChain(TxPos), + Mined(WitnessPos), /// Valid witness transaction which commits the most recent RGB state, but /// is not (yet) included into a layer 1 blockchain. Such transactions have @@ -329,97 +374,81 @@ pub enum TxOrd { /// /// NB: not each and every signed offchain transaction should have this /// status; all offchain cases which fall under [`Self::Archived`] must be - /// excluded. Valid cases for assigning [`Self::Offchain`] status are: + /// excluded. Valid cases for assigning [`Self::Tentative`] status are: /// - transaction is present in the memepool; /// - transaction is a part of transaction graph inside a state channel /// (only actual channel state is accounted for; all previous channel /// state must have corresponding transactions set to [`Self::Archived`]); /// - transaction is an RBF replacement prepared to be broadcast (with the /// previous transaction set to [`Self::Archived`] at the same moment). - #[display("offchain@{priority}")] - OffChain { - /// Used for internal ordering inside the offchain transaction graph, - /// for instance to ensure that HTLC transactions in a lightning channel - /// have higher priority (i.e. computed after) than commitment. - priority: u32, - }, + Tentative, } -impl TxOrd { +impl WitnessOrd { #[inline] - pub fn offchain(priority: u32) -> Self { TxOrd::OffChain { priority } } + pub fn is_valid(self) -> bool { self != Self::Archived } } -/// Information about public witness used for ordering according to the RGB -/// consensus rules. +/// Operation ordering priority for contract state computation according to +/// [RCP-240731A]. /// -/// First, we account for [`TxOrd`], such that those state which witness -/// transactions were mined earlier come into the state computing earlier as -/// well. However, if two witness transactions were mined in the same block, we -/// order them lexicographically basing on the witness id. -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display)] +/// The ordering is the following: +/// - Genesis is processed first. +/// - Other operations are ordered according to their witness transactions (see +/// [`WitnessOrd`] for the details). +/// - Extensions share witness transaction with the state transition which first +/// to close one of the seals defined in the extension, but are processed +/// before that state transition. +/// - If two or more operations share the same witness transaction ordering, +/// they are first ordered basing on their `nonce` value, and if it is also +/// the same, basing on their operation id value. +/// +/// [RCP-240731A]: https://github.com/RGB-WG/RFC/issues/10 +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_LOGIC)] +#[strict_type(lib = LIB_NAME_RGB_LOGIC, tags = custom)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate", rename_all = "camelCase") )] -#[display("{witness_id}/{pub_ord}")] -pub struct WitnessOrd { - pub pub_ord: TxOrd, - pub witness_id: XWitnessId, -} - -impl PartialOrd for WitnessOrd { - fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } -} - -impl Ord for WitnessOrd { - fn cmp(&self, other: &Self) -> Ordering { - if self == other { - return Ordering::Equal; - } - match self.pub_ord.cmp(&other.pub_ord) { - Ordering::Less => Ordering::Less, - Ordering::Greater => Ordering::Greater, - Ordering::Equal => self.witness_id.cmp(&other.witness_id), - } - } +pub enum OpOrd { + #[strict_type(tag = 0x00, dumb)] + Genesis, + #[strict_type(tag = 0x01)] + Extension { + witness: WitnessOrd, + // TODO: Consider using extension type here + nonce: u8, + opid: OpId, + }, + #[strict_type(tag = 0xFF)] + Transition { + witness: WitnessOrd, + // TODO: Consider using transition type here + nonce: u8, + opid: OpId, + }, } -impl WitnessOrd { - pub fn with(witness_id: XWitnessId, pub_ord: TxOrd) -> Self { - WitnessOrd { - witness_id, - pub_ord, - } - } - - pub fn from_mempool(witness_id: XWitnessId, priority: u32) -> Self { - WitnessOrd { - pub_ord: TxOrd::OffChain { priority }, - witness_id, - } +impl OpOrd { + #[inline] + pub fn is_archived(&self) -> bool { + matches!( + self, + Self::Extension { + witness: WitnessOrd::Archived, + .. + } | Self::Transition { + witness: WitnessOrd::Archived, + .. + } + ) } } -/// Consensus ordering of operations within a contract. -#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_LOGIC)] -#[cfg_attr( - feature = "serde", - derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") -)] -pub struct OpOrd { - pub witness_ord: WitnessOrd, - pub opid: OpId, -} - /// Consensus ordering of global state -#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_LOGIC)] #[cfg_attr( @@ -428,50 +457,37 @@ pub struct OpOrd { serde(crate = "serde_crate", rename_all = "camelCase") )] pub struct GlobalOrd { - // Absent for state defined in genesis - pub witness_ord: Option, - pub opid: OpId, + pub op_ord: OpOrd, pub idx: u16, } -impl PartialOrd for GlobalOrd { - fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } -} - -impl Ord for GlobalOrd { - fn cmp(&self, other: &Self) -> Ordering { - if self == other { - return Ordering::Equal; - } - match (self.witness_ord, &other.witness_ord) { - (None, None) => self.idx.cmp(&other.idx), - (None, Some(_)) => Ordering::Less, - (Some(_), None) => Ordering::Greater, - (Some(ord1), Some(ord2)) if ord1 == *ord2 => self.idx.cmp(&other.idx), - (Some(ord1), Some(ord2)) => ord1.cmp(ord2), +impl GlobalOrd { + pub fn genesis(idx: u16) -> Self { + Self { + op_ord: OpOrd::Genesis, + idx, } } -} - -impl GlobalOrd { - #[inline] - pub fn with_witness(opid: OpId, witness_id: XWitnessId, ord: TxOrd, idx: u16) -> Self { - GlobalOrd { - witness_ord: Some(WitnessOrd::with(witness_id, ord)), - opid, + pub fn transition(opid: OpId, idx: u16, nonce: u8, witness: WitnessOrd) -> Self { + Self { + op_ord: OpOrd::Transition { + witness, + nonce, + opid, + }, idx, } } - #[inline] - pub fn genesis(opid: OpId, idx: u16) -> Self { - GlobalOrd { - witness_ord: None, - opid, + pub fn extension(opid: OpId, idx: u16, nonce: u8, witness: WitnessOrd) -> Self { + Self { + op_ord: OpOrd::Extension { + witness, + nonce, + opid, + }, idx, } } - #[inline] - pub fn witness_id(&self) -> Option { self.witness_ord.map(|a| a.witness_id) } } pub trait GlobalStateIter { @@ -500,24 +516,17 @@ impl GlobalStateIter for &mut I { pub struct GlobalContractState { checked_depth: u24, - last_ord: GlobalOrd, + last_ord: Option, iter: I, } impl GlobalContractState { #[inline] - pub fn new(mut iter: I) -> Self { - let last_ord = iter.prev().map(|(ord, _)| ord).unwrap_or(GlobalOrd { - // This is dumb object which must always have the lowest ordering. - witness_ord: None, - opid: Bytes32::zero().into(), - idx: 0, - }); - iter.reset(u24::ZERO); + pub fn new(iter: I) -> Self { Self { iter, checked_depth: u24::ONE, - last_ord, + last_ord: None, } } @@ -526,21 +535,17 @@ impl GlobalContractState { fn prev_checked(&mut self) -> Option<(GlobalOrd, I::Data)> { let (ord, item) = self.iter.prev()?; - if ord >= self.last_ord { + if self.last_ord.map(|last| ord <= last).unwrap_or_default() { panic!( "global contract state iterator has invalid implementation: it fails to order \ global state according to the consensus ordering" ); } - if let Some(WitnessOrd { - pub_ord: TxOrd::Archived, - .. - }) = ord.witness_ord - { - panic!("invalid GlobalStateIter implementation returning WitnessAnchor::Archived") + if ord.op_ord.is_archived() { + panic!("invalid GlobalStateIter implementation returning WitnessOrd::Archived") } self.checked_depth += u24::ONE; - self.last_ord = ord; + self.last_ord = Some(ord); Some((ord, item)) } @@ -612,7 +617,9 @@ pub trait ContractStateAccess: Debug { pub trait ContractStateEvolve { type Context<'ctx>; fn init(context: Self::Context<'_>) -> Self; - fn evolve_state(&mut self, op: OpRef); + // TODO: distinguish contract validation failure errors from connectivity + // errors. Allow custom error types here. + fn evolve_state(&mut self, op: OrdOpRef) -> Result<(), confinement::Error>; } pub struct VmContext<'op, S: ContractStateAccess> { @@ -636,7 +643,7 @@ pub struct OpInfo<'op> { impl<'op> OpInfo<'op> { pub fn with( id: OpId, - op: &'op OpRef<'op>, + op: &'op OrdOpRef<'op>, prev_state: &'op Assignments, redeemed: &'op Valencies, ) -> Self { diff --git a/src/vm/macroasm.rs b/src/vm/macroasm.rs index 505dbc93..f05a3d71 100644 --- a/src/vm/macroasm.rs +++ b/src/vm/macroasm.rs @@ -27,7 +27,7 @@ macro_rules! rgbasm { use $crate::vm::{RgbIsa, ContractOp, TimechainOp}; use $crate::vm::aluasm_isa; use $crate::isa_instr; - aluasm_isa! { RgbIsa => $( $tt )+ } + aluasm_isa! { RgbIsa<_> => $( $tt )+ } } }}; } diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 9d47aa52..89ccf6ad 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -35,7 +35,7 @@ mod contract; pub use aluvm::aluasm_isa; pub use contract::{ ContractStateAccess, ContractStateEvolve, GlobalContractState, GlobalOrd, GlobalStateIter, - OpOrd, OpRef, TxOrd, TxPos, UnknownGlobalStateType, WitnessOrd, XWitnessId, XWitnessTx, + OpOrd, OrdOpRef, UnknownGlobalStateType, WitnessOrd, WitnessPos, XWitnessId, XWitnessTx, }; pub(crate) use contract::{OpInfo, VmContext}; pub use isa::RgbIsa; diff --git a/src/vm/op_contract.rs b/src/vm/op_contract.rs index a33b73b2..44de93f6 100644 --- a/src/vm/op_contract.rs +++ b/src/vm/op_contract.rs @@ -22,6 +22,8 @@ #![allow(clippy::unusual_byte_groupings)] +use std::borrow::Borrow; +use std::cell::RefCell; use std::collections::BTreeSet; use std::marker::PhantomData; use std::ops::RangeInclusive; @@ -29,7 +31,7 @@ use std::ops::RangeInclusive; use aluvm::isa::{Bytecode, BytecodeError, ExecStep, InstructionSet}; use aluvm::library::{CodeEofError, IsaSeg, LibSite, Read, Write}; use aluvm::reg::{CoreRegs, Reg, Reg16, Reg32, RegA, RegS}; -use amplify::num::{u3, u4}; +use amplify::num::{u24, u3, u4}; use amplify::Wrapper; use commit_verify::CommitVerify; @@ -44,22 +46,34 @@ use crate::{ pub enum ContractOp { /// Counts number of inputs (previous state entries) of the provided type /// and puts the number to the destination `a16` register. + /// + /// If the operation doesn't contain inputs with a given assignment type, + /// sets destination index to zero. Does not change `st0` register. #[display("cnp {0},a16{1}")] CnP(AssignmentType, Reg32), /// Counts number of outputs (owned state entries) of the provided type /// and puts the number to the destination `a16` register. + /// + /// If the operation doesn't contain inputs with a given assignment type, + /// sets destination index to zero. Does not change `st0` register. #[display("cns {0},a16{1}")] CnS(AssignmentType, Reg32), /// Counts number of global state items of the provided type affected by the /// current operation and puts the number to the destination `a8` register. + /// + /// If the operation doesn't contain inputs with a given assignment type, + /// sets destination index to zero. Does not change `st0` register. #[display("cng {0},a8{1}")] CnG(GlobalStateType, Reg32), /// Counts number of global state items of the provided type in the contract - /// state and puts the number to the destination `a16` register. - #[display("cnc {0},a16{1}")] + /// state and puts the number to the destination `a32` register. + /// + /// If the operation doesn't contain inputs with a given assignment type, + /// sets destination index to zero. Does not change `st0` register. + #[display("cnc {0},a32{1}")] CnC(GlobalStateType, Reg32), /// Loads input (previous) structured state with type id from the first @@ -104,19 +118,22 @@ pub enum ContractOp { LdG(GlobalStateType, Reg16, RegS), /// Loads part of the contract global state with type id from the first - /// argument at the depth from the second argument `a24` register into a + /// argument at the depth from the second argument `a32` register into a /// register provided in the third argument. /// - /// If the state is absent or concealed sets destination to `None`. - /// Does not modify content of `st0` register. - #[display("ldc {0},a24{1},{2}")] + /// If the contract doesn't have the provided global state type, or it + /// doesn't contain a value at the requested index, sets `st0` + /// to fail state and terminates the program. The value of the + /// destination register is not changed. + #[display("ldc {0},a32{1},{2}")] LdC(GlobalStateType, Reg16, RegS), /// Loads operation metadata with a type id from the first argument into a /// register provided in the second argument. /// - /// If the operation doesn't have metadata fails and sets `st0` to fail - /// state. + /// If the operation doesn't have metadata, sets `st0` to fail state and + /// terminates the program. The value of the destination register is not + /// changed. #[display("ldm {0},{1}")] LdM(MetaType, RegS), @@ -300,9 +317,13 @@ impl InstructionSet for ContractOp { context.op_info.global.get(state_type).map(|a| a.len_u16()), ); } - ContractOp::CnC(_state_type, _reg) => { - // TODO: implement global contract state - fail!() + ContractOp::CnC(state_type, reg) => { + if let Ok(mut global) = RefCell::borrow(&context.contract_state).global(*state_type) + { + regs.set_n(RegA::A32, *reg, global.size().to_u32()); + } else { + regs.set_n(RegA::A32, *reg, None::); + } } ContractOp::LdP(state_type, reg_32, reg) => { let Some(reg_32) = *regs.get_n(RegA::A16, *reg_32) else { @@ -354,8 +375,8 @@ impl InstructionSet for ContractOp { }; regs.set_n(RegA::A64, *reg, state.map(|s| s.value.as_u64())); } - ContractOp::LdG(state_type, reg_32, reg_s) => { - let Some(reg_32) = *regs.get_n(RegA::A8, *reg_32) else { + ContractOp::LdG(state_type, reg_8, reg_s) => { + let Some(reg_32) = *regs.get_n(RegA::A8, *reg_8) else { fail!() }; let index: u8 = reg_32.into(); @@ -371,9 +392,22 @@ impl InstructionSet for ContractOp { regs.set_s(*reg_s, Some(state.as_inner())); } - ContractOp::LdC(_state_type, _reg_24, _reg_s) => { - // TODO: implement global contract state - fail!() + ContractOp::LdC(state_type, reg_32, reg_s) => { + let state = RefCell::borrow(&context.contract_state); + let Ok(mut global) = state.global(*state_type) else { + fail!() + }; + let Some(reg_32) = *regs.get_n(RegA::A32, *reg_32) else { + fail!() + }; + let index: u32 = reg_32.into(); + let Ok(index) = u24::try_from(index) else { + fail!() + }; + let Some(state) = global.nth(index) else { + fail!() + }; + regs.set_s(*reg_s, Some(state.borrow().as_inner())); } ContractOp::LdM(type_id, reg) => { let Some(meta) = context.op_info.metadata.get(type_id) else { diff --git a/stl/AnchoredBundle.vesper b/stl/AnchoredBundle.vesper index b7ea1295..b32b9ce0 100644 --- a/stl/AnchoredBundle.vesper +++ b/stl/AnchoredBundle.vesper @@ -29,6 +29,7 @@ TransitionBundle rec value rec Transition ffv is U16 aka=Ffv contractId bytes len=32 aka=ContractId + nonce is U8 transitionType is U16 aka=TransitionType metadata map len=0..MAX8 aka=Metadata key is U16 aka=MetaType diff --git a/stl/RGBCommit@0.1.0.sta b/stl/RGBCommit@0.1.0.sta index 65cfa7a6..9b2b888c 100644 --- a/stl/RGBCommit@0.1.0.sta +++ b/stl/RGBCommit@0.1.0.sta @@ -1,5 +1,5 @@ -----BEGIN STRICT TYPE LIB----- -Id: stl:tjFc6jD7-fe78CxG-WdJlH!l-uXlFfW0-XwG1!qV-MNdtNGE#orbit-airport-voice +Id: stl:4WY0kCd1-qKjYuh5-4GgHKme-XIsmv98-W5Z9$8D-vMjFt!Y#miranda-blue-promise Name: RGBCommit Dependencies: StrictTypes#century-comrade-chess, @@ -8,7 +8,7 @@ Dependencies: Std#ralph-blue-lucky, CommitVerify#tennis-peace-olympic, Bitcoin#signal-color-cipher -Check-SHA256: 01bd46a72a4d496903d4a321d4f81dfe5d3faaa40ff499d1c0179208ade2ce71 +Check-SHA256: f92eee8c46b1d13a82140f1bc301abf4f112a700ff58dd49c0da0572d89f6747 2~tNwLvL+uX>Z4!V_T!KNI`QJ|h6;Zj^jB$MPK+?7Lu3>C`4HI)Q*?4^V{}w`aAk91a5aA+<>R2X hQO_4{AcS-HH^7AVzASV8M4NYxyCjHL2PwaO>Z8S`G>t*&Lor=XWH@ulIpd#VR%e3()@~+=qs(Ia|S|C @@ -134,137 +134,137 @@ fc_fP)zMd`Zf8beV{~tF1pxpD 002NB01rcNZewL(Y-MCYbaY{3XaxZP2LJ#-AOHD 0Z6?XZWsH8I~II?C0;dW+k!*yDqgzlqQwf$39g<|8VW;iZgg^CV{}Pm1pxpD002NB00~54bYW9;VRU5$ -0RRX906+i$000000096000000000R^cywiMb7^mG1_}daW_AJEn^6;37FKqUhx?i3R+Mr!fY&(;2BFL( -m@EZk_srD_V{dMBa$#e1Nn`<^2rNlD$O59e#ogQsB77jPl+h5j^*S;QWq5RDZgXjGZd7@2 -WdUS$9zv-Vp*%wog4O?q)g04AaHEjnO6;Ie%sNwVNZtr-WprU=VRT^u^?FS>S$_F2)vN@Mb6UJ-F(lri -_dqerx4lR4>iBsz2WM<=Vqt7^0p25#Yo@G%*b#-tU^&3KX?w7l?~*Sh8@1jRRblZzybED-b7^O8ZDnqB -a{*!M7xv9`tr;B~Bo!i4lRNTuBD!%MCoGl=mmvDN)M^NFWn^V#ZDnKu-_NO$^@rt6M7IGITmUKjm1~>v -&8b0-V>p(oz$%0233g#@Wo~0>Wpe@Dg=PS6VPp{$?vC--s`v@B8YHl)C#jpVFzBk!DMw8Sc42I3WMOn~ -asuJe-5feW*SKg&%h~b$G{NN>LxBDo)YaDXjV8yEYGsr6V0dsu5sjwLjgQcrOsaG1F{QvR+LMR3-^ZN{ -xOxY7X>@L7b8`aW(cK(6LD#rwNz2*s{WQVl8bg5o8r0R+^o=IRl4@nkxGg*8X!CPrawgw_sqk4BX8}k^ -^xj-FXm+)yumJ%NMR;^&ZgXjGZc}4uWo=;w1_*6sbYWy+bYTDq0d?d}_}|Wp0vpvv$c&#PW69R$ltr%d -a5t5w^x+8!q5uE@000000RI300000000(DmZ(?C=a{vkf)$WoGNrn+a000000RI300000000(kqWMyS-a{vhe -M(yUq2ps*m=2xUDT;RqCgn#@WzFu~@adfH5^@&-|0000000000{{R30000003t@9}X=iS2Wo~qH015$z -{^Dg=h-~N_zJ`Red1EINWrM}GXaQb}6c#qIM2EQnHo-KZ`k;Xmr`<4sJYKN!!u{G5u+^j1lf!PF4>GEG -0000000000{{R300000033g#@Wo~0>Wpe-t0Y>fS!w4MxxaL=+DqP^k2!wz9AHH68xp8!<%Jqp^&Hw-a -000000RI3000000010+sY-wa+bZ>G11OfmAZf|a7000011aog~WdH>M0%CAAe<9`Lptgpr6F_ -xjAC6(~TLj#*ewiHf`^rCgHqw;r~cW`-QV>+d2nR~0RR93 -14d?c1pxp60u4rWZf9v?Y-Lk)VRU5#0SE?SX>@ZoGynww000OKMs;pyX<}?;RC#b^0|5 -bs~EXcCXx(drQcb3B`Fx$)^%va$Ar)C7cIzWpi|HWpo0{EFN!zncXl9K5w2;FV{y1jDTJCC^p$-mHEbO -0#qkqh9c2>uJC38-{*D7fZ(%hZo23R4S;p`Q9JBQllDytVQh3vVR>b8b1?xVS5nwzfbg8kY9lvP5=0UVRLh3bW~wya{(zO4hF%Q&3qd{UvF)tP|M@Vc@bh1|A(%Z=^thBTg(V;WprU= -VRT^u^?FS>S$_F2)vN@Mb6UJ-F(lri_dqerx4lR4>iBsz2WM<=Vqt7^0p25#Yo@G%*b#-tU^&3KX?w7l -?~*Sh8@1jRRblZzybED-b7^O8ZDnqBa{*!M7xv9`tr;B~Bo!i4lRNTuBD!%MCoGl=mmvDN)M^QKVQgh? +0RRX906+i$000000096000000000R^cywiMb7^mG1`7jbW_AJEn^6;37FKqUhx?i3R+Mr!fY&(;2BFL( +m@EZk_srD_V{dMBa$#e1Nn`<^2rNlD$O59e#ogQsB77jPl+h5j^*S;IZf|a5WdHyH4P|(A +Wo~n6Z*Ek1aAg5xbsj>g6`?#s5rWnKhSeO?L~x^!;Y#eFP|P}0Z%EzBpbEf7FA*KKfDWJ +b8~5DZf#|5baMe=>KFFSbgda38zdDXQ0-0pHK5k@bh=O+>c= +6_h5K%L=laq&yA1JoJ^{7>oKLk +F4~iax8KK|47hp+cWHEPWpi@^;nCe3IziXCXi3Z2@%=Qxxis%Ku=2wF+7z(Wqt=tdZk`V^s(Ana000000093000000000Ma +Wn^V#ZF2w#0Y>fS!w4MxxaL=+DqP^k2!wz9AHH68xp8!<%Jqp^&Hw-a000000RI300000001IJrb7^O8 +ZDnqBa{vkfhyLPaScq)s9KMExvw34D6J>+NwrBxfixd_%u|$Wt0XD%jq57bK6Q|uUfIMEX^1}Vv6tLB! +)|10-o)0prc>n+a000000RI3000000010+sY-Mg^X=QT&2?0j!=EDda{kY~=q$*tC#t4Le{2#tvcDZqM +smk?aU=OZ$bvG|>z)+>9PT;Au-7)~D;-++htxcywiMb7^mG +RC#b^1pxp60s}^7b_D?d00Iq0b#7;AVr*qobYXO51OW&JVrg`9HZ%YQ0RR993`TWsXK7+=WmI`^Wdi{X +b#8NMXKrO=HZ($MbO;AWWo~72X>$e*17>D+0ot2U6Id2jc94hrndMfLayEe1ISdA&%p{mB1!VWk)d+KA +Xk~3-Nn`<(Qq$W5tE;F{pQrXd&=l*`O?@#x{Qdy?T_k!`1dtE~W^7?+a{}Sf-5feW*SKg&%h~b$G{NN> +LxBDo)YaDXjV8yEYGsr6V0dsu5sjwLjgQcrOsaG1F{QvR+LMR3-^ZN{xOxe6X>Db5bYX39002k^X>)UR +WpV+w=zxYCD0L!x4tB5Hm3vFbl?lapNXe%XU~*fKJ0+Y4bY*jNZe?@=$}AplgPGkh3_fq3Q7_j=2#kPT +_9!;lWR>~GYywm#VTK~nd#>BpbEf7FA*KKfDWJb8~5DZf#|5baMe=>KFFSbgda38zdDXQa{=9jW&m$tWDykZj`7#3_zANbB(SO{shhGe=&H{tM@n+a000000RI300000001IJrb7^O8ZDnqBa{vkfhyLPaScq)s9KMExvw34D +6J>+NwrBxfixd_%u|$Wt0XD%jq57bK6Q|uUfIMEX^1}Vv6tLB!)|10-o)0prc>n+a000000RI3000000 +010+sY-Mg^X=QT&2?0j!=EDda{kY~=q$*tC#t4Le{2#tvcDZqMsmk?aU=OZ$bvG|>z)+>9PT;Au-7)~D;-++hnxY;R&=Y*Tb$bY%qr015%s?vf5kh_h+&YE#h%O8d1V +_{UOl9{V;uR#^q%$8ES^HwOl>wiJLxBDo)YaDXjV8yEYGsr6 +V0dsu5sjwLjgQcrOsaG1F{QvR+LMR3-^ZN{xOxS1Wo=1h0!8YhU)%QMkO4aJ;_ZeCe;xE!X<$x_Fs4If +6Z`oP*=q!&6rQG)02XJT?*g=|B=zREie$*y(7k2+*P~cYjRc;WmI`^ +Wd#8M00In0Y;R&=Y*t}xb!Bq}0RRXAGM-jZ2Kh}DE2o;HYydTtf}Q!WH{}bI!u)W*#(e~Z0RR9100000 +|Nj60000002uWmRZggpMc?AIg1p)%fEFN!zncXl9K5w2;FV{y1jDTJCC^p$-mHEbO0#qkRz9SbZ=!8X@ +=Yuq$20sb<4l#S`iz7Vef}@Ca=a#qt2m;D19&dx0-7pM3Z=O*v*GCA9fL-<|HrZsA`NnJlR3}KjBNr;@ +ghiU?gEXK9KMDE{F?;HZBRuDVqlk6qmbd@_000000093F00000000F^Zg6#U1_B3ga%FZ;b#wuf5WIk~ +G+K)5%bR49t5yk`^qQ9la%FR6a&~280^!l!96CYQxM)es+421}!Q~o5fc_fP)zWj015*2Y!hN5_Bp3Y36tDM +M#=e#tGI($UA5U3KNx<*C>jbO<32;hs$B9ZCsU(1!DsC|W1LOd&b_IRG-(&Q$wPGkmB{9L9(7`0)Rt93 +YLV-HLXe?vTA1;^Q1`ZqBog<<0RR9100000|Nj600000021#ykb#!wD0RRaB)s0^W44ZlVPs)+VFdvI3ITQGP59r=ivkR6Lh9EroO>_;0000000030000000000B +Ph(?sa&l#EV`Xy&0t0PnZU6uR18re=0006EPjEwTZEb0EZDnqB1`7jbW_AJEn^6;37FKqUhx?i3R+Mr! +fY&(;2BFL(m@EZk_srD=Zf|a5WdHyH25)dwd2nR`=kby$tK%HuPpRtMKe5+wDRP}k(QuARKUbDjTz^bE +2yJC_VPs)+VFKaN-5feW*SKg&%h~b$G{NN>LxBDo)YaDXjV8yEYGrM&t;O}HAO^^zqT0%g+nC0;-MWK< +)&Ge4`aq}l(*_4;Y;R&=Y;ywP(cK(6LD#rwNz2*s{WQVl8bg5o8r0R+^o=IRl4@l*oM?JbaMjX(cK(6LD#rwNz2*s{WQVl8bg5o8r0R+^o=IRl4@l*oClv)aMjKgwAH@`bu1x<7g|G$};xvA~n-$_S2y$g)Wo2z;WCG#Q-5feW*SKg&%h~b$ +G{NN>LxBDo)YaDXjV8yEYGrM&t;O}HAO^^zqT0%g+nC0;-MWK<)&Ge4`aq}l(*_B4VQgh?V`*h`0^!l! +96CYQxM)es+421}!Q~o5fc_fP)zLxBDo)YaDXjV8yEYGpW{Lxv|61vo|v2!%ioJ +pYH;+t0eX2w~A!Q+0eaZ{MVycPK^T!VRUq1V`yzR000000RI3000000 +01i@Rc4c8~Wn@8gbYWv?1_A_TX>4Ty3ATR{-|K6Y3I$*BbiAvUS*tg{!Gb}P!O*^_P#qhP1ao0*bN~Pd +3{quwWnpY(WJFD+0ot2U6Id2jc94hrndMfLayEe1ISdA&%p{mB1!VWk)dgm3VP|s!;nCe3 +IziXCXi3Z2@%=QxOi(W05|TJ%PM*ricn_Pm +Xk-a=X>Db5bYX39002k{WMy_`Y;SO7asjsJfQB3>bs~EXcCXx(drQcb3B`Fx$)^%va$Ar)C7cOuWprUw +d2nTO015$hC`}q*rYXqYdo~D%m7H6OD0<^0n_2##VWXRdjy=DB@qgYOj0000000000{{R30000003ukO^Vqt7l +d2nTO015%s?vf5kh_h+&YE#h%O8d1V_{UOl9{V;uR#^q%KfZ0H=mhJ>?uV9R0ZFSEMRj;Km4qfBYZ5UUs>0bg9bqiCNA700000000300000000007XJu|>b7^w{ +7)aIA#9XnshcC})U)TI#r3b0kyqD7}ejM+$yUGm(3T1e7Wo~n6Z*Fq{3ISww9zv-Vp*%wog4O?q)g04A +aHEjnO6;Ie%sNwVNZtVhXadZr-SPY!h`6Z9%SYt5l1;p3@00000000300000000008a%FR6a&~280^!l!96CYQxM)es+421}!Q~o5fc_fP +)zD+0ot2U6Id2jc94hrndMfLayEe1ISdA&%p{mB1!VWk)e2*8Zgg^CV{}Pm0iOsgNjk^^ +qPoT1+zTRnAg`3vXv9d*8d@RXy~6c6G6imLZewKt009nka$#@6CZd7@2WdSr&53UoI8eY9A{1GER +g--GiI0S#x1is&)M%fmnGH3{GWprU=VRT^u^?FS>S$_F2)vN@Mb6UJ-F(lri_dqerx4lR4>iBsz2WM<= +Vqt7^0p25#Yo@G%*b#-tU^&3KX?w7l?~*Sh8@1jRRblZzyas7*aCLNZ0jZ*TSChz_$|Xx}eRkFNAr%^e +Ll(1e@}~9=0-ijXfD2)Bb7^O8ZDnqBa{)s3lIz?v1U>x&T2C;PAKlCCveQ{N4udSh#@3Dqj&%ukVQgh? V`*h`0o{dW0B>Pr5ftu@@z<*O39}j`u&O7io3b$Is?RA$O$l~kY-wa+bZ>G3;nCe3IziXCXi3Z2@%=Qx -vO>-_DBy8`Vb0jGrW9$=2qSMXvL3HGEG0000000000{{R30000003t@9}X=iS2Wo~qH015$z{^Dg=h-~N_zJ`Red1EINWrM}GXaQb} -6c#qIM2EQnHo-KZ`k;Xmr`<4sJYKN!!u{G5u+^j1lf!PF4>GEG0000000000{{R300000033g#@Wo~0> -Wpe-t0Y>fS!w4MxxaL=+DqP^k2!wz9AHH68xp8!<%Jqp^&Hw-a000000RI3000000010+sY-wa+bZ>G1 -1OfmAZf|a7000011aog~WdH>M0%CAAe<9`Lptgpr6F_xjAC6(~TLj#*ewiHf`^rCgHqw;r~cW -`-Qc;Wd#8M3IWybk`76TvuW{aQ_%-X`?VwZ$5L?~`!+pR -Sq0(b70Uq%!}cPEJ+)wh?w~HsM>Kh32^DD>YKF13Ts`WEp!#kA0000000030000000000HM{I9mVQf=$ -VRU6vV`ybC`}q*rYXqYd -o~D%m7H6OD0<^0n_2##VWXRdjy=DB@qgYOj2yJ0_Npxjxa{vGX4@YcoVqt7kbYXO5RC#b^1pxp60t`oN -Z(?C=R$**)Wpf1q00;pxo>ox?`Aroor<$W|05z3@o%ygg4E?M+l67UG^w8*<_XZ#%uyqCrG{{7b@t4MVjY>G@u4Q -3HlB(d+LiLJm-R=h;`?dxBvhE000000RImF0000000l{IaCLMB0taw%Wp+<>bODnPynwMZT8l5kSW@l} -O=!>^xB4~9n`Dx!RtcK)nwJQ2Wpib6c4cG&;nCe3IziXCXi3Z2@%=QxRw7=FYk8VVufK10Q-T=FR=Q=>S+XYD&lVN`i=Wd#8M00Ie3WprUyVQh6} -1pxpE002M$0000000030{{R3000008O=WapWMOn+1pxpG0d?d}_}|Wp0vpvv$c&#PW69R$ltr%da5t5w -^x+8!q5%{oJdRMsrjHBJ^EIe4enz&iEACnc`NWk%>en!wdoTb1000000093000000000Y5V`Fu4a%FB~ -Wpf4s18r$;00065ZDDu-00In8a6@lxZE19EWo~o^3Ik?lb^+R(Q4?4eR(6nw`9N|x?>fArE*KsLwol4Pgj!HjQmQ`GTOgji|WprU=VRT^v;nCe3IziXC -Xi3Z2@%=QxsZfv!yd427@;7veO2zMB=| -GX`mHaCLNZ0^!l!96CYQxM)es+421}!Q~o5fc_fP)zsZfv!yd427@; -7veO2zMB=|GYesJb7^O8ZDnqBa{}Sf-5feW*SKg&%h~b$G{NN>LxBDo)YaDXjV8yEYGpW{Lxv|61vo|< -S$`kJ6oIZx{|tq&1{dNqe!iO(;xhWpe`I(cK(6LD#rwNz2*s{WQVl8bg5o -8r0R+^o=IRl4@mbudT)PryvH%qoUf%jN6#Tx81sfg4O?s`uaep_R|IjcWHEPWpi@^;nCe3IziXCXi3Z2 -@%=QxClv)aMjKgwAH@`bu1x<7g|G$};xvA~n-$_S33g#@X=Gt^Z*l_R -(cK(6LD#rwNz2*s{WQVl8bg5o8r0R+^o=IRl4@mbudT)PryvH%qoUf%jN6#Tx81sfg4O?s`uaep_R|Ig -PjE?O1pxpD002NB00mEQZ*_DA0|IYw0hP$+dLDIRU(}XWLTZugenOC;Z(5k~zEJnJiX;;E#R7DB0f+wL -Wmt%8=p4R=gtK{LClh6Z#kObxUW*hKHnBv9xdLu)0006IPj_x*WK(oubY)XxXk~3-1OxyJWMyM)VRB(~ -X?A4*00039W_507X<}?;00jX7`Sh#^X0AbZX4L%*5q$))*;M@wXI>IJVg&1PPwC}G0t$0Lb#i57 -00jX8Me3tp+xFv-0Xp&G?S=|}9rRaeU`~uMrbA>C`}q*rYXqYdo~D%m7H6OD0<^0n_2##VWXRdjy=DB@ -qgYOj0}5eubYWv?ZDnqB00jX7LNH;4h{)8d6ed8&{mBRDiWpZ<6ZbNTv -ZE19EWo~o@0RRU806-xC2vTKaWo2z;WCZ~L3IRs#=EDda{kY~=q$*tC#t4Le{2#tvcDZqMsmk?Ml$g}C@DyY!@{4YR*LMYs=? -Zg_*ktx|21^lzg9sBTBv19V|$0m+Y=slx_K8vXre8<)H){QgX6j~{c$E$eY_=V_ZFuLe_NXk~3-1`Pvd -W_AJEn^6;37FKqUhx?i3R+Mr!fY&(;2BFL(m@EZk_srD=W^7?+a{}Sf-5feW*SKg&%h~b$G{NN>LxBDo -)YaDXjV8yEYGsr6V0dsu5sjwLjgQcrOsaG1F{QvR+LMR3-^ZN{xOxO`VQpmsMe3tp+xFv-0Xp&G?S=|} -9rRaeU`~uMrbA>C`}q*rQx*t>6v={gsJ=SZlTl1iF5eQ8IAl(q%E@>So406W33O>~Wpi|4ZEyepNC{+R -c4cgDaAk4ioJ -pYH;+t0eX2w~A!Q+0eaZ{MVycPK^Kn000000093000000000YTY;R&=Y*cx0Wpe-u0oCr34oQf!Y4K`P -(FaQVwIle)QgI&pHa%8Z1>xis%K=jn%|ogzQLxC5#{z1Bs(ImjcZKu%4y_xMoB3q3{22fM0000000930 -00000000VacWz~5RC#b^a{vkfhyLPaScq)s9KMExvw34D6J>+NwrBxfixd_%u|$Wt0VPFrzQMU~Cv3(o -CX8r!*SiR9zOp;)>$$b(q=dpw@&Et;000000RI300000001S3vY-Mg^c~p6DWpe-t0Y>fS!w4MxxaL=+ -DqP^k2!wz9AHH68xp8!<%Jqp^&Hw-a000000RI300000000(DfZe??6a{(Ag)zidWvABmX&uCxQ{9vUA -sn@)h(<^=)@3p(i4Fw8icywiMb7^mGa{vkfWOW`wsTH9-LlJ`2|Ay5Z(?oEikl{+~pis;@Q*TJ#0Rw0P -%&6V>N}#h955#ht!=;R2Lj=uo+MI7C_W0!u+yDRo000000RI300000001I?-VQzD2bZKvHa{vkfG*S<) -6P6lYy(#<=BR_>s@(?%#f7ArN-=Rj?7Ns(10d}<;Xp5rzopjE#5h98`u~h0v`BV8NkLOrpFzp4z*Z=?k -000000RI300000000?qrb7gXNWn=>3(cK(6LD#rwNz2*s{WQVl8bg5o8r0R+^o=IRl4@oCf)+{Nc)mXT -m=OBn8@DNvJ^I(u7Ttc@lJ^C)`OzK-Q)6glZDC1d1pxpD002NB018xcVQzD2bZKvH1_}daW_AJEn^6;3 -7FKqUhx?i3R+Mr!fY&(;2BFL(m@EZk_srD_V{dMBa$#e1Nn`<^2rNlD$O59e#ogQsB77jPl+h5j^*S;RbaG*Cb7^#GZ*Ek1aAg5BQV*^ZmKt8YDf|&5KZQ>65I6*X)C9iYp+?yjr7~y;ZDn*}WMOn+ -0rh%KI9Y!AFx9LCk8@hQXE7w+qW3^C%eTEp@#^?_H3w&GZ(?C=a{=BXk!z-`g4hv-$6z_YxoLZ_neUP> -BpbEf7FA*KKfDHMZg6#Ua{;NMdRLRko603iZGCpt_aPM;fT16ofolpo#8?XuHZHx7d=!p7E)2#$3Lc42H~ZewX>a{=9jW&m$tWDykZj`7#3_zANbB(SO{ -shhGe=&H{tM@LxBDo)YaDXjV8yEYGuo~ -Ej#9D^K)f#Cf|Xn@L3mU0Z2&n-dr?jcD1Ll0Ra$Ha$#@6CZbEf#WNc*y0}EqpZ*yf$Wprq7WCCv< -midRhTh1hu7-!n@1Cr{swqbZoGSd8tmgp<3rE@N~5GA>8Wft0d6dj=*oo`t>c$)o5X19O9`rXu=lIsX* -Zg6#UO<`~W6`5yb%eAXO2UPPRaj@((`=>9Tsh)f38uw_!yYu^q5NmF4cWzX2VQzD2bZKvHa{vkfmB{9L -9(7`0)Rt93YLV-HLXe?vTA1;^Q1`ZqBog<<0WvO>-_DBy8`Vb0jGrW9$=2qS -MXvL3HGEG0000000000{{R300000025D|^b#!w8 -3IT`y;$>KfZ0H=mhJ>?uVKfZ0H=mhJ>?uVa{vhe -M(yUq2ps*m=2xUDT;RqCgn#@WzFu~@adfH5^@&-|0000000000{{R300000033g#@X=Gt^Z*l+x0ssVV -Z*FA(00035b8l^B00jX8VsJHoA?4$swuZp1Wc+9AOf`(TIbyKWjTy4WkGaM+ZSSEb;k|42*wg~2q@ -3^Lq|9zft}OB~jx>)hO74peesZgXjLX>V>+d2nR~0RR934pez?WkYXmZE19EWo~o?0{{nSWo~72X>$Mt -0Rb~)Sy27nfgB_8)3w|}PX0nR=3w=3IXvnu`4)OW{2u`dbaG*Cb7^#GZ*Bku0s)^0EJ-@Z0;0Ob-P{Wz -d?2rs)M&&=&l*}G;Jw22Ix+z?QV*^ZmKt8YDf|&5KZQ>65I6*X)C9iYp+?yjr7~y&31xV6Wo~n6Z*Bku -0s)^0EJ-@Z0;0Ob-P{Wzd?2rs)M&&=&l*}G;Jw22Ix+!dbsj>g6`?#s5rWnKhSeO?L~x^!;Y#eFP|P}0 -Z%E!6RC#b^WI=OtX=iS8LTqVnWK(5fY*ctqbaDg)01ISgV{Bn^VRUJBWdH>M00;p&C-dJ*Ygad93@i9p -Cb+uV$agN<27ESr7(9FG*~&Hm0000000030{{R30000012xfI|XK7+=WdH>M00;rv#pxZ$?Eb+fZ@!;9 -xB`-n7hgEflW({{JNKm>5MosT0000000030{{R30000023UhRFbz^jOa%E%y1pxpE0f8RpmkyA>T}tj_ -kdvFcMGT4`fC%jFncQ)?C=$=&Q2+n{000000RR60000000RIYMbaY{3Xl-R~bN~eb00;rryfd-Ec2fx7 -@|$_F!~L1|Gb|wN=sA3IJojZB|09R9osd9G^&mV=@u*B|k@iff^?C=mvC@no9rx00000 -0096000000000SAVQgh?V`*h`1pxpF0Y>fS!w4MxxaL=+DqP^k2!wz9AHH68xp8!<%Jqp^&Hw-a00000 -0RI300000001H-OY-Mg^c~p6DWd#8M00IeCZ)s#xbYXO51pxp602Ek5Xklq?LTqVnWK(5fY*ctqbaDg& -00&}ebYpL6ZU6-V0&gCc`G>t*&Lor=XWH@ulIpd#VR%e3()@~+=qs(Ib4DkqE>N`F8f<{_M@^MEhcVy# -omh=bI-rl&{j_4Y)d2=oOFWB>&L0&gCc`G>t*&Lor=XWH@ulIpd#VR%e3()@~+=qs(Ib4DkqE>N`F -8f<{_M@^MEhcVy#omh=bI-rl&{j_4Y)e~4lXklq?LTqVnWK(5fY*ct@WCQ{L2V!Y-V{d7000jX8ZyuKU -hrL_QB$OCu+VTUE>b16EcuX?V{EC+7E3Kt-u*Pw&hI`xNV4B0;>oUbhHyi-Y#=22)QEgSwgb16EcuX?V{EC+7E3Kt-u*Pw&hI`xNV4B0;>oUbhHyi-Y#=22) -QEgSwgbW>$vYy<)T2V!Y-V{d7000jX8ZyuKUhrL_QB$OCu+VTUE>b16EcuX?V -{EC+7E3Kt-Xc_Cg)w39@m$R6qOEzWQ+NTC@=;b16EcuX?V{EC+7E3Kt-Xc_Cg)w39@m$R6qOEzWQ+NTC@=;?<6X>I@o0Rr`G6JjIwIj2eqliWu}$@z+_xPw?-wb>Rw7=FYk8VaL=Li5Yl(a@n1+Ku60FILp} -Zw|!7cE!MGSxid=WmW+OY-w?IX=DHe0Rr`G6JjIwIj2eqliWu}$@z+_xPw?-wb>Rw7=FYk8VaL=Li5Yl -(a@n1+Ku60FILp}Zw|!7cE!MGSxid=WmW +t*&Lor=XWH@ulIpd#VR%e3()@~+=qs(Ib1t_KCAn^8 +7TS9h9ibhaZ&^Bcn*B*;w|~I;-PD|t>j-IXaCLM|VQ>KznP+6nwW~k}RP!NmuV?G015$>$mV(;bz)!CmQ_M(k?Vd!kfCo{nDM?)_qK{868FUcln*p#G+NdC +Gjy~AR_gVLaFF}DZ0SlZTU?PU0ls0E0RR9100000|Nj60000005L9wuZgXjLX>V>*V`ybM?JbaMa-0f+wLWmt%8=p4R=gtK{LClh6Z#kObxUW*hKHnBv9xdAr8 +G@<&SffJ|QFn~N>u=2wF+7z(Wqt=tdZk`V^s(Ana000000093000000000YNb8~5DZf#|5baMa-0f+wL +Wmt%8=p4R=gtK{LClh6Z#kObxUW*hKHnBv9xdAr8G@<&SffJ|QFn~N>u=2wF+7z(Wqt=tdZk`V^s(Ana +000000093000000000SgVQgh?V`*h`00{v`?dHP>9R0ZFSEMRj;Km4qfBYZ5UUs>0bg9bqiCNA700000 +000300000000009c42I3WMOn~asUJZ00eGtZe;)f009JZZ*64&1pxwLa5aA+<>R2XhQO_4{AcS-HH^7A +VzASV8M4NYxyCka@1Z8)ymjIKNK5;L!8FkfGTe+FK;UUh9M-4n+}vRfRB~Z%b7^#GZ*Ek1aAgGn0006G +RC#b^LvL+uX>@I6Zgd0#00(DfZe??6a{vVa0W)M-Q2pM493%15wcJ8Z{z5k9VD)f0JnAj^7J5MZ9{~z< +a$#@6CZU6-W0iOsgNjk^^qPoT1+zTRnAg`3vXv9d*8d@RXy~6c6G66JF53UoI8eY9A{1GERg--Gi +I0S#x1is&)M%fmnGH3z`Wq5RDZgXjGZU6-W0iOsgNjk^^qPoT1+zTRnAg`3vXv9d*8d@RXy~6c6G67_D +9zv-Vp*%wog4O?q)g04AaHEjnO6;Ie%sNwVNZuM$d2nT9L349yXKr&sY-w&}Q)OXnRCrKyas&hb3uI+u +Y+-U?bZK^F00jX62mv`K^WREqS2tt~EBII@xVqZNcP`ond^UU-JbUWd$~FK100000009600000000039 +W_507X<}?;00jX62m#u~=^e=I{=p`1zMng|0+NmwUpUW`Z@54^_oW>WVpRYD0000000960000000006C +b98cbV{~&L00;qEk8=qnO(R<<%JIK< +1B78x*e6}1oxDzJ3ElvocGBqp0000000030{{R30000303So3~VPj}*Wo~o;1pxpE0aMUzRzj^*Tk1C) +pGbjYG7000000RR600000000~xMY-Mg^X=QT-0RRaBM(yUq2ps*m=2xUD +T;RqCgn#@WzFu~@adfH5^@&-|0000000000{{R30000003szxlWo~16RC#b^1pxp60tr@cX=GD$VRU5$ +0RR916j(!OVQFqcY-w&}Q)OXnRCrKyas&bZ2V!Y-V{d7000jX8ZyuKUhrL_QB$OCu+VTUE>b16EcuX?V +{EC+7E3Kt-MklB)P_)|`Y=H7dO_e!^G2i>0SdC0NppV!6v|_i_0S0Voadl~A00jX8ZyuKUhrL_QB$OCu ++VTUE>b16EcuX?V{EC+7E3Kt-MklB)P_)|`Y=H7dO_e!^G2i>0SdC0NppV!6v|_i_6IerNVQFqcY-w&} +Q)OXnRCsA*1OfmDVrg_^Z)t7-1pxwY9+vrsy<5&Clo)5)@&l6UwYFh+Ofu5^ik9drt)+9Y#&NEOd)wn+ +n#11fGQ~$X901P7x>0daZB@{PThHqO25f0@b!lV(1pxwY9+vrsy<5&Clo)5)@&l6UwYFh+Ofu5^ik9dr +t)+9Y#&NEOd)wn+n#11fGQ~$X901P7x>0daZB@{PThHqdSVL%GX>LL?_X=DTf00&}ebYpL6ZU6-V0`+VYVk7oBr%DNv+($;q`HHK!gIHa) +*%m(-e#9sm3ZsHT^UK%K(4i9Ajp1M~R@C@!4#dQE#lUD;OiKi1RsjZVX>oOFWB>&L0`+VYVk7oBr%DNv ++($;q`HHK!gIHa)*%m(-e#9sm3ZsHT^UK%K(4i9Ajp1M~R@C@!4#dQE#lUD;OiKi1Rs -----END STRICT TYPE LIB----- diff --git a/stl/RGBCommit@0.1.0.stl b/stl/RGBCommit@0.1.0.stl index 6fd344dd..b6024ead 100644 Binary files a/stl/RGBCommit@0.1.0.stl and b/stl/RGBCommit@0.1.0.stl differ diff --git a/stl/RGBCommit@0.1.0.sty b/stl/RGBCommit@0.1.0.sty index 654fc493..6d9bbaae 100644 --- a/stl/RGBCommit@0.1.0.sty +++ b/stl/RGBCommit@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: stl:tjFc6jD7-fe78CxG-WdJlH!l-uXlFfW0-XwG1!qV-MNdtNGE#orbit-airport-voice + Id: stl:4WY0kCd1-qKjYuh5-4GgHKme-XIsmv98-W5Z9$8D-vMjFt!Y#miranda-blue-promise Name: RGBCommit Version: 0.1.0 Description: Consensus commitment layer for RGB smart contracts @@ -211,9 +211,10 @@ data ContractId : [Byte ^ 32] @mnemonic(short-noise-postal) data DataState : [Byte] -@mnemonic(marco-taboo-trade) +@mnemonic(felix-random-mineral) data Extension : ffv Ffv , contractId ContractId + , nonce U8 , extensionType ExtensionType , metadata Metadata , globals GlobalState @@ -308,8 +309,9 @@ data Metadata : {MetaType -> ^ ..0xff MetaValue} @mnemonic(source-olga-mirage) data Occurrences : min U16, max U16 -@mnemonic(film-sting-tourist) +@mnemonic(chamber-provide-veteran) data OpCommitment : ffv Ffv + , nonce U8 , opType TypeCommitment , metadata CommitVerify.StrictHash , globals CommitVerify.MerkleHash @@ -369,9 +371,10 @@ data Schema : ffv Ffv @mnemonic(ramirez-patron-simon) data SchemaId : [Byte ^ 32] -@mnemonic(rainbow-program-george) +@mnemonic(abraham-think-brother) data Transition : ffv Ffv , contractId ContractId + , nonce U8 , transitionType TransitionType , metadata Metadata , globals GlobalState diff --git a/stl/RGBLogic@0.1.0.sta b/stl/RGBLogic@0.1.0.sta index e1aa2f8f..5ede3f96 100644 --- a/stl/RGBLogic@0.1.0.sta +++ b/stl/RGBLogic@0.1.0.sta @@ -1,33 +1,33 @@ -----BEGIN STRICT TYPE LIB----- -Id: stl:pxDxFGo9-MbacU6J-Qug1G$1-6LsuROd-Um1H$hU-T6o2Lgk#lobster-dilemma-famous +Id: stl:Yd7koRpf-hs7nsKX-TOLAnZl-hIfJ9wQ-M8J58hj-n60RcaA#pioneer-gong-smoke Name: RGBLogic Dependencies: BPCore#garbo-radius-peru, - RGBCommit#orbit-airport-voice, + RGBCommit#miranda-blue-promise, Bitcoin#signal-color-cipher -Check-SHA256: 38e2bb37b8957ba19378fef2e5a4ed01a3c40426fb19d763a30ef216e679caae +Check-SHA256: 6fb771ff6ce021d9af5e52a9614976e810066cc05e2177d98c99947c78139aa5 -2vSEvOmAmtV*_s;CP(yEWWwtR~>M;9#?)(c8mUNj% -`dgQjSJu&d2G{D9FxPD~VF^-4LPKwDZE1A%Y!hN5_Bp3Y36tDMM#=e#tGI($UA5U3KNx<*C>ja}LTPkk +2vSEvOmAmtV*_s;CP(yEWW#MKtkSBGhsMxw5cp3r9 +ndQi`YyI(VS@-_~yvW73<_S_qLPKwDZE1A%Y!hN5_Bp3Y36tDMM#=e#tGI($UA5U3KNx<*C>ja}LTPkk Z)t7=20~CnZ*pY?00DdlT>wB!7L}MA7sFvK#<=RP4S#T1Vv-hhTICs&5fM~jaB^jIPH$voP+@X(Ze?;0 wjY>38tto&d&=eG4cRAF#( Wpq+$XJ~Xna$#;`Xh%-ZT+rxDK6vW;JU&?LxLM72H?wDC1Zo}=N}D)4mkLjCa%FT-a&K>D2SRCdV{d70 -2LL6j^|=xh7rLW4)L(lQb*FJl;d*r#UC=Q#deq4+>4pnaV{&P5bV7M_WpgpRuIPk`cg3&=F>*1@lJ+pR +1^^|i^|=xh7rLW4)L(lQb*FJl;d*r#UC=Q#deq4+>4pnaV{&P5bV7M_WpgpRuIPk`cg3&=F>*1@lJ+pR DJ{*3f84s>#k$1lf7uIEVQ@}wWMxQUb7)_z*=^-NPQ?`2v5jYd+6t@dEhY>7H!Y*UdZb-BpG^u(WnpGh -V{&P5bfbbo^UK%K(4i9Ajp1M~R@C@!4#dQE#lUD;OiKi1Rs>XdX=JH|gm+V(X#23g?#G%T#8*SXRQUS6 -KbYXtkv-?PH+Tw3ZggdGZeeUtYqm29sTjYuk_~RiSfnI}BSuD1OfpDbYXCEWpn@q0RnFxmidRhTh1hu7-!n@1Cr{swqbZoGSd8tmgp<3rE?FqADBNH -?W>M^%H|xc>sh|Dn*!v8^Ea7rh?dzC2n+%RZ*X#DbN~eb0&gCc`G>t*&Lor=XWH@ulIpd#VR%e3()@~+ -=qs(Ib4O0kT+rxDK6vW;JU&?LxLM72H?wDC1Zo}=N}D)4mkCE~Z(?C=PjX}i0}6L(bZ%vHb5C+)00aU6 -1a5C`WdHyG0R(ezZDjxj0RbS#6~TkBW;)?;_iNzlVjb`6LkuT|LD`7wVC;Z8*z*K$aA{-$wlQ4lF#CP( -{0kA5beTu`TbGnq*3opF0S9kxLug@XZU6-V0fsUI!xLW{GR%jCD1BroNw?EK<*_GIjHR2Pc=tCz -wgL!mW@bZZVQFpv1_20ga%pdJX>@r200ae8cu;S11_B0XWoc(~Wpi|4ZEyepND5bJbZ%vH -b5C+)1_B0fbz)C)WC7fFvAaSw-)KgQ7fAeMNYpypNfHyE8AFfUoC2TNw^j*vX>@L7b8|^#0=6++>M;9# -?)(c8mUNj%`dgQjSJu&d2G{D9FxPD~VTo&4CC$c=UszhlV5m?Ru@{iVU*wrVdeH+Q@FPbX@c +V{&P5bg6}ecT=8d`>?<6$C@F;S3|*6`1-v+nBdcqJ?FPKcnV2wbY*gGVQf%qwlfK-7{9iX4Q|L-q$GzU +Mp|h#f#{Gz8SzLEaTf~c{WkYggkPIjuQHS#3Ua|L6d7%qre2Ut&TYD1OfpD +bYXCEWpn@q0RnFxmidRhTh1hu7-!n@1Cr{swqbZoGSd8tmgp<3rE?FqADBNH?W>M^%H|xc>sh|Dn*!v8 +^Ea7rh?dzC2n+%RZ*X#DbN~eb0&gCc`G>t*&Lor=XWH@ulIpd#VR%e3()@~+=qs(Ib4O0kT+rxDK6vW; +JU&?LxLM72H?wDC1Zo}=N}D)4mkCE~Z(?C=PjX}i0tIhyPjX}d5^!ZWmqRVqY#fz=E*t7rNLnhuswFP- +RvR}GCb>Ok18HP<00067PjF9iWCQ~M2WMq&WpinB0000131xV6Wo~n6Z*Bkv0|$3$bZ%vHa{=}XBbpbB +LK#dEwxUFHJ){RjtgLvL_X>0rou@Knb#Vo5Z*F5{00035Z*Xa30^w#fkSBGhsMxw5cp3r9ndQi`YyI(V +S@-_~yvW73=9S3idLDIRU(}XWLTZugenOC;Z(5k~zEJnJiX;;E#s3O)a$#@6CZU6=Y2X|?7Ze??G +0rm?cniq>g8B7tjqC|5&qz6Q-tazCB3Um~mr!r)9aRqK~ZewKt009JVaA{-$;bt_DCv~W(*t#Bg8Uf0g +<;b#Y{qb&D_x}UD$i=tjmB{9L9(7`0)Rt93YLV-HLXe?vTA1;^Q1`ZqBog<<3Rh`#Ze??GPjX}g0{{qN +a${&|c4cG$00036ZE0?0WB>&L0S>>o?Kom?q=ULN^A!11b?H{wM>P}NCm0qyW47Umu>uKnWo~p~bZK^F +0000AS7~%^Wpi^-Z*v9$254nzXJ~W)00aqiX>Db5bYX39002k -----END STRICT TYPE LIB----- diff --git a/stl/RGBLogic@0.1.0.stl b/stl/RGBLogic@0.1.0.stl index cb3f0d69..cd7d9362 100644 Binary files a/stl/RGBLogic@0.1.0.stl and b/stl/RGBLogic@0.1.0.stl differ diff --git a/stl/RGBLogic@0.1.0.sty b/stl/RGBLogic@0.1.0.sty index 3c1065df..5a827c31 100644 --- a/stl/RGBLogic@0.1.0.sty +++ b/stl/RGBLogic@0.1.0.sty @@ -1,5 +1,5 @@ {- - Id: stl:pxDxFGo9-MbacU6J-Qug1G$1-6LsuROd-Um1H$hU-T6o2Lgk#lobster-dilemma-famous + Id: stl:Yd7koRpf-hs7nsKX-TOLAnZl-hIfJ9wQ-M8J58hj-n60RcaA#pioneer-gong-smoke Name: RGBLogic Version: 0.1.0 Description: Consensus logic layer for RGB smart contracts @@ -18,15 +18,13 @@ import BPCore#garbo-radius-peru use TapretRightBranch#miracle-patriot-touch use OpretProof#good-village-flex -import RGBCommit#orbit-airport-voice - use XChainTxid#liquid-river-absorb +import RGBCommit#miranda-blue-promise use OpId#picnic-single-gloria import Bitcoin#signal-color-cipher use ScriptBytes#equator-cockpit-gong use TapNodeHash#paprika-amanda-hunter use LeafScript#bison-doctor-oscar - use Txid#shallow-light-reverse use InternalPk#habitat-paprika-oliver use LeafVer#benefit-carbon-africa use XOnlyPk#clever-swim-carpet @@ -36,20 +34,24 @@ import Bitcoin#signal-color-cipher data DbcProof : tapret#1 BPCore.TapretProof | opret BPCore.OpretProof -@mnemonic(fiber-lucas-harmony) -data GlobalOrd : witnessOrd WitnessOrd? - , opid RGBCommit.OpId - , idx U16 - -@mnemonic(degree-journal-mayor) -data TxOrd : archived () - | onChain TxPos - | offChain priority U32 - -@mnemonic(john-invest-weekend) -data TxPos : height U32, timestamp I64 - -@mnemonic(fish-yoyo-logic) -data WitnessOrd : pubOrd TxOrd, witnessId RGBCommit.XChainTxid +@mnemonic(east-sunset-extra) +data GlobalOrd : opOrd OpOrd, idx U16 + +@mnemonic(heroic-right-pepper) +data OpOrd : genesis () + | extension (witness WitnessOrd + , nonce U8 + , opid RGBCommit.OpId) + | transition#255 (witness WitnessOrd + , nonce U8 + , opid RGBCommit.OpId) + +@mnemonic(orange-john-cyclone) +data WitnessOrd : archived () + | mined WitnessPos + | tentative () + +@mnemonic(snow-local-tonight) +data WitnessPos : height U32, timestamp I64 diff --git a/stl/Transition.vesper b/stl/Transition.vesper index ce5df9f0..600ed68b 100644 --- a/stl/Transition.vesper +++ b/stl/Transition.vesper @@ -12,6 +12,7 @@ OpId commitment hasher=SHA256 tagged=urn:lnp-bp:rgb:operation#2024-02-03 OpCommitment rec ffv is U16 aka=Ffv + nonce is U8 opType union TypeCommitment genesis rec BaseCommitment wrapped tag=0 flags bytes len=1 aka=ReservedBytes1 @@ -39,6 +40,7 @@ OpCommitment rec Transition rec ffv is U16 aka=Ffv contractId bytes len=32 aka=ContractId + nonce is U8 transitionType is U16 aka=TransitionType metadata map len=0..MAX8 aka=Metadata key is U16 aka=MetaType