diff --git a/applications/tari_app_grpc/src/conversions/sidechain_features.rs b/applications/tari_app_grpc/src/conversions/sidechain_features.rs index c8329ec1cf..2384c834b5 100644 --- a/applications/tari_app_grpc/src/conversions/sidechain_features.rs +++ b/applications/tari_app_grpc/src/conversions/sidechain_features.rs @@ -25,29 +25,35 @@ use std::{ convert::{TryFrom, TryInto}, }; -use tari_common_types::types::{FixedHash, PublicKey}; -use tari_core::transactions::transaction_components::{ - bytes_into_fixed_string, - CheckpointParameters, - CommitteeMembers, - CommitteeSignatures, - ConstitutionChangeFlags, - ConstitutionChangeRules, - ContractAcceptance, - ContractAcceptanceRequirements, - ContractAmendment, - ContractCheckpoint, - ContractConstitution, - ContractDefinition, - ContractSpecification, - ContractUpdateProposal, - ContractUpdateProposalAcceptance, - FunctionRef, - PublicFunction, - RequirementsForConstitutionChange, - SideChainConsensus, - SideChainFeatures, - SignerSignature, +use tari_common_types::types::{FixedHash, PublicKey, Signature}; +use tari_core::{ + consensus::MaxSizeString, + transactions::transaction_components::{ + bytes_into_fixed_string, + BuildInfo, + CheckpointParameters, + CodeTemplateRegistration, + CommitteeMembers, + CommitteeSignatures, + ConstitutionChangeFlags, + ConstitutionChangeRules, + ContractAcceptance, + ContractAcceptanceRequirements, + ContractAmendment, + ContractCheckpoint, + ContractConstitution, + ContractDefinition, + ContractSpecification, + ContractUpdateProposal, + ContractUpdateProposalAcceptance, + FunctionRef, + PublicFunction, + RequirementsForConstitutionChange, + SideChainConsensus, + SideChainFeatures, + SignerSignature, + TemplateType, + }, }; use tari_utilities::ByteArray; @@ -64,6 +70,7 @@ impl From for grpc::SideChainFeatures { update_proposal_acceptance: value.update_proposal_acceptance.map(Into::into), amendment: value.amendment.map(Into::into), checkpoint: value.checkpoint.map(Into::into), + template_registration: value.template_registration.map(Into::into), } } } @@ -85,6 +92,10 @@ impl TryFrom for SideChainFeatures { .transpose()?; let amendment = features.amendment.map(ContractAmendment::try_from).transpose()?; let checkpoint = features.checkpoint.map(ContractCheckpoint::try_from).transpose()?; + let template_registration = features + .template_registration + .map(CodeTemplateRegistration::try_from) + .transpose()?; Ok(Self { contract_id: features.contract_id.try_into().map_err(|_| "Invalid contract_id")?, @@ -93,6 +104,7 @@ impl TryFrom for SideChainFeatures { acceptance, update_proposal, update_proposal_acceptance, + template_registration, amendment, checkpoint, }) @@ -140,10 +152,104 @@ impl TryFrom for SideChainFeatures { update_proposal_acceptance: None, amendment: None, checkpoint: None, + template_registration: None, + }) + } +} + +// -------------------------------- TemplateRegistration -------------------------------- // +impl TryFrom for CodeTemplateRegistration { + type Error = String; + + fn try_from(value: grpc::TemplateRegistration) -> Result { + Ok(Self { + author_public_key: PublicKey::from_bytes(&value.author_public_key).map_err(|e| e.to_string())?, + author_signature: value + .author_signature + .map(Signature::try_from) + .ok_or("author_signature not provided")??, + template_name: MaxSizeString::try_from(value.template_name).map_err(|e| e.to_string())?, + template_version: value + .template_version + .try_into() + .map_err(|_| "Invalid template version")?, + template_type: value + .template_type + .map(TryFrom::try_from) + .ok_or("Template type not provided")??, + build_info: value + .build_info + .map(TryFrom::try_from) + .ok_or("Build info not provided")??, + binary_sha: value.binary_sha.try_into().map_err(|_| "Invalid commit sha")?, + binary_url: MaxSizeString::try_from(value.binary_url).map_err(|e| e.to_string())?, + }) + } +} + +impl From for grpc::TemplateRegistration { + fn from(value: CodeTemplateRegistration) -> Self { + Self { + author_public_key: value.author_public_key.to_vec(), + author_signature: Some(value.author_signature.into()), + template_name: value.template_name.to_string(), + template_version: u32::from(value.template_version), + template_type: Some(value.template_type.into()), + build_info: Some(value.build_info.into()), + binary_sha: value.binary_sha.to_vec(), + binary_url: value.binary_url.to_string(), + } + } +} + +// -------------------------------- TemplateType -------------------------------- // +impl TryFrom for TemplateType { + type Error = String; + + fn try_from(value: grpc::TemplateType) -> Result { + let template_type = value.template_type.ok_or("Template type not provided")?; + match template_type { + grpc::template_type::TemplateType::Wasm(wasm) => Ok(TemplateType::Wasm { + abi_version: wasm.abi_version.try_into().map_err(|_| "abi_version overflowed")?, + }), + } + } +} + +impl From for grpc::TemplateType { + fn from(value: TemplateType) -> Self { + match value { + TemplateType::Wasm { abi_version } => Self { + template_type: Some(grpc::template_type::TemplateType::Wasm(grpc::WasmInfo { + abi_version: abi_version.into(), + })), + }, + } + } +} + +// -------------------------------- BuildInfo -------------------------------- // + +impl TryFrom for BuildInfo { + type Error = String; + + fn try_from(value: grpc::BuildInfo) -> Result { + Ok(Self { + repo_url: value.repo_url.try_into().map_err(|_| "Invalid repo url")?, + commit_hash: value.commit_hash.try_into().map_err(|_| "Invalid commit hash")?, }) } } +impl From for grpc::BuildInfo { + fn from(value: BuildInfo) -> Self { + Self { + repo_url: value.repo_url.into_string(), + commit_hash: value.commit_hash.into_vec(), + } + } +} + //---------------------------------- ContractDefinition --------------------------------------------// impl TryFrom for ContractDefinition { diff --git a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs index 6a6b29be7a..f597e94f91 100644 --- a/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs +++ b/applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs @@ -54,6 +54,8 @@ use tari_app_grpc::{ CreateFollowOnAssetCheckpointResponse, CreateInitialAssetCheckpointRequest, CreateInitialAssetCheckpointResponse, + CreateTemplateRegistrationRequest, + CreateTemplateRegistrationResponse, FileDeletedResponse, GetBalanceRequest, GetBalanceResponse, @@ -105,7 +107,7 @@ use tari_common_types::{ use tari_comms::{multiaddr::Multiaddr, types::CommsPublicKey, CommsNode}; use tari_core::transactions::{ tari_amount::MicroTari, - transaction_components::{OutputFeatures, SideChainFeatures, UnblindedOutput}, + transaction_components::{CodeTemplateRegistration, OutputFeatures, SideChainFeatures, UnblindedOutput}, }; use tari_utilities::{hex::Hex, ByteArray, Hashable}; use tari_wallet::{ @@ -1272,6 +1274,41 @@ impl wallet_server::Wallet for WalletGrpcServer { Ok(Response::new(FileDeletedResponse {})) } + + async fn create_template_registration( + &self, + request: Request, + ) -> Result, Status> { + let mut asset_manager = self.wallet.asset_manager.clone(); + let mut transaction_service = self.wallet.transaction_service.clone(); + let message = request.into_inner(); + + let template_registration = CodeTemplateRegistration::try_from( + message + .template_registration + .ok_or_else(|| Status::invalid_argument("template_registration is empty"))?, + ) + .map_err(|e| Status::invalid_argument(format!("template_registration is invalid: {}", e)))?; + + let message = format!("Template registration {}", template_registration.template_name); + + let (tx_id, transaction) = asset_manager + .create_code_template_registration(template_registration) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + debug!( + target: LOG_TARGET, + "Template registration transaction: {:?}", transaction + ); + + let _ = transaction_service + .submit_transaction(tx_id, transaction, 0.into(), message) + .await + .map_err(|e| Status::internal(e.to_string()))?; + + Ok(Response::new(CreateTemplateRegistrationResponse {})) + } } async fn handle_completed_tx( diff --git a/base_layer/core/src/chain_storage/tests/blockchain_database.rs b/base_layer/core/src/chain_storage/tests/blockchain_database.rs index d54c598ff2..f6e31e72b4 100644 --- a/base_layer/core/src/chain_storage/tests/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/tests/blockchain_database.rs @@ -51,7 +51,6 @@ use crate::{ }, txn_schema, }; - fn setup() -> BlockchainDatabase { create_new_blockchain() } diff --git a/base_layer/core/src/consensus/consensus_encoding.rs b/base_layer/core/src/consensus/consensus_encoding.rs index 98b053f747..9d84bf3a5a 100644 --- a/base_layer/core/src/consensus/consensus_encoding.rs +++ b/base_layer/core/src/consensus/consensus_encoding.rs @@ -30,10 +30,13 @@ mod hashing; mod integers; mod micro_tari; mod script; +mod string; mod vec; + use std::io; pub use hashing::{ConsensusHasher, DomainSeparatedConsensusHasher}; +pub use string::MaxSizeString; pub use vec::MaxSizeVec; pub use self::bytes::MaxSizeBytes; @@ -76,6 +79,12 @@ impl ToConsensusBytes fo } } +pub fn read_byte(reader: &mut R) -> Result { + let mut buf = [0u8; 1]; + reader.read_exact(&mut buf)?; + Ok(buf[0]) +} + #[cfg(test)] pub mod test { use super::*; diff --git a/base_layer/core/src/consensus/consensus_encoding/bytes.rs b/base_layer/core/src/consensus/consensus_encoding/bytes.rs index 6e29da6753..a3a4385fe5 100644 --- a/base_layer/core/src/consensus/consensus_encoding/bytes.rs +++ b/base_layer/core/src/consensus/consensus_encoding/bytes.rs @@ -21,6 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use std::{ + cmp, convert::TryFrom, io, io::{Error, Read, Write}, @@ -28,6 +29,7 @@ use std::{ }; use integer_encoding::{VarInt, VarIntReader, VarIntWriter}; +use serde::{Deserialize, Serialize}; use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; @@ -47,10 +49,34 @@ impl ConsensusEncodingSized for Vec { } } +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Default, Deserialize, Serialize)] pub struct MaxSizeBytes { inner: Vec, } +impl MaxSizeBytes { + pub fn into_vec(self) -> Vec { + self.inner + } + + pub fn from_bytes_checked>(bytes: T) -> Option { + let b = bytes.as_ref(); + if b.len() > MAX { + None + } else { + Some(Self { inner: b.to_vec() }) + } + } + + pub fn from_bytes_truncate>(bytes: T) -> Self { + let b = bytes.as_ref(); + let len = cmp::min(b.len(), MAX); + Self { + inner: b[..len].to_vec(), + } + } +} + impl From> for Vec { fn from(value: MaxSizeBytes) -> Self { value.inner @@ -68,6 +94,18 @@ impl TryFrom> for MaxSizeBytes { } } +impl ConsensusEncoding for MaxSizeBytes { + fn consensus_encode(&self, writer: &mut W) -> Result<(), io::Error> { + self.inner.consensus_encode(writer) + } +} + +impl ConsensusEncodingSized for MaxSizeBytes { + fn consensus_encode_exact_size(&self) -> usize { + self.inner.consensus_encode_exact_size() + } +} + impl ConsensusDecoding for MaxSizeBytes { fn consensus_decode(reader: &mut R) -> Result { let len = reader.read_varint()?; @@ -108,7 +146,8 @@ impl ConsensusEncoding for &[u8] { impl ConsensusEncodingSized for &[u8] { fn consensus_encode_exact_size(&self) -> usize { - self.len() + let len = self.len(); + len.required_space() + len } } @@ -139,7 +178,7 @@ mod test { use rand::{rngs::OsRng, RngCore}; use super::*; - use crate::consensus::{check_consensus_encoding_correctness, ToConsensusBytes}; + use crate::consensus::check_consensus_encoding_correctness; #[test] fn it_encodes_and_decodes_correctly() { @@ -147,9 +186,18 @@ mod test { OsRng.fill_bytes(&mut subject); check_consensus_encoding_correctness(subject).unwrap(); + // &[u8] consensus encoding + let mut buf = Vec::new(); + let slice = subject.as_slice(); + slice.consensus_encode(&mut buf).unwrap(); + assert_eq!(buf.len(), slice.consensus_encode_exact_size()); + let mut reader = buf.as_slice(); + let decoded: MaxSizeBytes<1024> = ConsensusDecoding::consensus_decode(&mut reader).unwrap(); + assert_eq!(&*decoded, slice); + assert!(reader.is_empty()); + // Get vec encoding with length byte - let encoded = subject.to_vec().to_consensus_bytes(); - let decoded = MaxSizeBytes::<1024>::consensus_decode(&mut encoded.as_slice()).unwrap(); - assert_eq!(*decoded, *subject.as_slice()); + let subject = MaxSizeBytes::<1024>::from_bytes_checked(&subject).unwrap(); + check_consensus_encoding_correctness(subject).unwrap(); } } diff --git a/base_layer/core/src/consensus/consensus_encoding/string.rs b/base_layer/core/src/consensus/consensus_encoding/string.rs new file mode 100644 index 0000000000..41bb97cc1a --- /dev/null +++ b/base_layer/core/src/consensus/consensus_encoding/string.rs @@ -0,0 +1,215 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::{ + convert::TryFrom, + fmt::Display, + io, + io::{Read, Write}, +}; + +use serde::{Deserialize, Serialize}; + +use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, MaxSizeBytes}; + +/// A string that can only be a up to MAX length long +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct MaxSizeString { + string: String, +} + +impl MaxSizeString { + pub fn from_str_checked(s: &str) -> Option { + if s.len() > MAX { + return None; + } + Some(Self { string: s.to_string() }) + } + + pub fn from_utf8_bytes_checked>(bytes: T) -> Option { + let b = bytes.as_ref(); + if b.len() > MAX { + return None; + } + + let s = String::from_utf8(b.to_vec()).ok()?; + Some(Self { string: s }) + } + + pub fn len(&self) -> usize { + self.string.len() + } + + pub fn is_empty(&self) -> bool { + self.string.is_empty() + } + + pub fn as_str(&self) -> &str { + &self.string + } + + pub fn into_string(self) -> String { + self.string + } +} + +impl TryFrom for MaxSizeString { + type Error = MaxSizeStringLengthError; + + fn try_from(value: String) -> Result { + if value.len() > MAX { + return Err(MaxSizeStringLengthError { + actual: value.len(), + expected: MAX, + }); + } + Ok(Self { string: value }) + } +} + +impl TryFrom<&str> for MaxSizeString { + type Error = MaxSizeStringLengthError; + + fn try_from(value: &str) -> Result { + if value.len() > MAX { + return Err(MaxSizeStringLengthError { + actual: value.len(), + expected: MAX, + }); + } + Ok(Self { + string: value.to_string(), + }) + } +} + +impl AsRef<[u8]> for MaxSizeString { + fn as_ref(&self) -> &[u8] { + self.string.as_ref() + } +} + +impl Display for MaxSizeString { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.string) + } +} + +impl ConsensusEncoding for MaxSizeString { + fn consensus_encode(&self, writer: &mut W) -> Result<(), io::Error> { + self.string.as_bytes().consensus_encode(writer) + } +} + +impl ConsensusEncodingSized for MaxSizeString { + fn consensus_encode_exact_size(&self) -> usize { + self.string.as_bytes().consensus_encode_exact_size() + } +} + +impl ConsensusDecoding for MaxSizeString { + fn consensus_decode(reader: &mut R) -> Result { + let raw_bytes = MaxSizeBytes::::consensus_decode(reader)?; + let s = String::from_utf8(raw_bytes.into_vec()) + .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Invalid UTF-8"))?; + Ok(Self { string: s }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("Invalid String length: expected {expected}, got {actual}")] +pub struct MaxSizeStringLengthError { + expected: usize, + actual: usize, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::consensus::check_consensus_encoding_correctness; + + mod from_str_checked { + use super::*; + + #[test] + fn it_returns_none_if_size_exceeded() { + let s = MaxSizeString::<10>::from_str_checked("12345678901234567890"); + assert_eq!(s, None); + } + + #[test] + fn it_returns_some_if_size_in_bounds() { + let s = MaxSizeString::<0>::from_str_checked("").unwrap(); + assert_eq!(s.as_str(), ""); + assert_eq!(s.len(), 0); + + let s = MaxSizeString::<10>::from_str_checked("1234567890").unwrap(); + assert_eq!(s.as_str(), "1234567890"); + assert_eq!(s.len(), 10); + + let s = MaxSizeString::<10>::from_str_checked("1234").unwrap(); + assert_eq!(s.as_str(), "1234"); + assert_eq!(s.len(), 4); + + let s = MaxSizeString::<8>::from_str_checked("🚀🚀").unwrap(); + assert_eq!(s.as_str(), "🚀🚀"); + // 8 here because an emoji char take 4 bytes each + assert_eq!(s.len(), 8); + } + } + + mod from_utf8_bytes_checked { + use super::*; + + #[test] + fn it_returns_none_if_size_exceeded() { + let s = MaxSizeString::<10>::from_utf8_bytes_checked(&[0u8; 11]); + assert_eq!(s, None); + } + + #[test] + fn it_returns_some_if_size_in_bounds() { + let s = MaxSizeString::<12>::from_utf8_bytes_checked("💡🧭🛖".as_bytes()).unwrap(); + assert_eq!(s.as_str(), "💡🧭🛖"); + assert_eq!(s.len(), 12); + } + + #[test] + fn it_returns_none_if_invalid_utf8() { + let s = MaxSizeString::<10>::from_utf8_bytes_checked(&[255u8; 10]); + assert_eq!(s, None); + } + } + + mod consensus_encoding { + use super::*; + + #[test] + fn it_encodes_and_decodes_correctly() { + let s = MaxSizeString::<16>::from_utf8_bytes_checked("💡🧭🛖".as_bytes()).unwrap(); + check_consensus_encoding_correctness(s).unwrap(); + + let s = MaxSizeString::<0>::from_str_checked("").unwrap(); + check_consensus_encoding_correctness(s).unwrap(); + } + } +} diff --git a/base_layer/core/src/consensus/mod.rs b/base_layer/core/src/consensus/mod.rs index 806ac13211..7f3a17aaac 100644 --- a/base_layer/core/src/consensus/mod.rs +++ b/base_layer/core/src/consensus/mod.rs @@ -33,12 +33,14 @@ mod consensus_encoding; #[cfg(test)] pub(crate) use consensus_encoding::test::check_consensus_encoding_correctness; pub use consensus_encoding::{ + read_byte, ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized, ConsensusHasher, DomainSeparatedConsensusHasher, MaxSizeBytes, + MaxSizeString, MaxSizeVec, ToConsensusBytes, }; diff --git a/base_layer/core/src/proto/mod.rs b/base_layer/core/src/proto/mod.rs index 41209a82b2..9294f73535 100644 --- a/base_layer/core/src/proto/mod.rs +++ b/base_layer/core/src/proto/mod.rs @@ -52,4 +52,6 @@ mod block; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] mod block_header; #[cfg(any(feature = "base_node", feature = "base_node_proto"))] +mod sidechain_features; +#[cfg(any(feature = "base_node", feature = "base_node_proto"))] mod utils; diff --git a/base_layer/core/src/proto/sidechain_features.proto b/base_layer/core/src/proto/sidechain_features.proto new file mode 100644 index 0000000000..801b6e0d3a --- /dev/null +++ b/base_layer/core/src/proto/sidechain_features.proto @@ -0,0 +1,175 @@ +// Copyright 2022 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +syntax = "proto3"; + +import "types.proto"; + +package tari.types; + +message SideChainFeatures { + bytes contract_id = 1; + ContractDefinition definition = 2; + TemplateRegistration template_registration = 3; + ContractConstitution constitution = 4; + ContractAcceptance acceptance = 5; + ContractUpdateProposal update_proposal = 6; + ContractUpdateProposalAcceptance update_proposal_acceptance = 7; + ContractAmendment amendment = 8; + ContractCheckpoint checkpoint = 9; +} + +message TemplateRegistration { + bytes author_public_key = 1; + Signature author_signature = 2; + string template_name = 3; + uint32 template_version = 4; + TemplateType template_type = 5; + BuildInfo build_info = 6; + bytes binary_sha = 7; + string binary_url = 8; +} + +message TemplateType { + oneof template_type { + WasmInfo wasm = 1; + } +} + message WasmInfo { + uint32 abi_version = 1; + } + + message BuildInfo { + string repo_url = 1; + bytes commit_hash = 2; +} + +message ContractCheckpoint { + uint64 checkpoint_number = 1; + bytes merkle_root = 2; + CommitteeSignatures signatures = 3; +} + +message ContractConstitution { + CommitteeMembers validator_committee = 1; + ContractAcceptanceRequirements acceptance_requirements = 2; + SideChainConsensus consensus = 3; + CheckpointParameters checkpoint_params = 4; + ConstitutionChangeRules constitution_change_rules = 5; +} + +message ContractAcceptanceRequirements { + uint64 acceptance_period_expiry = 1; + uint32 minimum_quorum_required = 2; +} + +message CommitteeMembers { + repeated bytes members = 1; +} + +message CheckpointParameters { + uint64 abandoned_interval = 1; + uint32 minimum_quorum_required = 2; + uint64 quarantine_interval = 3; +} + +message ConstitutionChangeRules { + uint32 change_flags = 1; + RequirementsForConstitutionChange requirements_for_constitution_change = 2; +} + +message RequirementsForConstitutionChange { + // The minimum required constitution committee signatures required for a constitution change proposal to pass. + uint32 minimum_constitution_committee_signatures = 1; + // An allowlist of keys that are able to accept and ratify the initial constitution and its amendments. If this is + // None, the constitution cannot be amended. + CommitteeMembers constitution_committee = 2; + // An allowlist of keys that can be used in case of checkpoint abandonment. These keys can quarantine the the constitution + // and if the quarantine period is exceeded, the backup keys can take over. + CommitteeMembers backup_keys = 3; +} + +enum SideChainConsensus { + UNSPECIFIED = 0; + BFT = 1; + PROOF_OF_WORK = 2; + MERKLE_ROOT = 3; +} + +// TODO: deprecated +message AssetOutputFeatures { + bytes public_key = 1; + repeated uint32 template_ids_implemented = 2; + repeated TemplateParameter template_parameters = 3; +} + +message TemplateParameter { + uint32 template_id = 1; + uint32 template_data_version = 2; + bytes template_data = 3; +} + +message MintNonFungibleFeatures { + bytes asset_public_key = 1; + Commitment asset_owner_commitment = 2; +} + +message SideChainCheckpointFeatures { + bytes merkle_root = 1; + repeated bytes committee = 2; +} + +message CommitteeDefinitionFeatures { + repeated bytes committee = 1; + uint64 effective_sidechain_height = 2; +} + +message ContractDefinition { + bytes contract_name = 1; + bytes contract_issuer = 2; + ContractSpecification contract_spec = 3; +} + +message ContractSpecification { + bytes runtime = 1; + repeated PublicFunction public_functions = 2; +} + +message PublicFunction { + bytes name = 1; + FunctionRef function = 2; +} + +message FunctionRef { + bytes template_id = 1; + uint32 function_id = 2; +} + +message ContractAcceptance { + bytes validator_node_public_key = 1; + Signature signature = 2; +} + +message ContractUpdateProposal { + uint64 proposal_id = 1; + Signature signature = 2; + ContractConstitution updated_constitution = 3; +} + +message ContractUpdateProposalAcceptance { + uint64 proposal_id = 1; + bytes validator_node_public_key = 2; + Signature signature = 3; +} + +message ContractAmendment { + uint64 proposal_id = 1; + CommitteeMembers validator_committee = 2; + CommitteeSignatures validator_signatures = 3; + ContractConstitution updated_constitution = 4; + uint64 activation_window = 5; +} + +message CommitteeSignatures { + repeated SignerSignature signatures = 1; +} diff --git a/base_layer/core/src/proto/sidechain_features.rs b/base_layer/core/src/proto/sidechain_features.rs new file mode 100644 index 0000000000..1473e716d6 --- /dev/null +++ b/base_layer/core/src/proto/sidechain_features.rs @@ -0,0 +1,863 @@ +// Copyright 2019, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Impls for sidechain_features proto + +use std::convert::{TryFrom, TryInto}; + +use tari_common_types::types::{Commitment, FixedHash, PublicKey, Signature}; +use tari_crypto::tari_utilities::ByteArray; + +use crate::{ + consensus::MaxSizeString, + proto, + transactions::transaction_components::{ + bytes_into_fixed_string, + AssetOutputFeatures, + BuildInfo, + CheckpointParameters, + CodeTemplateRegistration, + CommitteeDefinitionFeatures, + CommitteeMembers, + CommitteeSignatures, + ConstitutionChangeFlags, + ConstitutionChangeRules, + ContractAcceptance, + ContractAcceptanceRequirements, + ContractAmendment, + ContractCheckpoint, + ContractConstitution, + ContractDefinition, + ContractSpecification, + ContractUpdateProposal, + ContractUpdateProposalAcceptance, + FunctionRef, + MintNonFungibleFeatures, + PublicFunction, + RequirementsForConstitutionChange, + SideChainCheckpointFeatures, + SideChainConsensus, + SideChainFeatures, + SignerSignature, + TemplateParameter, + TemplateType, + }, +}; + +//---------------------------------- SideChainFeatures --------------------------------------------// +impl From for proto::types::SideChainFeatures { + fn from(value: SideChainFeatures) -> Self { + Self { + contract_id: value.contract_id.to_vec(), + definition: value.definition.map(Into::into), + template_registration: value.template_registration.map(Into::into), + constitution: value.constitution.map(Into::into), + acceptance: value.acceptance.map(Into::into), + update_proposal: value.update_proposal.map(Into::into), + update_proposal_acceptance: value.update_proposal_acceptance.map(Into::into), + amendment: value.amendment.map(Into::into), + checkpoint: value.checkpoint.map(Into::into), + } + } +} + +impl TryFrom for SideChainFeatures { + type Error = String; + + fn try_from(features: proto::types::SideChainFeatures) -> Result { + let contract_id = features.contract_id.try_into().map_err(|_| "Invalid contract_id")?; + let definition = features.definition.map(ContractDefinition::try_from).transpose()?; + let constitution = features.constitution.map(ContractConstitution::try_from).transpose()?; + let acceptance = features.acceptance.map(ContractAcceptance::try_from).transpose()?; + let template_registration = features + .template_registration + .map(CodeTemplateRegistration::try_from) + .transpose()?; + let update_proposal = features + .update_proposal + .map(ContractUpdateProposal::try_from) + .transpose()?; + let update_proposal_acceptance = features + .update_proposal_acceptance + .map(ContractUpdateProposalAcceptance::try_from) + .transpose()?; + let amendment = features.amendment.map(ContractAmendment::try_from).transpose()?; + let checkpoint = features.checkpoint.map(ContractCheckpoint::try_from).transpose()?; + + Ok(Self { + contract_id, + definition, + template_registration, + constitution, + acceptance, + update_proposal, + update_proposal_acceptance, + amendment, + checkpoint, + }) + } +} + +// -------------------------------- TemplateRegistration -------------------------------- // +impl TryFrom for CodeTemplateRegistration { + type Error = String; + + fn try_from(value: proto::types::TemplateRegistration) -> Result { + Ok(Self { + author_public_key: PublicKey::from_bytes(&value.author_public_key).map_err(|e| e.to_string())?, + author_signature: value + .author_signature + .map(Signature::try_from) + .ok_or("author_signature not provided")??, + template_name: MaxSizeString::try_from(value.template_name).map_err(|e| e.to_string())?, + template_version: value + .template_version + .try_into() + .map_err(|_| "Invalid template version")?, + template_type: value + .template_type + .map(TryFrom::try_from) + .ok_or("Template type not provided")??, + build_info: value + .build_info + .map(TryFrom::try_from) + .ok_or("Build info not provided")??, + binary_sha: value.binary_sha.try_into().map_err(|_| "Invalid commit sha")?, + binary_url: MaxSizeString::try_from(value.binary_url).map_err(|e| e.to_string())?, + }) + } +} + +impl From for proto::types::TemplateRegistration { + fn from(value: CodeTemplateRegistration) -> Self { + Self { + author_public_key: value.author_public_key.to_vec(), + author_signature: Some(value.author_signature.into()), + template_name: value.template_name.to_string(), + template_version: u32::from(value.template_version), + template_type: Some(value.template_type.into()), + build_info: Some(value.build_info.into()), + binary_sha: value.binary_sha.to_vec(), + binary_url: value.binary_url.to_string(), + } + } +} + +// -------------------------------- TemplateType -------------------------------- // +impl TryFrom for TemplateType { + type Error = String; + + fn try_from(value: proto::types::TemplateType) -> Result { + let template_type = value.template_type.ok_or("Template type not provided")?; + match template_type { + proto::types::template_type::TemplateType::Wasm(wasm) => Ok(TemplateType::Wasm { + abi_version: wasm.abi_version.try_into().map_err(|_| "abi_version overflowed")?, + }), + } + } +} + +impl From for proto::types::TemplateType { + fn from(value: TemplateType) -> Self { + match value { + TemplateType::Wasm { abi_version } => Self { + template_type: Some(proto::types::template_type::TemplateType::Wasm( + proto::types::WasmInfo { + abi_version: abi_version.into(), + }, + )), + }, + } + } +} + +// -------------------------------- BuildInfo -------------------------------- // + +impl TryFrom for BuildInfo { + type Error = String; + + fn try_from(value: proto::types::BuildInfo) -> Result { + Ok(Self { + repo_url: value.repo_url.try_into().map_err(|_| "Invalid repo url")?, + commit_hash: value.commit_hash.try_into().map_err(|_| "Invalid commit hash")?, + }) + } +} + +impl From for proto::types::BuildInfo { + fn from(value: BuildInfo) -> Self { + Self { + repo_url: value.repo_url.into_string(), + commit_hash: value.commit_hash.into_vec(), + } + } +} + +//---------------------------------- ContractConstitution --------------------------------------------// +impl From for proto::types::ContractConstitution { + fn from(value: ContractConstitution) -> Self { + Self { + validator_committee: Some(value.validator_committee.into()), + acceptance_requirements: Some(value.acceptance_requirements.into()), + consensus: value.consensus.into(), + checkpoint_params: Some(value.checkpoint_params.into()), + constitution_change_rules: Some(value.constitution_change_rules.into()), + } + } +} + +impl TryFrom for ContractConstitution { + type Error = String; + + fn try_from(value: proto::types::ContractConstitution) -> Result { + use num_traits::FromPrimitive; + let validator_committee = value + .validator_committee + .map(TryInto::try_into) + .ok_or("validator_committee not provided")??; + let acceptance_requirements = value + .acceptance_requirements + .map(TryInto::try_into) + .ok_or("acceptance_requirements not provided")??; + let consensus = SideChainConsensus::from_i32(value.consensus).ok_or("Invalid SideChainConsensus")?; + let checkpoint_params = value + .checkpoint_params + .map(TryInto::try_into) + .ok_or("checkpoint_params not provided")??; + let constitution_change_rules = value + .constitution_change_rules + .map(TryInto::try_into) + .ok_or("constitution_change_rules not provided")??; + + Ok(Self { + validator_committee, + acceptance_requirements, + consensus, + checkpoint_params, + constitution_change_rules, + }) + } +} + +//---------------------------------- ContractCheckpoint --------------------------------------------// +impl From for proto::types::ContractCheckpoint { + fn from(value: ContractCheckpoint) -> Self { + Self { + checkpoint_number: value.checkpoint_number, + merkle_root: value.merkle_root.to_vec(), + signatures: Some(value.signatures.into()), + } + } +} + +impl TryFrom for ContractCheckpoint { + type Error = String; + + fn try_from(value: proto::types::ContractCheckpoint) -> Result { + let merkle_root = value.merkle_root.try_into().map_err(|_| "Invalid merkle root")?; + let signatures = value.signatures.map(TryInto::try_into).transpose()?.unwrap_or_default(); + Ok(Self { + checkpoint_number: value.checkpoint_number, + merkle_root, + signatures, + }) + } +} + +//---------------------------------- ContractAcceptanceRequirements --------------------------------------------// +impl From for proto::types::ContractAcceptanceRequirements { + fn from(value: ContractAcceptanceRequirements) -> Self { + Self { + acceptance_period_expiry: value.acceptance_period_expiry, + minimum_quorum_required: value.minimum_quorum_required, + } + } +} + +impl TryFrom for ContractAcceptanceRequirements { + type Error = String; + + fn try_from(value: proto::types::ContractAcceptanceRequirements) -> Result { + Ok(Self { + acceptance_period_expiry: value.acceptance_period_expiry, + minimum_quorum_required: value.minimum_quorum_required, + }) + } +} + +//---------------------------------- ContractAcceptance --------------------------------------------// + +impl From for proto::types::ContractAcceptance { + fn from(value: ContractAcceptance) -> Self { + Self { + validator_node_public_key: value.validator_node_public_key.as_bytes().to_vec(), + signature: Some(value.signature.into()), + } + } +} + +impl TryFrom for ContractAcceptance { + type Error = String; + + fn try_from(value: proto::types::ContractAcceptance) -> Result { + let validator_node_public_key = + PublicKey::from_bytes(value.validator_node_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; + let signature = value + .signature + .ok_or_else(|| "signature not provided".to_string())? + .try_into()?; + + Ok(Self { + validator_node_public_key, + signature, + }) + } +} + +//---------------------------------- ContractUpdateProposal --------------------------------------------// + +impl From for proto::types::ContractUpdateProposal { + fn from(value: ContractUpdateProposal) -> Self { + Self { + proposal_id: value.proposal_id, + signature: Some(value.signature.into()), + updated_constitution: Some(value.updated_constitution.into()), + } + } +} + +impl TryFrom for ContractUpdateProposal { + type Error = String; + + fn try_from(value: proto::types::ContractUpdateProposal) -> Result { + let signature = value + .signature + .ok_or_else(|| "signature not provided".to_string())? + .try_into()?; + + let updated_constitution = value + .updated_constitution + .ok_or_else(|| "updated_constiution not provided".to_string())? + .try_into()?; + + Ok(Self { + proposal_id: value.proposal_id, + signature, + updated_constitution, + }) + } +} + +//---------------------------------- ContractUpdateProposalAcceptance --------------------------------------------// + +impl From for proto::types::ContractUpdateProposalAcceptance { + fn from(value: ContractUpdateProposalAcceptance) -> Self { + Self { + proposal_id: value.proposal_id, + validator_node_public_key: value.validator_node_public_key.as_bytes().to_vec(), + signature: Some(value.signature.into()), + } + } +} + +impl TryFrom for ContractUpdateProposalAcceptance { + type Error = String; + + fn try_from(value: proto::types::ContractUpdateProposalAcceptance) -> Result { + let validator_node_public_key = + PublicKey::from_bytes(value.validator_node_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; + let signature = value + .signature + .ok_or_else(|| "signature not provided".to_string())? + .try_into()?; + + Ok(Self { + proposal_id: value.proposal_id, + validator_node_public_key, + signature, + }) + } +} + +//---------------------------------- ContractAmendment --------------------------------------------// + +impl From for proto::types::ContractAmendment { + fn from(value: ContractAmendment) -> Self { + Self { + proposal_id: value.proposal_id, + validator_committee: Some(value.validator_committee.into()), + validator_signatures: Some(value.validator_signatures.into()), + updated_constitution: Some(value.updated_constitution.into()), + activation_window: value.activation_window, + } + } +} + +impl TryFrom for ContractAmendment { + type Error = String; + + fn try_from(value: proto::types::ContractAmendment) -> Result { + let validator_committee = value + .validator_committee + .map(TryInto::try_into) + .ok_or("validator_committee not provided")??; + + let validator_signatures = value + .validator_signatures + .map(TryInto::try_into) + .ok_or("validator_signatures not provided")??; + + let updated_constitution = value + .updated_constitution + .ok_or_else(|| "updated_constiution not provided".to_string())? + .try_into()?; + + Ok(Self { + proposal_id: value.proposal_id, + validator_committee, + validator_signatures, + updated_constitution, + activation_window: value.activation_window, + }) + } +} + +//---------------------------------- SideChainConsensus --------------------------------------------// +impl From for proto::types::SideChainConsensus { + fn from(value: SideChainConsensus) -> Self { + #[allow(clippy::enum_glob_use)] + use proto::types::SideChainConsensus::*; + match value { + SideChainConsensus::Bft => Bft, + SideChainConsensus::ProofOfWork => ProofOfWork, + SideChainConsensus::MerkleRoot => MerkleRoot, + } + } +} + +impl TryFrom for SideChainConsensus { + type Error = String; + + fn try_from(value: proto::types::SideChainConsensus) -> Result { + #[allow(clippy::enum_glob_use)] + use proto::types::SideChainConsensus::*; + match value { + Unspecified => Err("Side chain consensus not specified or invalid".to_string()), + Bft => Ok(SideChainConsensus::Bft), + ProofOfWork => Ok(SideChainConsensus::ProofOfWork), + MerkleRoot => Ok(SideChainConsensus::MerkleRoot), + } + } +} + +//---------------------------------- CheckpointParameters --------------------------------------------// +impl From for proto::types::CheckpointParameters { + fn from(value: CheckpointParameters) -> Self { + Self { + minimum_quorum_required: value.minimum_quorum_required, + abandoned_interval: value.abandoned_interval, + quarantine_interval: value.quarantine_interval, + } + } +} + +impl TryFrom for CheckpointParameters { + type Error = String; + + fn try_from(value: proto::types::CheckpointParameters) -> Result { + Ok(Self { + minimum_quorum_required: value.minimum_quorum_required, + abandoned_interval: value.abandoned_interval, + quarantine_interval: value.quarantine_interval, + }) + } +} + +//---------------------------------- ConstitutionChangeRules --------------------------------------------// +impl From for proto::types::ConstitutionChangeRules { + fn from(value: ConstitutionChangeRules) -> Self { + Self { + change_flags: value.change_flags.bits().into(), + requirements_for_constitution_change: value.requirements_for_constitution_change.map(Into::into), + } + } +} + +impl TryFrom for ConstitutionChangeRules { + type Error = String; + + fn try_from(value: proto::types::ConstitutionChangeRules) -> Result { + Ok(Self { + change_flags: u8::try_from(value.change_flags) + .ok() + .and_then(ConstitutionChangeFlags::from_bits) + .ok_or("Invalid change_flags")?, + requirements_for_constitution_change: value + .requirements_for_constitution_change + .map(RequirementsForConstitutionChange::try_from) + .transpose()?, + }) + } +} + +//---------------------------------- RequirementsForConstitutionChange --------------------------------------------// +impl From for proto::types::RequirementsForConstitutionChange { + fn from(value: RequirementsForConstitutionChange) -> Self { + Self { + minimum_constitution_committee_signatures: value.minimum_constitution_committee_signatures, + constitution_committee: value.constitution_committee.map(Into::into), + backup_keys: value.backup_keys.map(Into::into), + } + } +} + +impl TryFrom for RequirementsForConstitutionChange { + type Error = String; + + fn try_from(value: proto::types::RequirementsForConstitutionChange) -> Result { + Ok(Self { + minimum_constitution_committee_signatures: value.minimum_constitution_committee_signatures, + constitution_committee: value + .constitution_committee + .map(CommitteeMembers::try_from) + .transpose()?, + backup_keys: value.backup_keys.map(CommitteeMembers::try_from).transpose()?, + }) + } +} + +//---------------------------------- CommitteeMembers --------------------------------------------// +impl From for proto::types::CommitteeMembers { + fn from(value: CommitteeMembers) -> Self { + Self { + members: value.members().iter().map(|pk| pk.to_vec()).collect(), + } + } +} + +impl TryFrom for CommitteeMembers { + type Error = String; + + fn try_from(value: proto::types::CommitteeMembers) -> Result { + if value.members.len() > CommitteeMembers::MAX_MEMBERS { + return Err(format!( + "Too many committee members: expected {} but got {}", + CommitteeMembers::MAX_MEMBERS, + value.members.len() + )); + } + + let members = value + .members + .iter() + .enumerate() + .map(|(i, c)| { + PublicKey::from_bytes(c) + .map_err(|err| format!("committee member #{} was not a valid public key: {}", i + 1, err)) + }) + .collect::, _>>()?; + + let members = CommitteeMembers::try_from(members).map_err(|e| e.to_string())?; + Ok(members) + } +} + +//---------------------------------- CommitteeSignatures --------------------------------------------// +impl From for proto::types::CommitteeSignatures { + fn from(value: CommitteeSignatures) -> Self { + Self { + signatures: value.signatures().iter().map(Into::into).collect(), + } + } +} + +impl TryFrom for CommitteeSignatures { + type Error = String; + + fn try_from(value: proto::types::CommitteeSignatures) -> Result { + if value.signatures.len() > CommitteeSignatures::MAX_SIGNATURES { + return Err(format!( + "Too many committee signatures: expected {} but got {}", + CommitteeSignatures::MAX_SIGNATURES, + value.signatures.len() + )); + } + + let signatures = value + .signatures + .into_iter() + .enumerate() + .map(|(i, s)| { + SignerSignature::try_from(s) + .map_err(|err| format!("committee signature #{} was not a valid signature: {}", i + 1, err)) + }) + .collect::, _>>()?; + + let signatures = CommitteeSignatures::try_from(signatures).map_err(|e| e.to_string())?; + Ok(signatures) + } +} + +// TODO: deprecated + +impl TryFrom for AssetOutputFeatures { + type Error = String; + + fn try_from(features: proto::types::AssetOutputFeatures) -> Result { + let public_key = PublicKey::from_bytes(features.public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; + + Ok(Self { + public_key, + template_ids_implemented: features.template_ids_implemented, + template_parameters: features.template_parameters.into_iter().map(|s| s.into()).collect(), + }) + } +} + +impl From for proto::types::AssetOutputFeatures { + fn from(features: AssetOutputFeatures) -> Self { + Self { + public_key: features.public_key.as_bytes().to_vec(), + template_ids_implemented: features.template_ids_implemented, + template_parameters: features.template_parameters.into_iter().map(|tp| tp.into()).collect(), + } + } +} + +impl From for TemplateParameter { + fn from(source: proto::types::TemplateParameter) -> Self { + Self { + template_id: source.template_id, + template_data_version: source.template_data_version, + template_data: source.template_data, + } + } +} + +impl From for proto::types::TemplateParameter { + fn from(source: TemplateParameter) -> Self { + Self { + template_id: source.template_id, + template_data_version: source.template_data_version, + template_data: source.template_data, + } + } +} + +impl TryFrom for MintNonFungibleFeatures { + type Error = String; + + fn try_from(value: proto::types::MintNonFungibleFeatures) -> Result { + let asset_public_key = + PublicKey::from_bytes(value.asset_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; + + let asset_owner_commitment = value + .asset_owner_commitment + .map(|c| Commitment::from_bytes(&c.data)) + .ok_or_else(|| "asset_owner_commitment is missing".to_string())? + .map_err(|err| err.to_string())?; + Ok(Self { + asset_public_key, + asset_owner_commitment, + }) + } +} + +impl From for proto::types::MintNonFungibleFeatures { + fn from(value: MintNonFungibleFeatures) -> Self { + Self { + asset_public_key: value.asset_public_key.as_bytes().to_vec(), + asset_owner_commitment: Some(value.asset_owner_commitment.into()), + } + } +} + +impl TryFrom for SideChainCheckpointFeatures { + type Error = String; + + fn try_from(value: proto::types::SideChainCheckpointFeatures) -> Result { + if value.merkle_root.len() != FixedHash::byte_size() { + return Err(format!( + "Invalid side chain checkpoint merkle length {}", + value.merkle_root.len() + )); + } + let merkle_root = FixedHash::try_from(value.merkle_root).map_err(|e| e.to_string())?; + let committee = value + .committee + .into_iter() + .map(|c| PublicKey::from_bytes(&c).map_err(|err| format!("{:?}", err))) + .collect::>()?; + Ok(Self { merkle_root, committee }) + } +} + +impl From for proto::types::SideChainCheckpointFeatures { + fn from(value: SideChainCheckpointFeatures) -> Self { + Self { + merkle_root: value.merkle_root.as_bytes().to_vec(), + committee: value.committee.into_iter().map(|c| c.as_bytes().to_vec()).collect(), + } + } +} + +impl TryFrom for CommitteeDefinitionFeatures { + type Error = String; + + fn try_from(value: proto::types::CommitteeDefinitionFeatures) -> Result { + let committee = value + .committee + .into_iter() + .map(|c| PublicKey::from_bytes(&c).map_err(|err| format!("{:?}", err))) + .collect::>()?; + let effective_sidechain_height = value.effective_sidechain_height; + + Ok(Self { + committee, + effective_sidechain_height, + }) + } +} + +impl From for proto::types::CommitteeDefinitionFeatures { + fn from(value: CommitteeDefinitionFeatures) -> Self { + Self { + committee: value.committee.into_iter().map(|c| c.as_bytes().to_vec()).collect(), + effective_sidechain_height: value.effective_sidechain_height, + } + } +} + +//---------------------------------- ContractDefinition --------------------------------------------// + +impl TryFrom for ContractDefinition { + type Error = String; + + fn try_from(value: proto::types::ContractDefinition) -> Result { + let contract_name = bytes_into_fixed_string(value.contract_name); + + let contract_issuer = + PublicKey::from_bytes(value.contract_issuer.as_bytes()).map_err(|err| format!("{:?}", err))?; + + let contract_spec = value + .contract_spec + .map(ContractSpecification::try_from) + .ok_or_else(|| "contract_spec is missing".to_string())??; + + Ok(Self { + contract_name, + contract_issuer, + contract_spec, + }) + } +} + +impl From for proto::types::ContractDefinition { + fn from(value: ContractDefinition) -> Self { + let contract_name = value.contract_name.as_bytes().to_vec(); + let contract_issuer = value.contract_issuer.as_bytes().to_vec(); + + Self { + contract_name, + contract_issuer, + contract_spec: Some(value.contract_spec.into()), + } + } +} + +impl TryFrom for ContractSpecification { + type Error = String; + + fn try_from(value: proto::types::ContractSpecification) -> Result { + let runtime = bytes_into_fixed_string(value.runtime); + let public_functions = value + .public_functions + .into_iter() + .map(PublicFunction::try_from) + .collect::>()?; + + Ok(Self { + runtime, + public_functions, + }) + } +} + +impl From for proto::types::ContractSpecification { + fn from(value: ContractSpecification) -> Self { + let public_functions = value.public_functions.into_iter().map(|f| f.into()).collect(); + Self { + runtime: value.runtime.as_bytes().to_vec(), + public_functions, + } + } +} + +impl TryFrom for PublicFunction { + type Error = String; + + fn try_from(value: proto::types::PublicFunction) -> Result { + let function = value + .function + .map(FunctionRef::try_from) + .ok_or_else(|| "function is missing".to_string())??; + + Ok(Self { + name: bytes_into_fixed_string(value.name), + function, + }) + } +} + +impl From for proto::types::PublicFunction { + fn from(value: PublicFunction) -> Self { + Self { + name: value.name.as_bytes().to_vec(), + function: Some(value.function.into()), + } + } +} + +impl TryFrom for FunctionRef { + type Error = String; + + fn try_from(value: proto::types::FunctionRef) -> Result { + let template_id = FixedHash::try_from(value.template_id).map_err(|err| format!("{:?}", err))?; + let function_id = u16::try_from(value.function_id).map_err(|_| "Invalid function_id: overflowed u16")?; + + Ok(Self { + template_id, + function_id, + }) + } +} + +impl From for proto::types::FunctionRef { + fn from(value: FunctionRef) -> Self { + let template_id = value.template_id.as_bytes().to_vec(); + + Self { + template_id, + function_id: value.function_id.into(), + } + } +} diff --git a/base_layer/core/src/proto/transaction.rs b/base_layer/core/src/proto/transaction.rs index e5ea8dcb95..2b7dd50d38 100644 --- a/base_layer/core/src/proto/transaction.rs +++ b/base_layer/core/src/proto/transaction.rs @@ -27,7 +27,7 @@ use std::{ sync::Arc, }; -use tari_common_types::types::{BlindingFactor, BulletRangeProof, Commitment, FixedHash, PublicKey}; +use tari_common_types::types::{BlindingFactor, BulletRangeProof, Commitment, PublicKey}; use tari_crypto::tari_utilities::{ByteArray, ByteArrayError}; use tari_script::{ExecutionStack, TariScript}; use tari_utilities::convert::try_convert_all; @@ -39,37 +39,12 @@ use crate::{ aggregated_body::AggregateBody, tari_amount::MicroTari, transaction_components::{ - bytes_into_fixed_string, - AssetOutputFeatures, - CheckpointParameters, - CommitteeDefinitionFeatures, - CommitteeMembers, - CommitteeSignatures, - ConstitutionChangeFlags, - ConstitutionChangeRules, - ContractAcceptance, - ContractAcceptanceRequirements, - ContractAmendment, - ContractCheckpoint, - ContractConstitution, - ContractDefinition, - ContractSpecification, - ContractUpdateProposal, - ContractUpdateProposalAcceptance, EncryptedValue, - FunctionRef, KernelFeatures, - MintNonFungibleFeatures, OutputFeatures, OutputFeaturesVersion, OutputType, - PublicFunction, - RequirementsForConstitutionChange, - SideChainCheckpointFeatures, - SideChainConsensus, SideChainFeatures, - SignerSignature, - TemplateParameter, Transaction, TransactionInput, TransactionInputVersion, @@ -377,704 +352,6 @@ impl From for proto::types::OutputFeatures { } } -//---------------------------------- SideChainFeatures --------------------------------------------// -impl From for proto::types::SideChainFeatures { - fn from(value: SideChainFeatures) -> Self { - Self { - contract_id: value.contract_id.to_vec(), - definition: value.definition.map(Into::into), - constitution: value.constitution.map(Into::into), - acceptance: value.acceptance.map(Into::into), - update_proposal: value.update_proposal.map(Into::into), - update_proposal_acceptance: value.update_proposal_acceptance.map(Into::into), - amendment: value.amendment.map(Into::into), - checkpoint: value.checkpoint.map(Into::into), - } - } -} - -impl TryFrom for SideChainFeatures { - type Error = String; - - fn try_from(features: proto::types::SideChainFeatures) -> Result { - let contract_id = features.contract_id.try_into().map_err(|_| "Invalid contract_id")?; - let definition = features.definition.map(ContractDefinition::try_from).transpose()?; - let constitution = features.constitution.map(ContractConstitution::try_from).transpose()?; - let acceptance = features.acceptance.map(ContractAcceptance::try_from).transpose()?; - let update_proposal = features - .update_proposal - .map(ContractUpdateProposal::try_from) - .transpose()?; - let update_proposal_acceptance = features - .update_proposal_acceptance - .map(ContractUpdateProposalAcceptance::try_from) - .transpose()?; - let amendment = features.amendment.map(ContractAmendment::try_from).transpose()?; - let checkpoint = features.checkpoint.map(ContractCheckpoint::try_from).transpose()?; - - Ok(Self { - contract_id, - definition, - constitution, - acceptance, - update_proposal, - update_proposal_acceptance, - amendment, - checkpoint, - }) - } -} - -//---------------------------------- ContractConstitution --------------------------------------------// -impl From for proto::types::ContractConstitution { - fn from(value: ContractConstitution) -> Self { - Self { - validator_committee: Some(value.validator_committee.into()), - acceptance_requirements: Some(value.acceptance_requirements.into()), - consensus: value.consensus.into(), - checkpoint_params: Some(value.checkpoint_params.into()), - constitution_change_rules: Some(value.constitution_change_rules.into()), - } - } -} - -impl TryFrom for ContractConstitution { - type Error = String; - - fn try_from(value: proto::types::ContractConstitution) -> Result { - use num_traits::FromPrimitive; - let validator_committee = value - .validator_committee - .map(TryInto::try_into) - .ok_or("validator_committee not provided")??; - let acceptance_requirements = value - .acceptance_requirements - .map(TryInto::try_into) - .ok_or("acceptance_requirements not provided")??; - let consensus = SideChainConsensus::from_i32(value.consensus).ok_or("Invalid SideChainConsensus")?; - let checkpoint_params = value - .checkpoint_params - .map(TryInto::try_into) - .ok_or("checkpoint_params not provided")??; - let constitution_change_rules = value - .constitution_change_rules - .map(TryInto::try_into) - .ok_or("constitution_change_rules not provided")??; - - Ok(Self { - validator_committee, - acceptance_requirements, - consensus, - checkpoint_params, - constitution_change_rules, - }) - } -} - -//---------------------------------- ContractCheckpoint --------------------------------------------// -impl From for proto::types::ContractCheckpoint { - fn from(value: ContractCheckpoint) -> Self { - Self { - checkpoint_number: value.checkpoint_number, - merkle_root: value.merkle_root.to_vec(), - signatures: Some(value.signatures.into()), - } - } -} - -impl TryFrom for ContractCheckpoint { - type Error = String; - - fn try_from(value: proto::types::ContractCheckpoint) -> Result { - let merkle_root = value.merkle_root.try_into().map_err(|_| "Invalid merkle root")?; - let signatures = value.signatures.map(TryInto::try_into).transpose()?.unwrap_or_default(); - Ok(Self { - checkpoint_number: value.checkpoint_number, - merkle_root, - signatures, - }) - } -} - -//---------------------------------- ContractAcceptanceRequirements --------------------------------------------// -impl From for proto::types::ContractAcceptanceRequirements { - fn from(value: ContractAcceptanceRequirements) -> Self { - Self { - acceptance_period_expiry: value.acceptance_period_expiry, - minimum_quorum_required: value.minimum_quorum_required, - } - } -} - -impl TryFrom for ContractAcceptanceRequirements { - type Error = String; - - fn try_from(value: proto::types::ContractAcceptanceRequirements) -> Result { - Ok(Self { - acceptance_period_expiry: value.acceptance_period_expiry, - minimum_quorum_required: value.minimum_quorum_required, - }) - } -} - -//---------------------------------- ContractAcceptance --------------------------------------------// - -impl From for proto::types::ContractAcceptance { - fn from(value: ContractAcceptance) -> Self { - Self { - validator_node_public_key: value.validator_node_public_key.as_bytes().to_vec(), - signature: Some(value.signature.into()), - } - } -} - -impl TryFrom for ContractAcceptance { - type Error = String; - - fn try_from(value: proto::types::ContractAcceptance) -> Result { - let validator_node_public_key = - PublicKey::from_bytes(value.validator_node_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; - let signature = value - .signature - .ok_or_else(|| "signature not provided".to_string())? - .try_into()?; - - Ok(Self { - validator_node_public_key, - signature, - }) - } -} - -//---------------------------------- ContractUpdateProposal --------------------------------------------// - -impl From for proto::types::ContractUpdateProposal { - fn from(value: ContractUpdateProposal) -> Self { - Self { - proposal_id: value.proposal_id, - signature: Some(value.signature.into()), - updated_constitution: Some(value.updated_constitution.into()), - } - } -} - -impl TryFrom for ContractUpdateProposal { - type Error = String; - - fn try_from(value: proto::types::ContractUpdateProposal) -> Result { - let signature = value - .signature - .ok_or_else(|| "signature not provided".to_string())? - .try_into()?; - - let updated_constitution = value - .updated_constitution - .ok_or_else(|| "updated_constiution not provided".to_string())? - .try_into()?; - - Ok(Self { - proposal_id: value.proposal_id, - signature, - updated_constitution, - }) - } -} - -//---------------------------------- ContractUpdateProposalAcceptance --------------------------------------------// - -impl From for proto::types::ContractUpdateProposalAcceptance { - fn from(value: ContractUpdateProposalAcceptance) -> Self { - Self { - proposal_id: value.proposal_id, - validator_node_public_key: value.validator_node_public_key.as_bytes().to_vec(), - signature: Some(value.signature.into()), - } - } -} - -impl TryFrom for ContractUpdateProposalAcceptance { - type Error = String; - - fn try_from(value: proto::types::ContractUpdateProposalAcceptance) -> Result { - let validator_node_public_key = - PublicKey::from_bytes(value.validator_node_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; - let signature = value - .signature - .ok_or_else(|| "signature not provided".to_string())? - .try_into()?; - - Ok(Self { - proposal_id: value.proposal_id, - validator_node_public_key, - signature, - }) - } -} - -//---------------------------------- ContractAmendment --------------------------------------------// - -impl From for proto::types::ContractAmendment { - fn from(value: ContractAmendment) -> Self { - Self { - proposal_id: value.proposal_id, - validator_committee: Some(value.validator_committee.into()), - validator_signatures: Some(value.validator_signatures.into()), - updated_constitution: Some(value.updated_constitution.into()), - activation_window: value.activation_window, - } - } -} - -impl TryFrom for ContractAmendment { - type Error = String; - - fn try_from(value: proto::types::ContractAmendment) -> Result { - let validator_committee = value - .validator_committee - .map(TryInto::try_into) - .ok_or("validator_committee not provided")??; - - let validator_signatures = value - .validator_signatures - .map(TryInto::try_into) - .ok_or("validator_signatures not provided")??; - - let updated_constitution = value - .updated_constitution - .ok_or_else(|| "updated_constiution not provided".to_string())? - .try_into()?; - - Ok(Self { - proposal_id: value.proposal_id, - validator_committee, - validator_signatures, - updated_constitution, - activation_window: value.activation_window, - }) - } -} - -//---------------------------------- SideChainConsensus --------------------------------------------// -impl From for proto::types::SideChainConsensus { - fn from(value: SideChainConsensus) -> Self { - #[allow(clippy::enum_glob_use)] - use proto::types::SideChainConsensus::*; - match value { - SideChainConsensus::Bft => Bft, - SideChainConsensus::ProofOfWork => ProofOfWork, - SideChainConsensus::MerkleRoot => MerkleRoot, - } - } -} - -impl TryFrom for SideChainConsensus { - type Error = String; - - fn try_from(value: proto::types::SideChainConsensus) -> Result { - #[allow(clippy::enum_glob_use)] - use proto::types::SideChainConsensus::*; - match value { - Unspecified => Err("Side chain consensus not specified or invalid".to_string()), - Bft => Ok(SideChainConsensus::Bft), - ProofOfWork => Ok(SideChainConsensus::ProofOfWork), - MerkleRoot => Ok(SideChainConsensus::MerkleRoot), - } - } -} - -//---------------------------------- CheckpointParameters --------------------------------------------// -impl From for proto::types::CheckpointParameters { - fn from(value: CheckpointParameters) -> Self { - Self { - minimum_quorum_required: value.minimum_quorum_required, - abandoned_interval: value.abandoned_interval, - quarantine_interval: value.quarantine_interval, - } - } -} - -impl TryFrom for CheckpointParameters { - type Error = String; - - fn try_from(value: proto::types::CheckpointParameters) -> Result { - Ok(Self { - minimum_quorum_required: value.minimum_quorum_required, - abandoned_interval: value.abandoned_interval, - quarantine_interval: value.quarantine_interval, - }) - } -} - -//---------------------------------- ConstitutionChangeRules --------------------------------------------// -impl From for proto::types::ConstitutionChangeRules { - fn from(value: ConstitutionChangeRules) -> Self { - Self { - change_flags: value.change_flags.bits().into(), - requirements_for_constitution_change: value.requirements_for_constitution_change.map(Into::into), - } - } -} - -impl TryFrom for ConstitutionChangeRules { - type Error = String; - - fn try_from(value: proto::types::ConstitutionChangeRules) -> Result { - Ok(Self { - change_flags: u8::try_from(value.change_flags) - .ok() - .and_then(ConstitutionChangeFlags::from_bits) - .ok_or("Invalid change_flags")?, - requirements_for_constitution_change: value - .requirements_for_constitution_change - .map(RequirementsForConstitutionChange::try_from) - .transpose()?, - }) - } -} - -//---------------------------------- RequirementsForConstitutionChange --------------------------------------------// -impl From for proto::types::RequirementsForConstitutionChange { - fn from(value: RequirementsForConstitutionChange) -> Self { - Self { - minimum_constitution_committee_signatures: value.minimum_constitution_committee_signatures, - constitution_committee: value.constitution_committee.map(Into::into), - backup_keys: value.backup_keys.map(Into::into), - } - } -} - -impl TryFrom for RequirementsForConstitutionChange { - type Error = String; - - fn try_from(value: proto::types::RequirementsForConstitutionChange) -> Result { - Ok(Self { - minimum_constitution_committee_signatures: value.minimum_constitution_committee_signatures, - constitution_committee: value - .constitution_committee - .map(CommitteeMembers::try_from) - .transpose()?, - backup_keys: value.backup_keys.map(CommitteeMembers::try_from).transpose()?, - }) - } -} - -//---------------------------------- CommitteeMembers --------------------------------------------// -impl From for proto::types::CommitteeMembers { - fn from(value: CommitteeMembers) -> Self { - Self { - members: value.members().iter().map(|pk| pk.to_vec()).collect(), - } - } -} - -impl TryFrom for CommitteeMembers { - type Error = String; - - fn try_from(value: proto::types::CommitteeMembers) -> Result { - if value.members.len() > CommitteeMembers::MAX_MEMBERS { - return Err(format!( - "Too many committee members: expected {} but got {}", - CommitteeMembers::MAX_MEMBERS, - value.members.len() - )); - } - - let members = value - .members - .iter() - .enumerate() - .map(|(i, c)| { - PublicKey::from_bytes(c) - .map_err(|err| format!("committee member #{} was not a valid public key: {}", i + 1, err)) - }) - .collect::, _>>()?; - - let members = CommitteeMembers::try_from(members).map_err(|e| e.to_string())?; - Ok(members) - } -} - -//---------------------------------- CommitteeSignatures --------------------------------------------// -impl From for proto::types::CommitteeSignatures { - fn from(value: CommitteeSignatures) -> Self { - Self { - signatures: value.signatures().iter().map(Into::into).collect(), - } - } -} - -impl TryFrom for CommitteeSignatures { - type Error = String; - - fn try_from(value: proto::types::CommitteeSignatures) -> Result { - if value.signatures.len() > CommitteeSignatures::MAX_SIGNATURES { - return Err(format!( - "Too many committee signatures: expected {} but got {}", - CommitteeSignatures::MAX_SIGNATURES, - value.signatures.len() - )); - } - - let signatures = value - .signatures - .into_iter() - .enumerate() - .map(|(i, s)| { - SignerSignature::try_from(s) - .map_err(|err| format!("committee signature #{} was not a valid signature: {}", i + 1, err)) - }) - .collect::, _>>()?; - - let signatures = CommitteeSignatures::try_from(signatures).map_err(|e| e.to_string())?; - Ok(signatures) - } -} - -// TODO: deprecated - -impl TryFrom for AssetOutputFeatures { - type Error = String; - - fn try_from(features: proto::types::AssetOutputFeatures) -> Result { - let public_key = PublicKey::from_bytes(features.public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; - - Ok(Self { - public_key, - template_ids_implemented: features.template_ids_implemented, - template_parameters: features.template_parameters.into_iter().map(|s| s.into()).collect(), - }) - } -} - -impl From for proto::types::AssetOutputFeatures { - fn from(features: AssetOutputFeatures) -> Self { - Self { - public_key: features.public_key.as_bytes().to_vec(), - template_ids_implemented: features.template_ids_implemented, - template_parameters: features.template_parameters.into_iter().map(|tp| tp.into()).collect(), - } - } -} - -impl From for TemplateParameter { - fn from(source: proto::types::TemplateParameter) -> Self { - Self { - template_id: source.template_id, - template_data_version: source.template_data_version, - template_data: source.template_data, - } - } -} - -impl From for proto::types::TemplateParameter { - fn from(source: TemplateParameter) -> Self { - Self { - template_id: source.template_id, - template_data_version: source.template_data_version, - template_data: source.template_data, - } - } -} - -impl TryFrom for MintNonFungibleFeatures { - type Error = String; - - fn try_from(value: proto::types::MintNonFungibleFeatures) -> Result { - let asset_public_key = - PublicKey::from_bytes(value.asset_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?; - - let asset_owner_commitment = value - .asset_owner_commitment - .map(|c| Commitment::from_bytes(&c.data)) - .ok_or_else(|| "asset_owner_commitment is missing".to_string())? - .map_err(|err| err.to_string())?; - Ok(Self { - asset_public_key, - asset_owner_commitment, - }) - } -} - -impl From for proto::types::MintNonFungibleFeatures { - fn from(value: MintNonFungibleFeatures) -> Self { - Self { - asset_public_key: value.asset_public_key.as_bytes().to_vec(), - asset_owner_commitment: Some(value.asset_owner_commitment.into()), - } - } -} - -impl TryFrom for SideChainCheckpointFeatures { - type Error = String; - - fn try_from(value: proto::types::SideChainCheckpointFeatures) -> Result { - if value.merkle_root.len() != FixedHash::byte_size() { - return Err(format!( - "Invalid side chain checkpoint merkle length {}", - value.merkle_root.len() - )); - } - let merkle_root = FixedHash::try_from(value.merkle_root).map_err(|e| e.to_string())?; - let committee = value - .committee - .into_iter() - .map(|c| PublicKey::from_bytes(&c).map_err(|err| format!("{:?}", err))) - .collect::>()?; - Ok(Self { merkle_root, committee }) - } -} - -impl From for proto::types::SideChainCheckpointFeatures { - fn from(value: SideChainCheckpointFeatures) -> Self { - Self { - merkle_root: value.merkle_root.as_bytes().to_vec(), - committee: value.committee.into_iter().map(|c| c.as_bytes().to_vec()).collect(), - } - } -} - -impl TryFrom for CommitteeDefinitionFeatures { - type Error = String; - - fn try_from(value: proto::types::CommitteeDefinitionFeatures) -> Result { - let committee = value - .committee - .into_iter() - .map(|c| PublicKey::from_bytes(&c).map_err(|err| format!("{:?}", err))) - .collect::>()?; - let effective_sidechain_height = value.effective_sidechain_height; - - Ok(Self { - committee, - effective_sidechain_height, - }) - } -} - -impl From for proto::types::CommitteeDefinitionFeatures { - fn from(value: CommitteeDefinitionFeatures) -> Self { - Self { - committee: value.committee.into_iter().map(|c| c.as_bytes().to_vec()).collect(), - effective_sidechain_height: value.effective_sidechain_height, - } - } -} - -//---------------------------------- ContractDefinition --------------------------------------------// - -impl TryFrom for ContractDefinition { - type Error = String; - - fn try_from(value: proto::types::ContractDefinition) -> Result { - let contract_name = bytes_into_fixed_string(value.contract_name); - - let contract_issuer = - PublicKey::from_bytes(value.contract_issuer.as_bytes()).map_err(|err| format!("{:?}", err))?; - - let contract_spec = value - .contract_spec - .map(ContractSpecification::try_from) - .ok_or_else(|| "contract_spec is missing".to_string())??; - - Ok(Self { - contract_name, - contract_issuer, - contract_spec, - }) - } -} - -impl From for proto::types::ContractDefinition { - fn from(value: ContractDefinition) -> Self { - let contract_name = value.contract_name.as_bytes().to_vec(); - let contract_issuer = value.contract_issuer.as_bytes().to_vec(); - - Self { - contract_name, - contract_issuer, - contract_spec: Some(value.contract_spec.into()), - } - } -} - -impl TryFrom for ContractSpecification { - type Error = String; - - fn try_from(value: proto::types::ContractSpecification) -> Result { - let runtime = bytes_into_fixed_string(value.runtime); - let public_functions = value - .public_functions - .into_iter() - .map(PublicFunction::try_from) - .collect::>()?; - - Ok(Self { - runtime, - public_functions, - }) - } -} - -impl From for proto::types::ContractSpecification { - fn from(value: ContractSpecification) -> Self { - let public_functions = value.public_functions.into_iter().map(|f| f.into()).collect(); - Self { - runtime: value.runtime.as_bytes().to_vec(), - public_functions, - } - } -} - -impl TryFrom for PublicFunction { - type Error = String; - - fn try_from(value: proto::types::PublicFunction) -> Result { - let function = value - .function - .map(FunctionRef::try_from) - .ok_or_else(|| "function is missing".to_string())??; - - Ok(Self { - name: bytes_into_fixed_string(value.name), - function, - }) - } -} - -impl From for proto::types::PublicFunction { - fn from(value: PublicFunction) -> Self { - Self { - name: value.name.as_bytes().to_vec(), - function: Some(value.function.into()), - } - } -} - -impl TryFrom for FunctionRef { - type Error = String; - - fn try_from(value: proto::types::FunctionRef) -> Result { - let template_id = FixedHash::try_from(value.template_id).map_err(|err| format!("{:?}", err))?; - let function_id = u16::try_from(value.function_id).map_err(|_| "Invalid function_id: overflowed u16")?; - - Ok(Self { - template_id, - function_id, - }) - } -} - -impl From for proto::types::FunctionRef { - fn from(value: FunctionRef) -> Self { - let template_id = value.template_id.as_bytes().to_vec(); - - Self { - template_id, - function_id: value.function_id.into(), - } - } -} - //---------------------------------- AggregateBody --------------------------------------------// impl TryFrom for AggregateBody { @@ -1160,3 +437,13 @@ impl TryFrom> for proto::types::Transaction { } } } + +// impl TryFrom for Signature { +// type Error = String; +// +// fn try_from(value: proto::types::Signature) -> Result { +// let public_nonce = PublicKey::from_bytes(&value.public_nonce).map_err(|_| "Invalid signature public_nonce")?; +// let signature = PrivateKey::from_bytes(&value.signature).map_err(|_| "Invalid signature")?; +// Ok(Signature::new(public_nonce, signature)) +// } +// } diff --git a/base_layer/core/src/transactions/transaction_components/output_features.rs b/base_layer/core/src/transactions/transaction_components/output_features.rs index 87f8dcc1c0..ee97214baa 100644 --- a/base_layer/core/src/transactions/transaction_components/output_features.rs +++ b/base_layer/core/src/transactions/transaction_components/output_features.rs @@ -47,6 +47,7 @@ use crate::{ transactions::transaction_components::{ side_chain::SideChainFeatures, AssetOutputFeatures, + CodeTemplateRegistration, CommitteeDefinitionFeatures, CommitteeMembers, ContractCheckpoint, @@ -155,6 +156,19 @@ impl OutputFeatures { } } + /// Creates template registration output features + pub fn for_template_registration(template_registration: CodeTemplateRegistration) -> OutputFeatures { + OutputFeatures { + output_type: OutputType::CodeTemplateRegistration, + sidechain_features: Some(Box::new( + SideChainFeatures::builder(Default::default()) + .with_template_registration(template_registration) + .finish(), + )), + ..Default::default() + } + } + pub fn for_asset_registration( metadata: Vec, public_key: PublicKey, @@ -483,10 +497,11 @@ mod test { use std::{convert::TryInto, io::ErrorKind, iter}; use tari_common_types::types::Signature; + use tari_utilities::hex::from_hex; use super::*; use crate::{ - consensus::check_consensus_encoding_correctness, + consensus::{check_consensus_encoding_correctness, MaxSizeString}, transactions::transaction_components::{ bytes_into_fixed_string, side_chain::{ @@ -498,6 +513,8 @@ mod test { RequirementsForConstitutionChange, SideChainConsensus, }, + BuildInfo, + CodeTemplateRegistration, CommitteeSignatures, ContractAcceptance, ContractAmendment, @@ -509,6 +526,7 @@ mod test { FunctionRef, PublicFunction, SignerSignature, + TemplateType, }, }; @@ -555,6 +573,27 @@ mod test { sidechain_features: Some(Box::new(SideChainFeatures { contract_id: FixedHash::zero(), constitution: Some(constitution.clone()), + template_registration: Some(CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: MaxSizeString::from_str_checked("🚀🚀🚀🚀🚀🚀🚀🚀").unwrap(), + template_version: 1, + template_type: TemplateType::Wasm { abi_version: 123 }, + build_info: BuildInfo { + repo_url: "/dns/github.com/https/tari_project/wasm_examples".try_into().unwrap(), + commit_hash: from_hex("ea29c9f92973fb7eda913902ff6173c62cb1e5df") + .unwrap() + .try_into() + .unwrap(), + }, + binary_sha: from_hex("c93747637517e3de90839637f0ce1ab7c8a3800b") + .unwrap() + .try_into() + .unwrap(), + binary_url: "/dns4/github.com/https/tari_project/wasm_examples/releases/download/v0.0.6/coin.zip" + .try_into() + .unwrap(), + }), definition: Some(ContractDefinition { contract_name: bytes_into_fixed_string("name"), contract_issuer: PublicKey::default(), diff --git a/base_layer/core/src/transactions/transaction_components/output_type.rs b/base_layer/core/src/transactions/transaction_components/output_type.rs index 40cb2b76f6..71f2d31c70 100644 --- a/base_layer/core/src/transactions/transaction_components/output_type.rs +++ b/base_layer/core/src/transactions/transaction_components/output_type.rs @@ -44,30 +44,33 @@ pub enum OutputType { Coinbase = 1, /// Output is a burned output and can not be spent ever. Burn = 2, - /// Output defines a side-chain contract. - ContractDefinition = 3, + /// Output defines a registration for a validator node + ValidatorRegistration = 3, + /// Output defines a new re-usable code template. + CodeTemplateRegistration = 4, /// Output defines the constitution for a side-chain contract. - ContractConstitution = 4, + ContractConstitution = 5, /// Output indicates validator node acceptance to run a contract. - ContractValidatorAcceptance = 5, + ContractValidatorAcceptance = 6, /// Output is a contract checkpoint. - ContractCheckpoint = 6, + ContractCheckpoint = 7, /// Output that defines a contract constitution proposal. - ContractConstitutionProposal = 7, + ContractConstitutionProposal = 8, /// Output that indicates acceptance of an existing contract constitution amendment proposal. - ContractConstitutionChangeAcceptance = 8, + ContractConstitutionChangeAcceptance = 9, /// Output that defines an amendment of a contract constitution. - ContractAmendment = 9, - + ContractAmendment = 10, + /// Output defines a side-chain contract. + ContractDefinition = 11, // TODO: Remove these deprecated flags - NonFungible = 10, - AssetRegistration = 11, - MintNonFungible = 12, - BurnNonFungible = 13, - SidechainInitialCheckpoint = 14, - SidechainCheckpoint = 15, - CommitteeInitialDefinition = 16, - CommitteeDefinition = 17, + NonFungible = 100, + AssetRegistration = 101, + MintNonFungible = 102, + BurnNonFungible = 103, + SidechainInitialCheckpoint = 104, + SidechainCheckpoint = 105, + CommitteeInitialDefinition = 106, + CommitteeDefinition = 107, } impl OutputType { @@ -146,8 +149,11 @@ mod tests { fn it_converts_from_byte_to_output_type() { assert_eq!(OutputType::from_byte(0), Some(OutputType::Standard)); assert_eq!(OutputType::from_byte(1), Some(OutputType::Coinbase)); - assert_eq!(OutputType::from_byte(16), Some(OutputType::CommitteeInitialDefinition)); - assert_eq!(OutputType::from_byte(17), Some(OutputType::CommitteeDefinition)); - assert_eq!(OutputType::from_byte(18), None); + assert_eq!(OutputType::from_byte(2), Some(OutputType::Burn)); + assert_eq!(OutputType::from_byte(3), Some(OutputType::ValidatorRegistration)); + assert_eq!(OutputType::from_byte(4), Some(OutputType::CodeTemplateRegistration)); + assert_eq!(OutputType::from_byte(106), Some(OutputType::CommitteeInitialDefinition)); + assert_eq!(OutputType::from_byte(107), Some(OutputType::CommitteeDefinition)); + assert_eq!(OutputType::from_byte(108), None); } } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/contract_constitution.rs b/base_layer/core/src/transactions/transaction_components/side_chain/contract_constitution.rs index 115c5a2c1d..03d88f6053 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/contract_constitution.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/contract_constitution.rs @@ -31,7 +31,7 @@ use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; use super::CommitteeMembers; -use crate::consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; +use crate::consensus::{read_byte, ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}; /// # ContractConstitution /// @@ -191,9 +191,8 @@ impl ConsensusEncodingSized for ConstitutionChangeFlags { impl ConsensusDecoding for ConstitutionChangeFlags { fn consensus_decode(reader: &mut R) -> Result { - let mut buf = [0u8; 1]; - reader.read_exact(&mut buf)?; - let flags = ConstitutionChangeFlags::from_bits(buf[0]) + let flag_bits = read_byte(reader)?; + let flags = ConstitutionChangeFlags::from_bits(flag_bits) .ok_or_else(|| io::Error::new(ErrorKind::Other, "Invalid change flag"))?; Ok(flags) } diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs index e640c16c94..f7b03ecb35 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/mod.rs @@ -70,6 +70,9 @@ pub use contract_checkpoint::ContractCheckpoint; mod checkpoint_challenge; pub use checkpoint_challenge::CheckpointChallenge; +mod template_registration; +pub use template_registration::{BuildInfo, CodeTemplateRegistration, TemplateType}; + // Length of FixedString pub const FIXED_STR_LEN: usize = 32; pub type FixedString = [u8; FIXED_STR_LEN]; diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs b/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs index 739aa4e65f..2d2a4f0687 100644 --- a/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs +++ b/base_layer/core/src/transactions/transaction_components/side_chain/sidechain_features.rs @@ -34,13 +34,19 @@ use super::{ }; use crate::{ consensus::{ConsensusDecoding, ConsensusEncoding, ConsensusEncodingSized}, - transactions::transaction_components::{side_chain::contract_checkpoint::ContractCheckpoint, ContractConstitution}, + transactions::transaction_components::{ + side_chain::contract_checkpoint::ContractCheckpoint, + CodeTemplateRegistration, + ContractConstitution, + }, }; #[derive(Debug, Clone, Hash, PartialEq, Deserialize, Serialize, Eq)] pub struct SideChainFeatures { pub contract_id: FixedHash, pub definition: Option, + pub template_registration: Option, + pub constitution: Option, pub acceptance: Option, pub update_proposal: Option, @@ -63,6 +69,7 @@ impl ConsensusEncoding for SideChainFeatures { fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { self.contract_id.consensus_encode(writer)?; self.definition.consensus_encode(writer)?; + self.template_registration.consensus_encode(writer)?; self.constitution.consensus_encode(writer)?; self.acceptance.consensus_encode(writer)?; self.update_proposal.consensus_encode(writer)?; @@ -81,6 +88,7 @@ impl ConsensusDecoding for SideChainFeatures { Ok(Self { contract_id: FixedHash::consensus_decode(reader)?, definition: ConsensusDecoding::consensus_decode(reader)?, + template_registration: ConsensusDecoding::consensus_decode(reader)?, constitution: ConsensusDecoding::consensus_decode(reader)?, acceptance: ConsensusDecoding::consensus_decode(reader)?, update_proposal: ConsensusDecoding::consensus_decode(reader)?, @@ -101,6 +109,7 @@ impl SideChainFeaturesBuilder { features: SideChainFeatures { contract_id, definition: None, + template_registration: None, constitution: None, acceptance: None, update_proposal: None, @@ -111,6 +120,11 @@ impl SideChainFeaturesBuilder { } } + pub fn with_template_registration(mut self, template_registration: CodeTemplateRegistration) -> Self { + self.features.template_registration = Some(template_registration); + self + } + pub fn with_contract_definition(mut self, contract_definition: ContractDefinition) -> Self { self.features.definition = Some(contract_definition); self @@ -159,12 +173,14 @@ mod tests { use std::convert::TryInto; use tari_common_types::types::{PublicKey, Signature}; + use tari_utilities::hex::from_hex; use super::*; use crate::{ - consensus::check_consensus_encoding_correctness, + consensus::{check_consensus_encoding_correctness, MaxSizeString}, transactions::transaction_components::{ bytes_into_fixed_string, + BuildInfo, CheckpointParameters, CommitteeMembers, CommitteeSignatures, @@ -177,9 +193,11 @@ mod tests { RequirementsForConstitutionChange, SideChainConsensus, SignerSignature, + TemplateType, }, }; + #[allow(clippy::too_many_lines)] #[test] fn it_encodes_and_decodes_correctly() { let constitution = ContractConstitution { @@ -217,6 +235,27 @@ mod tests { let subject = SideChainFeatures { contract_id: FixedHash::zero(), constitution: Some(constitution.clone()), + template_registration: Some(CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: MaxSizeString::from_str_checked("🚀🚀🚀🚀🚀🚀🚀🚀").unwrap(), + template_version: 1, + template_type: TemplateType::Wasm { abi_version: 123 }, + build_info: BuildInfo { + repo_url: "/dns/github.com/https/tari_project/wasm_examples".try_into().unwrap(), + commit_hash: from_hex("ea29c9f92973fb7eda913902ff6173c62cb1e5df") + .unwrap() + .try_into() + .unwrap(), + }, + binary_sha: from_hex("c93747637517e3de90839637f0ce1ab7c8a3800b") + .unwrap() + .try_into() + .unwrap(), + binary_url: "/dns4/github.com/https/tari_project/wasm_examples/releases/download/v0.0.6/coin.zip" + .try_into() + .unwrap(), + }), definition: Some(ContractDefinition { contract_name: bytes_into_fixed_string("name"), contract_issuer: PublicKey::default(), diff --git a/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs b/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs new file mode 100644 index 0000000000..83f67156f6 --- /dev/null +++ b/base_layer/core/src/transactions/transaction_components/side_chain/template_registration.rs @@ -0,0 +1,186 @@ +// Copyright 2022. The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use std::io::{Error, ErrorKind, Read, Write}; + +use serde::{Deserialize, Serialize}; +use tari_common_types::types::{PublicKey, Signature}; + +use crate::consensus::{ + read_byte, + ConsensusDecoding, + ConsensusEncoding, + ConsensusEncodingSized, + MaxSizeBytes, + MaxSizeString, +}; + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub struct CodeTemplateRegistration { + pub author_public_key: PublicKey, + pub author_signature: Signature, + pub template_name: MaxSizeString<32>, + pub template_version: u16, + pub template_type: TemplateType, + pub build_info: BuildInfo, + pub binary_sha: MaxSizeBytes<32>, + pub binary_url: MaxSizeString<255>, +} + +impl ConsensusEncoding for CodeTemplateRegistration { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + self.author_public_key.consensus_encode(writer)?; + self.author_signature.consensus_encode(writer)?; + self.template_name.consensus_encode(writer)?; + self.template_version.consensus_encode(writer)?; + self.template_type.consensus_encode(writer)?; + self.build_info.consensus_encode(writer)?; + self.binary_sha.consensus_encode(writer)?; + self.binary_url.consensus_encode(writer)?; + Ok(()) + } +} + +impl ConsensusEncodingSized for CodeTemplateRegistration {} + +impl ConsensusDecoding for CodeTemplateRegistration { + fn consensus_decode(reader: &mut R) -> Result { + let author_public_key = PublicKey::consensus_decode(reader)?; + let author_signature = Signature::consensus_decode(reader)?; + let template_name = MaxSizeString::consensus_decode(reader)?; + let template_version = u16::consensus_decode(reader)?; + let template_type = TemplateType::consensus_decode(reader)?; + let build_info = BuildInfo::consensus_decode(reader)?; + let binary_sha = MaxSizeBytes::consensus_decode(reader)?; + let binary_url = MaxSizeString::consensus_decode(reader)?; + + Ok(CodeTemplateRegistration { + author_public_key, + author_signature, + template_name, + template_version, + template_type, + build_info, + binary_sha, + binary_url, + }) + } +} + +// -------------------------------- TemplateType -------------------------------- // + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub enum TemplateType { + /// Indicates that the template is a WASM module + Wasm { abi_version: u16 }, +} + +impl TemplateType { + fn as_type_byte(&self) -> u8 { + match self { + TemplateType::Wasm { .. } => 0, + } + } +} + +impl ConsensusEncoding for TemplateType { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + writer.write_all(&[self.as_type_byte()])?; + match self { + TemplateType::Wasm { abi_version } => { + abi_version.consensus_encode(writer)?; + }, + } + + Ok(()) + } +} + +impl ConsensusEncodingSized for TemplateType {} + +impl ConsensusDecoding for TemplateType { + fn consensus_decode(reader: &mut R) -> Result { + let type_byte = read_byte(reader)?; + match type_byte { + 0 => { + let abi_version = u16::consensus_decode(reader)?; + Ok(TemplateType::Wasm { abi_version }) + }, + _ => Err(Error::new(ErrorKind::InvalidData, "Invalid template type")), + } + } +} + +// -------------------------------- BuildInfo -------------------------------- // + +#[derive(Debug, Clone, Hash, PartialEq, Eq, Deserialize, Serialize)] +pub struct BuildInfo { + pub repo_url: MaxSizeString<255>, + pub commit_hash: MaxSizeBytes<32>, +} + +impl ConsensusEncoding for BuildInfo { + fn consensus_encode(&self, writer: &mut W) -> Result<(), Error> { + self.repo_url.consensus_encode(writer)?; + self.commit_hash.consensus_encode(writer)?; + Ok(()) + } +} + +impl ConsensusEncodingSized for BuildInfo {} + +impl ConsensusDecoding for BuildInfo { + fn consensus_decode(reader: &mut R) -> Result { + let repo_url = MaxSizeString::consensus_decode(reader)?; + let commit_hash = MaxSizeBytes::consensus_decode(reader)?; + Ok(Self { repo_url, commit_hash }) + } +} + +#[cfg(test)] +mod tests { + use std::convert::TryInto; + + use super::*; + use crate::consensus::check_consensus_encoding_correctness; + + #[test] + fn it_encodes_and_decodes_correctly() { + let subject = CodeTemplateRegistration { + author_public_key: Default::default(), + author_signature: Default::default(), + template_name: "🐢 all the way down".try_into().unwrap(), + template_version: 0xff, + template_type: TemplateType::Wasm { abi_version: 0xffff }, + build_info: BuildInfo { + repo_url: "https://github.com/tari-project/wasm_template.git".try_into().unwrap(), + commit_hash: Default::default(), + }, + binary_sha: Default::default(), + binary_url: "/dns4/github.com/tcp/443/http/tari-project/wasm_examples/releases/download/v0.0.6/coin.zip" + .try_into() + .unwrap(), + }; + + check_consensus_encoding_correctness(subject).unwrap(); + } +} diff --git a/base_layer/wallet/src/assets/asset_manager.rs b/base_layer/wallet/src/assets/asset_manager.rs index b038f95126..c1d9ded012 100644 --- a/base_layer/wallet/src/assets/asset_manager.rs +++ b/base_layer/wallet/src/assets/asset_manager.rs @@ -25,17 +25,21 @@ use tari_common_types::{ transaction::TxId, types::{Commitment, FixedHash, PublicKey, Signature}, }; -use tari_core::transactions::transaction_components::{ - CommitteeSignatures, - ContractAmendment, - ContractCheckpoint, - ContractDefinition, - ContractUpdateProposal, - OutputFeatures, - OutputType, - SideChainFeatures, - TemplateParameter, - Transaction, +use tari_core::transactions::{ + tari_amount::T, + transaction_components::{ + CodeTemplateRegistration, + CommitteeSignatures, + ContractAmendment, + ContractCheckpoint, + ContractDefinition, + ContractUpdateProposal, + OutputFeatures, + OutputType, + SideChainFeatures, + TemplateParameter, + Transaction, + }, }; use crate::{ @@ -104,6 +108,31 @@ impl AssetManager { convert_to_asset(output) } + pub async fn create_code_template_registration( + &mut self, + template_registration: CodeTemplateRegistration, + ) -> Result<(TxId, Transaction), WalletError> { + let output = self + .output_manager + .create_output_with_features(10 * T, OutputFeatures { + output_type: OutputType::CodeTemplateRegistration, + sidechain_features: Some(Box::new( + SideChainFeatures::builder(Default::default()) + .with_template_registration(template_registration) + .finish(), + )), + ..Default::default() + }) + .await?; + + let (tx_id, transaction) = self + .output_manager + .create_send_to_self_with_output(vec![output], ASSET_FPG.into(), UtxoSelectionCriteria::default()) + .await?; + + Ok((tx_id, transaction)) + } + pub async fn create_registration_transaction( &mut self, name: String, diff --git a/base_layer/wallet/src/assets/asset_manager_handle.rs b/base_layer/wallet/src/assets/asset_manager_handle.rs index 4536c15f45..db2b5e91d1 100644 --- a/base_layer/wallet/src/assets/asset_manager_handle.rs +++ b/base_layer/wallet/src/assets/asset_manager_handle.rs @@ -25,6 +25,7 @@ use tari_common_types::{ types::{Commitment, FixedHash, PublicKey, Signature}, }; use tari_core::transactions::transaction_components::{ + CodeTemplateRegistration, CommitteeSignatures, ContractAmendment, ContractDefinition, @@ -81,6 +82,25 @@ impl AssetManagerHandle { } } + pub async fn create_code_template_registration( + &mut self, + registration: CodeTemplateRegistration, + ) -> Result<(TxId, Transaction), WalletError> { + match self + .handle + .call(AssetManagerRequest::CreateCodeTemplateRegistrationTransaction { + registration: Box::new(registration), + }) + .await?? + { + AssetManagerResponse::CreateCodeTemplateRegistration { transaction, tx_id } => Ok((tx_id, *transaction)), + _ => Err(WalletError::UnexpectedApiResponse { + method: "create_registration_transaction".to_string(), + api: "AssetManagerService".to_string(), + }), + } + } + pub async fn create_initial_asset_checkpoint( &mut self, contract_id: FixedHash, diff --git a/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs b/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs index c5147fcb0e..58524158a1 100644 --- a/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs +++ b/base_layer/wallet/src/assets/infrastructure/asset_manager_service.rs @@ -247,6 +247,13 @@ impl AssetManagerService { tx_id, }) }, + AssetManagerRequest::CreateCodeTemplateRegistrationTransaction { registration } => { + let (tx_id, transaction) = self.manager.create_code_template_registration(*registration).await?; + Ok(AssetManagerResponse::CreateCodeTemplateRegistration { + transaction: Box::new(transaction), + tx_id, + }) + }, } } } diff --git a/base_layer/wallet/src/assets/infrastructure/mod.rs b/base_layer/wallet/src/assets/infrastructure/mod.rs index bf53f6e195..a17e799416 100644 --- a/base_layer/wallet/src/assets/infrastructure/mod.rs +++ b/base_layer/wallet/src/assets/infrastructure/mod.rs @@ -27,6 +27,7 @@ use tari_common_types::{ types::{Commitment, FixedHash, PublicKey, Signature}, }; use tari_core::transactions::transaction_components::{ + CodeTemplateRegistration, CommitteeSignatures, ContractAmendment, ContractDefinition, @@ -48,6 +49,9 @@ pub enum AssetManagerRequest { GetOwnedAsset { public_key: PublicKey, }, + CreateCodeTemplateRegistrationTransaction { + registration: Box, + }, CreateRegistrationTransaction { name: String, public_key: Box, @@ -104,6 +108,7 @@ pub enum AssetManagerResponse { ListOwned { assets: Vec }, GetOwnedAsset { asset: Box }, CreateRegistrationTransaction { transaction: Box, tx_id: TxId }, + CreateCodeTemplateRegistration { transaction: Box, tx_id: TxId }, CreateMintingTransaction { transaction: Box, tx_id: TxId }, CreateInitialCheckpoint { transaction: Box, tx_id: TxId }, CreateFollowOnCheckpoint { transaction: Box, tx_id: TxId },