From ed9227de23f9f188610c35e4a31729010b032dca Mon Sep 17 00:00:00 2001 From: Jakub Zajkowski Date: Thu, 26 Sep 2024 14:39:31 +0200 Subject: [PATCH] Changing TransactionV1 structure. From now on a TransactionV1 consists of hash, payload and approvals. payload is a merge of header and body concepts from before. body is not represented in a type-constrained way, instead we use an amorphic fields of type BTreeMap which is more resistant to future changes. --- binary_port/src/error_code.rs | 2 +- execution_engine/src/engine_state/mod.rs | 5 +- execution_engine/src/engine_state/wasm_v1.rs | 582 ++++--- .../src/execute_request_builder.rs | 14 +- .../test/contract_api/add_contract_version.rs | 120 +- node/src/components/block_validator/state.rs | 24 +- node/src/components/block_validator/tests.rs | 94 +- node/src/components/contract_runtime/error.rs | 2 + .../components/contract_runtime/operations.rs | 80 +- node/src/components/contract_runtime/types.rs | 10 +- node/src/components/contract_runtime/utils.rs | 35 +- node/src/components/event_stream_server.rs | 11 +- .../components/event_stream_server/event.rs | 2 +- .../fetcher_impls/transaction_fetcher.rs | 5 +- node/src/components/storage.rs | 13 +- node/src/components/transaction_acceptor.rs | 112 +- .../components/transaction_acceptor/event.rs | 8 +- .../components/transaction_acceptor/tests.rs | 98 +- node/src/components/transaction_buffer.rs | 22 +- .../components/transaction_buffer/tests.rs | 37 +- node/src/effect.rs | 4 +- node/src/effect/requests.rs | 4 +- node/src/reactor/main_reactor/tests.rs | 4 +- .../main_reactor/tests/transactions.rs | 72 +- node/src/testing/fake_transaction_acceptor.rs | 14 +- node/src/types.rs | 4 +- node/src/types/appendable_block.rs | 12 +- node/src/types/block/meta_block.rs | 12 +- node/src/types/transaction.rs | 3 +- .../src/types/transaction/meta_transaction.rs | 279 ++++ .../meta_transaction/meta_transaction_v1.rs | 586 +++++++ .../meta_transaction/tranasction_lane.rs | 161 ++ .../meta_transaction/transaction_header.rs | 77 + .../transaction/transaction_footprint.rs | 39 +- node/src/utils/specimen.rs | 9 +- resources/local/chainspec.toml.in | 3 +- resources/production/chainspec.toml | 3 +- resources/test/sse_data_schema.json | 418 +---- types/src/block.rs | 29 +- types/src/block/block_body/block_body_v2.rs | 50 +- types/src/block/block_v2.rs | 33 +- .../test_block_v2_builder.rs | 122 +- types/src/chainspec.rs | 29 +- types/src/chainspec/transaction_config.rs | 6 +- .../transaction_v1_config.rs | 782 +++++++--- types/src/deploy_info.rs | 3 +- types/src/gens.rs | 138 +- types/src/lib.rs | 38 +- types/src/transaction.rs | 227 +-- types/src/transaction/deploy.rs | 10 +- .../initiator_addr_and_secret_key.rs | 2 +- types/src/transaction/pricing_mode.rs | 185 ++- types/src/transaction/transaction_category.rs | 71 - types/src/transaction/transaction_header.rs | 129 -- types/src/transaction/transaction_target.rs | 51 +- types/src/transaction/transaction_v1.rs | 1375 +++-------------- .../{transaction_v1_body => }/arg_handling.rs | 61 +- .../transaction/transaction_v1/errors_v1.rs | 73 +- .../transaction_v1/transaction_v1_body.rs | 661 -------- .../transaction_v1/transaction_v1_builder.rs | 448 ++++-- .../transaction_v1_builder/error.rs | 9 + .../transaction_v1/transaction_v1_category.rs | 76 - .../transaction_v1/transaction_v1_header.rs | 271 ---- .../transaction_v1/transaction_v1_payload.rs | 296 ++++ 64 files changed, 4280 insertions(+), 3875 deletions(-) create mode 100644 node/src/types/transaction/meta_transaction.rs create mode 100644 node/src/types/transaction/meta_transaction/meta_transaction_v1.rs create mode 100644 node/src/types/transaction/meta_transaction/tranasction_lane.rs create mode 100644 node/src/types/transaction/meta_transaction/transaction_header.rs delete mode 100644 types/src/transaction/transaction_category.rs delete mode 100644 types/src/transaction/transaction_header.rs rename types/src/transaction/transaction_v1/{transaction_v1_body => }/arg_handling.rs (94%) delete mode 100644 types/src/transaction/transaction_v1/transaction_v1_body.rs delete mode 100644 types/src/transaction/transaction_v1/transaction_v1_category.rs delete mode 100644 types/src/transaction/transaction_v1/transaction_v1_header.rs create mode 100644 types/src/transaction/transaction_v1/transaction_v1_payload.rs diff --git a/binary_port/src/error_code.rs b/binary_port/src/error_code.rs index 9b552d1f7b..6ebe361a3f 100644 --- a/binary_port/src/error_code.rs +++ b/binary_port/src/error_code.rs @@ -507,7 +507,7 @@ impl From for ErrorCode { InvalidTransactionV1::EntryPointCannotBeCall => { ErrorCode::InvalidTransactionEntryPointCannotBeCall } - InvalidTransactionV1::InvalidTransactionKind(_) => { + InvalidTransactionV1::InvalidTransactionLane(_) => { ErrorCode::InvalidTransactionInvalidTransactionKind } InvalidTransactionV1::GasPriceToleranceTooLow { .. } => { diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index 4dd5d69608..755fd4e9c8 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -23,7 +23,10 @@ pub use engine_config::{ }; pub use error::Error; use execution_kind::ExecutionKind; -pub use wasm_v1::{ExecutableItem, InvalidRequest, WasmV1Request, WasmV1Result}; +pub use wasm_v1::{ + ExecutableItem, InvalidRequest, SessionDataDeploy, SessionDataV1, SessionInputData, + WasmV1Request, WasmV1Result, +}; /// The maximum amount of motes that payment code execution can cost. pub const MAX_PAYMENT_AMOUNT: u64 = 2_500_000_000; diff --git a/execution_engine/src/engine_state/wasm_v1.rs b/execution_engine/src/engine_state/wasm_v1.rs index 75196cc26e..dc43c4c683 100644 --- a/execution_engine/src/engine_state/wasm_v1.rs +++ b/execution_engine/src/engine_state/wasm_v1.rs @@ -1,7 +1,4 @@ -use std::{ - collections::BTreeSet, - convert::{TryFrom, TryInto}, -}; +use std::{collections::BTreeSet, convert::TryFrom}; use serde::Serialize; use thiserror::Error; @@ -10,14 +7,193 @@ use casper_storage::data_access_layer::TransferResult; use casper_types::{ account::AccountHash, bytesrepr::Bytes, contract_messages::Messages, execution::Effects, BlockTime, DeployHash, Digest, ExecutableDeployItem, Gas, InitiatorAddr, Phase, PricingMode, - RuntimeArgs, Transaction, TransactionCategory, TransactionEntryPoint, TransactionHash, - TransactionInvocationTarget, TransactionTarget, TransactionV1, Transfer, + RuntimeArgs, TransactionEntryPoint, TransactionHash, TransactionInvocationTarget, + TransactionTarget, TransactionV1Hash, Transfer, }; use crate::engine_state::{DeployItem, Error as EngineError}; const DEFAULT_ENTRY_POINT: &str = "call"; +/// Structure that needs to be filled with data so the engine can assemble wasm for deploy. +pub struct SessionDataDeploy<'a> { + deploy_hash: &'a DeployHash, + session: &'a ExecutableDeployItem, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, +} + +impl<'a> SessionDataDeploy<'a> { + /// Constructor + pub fn new( + deploy_hash: &'a DeployHash, + session: &'a ExecutableDeployItem, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, + ) -> Self { + Self { + deploy_hash, + session, + initiator_addr, + signers, + is_standard_payment, + } + } + + /// Deploy hash of the deploy + pub fn deploy_hash(&self) -> &DeployHash { + self.deploy_hash + } + + /// executable item of the deploy + pub fn session(&self) -> &ExecutableDeployItem { + self.session + } + + /// initiator address of the deploy + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + /// signers of the deploy + pub fn signers(&self) -> BTreeSet { + self.signers.clone() + } +} + +/// Structure that needs to be filled with data so the engine can assemble wasm for v1. +pub struct SessionDataV1<'a> { + args: &'a RuntimeArgs, + target: &'a TransactionTarget, + entry_point: &'a TransactionEntryPoint, + is_install_upgrade: bool, + hash: &'a TransactionV1Hash, + pricing_mode: &'a PricingMode, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, +} + +impl<'a> SessionDataV1<'a> { + #[allow(clippy::too_many_arguments)] + /// Constructor + pub fn new( + args: &'a RuntimeArgs, + target: &'a TransactionTarget, + entry_point: &'a TransactionEntryPoint, + is_install_upgrade: bool, + hash: &'a TransactionV1Hash, + pricing_mode: &'a PricingMode, + initiator_addr: InitiatorAddr, + signers: BTreeSet, + is_standard_payment: bool, + ) -> Self { + Self { + args, + target, + entry_point, + is_install_upgrade, + hash, + pricing_mode, + initiator_addr, + signers, + is_standard_payment, + } + } + + /// Runtime args passed with the transaction. + pub fn args(&self) -> &RuntimeArgs { + self.args + } + + /// Target of the transaction. + pub fn target(&self) -> &TransactionTarget { + self.target + } + + /// Entry point of the transaction + pub fn entry_point(&self) -> &TransactionEntryPoint { + self.entry_point + } + + /// Should session be allowed to perform install/upgrade operations + pub fn is_install_upgrade(&self) -> bool { + self.is_install_upgrade + } + + /// Hash of the transaction + pub fn hash(&self) -> &TransactionV1Hash { + self.hash + } + + /// initiator address of the transaction + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + /// signers of the transaction + pub fn signers(&self) -> BTreeSet { + self.signers.clone() + } + + /// Pricing mode of the transaction + pub fn pricing_mode(&self) -> &PricingMode { + self.pricing_mode + } +} + +/// Wrapper enum abstracting data for assmbling WasmV1Requests +pub enum SessionInputData<'a> { + /// Variant for sessions created from deploy transactions + DeploySessionData { + /// Deploy session data + data: SessionDataDeploy<'a>, + }, + /// Variant for sessions created from v1 transactions + SessionDataV1 { + /// v1 session data + data: SessionDataV1<'a>, + }, +} + +impl<'a> SessionInputData<'a> { + /// Transaction hash for the session + pub fn transaction_hash(&self) -> TransactionHash { + match self { + SessionInputData::DeploySessionData { data } => { + TransactionHash::Deploy(*data.deploy_hash()) + } + SessionInputData::SessionDataV1 { data } => TransactionHash::V1(*data.hash()), + } + } + + /// Initiator address for the session + pub fn initiator_addr(&self) -> &InitiatorAddr { + match self { + SessionInputData::DeploySessionData { data } => data.initiator_addr(), + SessionInputData::SessionDataV1 { data } => data.initiator_addr(), + } + } + + /// Signers for the session + pub fn signers(&self) -> BTreeSet { + match self { + SessionInputData::DeploySessionData { data } => data.signers(), + SessionInputData::SessionDataV1 { data } => data.signers(), + } + } + + /// determines if the transaction from which this session data was created is a standard payment + pub fn is_standard_payment(&self) -> bool { + match self { + SessionInputData::DeploySessionData { data } => data.is_standard_payment, + SessionInputData::SessionDataV1 { data } => data.is_standard_payment, + } + } +} + /// Error returned if constructing a new [`WasmV1Request`] fails. #[derive(Clone, Eq, PartialEq, Error, Serialize, Debug)] pub enum InvalidRequest { @@ -120,18 +296,12 @@ impl WasmV1Request { state_hash: Digest, block_time: BlockTime, gas_limit: Gas, - transaction: &Transaction, + session_input_data: &SessionInputData, ) -> Result { - let info = match transaction { - Transaction::Deploy(deploy) => { - SessionInfo::try_from((deploy.session(), deploy.hash()))? - } - Transaction::V1(v1_txn) => SessionInfo::try_from(v1_txn)?, - }; - - let transaction_hash = transaction.hash(); - let initiator_addr = transaction.initiator_addr(); - let authorization_keys = transaction.signers(); + let info = SessionInfo::try_from(session_input_data)?; + let transaction_hash = session_input_data.transaction_hash(); + let initiator_addr = session_input_data.initiator_addr().clone(); + let authorization_keys = session_input_data.signers().clone(); Ok(WasmV1Request::new_from_executable_info( state_hash, block_time, @@ -148,18 +318,12 @@ impl WasmV1Request { state_hash: Digest, block_time: BlockTime, gas_limit: Gas, - transaction: &Transaction, + session_input_data: &SessionInputData, ) -> Result { - let info = match transaction { - Transaction::Deploy(deploy) => { - PaymentInfo::try_from((deploy.payment(), deploy.hash()))? - } - Transaction::V1(v1_txn) => PaymentInfo::try_from(v1_txn)?, - }; - - let transaction_hash = transaction.hash(); - let initiator_addr = transaction.initiator_addr(); - let authorization_keys = transaction.signers(); + let info = PaymentInfo::try_from(session_input_data)?; + let transaction_hash = session_input_data.transaction_hash(); + let initiator_addr = session_input_data.initiator_addr().clone(); + let authorization_keys = session_input_data.signers().clone(); Ok(WasmV1Request::new_from_executable_info( state_hash, block_time, @@ -184,7 +348,8 @@ impl WasmV1Request { .. }: &DeployItem, ) -> Result { - let info = SessionInfo::try_from((session, deploy_hash))?; + let transaction_hash = TransactionHash::Deploy(*deploy_hash); + let info = build_session_info_for_executable_item(session, transaction_hash)?; let transaction_hash = TransactionHash::Deploy(*deploy_hash); let initiator_addr = InitiatorAddr::AccountHash(*address); let authorization_keys = authorization_keys.clone(); @@ -212,7 +377,8 @@ impl WasmV1Request { .. }: &DeployItem, ) -> Result { - let info = PaymentInfo::try_from((payment, deploy_hash))?; + let transaction_hash = TransactionHash::Deploy(*deploy_hash); + let info = build_payment_info_for_executable_item(payment, transaction_hash)?; let transaction_hash = TransactionHash::Deploy(*deploy_hash); let initiator_addr = InitiatorAddr::AccountHash(*address); let authorization_keys = authorization_keys.clone(); @@ -403,88 +569,117 @@ impl Executable for SessionInfo { } } -impl TryFrom<(&ExecutableDeployItem, &DeployHash)> for SessionInfo { +impl TryFrom<&SessionInputData<'_>> for PaymentInfo { type Error = InvalidRequest; - fn try_from( - (session_item, deploy_hash): (&ExecutableDeployItem, &DeployHash), - ) -> Result { - let transaction_hash = TransactionHash::Deploy(*deploy_hash); - let session: ExecutableItem; - let session_entry_point: String; - let session_args: RuntimeArgs; - match session_item { - ExecutableDeployItem::ModuleBytes { module_bytes, args } => { - session = ExecutableItem::LegacyDeploy(module_bytes.clone()); - session_entry_point = DEFAULT_ENTRY_POINT.to_string(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredContractByHash { - hash, - entry_point, - args, - } => { - session = ExecutableItem::Invocation( - TransactionInvocationTarget::new_invocable_entity(*hash), - ); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredContractByName { - name, - entry_point, - args, - } => { - session = ExecutableItem::Invocation( - TransactionInvocationTarget::new_invocable_entity_alias(name.clone()), - ); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredVersionedContractByHash { - hash, - version, - entry_point, - args, - } => { - session = ExecutableItem::Invocation(TransactionInvocationTarget::new_package( - *hash, *version, - )); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::StoredVersionedContractByName { - name, - version, - entry_point, - args, - } => { - session = ExecutableItem::Invocation( - TransactionInvocationTarget::new_package_alias(name.clone(), *version), - ); - session_entry_point = entry_point.clone(); - session_args = args.clone(); - } - ExecutableDeployItem::Transfer { .. } => { - return Err(InvalidRequest::UnsupportedMode( - transaction_hash, - session_item.to_string(), - )); - } + fn try_from(input_data: &SessionInputData) -> Result { + match input_data { + SessionInputData::DeploySessionData { data } => PaymentInfo::try_from(data), + SessionInputData::SessionDataV1 { data } => PaymentInfo::try_from(data), } + } +} - Ok(SessionInfo(ExecutableInfo { - item: session, - entry_point: session_entry_point, - args: session_args, - })) +impl TryFrom<&SessionInputData<'_>> for SessionInfo { + type Error = InvalidRequest; + + fn try_from(input_data: &SessionInputData) -> Result { + match input_data { + SessionInputData::DeploySessionData { data } => SessionInfo::try_from(data), + SessionInputData::SessionDataV1 { data } => SessionInfo::try_from(data), + } + } +} + +impl TryFrom<&SessionDataDeploy<'_>> for SessionInfo { + type Error = InvalidRequest; + + fn try_from(deploy_data: &SessionDataDeploy) -> Result { + let transaction_hash = TransactionHash::Deploy(*deploy_data.deploy_hash()); + let session_item = deploy_data.session(); + build_session_info_for_executable_item(session_item, transaction_hash) + } +} + +fn build_session_info_for_executable_item( + session_item: &ExecutableDeployItem, + transaction_hash: TransactionHash, +) -> Result { + let session: ExecutableItem; + let session_entry_point: String; + let session_args: RuntimeArgs; + match session_item { + ExecutableDeployItem::ModuleBytes { module_bytes, args } => { + session = ExecutableItem::LegacyDeploy(module_bytes.clone()); + session_entry_point = DEFAULT_ENTRY_POINT.to_string(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredContractByHash { + hash, + entry_point, + args, + } => { + session = ExecutableItem::Invocation( + TransactionInvocationTarget::new_invocable_entity(*hash), + ); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredContractByName { + name, + entry_point, + args, + } => { + session = ExecutableItem::Invocation( + TransactionInvocationTarget::new_invocable_entity_alias(name.clone()), + ); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredVersionedContractByHash { + hash, + version, + entry_point, + args, + } => { + session = ExecutableItem::Invocation(TransactionInvocationTarget::new_package( + *hash, *version, + )); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::StoredVersionedContractByName { + name, + version, + entry_point, + args, + } => { + session = ExecutableItem::Invocation(TransactionInvocationTarget::new_package_alias( + name.clone(), + *version, + )); + session_entry_point = entry_point.clone(); + session_args = args.clone(); + } + ExecutableDeployItem::Transfer { .. } => { + return Err(InvalidRequest::UnsupportedMode( + transaction_hash, + session_item.to_string(), + )); + } } + + Ok(SessionInfo(ExecutableInfo { + item: session, + entry_point: session_entry_point, + args: session_args, + })) } -impl TryFrom<&TransactionV1> for SessionInfo { +impl TryFrom<&SessionDataV1<'_>> for SessionInfo { type Error = InvalidRequest; - fn try_from(v1_txn: &TransactionV1) -> Result { + fn try_from(v1_txn: &SessionDataV1) -> Result { let transaction_hash = TransactionHash::V1(*v1_txn.hash()); let args = v1_txn.args().clone(); let session = match v1_txn.target() { @@ -496,7 +691,10 @@ impl TryFrom<&TransactionV1> for SessionInfo { } TransactionTarget::Stored { id, .. } => { let TransactionEntryPoint::Custom(entry_point) = v1_txn.entry_point() else { - return Err(InvalidRequest::InvalidEntryPoint(transaction_hash, v1_txn.entry_point().to_string())); + return Err(InvalidRequest::InvalidEntryPoint( + transaction_hash, + v1_txn.entry_point().to_string(), + )); }; let item = ExecutableItem::Invocation(id.clone()); ExecutableInfo { @@ -512,28 +710,16 @@ impl TryFrom<&TransactionV1> for SessionInfo { v1_txn.entry_point().to_string(), )); }; - let category = v1_txn.transaction_category(); - let category: TransactionCategory = category.try_into().map_err(|_| { - InvalidRequest::InvalidCategory(transaction_hash, category.to_string()) - })?; - let item = match category { - TransactionCategory::InstallUpgrade => ExecutableItem::SessionBytes { - kind: SessionKind::InstallUpgradeBytecode, - module_bytes: module_bytes.clone(), - }, - TransactionCategory::Large - | TransactionCategory::Medium - | TransactionCategory::Small => ExecutableItem::SessionBytes { - kind: SessionKind::GenericBytecode, - module_bytes: module_bytes.clone(), - }, - _ => { - return Err(InvalidRequest::InvalidCategory( - transaction_hash, - category.to_string(), - )) - } + let kind = if v1_txn.is_install_upgrade() { + SessionKind::InstallUpgradeBytecode + } else { + SessionKind::GenericBytecode + }; + let item = ExecutableItem::SessionBytes { + kind, + module_bytes: module_bytes.clone(), }; + ExecutableInfo { item, entry_point: DEFAULT_ENTRY_POINT.to_owned(), @@ -567,85 +753,91 @@ impl Executable for PaymentInfo { } } -impl TryFrom<(&ExecutableDeployItem, &DeployHash)> for PaymentInfo { +impl TryFrom<&SessionDataDeploy<'_>> for PaymentInfo { type Error = InvalidRequest; - fn try_from( - (payment_item, deploy_hash): (&ExecutableDeployItem, &DeployHash), - ) -> Result { - let transaction_hash = TransactionHash::Deploy(*deploy_hash); - match payment_item { - ExecutableDeployItem::ModuleBytes { module_bytes, args } => { - let payment = if module_bytes.is_empty() { - return Err(InvalidRequest::UnsupportedMode( - transaction_hash, - "standard payment is no longer handled by the execution engine".to_string(), - )); - } else { - ExecutableItem::PaymentBytes(module_bytes.clone()) - }; - Ok(PaymentInfo(ExecutableInfo { - item: payment, - entry_point: DEFAULT_ENTRY_POINT.to_string(), - args: args.clone(), - })) - } - ExecutableDeployItem::StoredContractByHash { - hash, - args, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByHash(hash.value())), - entry_point: entry_point.clone(), - args: args.clone(), - })), - ExecutableDeployItem::StoredContractByName { - name, - args, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByName(name.clone())), - entry_point: entry_point.clone(), - args: args.clone(), - })), - ExecutableDeployItem::StoredVersionedContractByHash { - args, - hash, - version, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageHash { - addr: hash.value(), - version: *version, - }), - entry_point: entry_point.clone(), - args: args.clone(), - })), - ExecutableDeployItem::StoredVersionedContractByName { - name, - version, - args, - entry_point, - } => Ok(PaymentInfo(ExecutableInfo { - item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageName { - name: name.clone(), - version: *version, - }), - entry_point: entry_point.clone(), + fn try_from(deploy_data: &SessionDataDeploy) -> Result { + let payment_item = deploy_data.session(); + let transaction_hash = TransactionHash::Deploy(*deploy_data.deploy_hash()); + build_payment_info_for_executable_item(payment_item, transaction_hash) + } +} + +fn build_payment_info_for_executable_item( + payment_item: &ExecutableDeployItem, + transaction_hash: TransactionHash, +) -> Result { + match payment_item { + ExecutableDeployItem::ModuleBytes { module_bytes, args } => { + let payment = if module_bytes.is_empty() { + return Err(InvalidRequest::UnsupportedMode( + transaction_hash, + "standard payment is no longer handled by the execution engine".to_string(), + )); + } else { + ExecutableItem::PaymentBytes(module_bytes.clone()) + }; + Ok(PaymentInfo(ExecutableInfo { + item: payment, + entry_point: DEFAULT_ENTRY_POINT.to_string(), args: args.clone(), - })), - ExecutableDeployItem::Transfer { .. } => Err(InvalidRequest::UnexpectedVariant( - transaction_hash, - "payment item".to_string(), - )), + })) } + ExecutableDeployItem::StoredContractByHash { + hash, + args, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByHash(hash.value())), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::StoredContractByName { + name, + args, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByName(name.clone())), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::StoredVersionedContractByHash { + args, + hash, + version, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageHash { + addr: hash.value(), + version: *version, + }), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::StoredVersionedContractByName { + name, + version, + args, + entry_point, + } => Ok(PaymentInfo(ExecutableInfo { + item: ExecutableItem::Invocation(TransactionInvocationTarget::ByPackageName { + name: name.clone(), + version: *version, + }), + entry_point: entry_point.clone(), + args: args.clone(), + })), + ExecutableDeployItem::Transfer { .. } => Err(InvalidRequest::UnexpectedVariant( + transaction_hash, + "payment item".to_string(), + )), } } -impl TryFrom<&TransactionV1> for PaymentInfo { +impl TryFrom<&SessionDataV1<'_>> for PaymentInfo { type Error = InvalidRequest; - fn try_from(v1_txn: &TransactionV1) -> Result { + fn try_from(v1_txn: &SessionDataV1) -> Result { let transaction_hash = TransactionHash::V1(*v1_txn.hash()); match v1_txn.pricing_mode() { mode @ PricingMode::Classic { diff --git a/execution_engine_testing/test_support/src/execute_request_builder.rs b/execution_engine_testing/test_support/src/execute_request_builder.rs index 3000a00852..430aac2302 100644 --- a/execution_engine_testing/test_support/src/execute_request_builder.rs +++ b/execution_engine_testing/test_support/src/execute_request_builder.rs @@ -1,12 +1,12 @@ use std::collections::BTreeSet; use casper_execution_engine::engine_state::{ - deploy_item::DeployItem, ExecutableItem, WasmV1Request, + deploy_item::DeployItem, ExecutableItem, SessionInputData, WasmV1Request, }; use casper_types::{ account::AccountHash, addressable_entity::DEFAULT_ENTRY_POINT_NAME, runtime_args, AddressableEntityHash, BlockTime, Digest, EntityVersion, Gas, InitiatorAddr, PackageHash, - Phase, RuntimeArgs, Transaction, TransactionHash, TransactionV1Hash, + Phase, RuntimeArgs, TransactionHash, TransactionV1Hash, }; use crate::{DeployItemBuilder, ARG_AMOUNT, DEFAULT_BLOCK_TIME, DEFAULT_PAYMENT}; @@ -49,13 +49,13 @@ impl ExecuteRequestBuilder { pub const DEFAULT_ENTRY_POINT: &'static str = "call"; /// Converts a `Transaction` into an `ExecuteRequestBuilder`. - pub fn from_transaction(txn: &Transaction) -> Self { - let authorization_keys = txn.authorization_keys(); + pub fn from_session_input_data(session_input_data: &SessionInputData) -> Self { + let authorization_keys = session_input_data.signers(); let session = WasmV1Request::new_session( Self::DEFAULT_STATE_HASH, BlockTime::new(DEFAULT_BLOCK_TIME), Gas::new(5_000_000_000_000_u64), // TODO - set proper value - txn, + session_input_data, ) .unwrap(); @@ -63,7 +63,7 @@ impl ExecuteRequestBuilder { let payment_gas_limit: Gas; let payment_entry_point: String; let payment_args: RuntimeArgs; - if txn.is_standard_payment() { + if session_input_data.is_standard_payment() { payment = None; payment_gas_limit = Gas::zero(); payment_entry_point = DEFAULT_ENTRY_POINT_NAME.to_string(); @@ -73,7 +73,7 @@ impl ExecuteRequestBuilder { Self::DEFAULT_STATE_HASH, BlockTime::new(DEFAULT_BLOCK_TIME), Gas::new(5_000_000_000_000_u64), // TODO - set proper value - txn, + session_input_data, ) .unwrap(); payment = Some(request.executable_item); diff --git a/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs b/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs index ffc94a8817..3fca7d7280 100644 --- a/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs +++ b/execution_engine_testing/tests/src/test/contract_api/add_contract_version.rs @@ -2,15 +2,23 @@ use casper_engine_test_support::{ utils, ExecuteRequestBuilder, LmdbWasmTestBuilder, DEFAULT_ACCOUNT_ADDR, DEFAULT_ACCOUNT_SECRET_KEY, LOCAL_GENESIS_REQUEST, }; -use casper_execution_engine::{engine_state::Error as StateError, execution::ExecError}; +use casper_execution_engine::{ + engine_state::{Error as StateError, SessionDataDeploy, SessionDataV1, SessionInputData}, + execution::ExecError, +}; use casper_types::{ - ApiError, BlockTime, RuntimeArgs, Transaction, TransactionCategory, TransactionV1Builder, + ApiError, BlockTime, InitiatorAddr, Phase, PricingMode, RuntimeArgs, Transaction, + TransactionEntryPoint, TransactionTarget, TransactionV1Builder, }; const CONTRACT: &str = "do_nothing_stored.wasm"; const CHAIN_NAME: &str = "a"; const BLOCK_TIME: BlockTime = BlockTime::new(10); +pub(crate) const ARGS_MAP_KEY: u16 = 0; +pub(crate) const TARGET_MAP_KEY: u16 = 1; +pub(crate) const ENTRY_POINT_MAP_KEY: u16 = 2; + #[ignore] #[test] fn should_allow_add_contract_version_via_deploy() { @@ -24,24 +32,59 @@ fn should_allow_add_contract_version_via_deploy() { builder.exec(deploy_request).expect_success().commit(); } -fn try_add_contract_version(kind: TransactionCategory, should_succeed: bool) { +fn try_add_contract_version(is_install_upgrade: bool, should_succeed: bool) { let mut builder = LmdbWasmTestBuilder::default(); builder.run_genesis(LOCAL_GENESIS_REQUEST.clone()).commit(); let module_bytes = utils::read_wasm_file(CONTRACT); let txn = Transaction::from( - TransactionV1Builder::new_session(kind, module_bytes) + TransactionV1Builder::new_session(is_install_upgrade, module_bytes) .with_secret_key(&DEFAULT_ACCOUNT_SECRET_KEY) .with_chain_name(CHAIN_NAME) .build() .unwrap(), ); - - let txn_request = ExecuteRequestBuilder::from_transaction(&txn) - .with_block_time(BLOCK_TIME) - .build(); - + let txn_request = match txn { + Transaction::Deploy(ref deploy) => { + let initiator_addr = txn.initiator_addr(); + let is_standard_payment = deploy.payment().is_standard_payment(Phase::Payment); + let session_input_data = + to_deploy_session_input_data(is_standard_payment, initiator_addr, &txn); + ExecuteRequestBuilder::from_session_input_data(&session_input_data) + .with_block_time(BLOCK_TIME) + .build() + } + Transaction::V1(ref v1) => { + let initiator_addr = txn.initiator_addr(); + let is_standard_payment = if let PricingMode::Classic { + standard_payment, .. + } = v1.pricing_mode() + { + *standard_payment + } else { + true + }; + let args = v1.deserialize_field::(ARGS_MAP_KEY).unwrap(); + let target = v1 + .deserialize_field::(TARGET_MAP_KEY) + .unwrap(); + let entry_point = v1 + .deserialize_field::(ENTRY_POINT_MAP_KEY) + .unwrap(); + let session_input_data = to_v1_session_input_data( + is_standard_payment, + initiator_addr, + &args, + &target, + &entry_point, + &txn, + ); + ExecuteRequestBuilder::from_session_input_data(&session_input_data) + .with_block_time(BLOCK_TIME) + .build() + } + }; builder.exec(txn_request); if should_succeed { @@ -53,14 +96,69 @@ fn try_add_contract_version(kind: TransactionCategory, should_succeed: bool) { } } +fn to_deploy_session_input_data( + is_standard_payment: bool, + initiator_addr: InitiatorAddr, + txn: &Transaction, +) -> SessionInputData<'_> { + match txn { + Transaction::Deploy(deploy) => { + let data = SessionDataDeploy::new( + deploy.hash(), + deploy.session(), + initiator_addr, + txn.signers().clone(), + is_standard_payment, + ); + SessionInputData::DeploySessionData { data } + } + Transaction::V1(_) => { + panic!("unexpected transaction v1"); + } + } +} + +fn to_v1_session_input_data<'a>( + is_standard_payment: bool, + initiator_addr: InitiatorAddr, + args: &'a RuntimeArgs, + target: &'a TransactionTarget, + entry_point: &'a TransactionEntryPoint, + txn: &'a Transaction, +) -> SessionInputData<'a> { + let is_install_upgrade = match target { + TransactionTarget::Session { + is_install_upgrade, .. + } => *is_install_upgrade, + _ => false, + }; + match txn { + Transaction::Deploy(_) => panic!("unexpected deploy transaction"), + Transaction::V1(transaction_v1) => { + let data = SessionDataV1::new( + args, + target, + entry_point, + is_install_upgrade, + transaction_v1.hash(), + transaction_v1.pricing_mode(), + initiator_addr, + txn.signers().clone(), + is_standard_payment, + ); + SessionInputData::SessionDataV1 { data } + } + } +} + #[ignore] #[test] fn should_allow_add_contract_version_via_transaction_v1_installer_upgrader() { - try_add_contract_version(TransactionCategory::InstallUpgrade, true) + try_add_contract_version(true, true) } #[ignore] #[test] fn should_disallow_add_contract_version_via_transaction_v1_standard() { - try_add_contract_version(TransactionCategory::Large, false) + try_add_contract_version(false, false) } diff --git a/node/src/components/block_validator/state.rs b/node/src/components/block_validator/state.rs index b614110537..f6ed8b0e24 100644 --- a/node/src/components/block_validator/state.rs +++ b/node/src/components/block_validator/state.rs @@ -125,7 +125,7 @@ impl BlockValidationState { return (state, Some(responder)); } - if Self::validate_transaction_category_counts(proposed_block, &chainspec.transaction_config) + if Self::validate_transaction_lane_counts(proposed_block, &chainspec.transaction_config) .is_err() { let state = BlockValidationState::Invalid(proposed_block.timestamp()); @@ -187,15 +187,15 @@ impl BlockValidationState { (state, None) } - fn validate_transaction_category_counts( + fn validate_transaction_lane_counts( block: &ProposedBlock, config: &TransactionConfig, ) -> Result<(), ()> { - for supported_category in config.transaction_v1_config.get_supported_categories() { - let transactions = block.value().count(Some(supported_category)); + for supported_lane in config.transaction_v1_config.get_supported_lanes() { + let transactions = block.value().count(Some(supported_lane)); let lane_count_limit = config .transaction_v1_config - .get_max_transaction_count(supported_category); + .get_max_transaction_count(supported_lane); if lane_count_limit < transactions as u64 { warn!("too many transactions in category: {lane_count_limit}"); return Err(()); @@ -550,7 +550,7 @@ mod tests { use rand::Rng; use casper_types::{ - testing::TestRng, ChainspecRawBytes, TimeDiff, Transaction, TransactionHash, + testing::TestRng, ChainspecRawBytes, TimeDiff, Transaction, TransactionHash, TransactionV1, }; use super::{super::tests::*, *}; @@ -611,8 +611,8 @@ mod tests { let mut ret = vec![]; for _ in 0..auction_count { let txn = new_auction(self.rng, timestamp, ttl); - ret.push((TransactionHash::V1(*txn.hash()), txn.approvals().clone())); - self.transactions.push(Transaction::V1(txn)); + ret.push((txn.hash(), txn.approvals().clone())); + self.transactions.push(txn); } ret }; @@ -620,9 +620,11 @@ mod tests { let install_upgrade_for_block = { let mut ret = vec![]; for _ in 0..install_upgrade_count { - let txn = new_install_upgrade(self.rng, timestamp, ttl); - ret.push((TransactionHash::V1(*txn.hash()), txn.approvals().clone())); - self.transactions.push(Transaction::V1(txn)); + let txn: Transaction = + TransactionV1::random_install_upgrade(self.rng, Some(timestamp), Some(ttl)) + .into(); + ret.push((txn.hash(), txn.approvals().clone())); + self.transactions.push(txn); } ret }; diff --git a/node/src/components/block_validator/tests.rs b/node/src/components/block_validator/tests.rs index 9362230ae0..4ad1bd684f 100644 --- a/node/src/components/block_validator/tests.rs +++ b/node/src/components/block_validator/tests.rs @@ -8,8 +8,8 @@ use casper_types::{ bytesrepr::Bytes, runtime_args, system::standard_payment::ARG_AMOUNT, testing::TestRng, Block, BlockSignatures, BlockSignaturesV2, Chainspec, ChainspecRawBytes, Deploy, ExecutableDeployItem, FinalitySignatureV2, RuntimeArgs, SecretKey, TestBlockBuilder, TimeDiff, Transaction, - TransactionV1, TransactionV1Config, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, - U512, + TransactionHash, TransactionId, TransactionV1, TransactionV1Config, AUCTION_LANE_ID, + INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, MINT_LANE_ID, U512, }; use crate::{ @@ -25,8 +25,6 @@ use crate::{ use super::*; -const LARGE_LANE_ID: u8 = 3; - #[derive(Debug, From)] enum ReactorEvent { #[from] @@ -154,7 +152,7 @@ pub(super) fn new_proposed_block_with_cited_signatures( INSTALL_UPGRADE_LANE_ID, install_upgrade.into_iter().collect(), ); - ret.insert(LARGE_LANE_ID, standard.into_iter().collect()); + ret.insert(LARGE_WASM_LANE_ID, standard.into_iter().collect()); ret }; let block_payload = BlockPayload::new(transactions, vec![], cited_signatures, true, 1u8); @@ -182,23 +180,29 @@ pub(super) fn new_v1_standard( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> TransactionV1 { - TransactionV1::random_wasm(rng, Some(timestamp), Some(ttl)) +) -> Transaction { + let transaction_v1 = TransactionV1::random_wasm(rng, Some(timestamp), Some(ttl)); + Transaction::V1(transaction_v1) } -pub(super) fn new_auction(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> TransactionV1 { - TransactionV1::random_auction(rng, Some(timestamp), Some(ttl)) +pub(super) fn new_auction(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Transaction { + let transaction_v1 = TransactionV1::random_auction(rng, Some(timestamp), Some(ttl)); + Transaction::V1(transaction_v1) } pub(super) fn new_install_upgrade( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> TransactionV1 { - TransactionV1::random_install_upgrade(rng, Some(timestamp), Some(ttl)) +) -> Transaction { + TransactionV1::random_install_upgrade(rng, Some(timestamp), Some(ttl)).into() } -pub(super) fn new_legacy_deploy(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Deploy { +pub(super) fn new_legacy_deploy( + rng: &mut TestRng, + timestamp: Timestamp, + ttl: TimeDiff, +) -> Transaction { let secret_key = SecretKey::random(rng); let chain_name = "chain".to_string(); let payment = ExecutableDeployItem::ModuleBytes { @@ -223,21 +227,22 @@ pub(super) fn new_legacy_deploy(rng: &mut TestRng, timestamp: Timestamp, ttl: Ti &secret_key, None, ) + .into() } pub(super) fn new_v1_transfer( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> TransactionV1 { - TransactionV1::random_transfer(rng, Some(timestamp), Some(ttl)) +) -> Transaction { + TransactionV1::random_transfer(rng, Some(timestamp), Some(ttl)).into() } pub(super) fn new_legacy_transfer( rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff, -) -> Deploy { +) -> Transaction { let secret_key = SecretKey::random(rng); let chain_name = "chain".to_string(); let payment = ExecutableDeployItem::ModuleBytes { @@ -261,21 +266,22 @@ pub(super) fn new_legacy_transfer( &secret_key, None, ) + .into() } pub(super) fn new_mint(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Transaction { if rng.gen() { - new_v1_transfer(rng, timestamp, ttl).into() + new_v1_transfer(rng, timestamp, ttl) } else { - new_legacy_transfer(rng, timestamp, ttl).into() + new_legacy_transfer(rng, timestamp, ttl) } } pub(super) fn new_standard(rng: &mut TestRng, timestamp: Timestamp, ttl: TimeDiff) -> Transaction { if rng.gen() { - new_v1_standard(rng, timestamp, ttl).into() + new_v1_standard(rng, timestamp, ttl) } else { - new_legacy_deploy(rng, timestamp, ttl).into() + new_legacy_deploy(rng, timestamp, ttl) } } @@ -286,8 +292,8 @@ pub(super) fn new_non_transfer( ) -> Transaction { match rng.gen_range(0..3) { 0 => new_standard(rng, timestamp, ttl), - 1 => new_install_upgrade(rng, timestamp, ttl).into(), - 2 => new_auction(rng, timestamp, ttl).into(), + 1 => new_install_upgrade(rng, timestamp, ttl), + 2 => new_auction(rng, timestamp, ttl), _ => unreachable!(), } } @@ -795,8 +801,8 @@ async fn ttl() { new_non_transfer(&mut rng, 900.into(), ttl), ]; let transfers: Vec = vec![ - new_v1_transfer(&mut rng, 1000.into(), ttl).into(), - new_v1_transfer(&mut rng, 900.into(), ttl).into(), + new_v1_transfer(&mut rng, 1000.into(), ttl), + new_v1_transfer(&mut rng, 900.into(), ttl), ]; let mut transactions_context = ValidationContext::new() @@ -854,10 +860,10 @@ async fn transfer_transaction_mixup_and_replay() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); // First we make sure that our transfers and transactions would normally be valid. let transactions = vec![deploy_legacy.clone(), transaction_v1.clone()]; @@ -985,7 +991,7 @@ async fn should_fetch_from_multiple_peers() { .map(|i| new_non_transfer(&mut rng, (900 + i).into(), ttl)) .collect_vec(); let transfers = (0..peer_count) - .map(|i| Transaction::V1(new_v1_transfer(&mut rng, (1000 + i).into(), ttl))) + .map(|i| new_v1_transfer(&mut rng, (1000 + i).into(), ttl)) .collect_vec(); // Assemble the block to be validated. @@ -1156,10 +1162,10 @@ async fn should_validate_block_with_signatures() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) @@ -1183,10 +1189,10 @@ async fn should_fetch_missing_signature() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) @@ -1213,10 +1219,10 @@ async fn should_fail_if_unable_to_fetch_signature() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) @@ -1263,10 +1269,10 @@ async fn should_validate_with_delayed_block() { let mut rng = TestRng::new(); let ttl = TimeDiff::from_millis(200); let timestamp = Timestamp::from(1000); - let deploy_legacy = Transaction::from(new_legacy_deploy(&mut rng, timestamp, ttl)); - let transaction_v1 = Transaction::from(new_v1_standard(&mut rng, timestamp, ttl)); - let transfer_legacy = Transaction::from(new_legacy_transfer(&mut rng, timestamp, ttl)); - let transfer_v1 = Transaction::from(new_v1_transfer(&mut rng, timestamp, ttl)); + let deploy_legacy = new_legacy_deploy(&mut rng, timestamp, ttl); + let transaction_v1 = new_v1_standard(&mut rng, timestamp, ttl); + let transfer_legacy = new_legacy_transfer(&mut rng, timestamp, ttl); + let transfer_v1 = new_v1_transfer(&mut rng, timestamp, ttl); let context = ValidationContext::new() .with_num_validators(&mut rng, 3) diff --git a/node/src/components/contract_runtime/error.rs b/node/src/components/contract_runtime/error.rs index 5e300836cd..bd35a17760 100644 --- a/node/src/components/contract_runtime/error.rs +++ b/node/src/components/contract_runtime/error.rs @@ -154,4 +154,6 @@ pub enum BlockExecutionError { #[error("Unsupported execution kind: {0}")] /// Unsupported execution kind UnsupportedTransactionKind(u8), + #[error("Error while converting transaction to internal representation: {0}")] + TransactionConversion(String), } diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index 3ccc5d8f23..f64113a02b 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -1,6 +1,5 @@ -use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::Instant}; - use itertools::Itertools; +use std::{collections::BTreeMap, convert::TryInto, sync::Arc, time::Instant}; use tracing::{debug, error, info, trace, warn}; use casper_execution_engine::engine_state::{ExecutionEngineV1, WasmV1Request, WasmV1Result}; @@ -28,7 +27,7 @@ use casper_types::{ execution::{Effects, ExecutionResult, TransformKindV2, TransformV2}, system::handle_payment::ARG_AMOUNT, BlockHash, BlockHeader, BlockTime, BlockV2, CLValue, Chainspec, ChecksumRegistry, Digest, - EraEndV2, EraId, FeeHandling, Gas, GasLimited, Key, ProtocolVersion, PublicKey, RefundHandling, + EraEndV2, EraId, FeeHandling, Gas, Key, ProtocolVersion, PublicKey, RefundHandling, Transaction, AUCTION_LANE_ID, MINT_LANE_ID, U512, }; @@ -41,7 +40,7 @@ use super::{ use crate::{ components::fetcher::FetchItem, contract_runtime::types::ExecutionArtifactBuilder, - types::{self, Chunkable, ExecutableBlock, InternalEraReport}, + types::{self, Chunkable, ExecutableBlock, InternalEraReport, MetaTransaction}, }; /// Executes a finalized block. @@ -135,14 +134,17 @@ pub fn execute_finalized_block( } } - for transaction in executable_block.transactions { - let mut artifact_builder = ExecutionArtifactBuilder::new(&transaction); + let transaction_config = &chainspec.transaction_config; + for stored_transaction in executable_block.transactions { + let mut artifact_builder = ExecutionArtifactBuilder::new(&stored_transaction); + let transaction = MetaTransaction::from(&stored_transaction, transaction_config) + .map_err(|err| BlockExecutionError::TransactionConversion(err.to_string()))?; let initiator_addr = transaction.initiator_addr(); let transaction_hash = transaction.hash(); let runtime_args = transaction.session_args().clone(); let entry_point = transaction.entry_point(); - let authorization_keys = transaction.authorization_keys(); + let authorization_keys = transaction.signers(); /* we solve for halting state using a `gas limit` which is the maximum amount of @@ -165,19 +167,24 @@ pub fn execute_finalized_block( */ // NOTE: this is the allowed computation limit (gas limit) - let gas_limit = match transaction.gas_limit(chainspec) { - Ok(gas) => gas, - Err(ite) => { - debug!(%transaction_hash, %ite, "invalid transaction (gas limit)"); - artifact_builder.with_invalid_transaction(&ite); - artifacts.push(artifact_builder.build()); - continue; - } - }; + let gas_limit = + match stored_transaction.gas_limit(chainspec, transaction.transaction_lane()) { + Ok(gas) => gas, + Err(ite) => { + debug!(%transaction_hash, %ite, "invalid transaction (gas limit)"); + artifact_builder.with_invalid_transaction(&ite); + artifacts.push(artifact_builder.build()); + continue; + } + }; artifact_builder.with_gas_limit(gas_limit); // NOTE: this is the actual adjusted cost that we charge for (gas limit * gas price) - let cost = match transaction.gas_cost(chainspec, current_gas_price) { + let cost = match stored_transaction.gas_cost( + chainspec, + transaction.transaction_lane(), + current_gas_price, + ) { Ok(motes) => motes.value(), Err(ite) => { debug!(%transaction_hash, "invalid transaction (motes conversion)"); @@ -232,11 +239,12 @@ pub fn execute_finalized_block( // using a multiple of a small value. chainspec.transaction_config.native_transfer_minimum_motes * 5, ); + let session_input_data = transaction.to_session_input_data(); let pay_result = match WasmV1Request::new_custom_payment( state_root_hash, block_time, custom_payment_gas_limit, - &transaction, + &session_input_data, ) { Ok(mut pay_request) => { // We'll send a hint to the custom payment logic on the amount @@ -276,12 +284,12 @@ pub fn execute_finalized_block( ProofHandling::NoProofs, )); - let category = transaction.transaction_category(); + let lane_id = transaction.transaction_lane(); let allow_execution = { let is_not_penalized = !balance_identifier.is_penalty(); let sufficient_balance = initial_balance_result.is_sufficient(cost); - let is_supported = chainspec.is_supported(category); + let is_supported = chainspec.is_supported(lane_id); trace!(%transaction_hash, ?sufficient_balance, ?is_not_penalized, ?is_supported, "payment preprocessing"); is_not_penalized && sufficient_balance && is_supported }; @@ -305,9 +313,9 @@ pub fn execute_finalized_block( .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; } - trace!(%transaction_hash, ?category, "eligible for execution"); - match category { - category if category == MINT_LANE_ID => { + trace!(%transaction_hash, ?lane_id, "eligible for execution"); + match lane_id { + lane_id if lane_id == MINT_LANE_ID => { let transfer_result = scratch_state.transfer(TransferRequest::with_runtime_args( native_runtime_config.clone(), @@ -326,7 +334,7 @@ pub fn execute_finalized_block( .with_transfer_result(transfer_result) .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; } - category if category == AUCTION_LANE_ID => { + lane_id if lane_id == AUCTION_LANE_ID => { match AuctionMethod::from_parts(entry_point, &runtime_args, chainspec) { Ok(auction_method) => { let bidding_result = scratch_state.bidding(BiddingRequest::new( @@ -360,17 +368,18 @@ pub fn execute_finalized_block( } _ => { let wasm_v1_start = Instant::now(); + let session_input_data = transaction.to_session_input_data(); match WasmV1Request::new_session( state_root_hash, block_time, gas_limit, - &transaction, + &session_input_data, ) { Ok(wasm_v1_request) => { - trace!(%transaction_hash, ?category, ?wasm_v1_request, "able to get wasm v1 request"); + trace!(%transaction_hash, ?lane_id, ?wasm_v1_request, "able to get wasm v1 request"); let wasm_v1_result = execution_engine_v1.execute(&scratch_state, wasm_v1_request); - trace!(%transaction_hash, ?category, ?wasm_v1_result, "able to get wasm v1 result"); + trace!(%transaction_hash, ?lane_id, ?wasm_v1_result, "able to get wasm v1 result"); state_root_hash = scratch_state.commit_effects( state_root_hash, wasm_v1_result.effects().clone(), @@ -381,7 +390,7 @@ pub fn execute_finalized_block( .map_err(|_| BlockExecutionError::RootNotFound(state_root_hash))?; } Err(ire) => { - debug!(%transaction_hash, ?category, ?ire, "unable to get wasm v1 request"); + debug!(%transaction_hash, ?lane_id, ?ire, "unable to get wasm v1 request"); artifact_builder.with_invalid_wasm_v1_request(&ire); } }; @@ -1009,19 +1018,25 @@ pub(super) fn speculatively_execute( chainspec: &Chainspec, execution_engine_v1: &ExecutionEngineV1, block_header: BlockHeader, - transaction: Transaction, + input_transaction: Transaction, ) -> SpeculativeExecutionResult where S: StateProvider, { + let transaction_config = &chainspec.transaction_config; + let maybe_transaction = MetaTransaction::from(&input_transaction, transaction_config); + if let Err(error) = maybe_transaction { + return SpeculativeExecutionResult::invalid_transaction(error); + } + let transaction = maybe_transaction.unwrap(); let state_root_hash = block_header.state_root_hash(); let block_time = block_header .timestamp() .saturating_add(chainspec.core_config.minimum_block_time); - let gas_limit = match transaction.gas_limit(chainspec) { + let gas_limit = match input_transaction.gas_limit(chainspec, transaction.transaction_lane()) { Ok(gas_limit) => gas_limit, Err(_) => { - return SpeculativeExecutionResult::invalid_gas_limit(transaction); + return SpeculativeExecutionResult::invalid_gas_limit(input_transaction); } }; @@ -1050,11 +1065,12 @@ where block_header.block_hash(), )) } else { + let session_input_data = transaction.to_session_input_data(); let wasm_v1_result = match WasmV1Request::new_session( *state_root_hash, block_time.into(), gas_limit, - &transaction, + &session_input_data, ) { Ok(wasm_v1_request) => execution_engine_v1.execute(state_provider, wasm_v1_request), Err(error) => WasmV1Result::invalid_executable_item(gas_limit, error), diff --git a/node/src/components/contract_runtime/types.rs b/node/src/components/contract_runtime/types.rs index bd65ddf60e..469ebc7318 100644 --- a/node/src/components/contract_runtime/types.rs +++ b/node/src/components/contract_runtime/types.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, sync::Arc}; +use crate::types::TransactionHeader; use casper_types::{execution::PaymentInfo, InitiatorAddr, Transfer}; use datasize::DataSize; use serde::Serialize; @@ -18,8 +19,7 @@ use casper_types::{ contract_messages::Messages, execution::{Effects, ExecutionResult, ExecutionResultV2}, BlockHash, BlockHeaderV2, BlockV2, Digest, EraId, Gas, InvalidDeploy, InvalidTransaction, - InvalidTransactionV1, ProtocolVersion, PublicKey, Transaction, TransactionHash, - TransactionHeader, U512, + InvalidTransactionV1, ProtocolVersion, PublicKey, Transaction, TransactionHash, U512, }; /// Request for validator weights for a specific era. @@ -83,7 +83,7 @@ impl ExecutionArtifactBuilder { ExecutionArtifactBuilder { effects: Effects::new(), hash: transaction.hash(), - header: transaction.header(), + header: transaction.into(), error_message: None, transfers: vec![], messages: Default::default(), @@ -387,6 +387,10 @@ impl SpeculativeExecutionResult { ), } } + + pub fn invalid_transaction(error: InvalidTransaction) -> Self { + SpeculativeExecutionResult::InvalidTransaction(error) + } } /// State to use to construct the next block in the blockchain. Includes the state root hash for the diff --git a/node/src/components/contract_runtime/utils.rs b/node/src/components/contract_runtime/utils.rs index 67f66f9837..46abc2c5be 100644 --- a/node/src/components/contract_runtime/utils.rs +++ b/node/src/components/contract_runtime/utils.rs @@ -35,9 +35,7 @@ use casper_storage::{ }, global_state::state::{lmdb::LmdbGlobalState, CommitProvider, StateProvider}, }; -use casper_types::{ - BlockHash, Chainspec, Digest, EraId, Gas, GasLimited, Key, ProtocolUpgradeConfig, -}; +use casper_types::{BlockHash, Chainspec, Digest, EraId, Gas, Key, ProtocolUpgradeConfig}; /// Maximum number of resource intensive tasks that can be run in parallel. /// @@ -137,12 +135,18 @@ pub(super) async fn exec_or_requeue( let switch_block_utilization_score = { let mut has_hit_slot_limt = false; + let mut transaction_hash_to_lane_id = HashMap::new(); - for (category, transactions) in executable_block.transaction_map.iter() { + for (lane_id, transactions) in executable_block.transaction_map.iter() { + transaction_hash_to_lane_id.extend( + transactions + .iter() + .map(|transaction| (transaction, *lane_id)), + ); let max_count = chainspec .transaction_config .transaction_v1_config - .get_max_transaction_count(*category); + .get_max_transaction_count(*lane_id); if max_count == transactions.len() as u64 { has_hit_slot_limt = true; } @@ -162,16 +166,25 @@ pub(super) async fn exec_or_requeue( Ratio::new(total_size_of_transactions * 100, max_block_size).to_integer() }; - let gas_utilization: u64 = { let total_gas_limit: u64 = executable_block .transactions .iter() - .map(|transaction| match transaction.gas_limit(&chainspec) { - Ok(gas_limit) => gas_limit.value().as_u64(), - Err(_) => { - warn!("Unable to determine gas limit"); - 0u64 + .map(|transaction| { + match transaction_hash_to_lane_id.get(&transaction.hash()) { + Some(lane_id) => { + match &transaction.gas_limit(&chainspec, *lane_id) { + Ok(gas_limit) => gas_limit.value().as_u64(), + Err(_) => { + warn!("Unable to determine gas limit"); + 0u64 + } + } + } + None => { + warn!("Unable to determine gas limit"); + 0u64 + } } }) .sum(); diff --git a/node/src/components/event_stream_server.rs b/node/src/components/event_stream_server.rs index ff7f0882bd..e8d9820e3c 100644 --- a/node/src/components/event_stream_server.rs +++ b/node/src/components/event_stream_server.rs @@ -35,13 +35,14 @@ use tokio::sync::{ use tracing::{error, info, warn}; use warp::Filter; -use casper_types::{InitiatorAddr, ProtocolVersion, TransactionHeader}; +use casper_types::{InitiatorAddr, ProtocolVersion}; use super::Component; use crate::{ components::{ComponentState, InitializedComponent, PortBoundComponent}, effect::{EffectBuilder, Effects}, reactor::main_reactor::MainEvent, + types::TransactionHeader, utils::{self, ListeningError}, NodeRng, }; @@ -304,10 +305,10 @@ where deploy_header.timestamp(), deploy_header.ttl(), ), - TransactionHeader::V1(txn_header) => ( - txn_header.initiator_addr().clone(), - txn_header.timestamp(), - txn_header.ttl(), + TransactionHeader::V1(metadata) => ( + metadata.initiator_addr().clone(), + metadata.timestamp(), + metadata.ttl(), ), }; self.broadcast(SseData::TransactionProcessed { diff --git a/node/src/components/event_stream_server/event.rs b/node/src/components/event_stream_server/event.rs index 7eec8d1b68..d9414ded2b 100644 --- a/node/src/components/event_stream_server/event.rs +++ b/node/src/components/event_stream_server/event.rs @@ -3,13 +3,13 @@ use std::{ sync::Arc, }; +use crate::types::TransactionHeader; use itertools::Itertools; use casper_types::{ contract_messages::Messages, execution::{Effects, ExecutionResult}, Block, BlockHash, EraId, FinalitySignature, PublicKey, Timestamp, Transaction, TransactionHash, - TransactionHeader, }; #[derive(Debug)] diff --git a/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs b/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs index 2b1bdc8e91..1e79c7a9c3 100644 --- a/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs +++ b/node/src/components/fetcher/fetcher_impls/transaction_fetcher.rs @@ -26,10 +26,7 @@ impl FetchItem for Transaction { } fn validate(&self, _metadata: &EmptyValidationMetadata) -> Result<(), Self::ValidationError> { - match self { - Transaction::Deploy(deploy) => deploy.is_valid().map_err(Into::into), - Transaction::V1(txn) => txn.verify().map_err(Into::into), - } + self.verify() } } diff --git a/node/src/components/storage.rs b/node/src/components/storage.rs index f3ccaeeb8a..1b530b8594 100644 --- a/node/src/components/storage.rs +++ b/node/src/components/storage.rs @@ -69,8 +69,7 @@ use casper_types::{ Approval, ApprovalsHash, AvailableBlockRange, Block, BlockBody, BlockHash, BlockHeader, BlockSignatures, BlockSignaturesV1, BlockSignaturesV2, BlockV2, ChainNameDigest, DeployHash, EraId, ExecutionInfo, FinalitySignature, ProtocolVersion, SignedBlockHeader, Timestamp, - Transaction, TransactionConfig, TransactionHash, TransactionHeader, TransactionId, Transfer, - U512, + Transaction, TransactionConfig, TransactionHash, TransactionId, Transfer, U512, }; use datasize::DataSize; use num_rational::Ratio; @@ -94,7 +93,7 @@ use crate::{ types::{ BlockExecutionResultsOrChunk, BlockExecutionResultsOrChunkId, BlockWithMetadata, ExecutableBlock, LegacyDeploy, MaxTtl, NodeId, NodeRng, SyncLeap, SyncLeapIdentifier, - VariantMismatch, + TransactionHeader, VariantMismatch, }, utils::{display_error, WithDir}, }; @@ -2016,11 +2015,9 @@ impl Storage { deploy.take_header().into(), execution_result, )), - Some(Transaction::V1(transaction_v1)) => ret.push(( - transaction_hash, - transaction_v1.take_header().into(), - execution_result, - )), + Some(Transaction::V1(transaction_v1)) => { + ret.push((transaction_hash, (&transaction_v1).into(), execution_result)) + } }; } Ok(Some(ret)) diff --git a/node/src/components/transaction_acceptor.rs b/node/src/components/transaction_acceptor.rs index 8abbf5b67f..dd331e3314 100644 --- a/node/src/components/transaction_acceptor.rs +++ b/node/src/components/transaction_acceptor.rs @@ -17,7 +17,7 @@ use casper_types::{ AddressableEntityHash, AddressableEntityIdentifier, BlockHeader, Chainspec, EntityAddr, EntityVersion, EntityVersionKey, EntryPoint, EntryPointAddr, ExecutableDeployItem, ExecutableDeployItemIdentifier, InitiatorAddr, Key, Package, PackageAddr, PackageHash, - PackageIdentifier, Transaction, TransactionEntryPoint, TransactionInvocationTarget, + PackageIdentifier, Timestamp, Transaction, TransactionEntryPoint, TransactionInvocationTarget, TransactionTarget, DEFAULT_ENTRY_POINT_NAME, U512, }; @@ -29,6 +29,7 @@ use crate::{ EffectBuilder, EffectExt, Effects, Responder, }, fatal, + types::MetaTransaction, utils::Source, NodeRng, }; @@ -105,29 +106,42 @@ impl TransactionAcceptor { fn accept( &mut self, effect_builder: EffectBuilder, - transaction: Transaction, + input_transaction: Transaction, source: Source, maybe_responder: Option>>, ) -> Effects { - debug!(%source, %transaction, "checking transaction before accepting"); - let event_metadata = Box::new(EventMetadata::new(transaction, source, maybe_responder)); - - let is_config_compliant = match &event_metadata.transaction { - Transaction::Deploy(deploy) => deploy - .is_config_compliant( - &self.chainspec, - self.acceptor_config.timestamp_leeway, - event_metadata.verification_start_timestamp, - ) - .map_err(|err| Error::InvalidTransaction(err.into())), - Transaction::V1(txn) => txn - .is_config_compliant( - &self.chainspec, - self.acceptor_config.timestamp_leeway, - event_metadata.verification_start_timestamp, - ) - .map_err(|err| Error::InvalidTransaction(err.into())), + debug!(%source, %input_transaction, "checking transaction before accepting"); + let verification_start_timestamp = Timestamp::now(); + let transaction_config = &self.chainspec.as_ref().transaction_config; + let maybe_meta_transaction = MetaTransaction::from(&input_transaction, transaction_config); + let meta_transaction = match maybe_meta_transaction { + Ok(transaction) => transaction, + Err(err) => { + return self.reject_transaction_direct( + effect_builder, + input_transaction, + source, + maybe_responder, + verification_start_timestamp, + Error::InvalidTransaction(err), + ); + } }; + let event_metadata = Box::new(EventMetadata::new( + input_transaction, + meta_transaction, + source, + maybe_responder, + verification_start_timestamp, + )); + let is_config_compliant = event_metadata + .meta_transaction + .is_config_compliant( + &self.chainspec, + self.acceptor_config.timestamp_leeway, + verification_start_timestamp, + ) + .map_err(Error::InvalidTransaction); if let Err(error) = is_config_compliant { return self.reject_transaction(effect_builder, *event_metadata, error); @@ -356,11 +370,11 @@ impl TransactionAcceptor { event_metadata: Box, block_header: Box, ) -> Effects { - match &event_metadata.transaction { - Transaction::Deploy(_) => { + match &event_metadata.meta_transaction { + MetaTransaction::Deploy(_) => { self.verify_deploy_session(effect_builder, event_metadata, block_header) } - Transaction::V1(_) => { + MetaTransaction::V1(_) => { self.verify_transaction_v1_body(effect_builder, event_metadata, block_header) } } @@ -372,9 +386,9 @@ impl TransactionAcceptor { event_metadata: Box, block_header: Box, ) -> Effects { - let session = match &event_metadata.transaction { - Transaction::Deploy(deploy) => deploy.session(), - Transaction::V1(txn) => { + let session = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) => deploy.session(), + MetaTransaction::V1(txn) => { error!(%txn, "should only handle deploys in verify_deploy_session"); return self.reject_transaction( effect_builder, @@ -478,8 +492,8 @@ impl TransactionAcceptor { CryptoValidation, } - let next_step = match &event_metadata.transaction { - Transaction::Deploy(deploy) => { + let next_step = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) => { error!( %deploy, "should only handle version 1 transactions in verify_transaction_v1_body" @@ -490,7 +504,7 @@ impl TransactionAcceptor { Error::ExpectedTransactionV1, ); } - Transaction::V1(txn) => match txn.target() { + MetaTransaction::V1(txn) => match txn.target() { TransactionTarget::Stored { id, .. } => match id { TransactionInvocationTarget::ByHash(entity_addr) => { NextStep::GetContract(EntityAddr::SmartContract(*entity_addr)) @@ -559,16 +573,18 @@ impl TransactionAcceptor { return self.reject_transaction(effect_builder, *event_metadata, error); } - let maybe_entry_point_name = match &event_metadata.transaction { - Transaction::Deploy(deploy) if is_payment => { + let maybe_entry_point_name = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) if is_payment => { Some(deploy.payment().entry_point_name().to_string()) } - Transaction::Deploy(deploy) => Some(deploy.session().entry_point_name().to_string()), - Transaction::V1(_) if is_payment => { + MetaTransaction::Deploy(deploy) => { + Some(deploy.session().entry_point_name().to_string()) + } + MetaTransaction::V1(_) if is_payment => { error!("should not fetch a contract to validate payment logic for transaction v1s"); None } - Transaction::V1(txn) => match txn.entry_point() { + MetaTransaction::V1(txn) => match txn.entry_point() { TransactionEntryPoint::Call => Some(DEFAULT_ENTRY_POINT_NAME.to_owned()), TransactionEntryPoint::Custom(name) => Some(name.clone()), TransactionEntryPoint::Transfer @@ -737,11 +753,11 @@ impl TransactionAcceptor { effect_builder: EffectBuilder, event_metadata: Box, ) -> Effects { - let is_valid = match &event_metadata.transaction { - Transaction::Deploy(deploy) => deploy + let is_valid = match &event_metadata.meta_transaction { + MetaTransaction::Deploy(deploy) => deploy .is_valid() .map_err(|err| Error::InvalidTransaction(err.into())), - Transaction::V1(txn) => txn + MetaTransaction::V1(txn) => txn .verify() .map_err(|err| Error::InvalidTransaction(err.into())), }; @@ -775,11 +791,32 @@ impl TransactionAcceptor { ) -> Effects { debug!(%error, transaction = %event_metadata.transaction, "rejected transaction"); let EventMetadata { + meta_transaction: _, transaction, source, maybe_responder, verification_start_timestamp, } = event_metadata; + self.reject_transaction_direct( + effect_builder, + transaction, + source, + maybe_responder, + verification_start_timestamp, + error, + ) + } + + fn reject_transaction_direct( + &self, + effect_builder: EffectBuilder, + transaction: Transaction, + source: Source, + maybe_responder: Option>>, + verification_start_timestamp: Timestamp, + error: Error, + ) -> Effects { + debug!(%error, transaction = %transaction, "rejected transaction"); self.metrics.observe_rejected(verification_start_timestamp); let mut effects = Effects::new(); if let Some(responder) = maybe_responder { @@ -847,6 +884,7 @@ impl TransactionAcceptor { is_new: bool, ) -> Effects { let EventMetadata { + meta_transaction: _, transaction, source, maybe_responder, diff --git a/node/src/components/transaction_acceptor/event.rs b/node/src/components/transaction_acceptor/event.rs index 459608144f..60c9539e9a 100644 --- a/node/src/components/transaction_acceptor/event.rs +++ b/node/src/components/transaction_acceptor/event.rs @@ -8,12 +8,13 @@ use casper_types::{ }; use super::{Error, Source}; -use crate::effect::Responder; +use crate::{effect::Responder, types::MetaTransaction}; /// A utility struct to hold duplicated information across events. #[derive(Debug, Serialize)] pub(crate) struct EventMetadata { pub(crate) transaction: Transaction, + pub(crate) meta_transaction: MetaTransaction, pub(crate) source: Source, pub(crate) maybe_responder: Option>>, pub(crate) verification_start_timestamp: Timestamp, @@ -22,14 +23,17 @@ pub(crate) struct EventMetadata { impl EventMetadata { pub(crate) fn new( transaction: Transaction, + meta_transaction: MetaTransaction, source: Source, maybe_responder: Option>>, + verification_start_timestamp: Timestamp, ) -> Self { EventMetadata { transaction, + meta_transaction, source, maybe_responder, - verification_start_timestamp: Timestamp::now(), + verification_start_timestamp, } } } diff --git a/node/src/components/transaction_acceptor/tests.rs b/node/src/components/transaction_acceptor/tests.rs index c588c9cdaa..7c6d3cac2d 100644 --- a/node/src/components/transaction_acceptor/tests.rs +++ b/node/src/components/transaction_acceptor/tests.rs @@ -37,8 +37,7 @@ use casper_types::{ Block, BlockV2, CLValue, Chainspec, ChainspecRawBytes, Contract, Deploy, EntryPointValue, EraId, HashAddr, InvalidDeploy, InvalidTransaction, InvalidTransactionV1, Package, PricingMode, ProtocolVersion, PublicKey, SecretKey, StoredValue, TestBlockBuilder, TimeDiff, Timestamp, - Transaction, TransactionCategory, TransactionConfig, TransactionV1, TransactionV1Builder, URef, - U512, + Transaction, TransactionConfig, TransactionV1, TransactionV1Builder, URef, U512, }; use super::*; @@ -281,15 +280,12 @@ impl TestScenario { } TestScenario::FromPeerExpired(TxnType::V1) | TestScenario::FromClientExpired(TxnType::V1) => { - let txn = TransactionV1Builder::new_session( - TransactionCategory::Large, - Bytes::from(vec![1]), - ) - .with_chain_name("casper-example") - .with_timestamp(Timestamp::zero()) - .with_secret_key(&secret_key) - .build() - .unwrap(); + let txn = TransactionV1Builder::new_session(false, Bytes::from(vec![1])) + .with_chain_name("casper-example") + .with_timestamp(Timestamp::zero()) + .with_secret_key(&secret_key) + .build() + .unwrap(); Transaction::from(txn) } TestScenario::FromPeerValidTransaction(txn_type) @@ -305,15 +301,12 @@ impl TestScenario { | TestScenario::FromClientAccountWithInsufficientWeight(txn_type) => match txn_type { TxnType::Deploy => Transaction::from(Deploy::random_valid_native_transfer(rng)), TxnType::V1 => { - let txn = TransactionV1Builder::new_session( - TransactionCategory::Large, - Bytes::from(vec![1]), - ) - .with_chain_name("casper-example") - .with_timestamp(Timestamp::now()) - .with_secret_key(&secret_key) - .build() - .unwrap(); + let txn = TransactionV1Builder::new_session(false, Bytes::from(vec![1])) + .with_chain_name("casper-example") + .with_timestamp(Timestamp::now()) + .with_secret_key(&secret_key) + .build() + .unwrap(); Transaction::from(txn) } }, @@ -323,15 +316,12 @@ impl TestScenario { Transaction::from(deploy) } TestScenario::FromClientSignedByAdmin(TxnType::V1) => { - let txn = TransactionV1Builder::new_session( - TransactionCategory::Large, - Bytes::from(vec![1]), - ) - .with_chain_name("casper-example") - .with_timestamp(Timestamp::now()) - .with_secret_key(admin) - .build() - .unwrap(); + let txn = TransactionV1Builder::new_session(false, Bytes::from(vec![1])) + .with_chain_name("casper-example") + .with_timestamp(Timestamp::now()) + .with_secret_key(admin) + .build() + .unwrap(); Transaction::from(txn) } TestScenario::AccountWithUnknownBalance @@ -518,16 +508,13 @@ impl TestScenario { ), ), TxnType::V1 => { - let txn = TransactionV1Builder::new_session( - TransactionCategory::Large, - Bytes::from(vec![1]), - ) - .with_chain_name("casper-example") - .with_timestamp(timestamp) - .with_ttl(ttl) - .with_secret_key(&secret_key) - .build() - .unwrap(); + let txn = TransactionV1Builder::new_session(false, Bytes::from(vec![1])) + .with_chain_name("casper-example") + .with_timestamp(timestamp) + .with_ttl(ttl) + .with_secret_key(&secret_key) + .build() + .unwrap(); Transaction::from(txn) } } @@ -544,16 +531,13 @@ impl TestScenario { ), ), TxnType::V1 => { - let txn = TransactionV1Builder::new_session( - TransactionCategory::Large, - Bytes::from(vec![1]), - ) - .with_chain_name("casper-example") - .with_timestamp(timestamp) - .with_ttl(ttl) - .with_secret_key(&secret_key) - .build() - .unwrap(); + let txn = TransactionV1Builder::new_session(false, Bytes::from(vec![1])) + .with_chain_name("casper-example") + .with_timestamp(timestamp) + .with_ttl(ttl) + .with_secret_key(&secret_key) + .build() + .unwrap(); Transaction::from(txn) } } @@ -576,6 +560,7 @@ impl TestScenario { let fixed_mode_transaction = TransactionV1Builder::new_random(rng) .with_pricing_mode(PricingMode::Fixed { gas_price_tolerance: TOO_LOW_GAS_PRICE_TOLERANCE, + additional_computation_factor: 0, }) .with_chain_name("casper-example") .build() @@ -1058,12 +1043,20 @@ fn inject_balance_check_for_peer( source: Source, rng: &mut TestRng, responder: Responder>, + chainspec: &Chainspec, ) -> impl FnOnce(EffectBuilder) -> Effects { let txn = txn.clone(); let block = TestBlockBuilder::new().build(rng); let block_header = Box::new(block.header().clone().into()); + let meta_transaction = MetaTransaction::from(&txn, &chainspec.transaction_config).unwrap(); |effect_builder: EffectBuilder| { - let event_metadata = Box::new(EventMetadata::new(txn, source, Some(responder))); + let event_metadata = Box::new(EventMetadata::new( + txn, + meta_transaction, + source, + Some(responder), + Timestamp::now(), + )); effect_builder .into_inner() .schedule( @@ -1089,9 +1082,10 @@ async fn run_transaction_acceptor_without_timeout( <(Chainspec, ChainspecRawBytes)>::from_resources("local"); chainspec.core_config.administrators = iter::once(PublicKey::from(&admin)).collect(); + let chainspec = Arc::new(chainspec); let mut runner: Runner> = Runner::new( test_scenario, - Arc::new(chainspec), + chainspec.clone(), Arc::new(chainspec_raw_bytes), rng, ) @@ -1141,12 +1135,14 @@ async fn run_transaction_acceptor_without_timeout( if test_scenario == TestScenario::BalanceCheckForDeploySentByPeer { let (txn_sender, _) = oneshot::channel(); let txn_responder = Responder::without_shutdown(txn_sender); + let chainspec = chainspec.as_ref().clone(); runner .process_injected_effects(inject_balance_check_for_peer( &txn, source.clone(), rng, txn_responder, + &chainspec, )) .await; while runner.try_crank(rng).await == TryCrankOutcome::NoEventsToProcess { diff --git a/node/src/components/transaction_buffer.rs b/node/src/components/transaction_buffer.rs index a79a5c8851..b19c475173 100644 --- a/node/src/components/transaction_buffer.rs +++ b/node/src/components/transaction_buffer.rs @@ -260,6 +260,7 @@ impl TransactionBuffer { error!(%transaction_hash, "TransactionBuffer: invalid transaction must not be buffered"); return; } + if self .hold .values() @@ -268,6 +269,7 @@ impl TransactionBuffer { info!(%transaction_hash, "TransactionBuffer: attempt to register already held transaction"); return; } + let footprint = match TransactionFootprint::new(&self.chainspec, &transaction) { Ok(footprint) => footprint, Err(invalid_transaction_error) => { @@ -410,7 +412,7 @@ impl TransactionBuffer { let mut buckets: HashMap<_, Vec<_>> = HashMap::new(); for (transaction_hash, footprint) in proposable { buckets - .entry(&footprint.body_hash) + .entry(&footprint.payload_hash) .and_modify(|vec| vec.push((*transaction_hash, footprint))) .or_insert(vec![(*transaction_hash, footprint)]); } @@ -459,9 +461,9 @@ impl TransactionBuffer { let iter_limit = self.buffer.len() * 4; let mut buckets = self.buckets(current_era_gas_price); - let mut body_hashes_queue: VecDeque<_> = buckets.keys().cloned().collect(); + let mut payload_hashes_queue: VecDeque<_> = buckets.keys().cloned().collect(); - while let Some(body_hash) = body_hashes_queue.pop_front() { + while let Some(payload_hash) = payload_hashes_queue.pop_front() { if Timestamp::now() > request_expiry { break; } @@ -475,14 +477,14 @@ impl TransactionBuffer { } let Some((transaction_hash, footprint)) = - buckets.get_mut(body_hash).and_then(Vec::<_>::pop) + buckets.get_mut(payload_hash).and_then(Vec::<_>::pop) else { continue; }; // bucket wasn't empty - push the hash back into the queue to be processed again on the // next pass - body_hashes_queue.push_back(body_hash); + payload_hashes_queue.push_back(payload_hash); if footprint.is_mint() && have_hit_mint_limit { continue; @@ -522,15 +524,15 @@ impl TransactionBuffer { ); dead.insert(transaction_hash); } - AddError::Count(category) => { - match category { - category if category == MINT_LANE_ID => { + AddError::Count(lane_id) => { + match lane_id { + lane_id if lane_id == MINT_LANE_ID => { have_hit_mint_limit = true; } - category if category == AUCTION_LANE_ID => { + lane_id if lane_id == AUCTION_LANE_ID => { have_hit_auction_limit = true; } - category if category == INSTALL_UPGRADE_LANE_ID => { + lane_id if lane_id == INSTALL_UPGRADE_LANE_ID => { have_hit_install_upgrade_limit = true; } _ => { diff --git a/node/src/components/transaction_buffer/tests.rs b/node/src/components/transaction_buffer/tests.rs index 4119141304..21ccd6c91c 100644 --- a/node/src/components/transaction_buffer/tests.rs +++ b/node/src/components/transaction_buffer/tests.rs @@ -5,7 +5,8 @@ use rand::{seq::SliceRandom, Rng}; use casper_types::{ testing::TestRng, Deploy, EraId, SecretKey, TestBlockBuilder, TimeDiff, Transaction, - TransactionConfig, TransactionV1, TransactionV1Config, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, + TransactionConfig, TransactionLimitsDefinition, TransactionV1, TransactionV1Config, + DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, LARGE_WASM_LANE_ID, }; use super::*; @@ -19,7 +20,6 @@ use crate::{ const ERA_ONE: EraId = EraId::new(1u64); const GAS_PRICE_TOLERANCE: u8 = 1; const DEFAULT_MINIMUM_GAS_PRICE: u8 = 1; -const LARGE_LANE_ID: u8 = 3; fn get_appendable_block( rng: &mut TestRng, @@ -53,7 +53,7 @@ fn get_appendable_block( // Generates valid transactions fn create_valid_transaction( rng: &mut TestRng, - transaction_category: u8, + transaction_lane: u8, strict_timestamp: Option, with_ttl: Option, ) -> Transaction { @@ -66,8 +66,8 @@ fn create_valid_transaction( None => Timestamp::now(), }; - match transaction_category { - transaction_category if transaction_category == MINT_LANE_ID => { + match transaction_lane { + transaction_lane if transaction_lane == MINT_LANE_ID => { if rng.gen() { Transaction::V1(TransactionV1::random_transfer( rng, @@ -82,10 +82,10 @@ fn create_valid_transaction( )) } } - transaction_category if transaction_category == INSTALL_UPGRADE_LANE_ID => Transaction::V1( + transaction_lane if transaction_lane == INSTALL_UPGRADE_LANE_ID => Transaction::V1( TransactionV1::random_install_upgrade(rng, strict_timestamp, with_ttl), ), - transaction_category if transaction_category == AUCTION_LANE_ID => Transaction::V1( + transaction_lane if transaction_lane == AUCTION_LANE_ID => Transaction::V1( TransactionV1::random_auction(rng, strict_timestamp, with_ttl), ), _ => { @@ -163,17 +163,17 @@ const fn all_categories() -> [u8; 4] { MINT_LANE_ID, INSTALL_UPGRADE_LANE_ID, AUCTION_LANE_ID, - LARGE_LANE_ID, + LARGE_WASM_LANE_ID, ] } #[test] fn register_transaction_and_check_size() { let mut rng = TestRng::new(); - + let chainspec = Chainspec::default(); for category in all_categories() { let mut transaction_buffer = TransactionBuffer::new( - Arc::new(Chainspec::default()), + Arc::new(chainspec.clone()), Config::default(), &Registry::new(), ) @@ -194,7 +194,7 @@ fn register_transaction_and_check_size() { .get(rng.gen_range(0..num_valid_transactions)) .unwrap() .clone(); - transaction_buffer.register_transaction(duplicate_transaction); + transaction_buffer.register_transaction(duplicate_transaction.clone()); assert_container_sizes(&transaction_buffer, valid_transactions.len(), 0, 0); // Insert transaction without footprint @@ -836,7 +836,8 @@ fn register_transactions_and_blocks() { .cloned() .peekable(); assert!(held_transactions.peek().is_some()); - held_transactions.for_each(|transaction| transaction_buffer.register_transaction(transaction)); + held_transactions + .for_each(|transaction| transaction_buffer.register_transaction(transaction.clone())); assert_container_sizes( &transaction_buffer, block_transaction.len() + valid_transactions.len(), @@ -1090,11 +1091,13 @@ fn make_test_chainspec(max_standard_count: u64, max_mint_count: u64) -> Arc { @@ -2964,6 +2964,7 @@ async fn run_gas_price_scenario(gas_price_scenario: GasPriceScenario) { .with_ttl(TimeDiff::from_seconds(120 * 10)) .with_pricing_mode(PricingMode::Fixed { gas_price_tolerance: max_gas_price, + additional_computation_factor: 0, }) .build() .expect("must get transaction"); @@ -2996,6 +2997,7 @@ async fn run_gas_price_scenario(gas_price_scenario: GasPriceScenario) { .with_secret_key(&alice_secret_key) .with_pricing_mode(PricingMode::Fixed { gas_price_tolerance: max_gas_price, + additional_computation_factor: 0, }) .build() .expect("must get transaction"); diff --git a/node/src/reactor/main_reactor/tests/transactions.rs b/node/src/reactor/main_reactor/tests/transactions.rs index 09baf1f6ac..631cb9c094 100644 --- a/node/src/reactor/main_reactor/tests/transactions.rs +++ b/node/src/reactor/main_reactor/tests/transactions.rs @@ -1,4 +1,5 @@ use super::*; +use crate::types::MetaTransaction; use casper_execution_engine::engine_state::MAX_PAYMENT_AMOUNT; use casper_storage::data_access_layer::{ AddressableEntityRequest, BalanceIdentifier, ProofHandling, QueryRequest, QueryResult, @@ -8,7 +9,7 @@ use casper_types::{ addressable_entity::NamedKeyAddr, runtime_args, system::mint::{ARG_AMOUNT, ARG_TARGET}, - AddressableEntity, Digest, EntityAddr, ExecutionInfo, GasLimited, TransactionCategory, + AddressableEntity, Digest, EntityAddr, ExecutionInfo, LARGE_WASM_LANE_ID, }; use once_cell::sync::Lazy; @@ -33,7 +34,6 @@ static CHARLIE_PUBLIC_KEY: Lazy = const MIN_GAS_PRICE: u8 = 5; const CHAIN_NAME: &str = "single-transaction-test-net"; -const LARGE_LANE_ID: u8 = 3; async fn transfer_to_account>( fixture: &mut TestFixture, @@ -90,7 +90,7 @@ async fn send_wasm_transaction( let chain_name = fixture.chainspec.network_config.name.clone(); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, Bytes::from(vec![1])) + TransactionV1Builder::new_session(false, Bytes::from(vec![1])) .with_chain_name(chain_name) .with_pricing_mode(pricing) .with_initiator_addr(PublicKey::from(from)) @@ -391,6 +391,7 @@ async fn transfer_cost_fixed_price_no_fee_no_refund() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, Some(0xDEADBEEF), ) @@ -496,6 +497,7 @@ async fn should_accept_transfer_without_id() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, None, ) @@ -536,6 +538,7 @@ async fn failed_transfer_cost_fixed_price_no_fee_no_refund() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, None, ) @@ -551,6 +554,7 @@ async fn failed_transfer_cost_fixed_price_no_fee_no_refund() { PublicKey::from(&*bob_secret_key), PricingMode::Fixed { gas_price_tolerance: 1, + additional_computation_factor: 0, }, None, ) @@ -772,6 +776,7 @@ async fn native_operations_fees_are_not_refunded() { PublicKey::from(&*charlie_secret_key), PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, None, ) @@ -869,6 +874,7 @@ async fn wasm_transaction_fees_are_refunded() { &bob_secret_key, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, ) .await; @@ -877,7 +883,7 @@ async fn wasm_transaction_fees_are_refunded() { let expected_transaction_gas: u64 = fixture .chainspec - .get_max_gas_limit_by_category(LARGE_LANE_ID); + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID); let expected_transaction_cost = expected_transaction_gas * MIN_GAS_PRICE as u64; assert_exec_result_cost( exec_result, @@ -1192,7 +1198,7 @@ async fn wasm_transaction_refunds_are_burnt(txn_pricing_mode: PricingMode) { let expected_transaction_gas: u64 = gas_limit.unwrap_or( test.chainspec() - .get_max_gas_limit_by_category(LARGE_LANE_ID), + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; @@ -1249,6 +1255,7 @@ async fn wasm_transaction_refunds_are_burnt(txn_pricing_mode: PricingMode) { async fn wasm_transaction_refunds_are_burnt_fixed_pricing() { wasm_transaction_refunds_are_burnt(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1293,7 +1300,7 @@ async fn only_refunds_are_burnt_no_fee(txn_pricing_mode: PricingMode) { // Fixed transaction pricing. let expected_transaction_gas: u64 = gas_limit.unwrap_or( test.chainspec() - .get_max_gas_limit_by_category(LARGE_LANE_ID), + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; @@ -1350,6 +1357,7 @@ async fn only_refunds_are_burnt_no_fee(txn_pricing_mode: PricingMode) { async fn only_refunds_are_burnt_no_fee_fixed_pricing() { only_refunds_are_burnt_no_fee(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1385,7 +1393,7 @@ async fn fees_and_refunds_are_burnt_separately(txn_pricing_mode: PricingMode) { // Fixed transaction pricing. let expected_transaction_gas: u64 = gas_limit.unwrap_or( test.chainspec() - .get_max_gas_limit_by_category(LARGE_LANE_ID), + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; @@ -1444,6 +1452,7 @@ async fn fees_and_refunds_are_burnt_separately(txn_pricing_mode: PricingMode) { async fn fees_and_refunds_are_burnt_separately_fixed_pricing() { fees_and_refunds_are_burnt_separately(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1480,7 +1489,7 @@ async fn refunds_are_payed_and_fees_are_burnt(txn_pricing_mode: PricingMode) { // Fixed transaction pricing. let expected_transaction_gas: u64 = gas_limit.unwrap_or( test.chainspec() - .get_max_gas_limit_by_category(LARGE_LANE_ID), + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; @@ -1545,6 +1554,7 @@ async fn refunds_are_payed_and_fees_are_burnt(txn_pricing_mode: PricingMode) { async fn refunds_are_payed_and_fees_are_burnt_fixed_pricing() { refunds_are_payed_and_fees_are_burnt(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1577,10 +1587,15 @@ async fn refunds_are_payed_and_fees_are_on_hold(txn_pricing_mode: PricingMode) { .await; let txn = invalid_wasm_txn(BOB_SECRET_KEY.clone(), txn_pricing_mode); + let meta_transaction = + MetaTransaction::from(&txn, &test.chainspec().transaction_config).unwrap(); // Fixed transaction pricing. let expected_consumed_gas = Gas::new(0); // expect that this transaction doesn't consume any gas since it has invalid wasm. - let expected_transaction_cost = - txn.gas_limit(test.chainspec()).unwrap().value() * min_gas_price; + let expected_transaction_cost = meta_transaction + .gas_limit(test.chainspec()) + .unwrap() + .value() + * min_gas_price; test.fixture .run_until_consensus_in_era(ERA_ONE, ONE_MIN) @@ -1645,6 +1660,7 @@ async fn refunds_are_payed_and_fees_are_on_hold(txn_pricing_mode: PricingMode) { async fn refunds_are_payed_and_fees_are_on_hold_fixed_pricing() { refunds_are_payed_and_fees_are_on_hold(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -1688,7 +1704,7 @@ async fn only_refunds_are_burnt_no_fee_custom_payment() { let expected_transaction_cost = expected_transaction_gas * MIN_GAS_PRICE as u64; let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_chain_name(CHAIN_NAME) .with_pricing_mode(PricingMode::Classic { payment_amount: expected_transaction_gas, @@ -1786,7 +1802,7 @@ async fn no_refund_no_fee_custom_payment() { let expected_transaction_cost = expected_transaction_gas * MIN_GAS_PRICE as u64; let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_chain_name(CHAIN_NAME) .with_pricing_mode(PricingMode::Classic { payment_amount: expected_transaction_gas, @@ -1954,6 +1970,7 @@ async fn transfer_fee_is_burnt_no_refund(txn_pricing_mode: PricingMode) { async fn transfer_fee_is_burnt_no_refund_fixed_pricing() { transfer_fee_is_burnt_no_refund(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2064,6 +2081,7 @@ async fn fee_is_payed_to_proposer_no_refund(txn_pricing_mode: PricingMode) { async fn fee_is_payed_to_proposer_no_refund_fixed_pricing() { fee_is_payed_to_proposer_no_refund(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2109,7 +2127,7 @@ async fn wasm_transaction_fees_are_refunded_to_proposer(txn_pricing_mode: Pricin let expected_transaction_gas: u64 = gas_limit.unwrap_or( test.chainspec() - .get_max_gas_limit_by_category(LARGE_LANE_ID), + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID), ); let expected_transaction_cost = expected_transaction_gas * min_gas_price as u64; @@ -2165,6 +2183,7 @@ async fn wasm_transaction_fees_are_refunded_to_proposer(txn_pricing_mode: Pricin async fn wasm_transaction_fees_are_refunded_to_proposer_fixed_pricing() { wasm_transaction_fees_are_refunded_to_proposer(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2303,6 +2322,7 @@ async fn fee_is_accumulated_and_distributed_no_refund(txn_pricing_mode: PricingM async fn fee_is_accumulated_and_distributed_no_refund_fixed_pricing() { fee_is_accumulated_and_distributed_no_refund(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2338,7 +2358,7 @@ fn transfer_txn>( fn invalid_wasm_txn(initiator: Arc, pricing_mode: PricingMode) -> Transaction { let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, Bytes::from(vec![1])) + TransactionV1Builder::new_session(false, Bytes::from(vec![1])) .with_chain_name(CHAIN_NAME) .with_pricing_mode(pricing_mode) .with_initiator_addr(PublicKey::from(&*initiator)) @@ -2362,6 +2382,7 @@ fn match_pricing_mode(txn_pricing_mode: &PricingMode) -> (PricingHandling, u8, O ), PricingMode::Fixed { gas_price_tolerance, + .. } => (PricingHandling::Fixed, *gas_price_tolerance, None), PricingMode::Reserved { .. } => unimplemented!(), } @@ -2371,6 +2392,7 @@ fn match_pricing_mode(txn_pricing_mode: &PricingMode) -> (PricingHandling, u8, O async fn holds_should_be_added_and_cleared_fixed_pricing() { holds_should_be_added_and_cleared(PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }) .await; } @@ -2500,6 +2522,7 @@ async fn fee_holds_are_amortized() { BOB_SECRET_KEY.clone(), PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, ); @@ -2515,7 +2538,7 @@ async fn fee_holds_are_amortized() { // Fixed transaction pricing. let expected_transaction_gas: u64 = test .chainspec() - .get_max_gas_limit_by_category(LARGE_LANE_ID); + .get_max_gas_limit_by_category(LARGE_WASM_LANE_ID); let expected_transaction_cost = expected_transaction_gas * MIN_GAS_PRICE as u64; @@ -2633,6 +2656,7 @@ async fn sufficient_balance_is_available_after_amortization() { &CHARLIE_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, transfer_amount, ); @@ -2664,6 +2688,7 @@ async fn sufficient_balance_is_available_after_amortization() { &BOB_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, min_transfer_amount, ); @@ -2704,6 +2729,7 @@ async fn sufficient_balance_is_available_after_amortization() { &BOB_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, min_transfer_amount, ); @@ -2749,6 +2775,7 @@ async fn validator_credit_is_written_and_cleared_after_auction() { &CHARLIE_PUBLIC_KEY, PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }, transfer_amount, ); @@ -3046,7 +3073,7 @@ async fn insufficient_funds_transfer_from_purse() { Bytes::from(std::fs::read(purse_create_contract).expect("cannot read module bytes")); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_runtime_args( runtime_args! { "destination" => purse_name, "amount" => U512::zero() }, ) @@ -3171,7 +3198,7 @@ async fn charge_when_session_code_succeeds() { let transferred_amount = 1; let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_runtime_args(runtime_args! { ARG_TARGET => CHARLIE_PUBLIC_KEY.to_account_hash(), ARG_AMOUNT => U512::from(transferred_amount) @@ -3232,7 +3259,7 @@ async fn charge_when_session_code_fails_with_user_error() { let (alice_initial_balance, bob_initial_balance, _) = test.get_balances(None); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) .build() @@ -3296,7 +3323,7 @@ async fn charge_when_session_code_runs_out_of_gas() { let (alice_initial_balance, bob_initial_balance, _) = test.get_balances(None); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) .build() @@ -3364,7 +3391,7 @@ async fn successful_purse_to_purse_transfer() { Bytes::from(std::fs::read(purse_create_contract).expect("cannot read module bytes")); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_runtime_args( runtime_args! { "destination" => purse_name, "amount" => U512::from(MAX_PAYMENT_AMOUNT) + U512::one() }, ) @@ -3457,7 +3484,7 @@ async fn successful_purse_to_account_transfer() { Bytes::from(std::fs::read(purse_create_contract).expect("cannot read module bytes")); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_runtime_args( runtime_args! { "destination" => purse_name, "amount" => U512::from(MAX_PAYMENT_AMOUNT) + U512::one() }, ) @@ -3618,7 +3645,7 @@ async fn out_of_gas_txn_does_not_produce_effects() { Bytes::from(std::fs::read(revert_contract).expect("cannot read module bytes")); let mut txn = Transaction::from( - TransactionV1Builder::new_session(TransactionCategory::Large, module_bytes) + TransactionV1Builder::new_session(false, module_bytes) .with_chain_name(CHAIN_NAME) .with_initiator_addr(BOB_PUBLIC_KEY.clone()) .build() @@ -3678,6 +3705,7 @@ async fn gas_holds_accumulate_for_multiple_transactions_in_the_same_block() { let chain_name = test.fixture.chainspec.network_config.name.clone(); let txn_pricing_mode = PricingMode::Fixed { gas_price_tolerance: MIN_GAS_PRICE, + additional_computation_factor: 0, }; let expected_transfer_gas = test.chainspec().system_costs_config.mint_costs().transfer; let expected_transfer_cost: U512 = U512::from(expected_transfer_gas) * MIN_GAS_PRICE; diff --git a/node/src/testing/fake_transaction_acceptor.rs b/node/src/testing/fake_transaction_acceptor.rs index 3e8e21eee7..e275477984 100644 --- a/node/src/testing/fake_transaction_acceptor.rs +++ b/node/src/testing/fake_transaction_acceptor.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use tracing::debug; -use casper_types::Transaction; +use casper_types::{Chainspec, Timestamp, Transaction}; pub(crate) use crate::components::transaction_acceptor::{Error, Event}; use crate::{ @@ -20,6 +20,7 @@ use crate::{ announcements::TransactionAcceptorAnnouncement, requests::StorageRequest, EffectBuilder, EffectExt, Effects, Responder, }, + types::MetaTransaction, utils::Source, NodeRng, }; @@ -39,11 +40,15 @@ impl ReactorEventT for REv where #[derive(Debug)] pub struct FakeTransactionAcceptor { is_active: bool, + chainspec: Chainspec, } impl FakeTransactionAcceptor { pub(crate) fn new() -> Self { - FakeTransactionAcceptor { is_active: true } + FakeTransactionAcceptor { + is_active: true, + chainspec: Chainspec::default(), + } } pub(crate) fn set_active(&mut self, new_setting: bool) { @@ -57,10 +62,14 @@ impl FakeTransactionAcceptor { source: Source, maybe_responder: Option>>, ) -> Effects { + let meta_transaction = + MetaTransaction::from(&transaction, &self.chainspec.transaction_config).unwrap(); let event_metadata = Box::new(EventMetadata::new( transaction.clone(), + meta_transaction, source, maybe_responder, + Timestamp::now(), )); effect_builder .put_transaction_to_storage(transaction) @@ -77,6 +86,7 @@ impl FakeTransactionAcceptor { is_new: bool, ) -> Effects { let EventMetadata { + meta_transaction: _, transaction, source, maybe_responder, diff --git a/node/src/types.rs b/node/src/types.rs index 95a836b2c0..af88a7076a 100644 --- a/node/src/types.rs +++ b/node/src/types.rs @@ -36,7 +36,9 @@ pub use node_config::{NodeConfig, SyncHandling}; pub(crate) use node_id::NodeId; pub use status_feed::{ChainspecInfo, GetStatusResult, StatusFeed}; pub(crate) use sync_leap::{GlobalStatesMetadata, SyncLeap, SyncLeapIdentifier}; -pub(crate) use transaction::{LegacyDeploy, TransactionFootprint}; +pub(crate) use transaction::{ + LegacyDeploy, MetaTransaction, TransactionFootprint, TransactionHeader, +}; pub(crate) use validator_matrix::{EraValidatorWeights, SignatureWeight, ValidatorMatrix}; pub use value_or_chunk::{ ChunkingError, TrieOrChunk, TrieOrChunkId, TrieOrChunkIdDisplay, ValueOrChunk, diff --git a/node/src/types/appendable_block.rs b/node/src/types/appendable_block.rs index 0d98e98b9d..15876f9292 100644 --- a/node/src/types/appendable_block.rs +++ b/node/src/types/appendable_block.rs @@ -83,7 +83,7 @@ impl AppendableBlock { if expires < self.timestamp { return Err(AddError::Expired); } - let category = footprint.category; + let category = footprint.lane_id; let limit = self .transaction_config .transaction_v1_config @@ -92,7 +92,7 @@ impl AppendableBlock { let count = self .transactions .iter() - .filter(|(_, item)| item.category == category) + .filter(|(_, item)| item.lane_id == category) .count(); if count.checked_add(1).ok_or(AddError::Count(category))? > limit as usize { return Err(AddError::Count(category)); @@ -161,7 +161,7 @@ impl AppendableBlock { items: &BTreeMap, ) { let mut ret = vec![]; - for (x, y) in items.iter().filter(|(_, y)| y.category == category) { + for (x, y) in items.iter().filter(|(_, y)| y.lane_id == category) { ret.push((*x, y.approvals.clone())); } if !ret.is_empty() { @@ -177,9 +177,9 @@ impl AppendableBlock { .transaction_v1_config .wasm_lanes .iter() - .map(|lane| lane[0]) + .map(|lane| lane.id()) { - collate(lane_id as u8, &mut transactions, &footprints); + collate(lane_id, &mut transactions, &footprints); } BlockPayload::new( @@ -198,7 +198,7 @@ impl AppendableBlock { fn category_count(&self, category: u8) -> usize { self.transactions .iter() - .filter(|(_, f)| f.category == category) + .filter(|(_, f)| f.lane_id == category) .count() } diff --git a/node/src/types/block/meta_block.rs b/node/src/types/block/meta_block.rs index 93b38957bd..e555d1266e 100644 --- a/node/src/types/block/meta_block.rs +++ b/node/src/types/block/meta_block.rs @@ -3,12 +3,12 @@ mod state; use std::{convert::TryFrom, sync::Arc}; +use crate::types::TransactionHeader; use datasize::DataSize; use serde::Serialize; use casper_types::{ execution::ExecutionResult, ActivationPoint, Block, BlockHash, BlockV2, EraId, TransactionHash, - TransactionHeader, }; pub(crate) use merge_mismatch_error::MergeMismatchError; @@ -185,7 +185,7 @@ mod tests { let txn = TransactionV1::random(rng); let execution_results = vec![ExecutionArtifact::new( TransactionHash::V1(*txn.hash()), - TransactionHeader::V1(txn.take_header()), + (&txn).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; @@ -240,7 +240,7 @@ mod tests { let txn = TransactionV1::random(rng); let execution_results = vec![ExecutionArtifact::new( TransactionHash::V1(*txn.hash()), - TransactionHeader::V1(txn.take_header()), + (&txn).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; @@ -278,7 +278,7 @@ mod tests { let txn = TransactionV1::random(rng); let execution_results = vec![ExecutionArtifact::new( TransactionHash::V1(*txn.hash()), - TransactionHeader::V1(txn.take_header()), + (&txn).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; @@ -311,14 +311,14 @@ mod tests { let txn1 = TransactionV1::random(rng); let execution_results1 = vec![ExecutionArtifact::new( TransactionHash::V1(*txn1.hash()), - TransactionHeader::V1(txn1.take_header()), + (&txn1).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; let txn2 = TransactionV1::random(rng); let execution_results2 = vec![ExecutionArtifact::new( TransactionHash::V1(*txn2.hash()), - TransactionHeader::V1(txn2.take_header()), + (&txn2).into(), ExecutionResult::from(ExecutionResultV2::random(rng)), Vec::new(), )]; diff --git a/node/src/types/transaction.rs b/node/src/types/transaction.rs index 3fa0faaa3b..1544b84ce3 100644 --- a/node/src/types/transaction.rs +++ b/node/src/types/transaction.rs @@ -1,5 +1,6 @@ mod deploy; +mod meta_transaction; mod transaction_footprint; - pub(crate) use deploy::LegacyDeploy; +pub(crate) use meta_transaction::{MetaTransaction, TransactionHeader}; pub(crate) use transaction_footprint::TransactionFootprint; diff --git a/node/src/types/transaction/meta_transaction.rs b/node/src/types/transaction/meta_transaction.rs new file mode 100644 index 0000000000..f7abe3933f --- /dev/null +++ b/node/src/types/transaction/meta_transaction.rs @@ -0,0 +1,279 @@ +mod meta_transaction_v1; +mod tranasction_lane; +mod transaction_header; +pub(crate) use transaction_header::*; + +use casper_execution_engine::engine_state::{SessionDataDeploy, SessionDataV1, SessionInputData}; +use casper_types::{ + account::AccountHash, bytesrepr::ToBytes, Approval, Chainspec, Deploy, Digest, Gas, GasLimited, + InitiatorAddr, InvalidTransaction, Phase, PricingMode, RuntimeArgs, TimeDiff, Timestamp, + Transaction, TransactionConfig, TransactionEntryPoint, TransactionHash, TransactionTarget, + INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, MINT_LANE_ID, +}; +use core::fmt::{self, Debug, Display, Formatter}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +pub(crate) use meta_transaction_v1::MetaTransactionV1; +use serde::Serialize; +use std::collections::BTreeSet; + +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[derive(Clone, Debug, Serialize)] +pub(crate) enum MetaTransaction { + Deploy(Deploy), + V1(MetaTransactionV1), +} + +impl MetaTransaction { + /// Returns the `TransactionHash` identifying this transaction. + pub fn hash(&self) -> TransactionHash { + match self { + MetaTransaction::Deploy(deploy) => TransactionHash::from(*deploy.hash()), + MetaTransaction::V1(txn) => TransactionHash::from(*txn.hash()), + } + } + + /// Timestamp. + pub fn timestamp(&self) -> Timestamp { + match self { + MetaTransaction::Deploy(deploy) => deploy.header().timestamp(), + MetaTransaction::V1(v1) => v1.timestamp(), + } + } + + /// Time to live. + pub fn ttl(&self) -> TimeDiff { + match self { + MetaTransaction::Deploy(deploy) => deploy.header().ttl(), + MetaTransaction::V1(v1) => v1.ttl(), + } + } + + /// Returns the `Approval`s for this transaction. + pub fn approvals(&self) -> BTreeSet { + match self { + MetaTransaction::Deploy(deploy) => deploy.approvals().clone(), + MetaTransaction::V1(v1) => v1.approvals().clone(), + } + } + + /// Returns the address of the initiator of the transaction. + pub fn initiator_addr(&self) -> InitiatorAddr { + match self { + MetaTransaction::Deploy(deploy) => InitiatorAddr::PublicKey(deploy.account().clone()), + MetaTransaction::V1(txn) => txn.initiator_addr().clone(), + } + } + + /// Returns the set of account hashes corresponding to the public keys of the approvals. + pub fn signers(&self) -> BTreeSet { + match self { + MetaTransaction::Deploy(deploy) => deploy + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + MetaTransaction::V1(txn) => txn + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + } + } + + /// Returns `true` if `self` represents a native transfer deploy or a native V1 transaction. + pub fn is_native(&self) -> bool { + match self { + MetaTransaction::Deploy(deploy) => deploy.is_transfer(), + MetaTransaction::V1(v1_txn) => *v1_txn.target() == TransactionTarget::Native, + } + } + + /// Should this transaction use standard payment processing? + pub fn is_standard_payment(&self) -> bool { + match self { + MetaTransaction::Deploy(deploy) => deploy.payment().is_standard_payment(Phase::Payment), + MetaTransaction::V1(v1) => { + if let PricingMode::Classic { + standard_payment, .. + } = v1.pricing_mode() + { + *standard_payment + } else { + true + } + } + } + } + + /// Authorization keys. + pub fn authorization_keys(&self) -> BTreeSet { + match self { + MetaTransaction::Deploy(deploy) => deploy + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + MetaTransaction::V1(transaction_v1) => transaction_v1 + .approvals() + .iter() + .map(|approval| approval.signer().to_account_hash()) + .collect(), + } + } + + /// The session args. + pub fn session_args(&self) -> &RuntimeArgs { + match self { + MetaTransaction::Deploy(deploy) => deploy.session().args(), + MetaTransaction::V1(transaction_v1) => transaction_v1.args(), + } + } + + /// The entry point. + pub fn entry_point(&self) -> TransactionEntryPoint { + match self { + MetaTransaction::Deploy(deploy) => deploy.session().entry_point_name().into(), + MetaTransaction::V1(transaction_v1) => transaction_v1.entry_point().clone(), + } + } + + /// The transaction lane. + pub fn transaction_lane(&self) -> u8 { + match self { + MetaTransaction::Deploy(deploy) => { + if deploy.is_transfer() { + MINT_LANE_ID + } else { + LARGE_WASM_LANE_ID + } + } + MetaTransaction::V1(v1) => v1.transaction_lane(), + } + } + + /// Returns the gas price tolerance. + pub fn gas_price_tolerance(&self) -> Result { + match self { + MetaTransaction::Deploy(deploy) => deploy + .gas_price_tolerance() + .map_err(InvalidTransaction::from), + MetaTransaction::V1(v1) => Ok(v1.gas_price_tolerance()), + } + } + + pub fn gas_limit(&self, chainspec: &Chainspec) -> Result { + match self { + MetaTransaction::Deploy(deploy) => deploy + .gas_limit(chainspec) + .map_err(InvalidTransaction::from), + MetaTransaction::V1(v1) => v1.gas_limit(chainspec), + } + } + + /// Is the transaction the legacy deploy variant. + pub fn is_legacy_transaction(&self) -> bool { + match self { + MetaTransaction::Deploy(_) => true, + MetaTransaction::V1(_) => false, + } + } + + pub fn from( + transaction: &Transaction, + transaction_config: &TransactionConfig, + ) -> Result { + match transaction { + Transaction::Deploy(deploy) => Ok(MetaTransaction::Deploy(deploy.clone())), + Transaction::V1(v1) => { + MetaTransactionV1::from(v1, transaction_config).map(MetaTransaction::V1) + } + } + } + + pub fn is_config_compliant( + &self, + chainspec: &Chainspec, + timestamp_leeway: TimeDiff, + at: Timestamp, + ) -> Result<(), InvalidTransaction> { + match self { + MetaTransaction::Deploy(deploy) => deploy + .is_config_compliant(chainspec, timestamp_leeway, at) + .map_err(InvalidTransaction::from), + MetaTransaction::V1(v1) => v1 + .is_config_compliant(chainspec, timestamp_leeway, at) + .map_err(InvalidTransaction::from), + } + } + + pub fn payload_hash(&self) -> Digest { + match self { + MetaTransaction::Deploy(deploy) => *deploy.body_hash(), + MetaTransaction::V1(v1) => *v1.payload_hash(), + } + } + + pub fn to_session_input_data(&self) -> SessionInputData { + let initiator_addr = self.initiator_addr(); + let is_standard_payment = self.is_standard_payment(); + match self { + MetaTransaction::Deploy(deploy) => { + let data = SessionDataDeploy::new( + deploy.hash(), + deploy.session(), + initiator_addr, + self.signers().clone(), + is_standard_payment, + ); + SessionInputData::DeploySessionData { data } + } + MetaTransaction::V1(v1) => { + let data = SessionDataV1::new( + v1.args(), + v1.target(), + v1.entry_point(), + v1.transaction_lane() == INSTALL_UPGRADE_LANE_ID, + v1.hash(), + v1.pricing_mode(), + initiator_addr, + self.signers().clone(), + is_standard_payment, + ); + SessionInputData::SessionDataV1 { data } + } + } + } + + /// Size estimate. + pub fn size_estimate(&self) -> usize { + match self { + MetaTransaction::Deploy(deploy) => deploy.serialized_length(), + MetaTransaction::V1(v1) => v1.serialized_length(), + } + } +} + +impl Display for MetaTransaction { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match self { + MetaTransaction::Deploy(deploy) => Display::fmt(deploy, formatter), + MetaTransaction::V1(txn) => Display::fmt(txn, formatter), + } + } +} + +#[cfg(test)] +mod proptests { + use super::*; + use casper_types::gens::transaction_arb; + use proptest::prelude::*; + + proptest! { + #[test] + fn construction_roundtrip(transaction in transaction_arb()) { + let maybe_transaction = MetaTransaction::from(&transaction, &TransactionConfig::default()); + assert!(maybe_transaction.is_ok()); + } + } +} diff --git a/node/src/types/transaction/meta_transaction/meta_transaction_v1.rs b/node/src/types/transaction/meta_transaction/meta_transaction_v1.rs new file mode 100644 index 0000000000..19328d97ed --- /dev/null +++ b/node/src/types/transaction/meta_transaction/meta_transaction_v1.rs @@ -0,0 +1,586 @@ +use super::tranasction_lane::{calculate_transaction_lane, TransactionLane}; +use casper_types::{ + arg_handling, bytesrepr::ToBytes, crypto, Approval, Chainspec, Digest, DisplayIter, Gas, + InitiatorAddr, InvalidTransaction, InvalidTransactionV1, PricingHandling, PricingMode, + RuntimeArgs, TimeDiff, Timestamp, TransactionConfig, TransactionEntryPoint, + TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1ExcessiveSizeError, + TransactionV1Hash, U512, +}; +use core::fmt::{self, Debug, Display, Formatter}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(any(feature = "once_cell", test))] +use once_cell::sync::OnceCell; +use serde::Serialize; +use std::collections::BTreeSet; +use tracing::debug; + +const ARGS_MAP_KEY: u16 = 0; +const TARGET_MAP_KEY: u16 = 1; +const ENTRY_POINT_MAP_KEY: u16 = 2; +const SCHEDULING_MAP_KEY: u16 = 3; + +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[derive(Clone, Debug, Serialize)] +pub struct MetaTransactionV1 { + hash: TransactionV1Hash, + chain_name: String, + timestamp: Timestamp, + ttl: TimeDiff, + pricing_mode: PricingMode, + initiator_addr: InitiatorAddr, + args: RuntimeArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + transaction_lane: TransactionLane, + scheduling: TransactionScheduling, + approvals: BTreeSet, + serialized_length: usize, + payload_hash: Digest, + has_valid_hash: Result<(), InvalidTransactionV1>, + #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))] + #[cfg_attr( + all(any(feature = "once_cell", test), feature = "datasize"), + data_size(skip) + )] + #[cfg(any(feature = "once_cell", test))] + is_verified: OnceCell>, +} + +impl MetaTransactionV1 { + pub fn from( + v1: &TransactionV1, + transaction_config: &TransactionConfig, + ) -> Result { + let args: RuntimeArgs = v1.deserialize_field(ARGS_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let target: TransactionTarget = v1.deserialize_field(TARGET_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let entry_point: TransactionEntryPoint = + v1.deserialize_field(ENTRY_POINT_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let scheduling: TransactionScheduling = + v1.deserialize_field(SCHEDULING_MAP_KEY).map_err(|error| { + InvalidTransaction::V1(InvalidTransactionV1::CouldNotDeserializeField { error }) + })?; + let payload_hash = v1.payload_hash()?; + let serialized_length = v1.serialized_length(); + + let lane_id = calculate_transaction_lane( + &entry_point, + &target, + v1.pricing_mode().additional_computation_factor(), + transaction_config, + serialized_length as u64, + )?; + let transaction_lane = + TransactionLane::try_from(lane_id).map_err(Into::::into)?; + let has_valid_hash = v1.has_valid_hash(); + Ok(MetaTransactionV1::new( + *v1.hash(), + v1.chain_name().to_string(), + v1.timestamp(), + v1.ttl(), + v1.pricing_mode().clone(), + v1.initiator_addr().clone(), + args, + target, + entry_point, + transaction_lane, + scheduling, + serialized_length, + payload_hash, + v1.approvals().clone(), + has_valid_hash, + )) + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + hash: TransactionV1Hash, + chain_name: String, + timestamp: Timestamp, + ttl: TimeDiff, + pricing_mode: PricingMode, + initiator_addr: InitiatorAddr, + args: RuntimeArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + transaction_lane: TransactionLane, + scheduling: TransactionScheduling, + serialized_length: usize, + payload_hash: Digest, + approvals: BTreeSet, + has_valid_hash: Result<(), InvalidTransactionV1>, + ) -> Self { + Self { + hash, + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + args, + target, + entry_point, + transaction_lane, + scheduling, + approvals, + serialized_length, + payload_hash, + has_valid_hash, + #[cfg(any(feature = "once_cell", test))] + is_verified: OnceCell::new(), + } + } + + /// Returns the runtime args of the transaction. + pub fn args(&self) -> &RuntimeArgs { + &self.args + } + + /// Returns the `DeployHash` identifying this `Deploy`. + pub fn hash(&self) -> &TransactionV1Hash { + &self.hash + } + + /// Returns the `Approvals`. + pub fn approvals(&self) -> &BTreeSet { + &self.approvals + } + + /// Returns `Ok` if and only if: + /// * the transaction hash is correct (see [`TransactionV1::has_valid_hash`] for details) + /// * approvals are non empty, and + /// * all approvals are valid signatures of the signed hash + pub fn verify(&self) -> Result<(), InvalidTransactionV1> { + #[cfg(any(feature = "once_cell", test))] + return self.is_verified.get_or_init(|| self.do_verify()).clone(); + + #[cfg(not(any(feature = "once_cell", test)))] + self.do_verify() + } + + /// Returns `Ok` if and only if this transaction's body hashes to the value of `body_hash()`, + /// and if this transaction's header hashes to the value claimed as the transaction hash. + pub fn has_valid_hash(&self) -> &Result<(), InvalidTransactionV1> { + &self.has_valid_hash + } + + fn do_verify(&self) -> Result<(), InvalidTransactionV1> { + if self.approvals.is_empty() { + debug!(?self, "transaction has no approvals"); + return Err(InvalidTransactionV1::EmptyApprovals); + } + + self.has_valid_hash().clone()?; + + for (index, approval) in self.approvals.iter().enumerate() { + if let Err(error) = crypto::verify(self.hash, approval.signature(), approval.signer()) { + debug!( + ?self, + "failed to verify transaction approval {}: {}", index, error + ); + return Err(InvalidTransactionV1::InvalidApproval { index, error }); + } + } + + Ok(()) + } + + /// Returns the entry point of the transaction. + pub fn entry_point(&self) -> &TransactionEntryPoint { + &self.entry_point + } + + /// Returns the transaction lane. + pub fn transaction_lane(&self) -> u8 { + self.transaction_lane as u8 + } + + /// Returns payload hash of the transaction. + pub fn payload_hash(&self) -> &Digest { + &self.payload_hash + } + + /// Returns the pricing mode for the transaction. + pub fn pricing_mode(&self) -> &PricingMode { + &self.pricing_mode + } + + /// Returns the initiator_addr of the transaction. + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + /// Returns the target of the transaction. + pub fn target(&self) -> &TransactionTarget { + &self.target + } + + /// Returns `true` if the serialized size of the transaction is not greater than + /// `max_transaction_size`. + fn is_valid_size( + &self, + max_transaction_size: u32, + ) -> Result<(), TransactionV1ExcessiveSizeError> { + let actual_transaction_size = self.serialized_length; + if actual_transaction_size > max_transaction_size as usize { + return Err(TransactionV1ExcessiveSizeError { + max_transaction_size, + actual_transaction_size, + }); + } + Ok(()) + } + + /// Returns the creation timestamp of the `Deploy`. + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + + /// Returns the duration after the creation timestamp for which the `Deploy` will stay valid. + /// + /// After this duration has ended, the `Deploy` will be considered expired. + pub fn ttl(&self) -> TimeDiff { + self.ttl + } + + /// Returns `Ok` if and only if: + /// * the chain_name is correct, + /// * the configured parameters are complied with at the given timestamp + pub fn is_config_compliant( + &self, + chainspec: &Chainspec, + timestamp_leeway: TimeDiff, + at: Timestamp, + ) -> Result<(), InvalidTransactionV1> { + let transaction_config = chainspec.transaction_config.clone(); + self.is_valid_size( + transaction_config + .transaction_v1_config + .get_max_serialized_length(self.transaction_lane as u8) as u32, + )?; + + let chain_name = chainspec.network_config.name.clone(); + + if self.chain_name != chain_name { + debug!( + transaction_hash = %self.hash(), + chain_name = %self.chain_name, + timestamp= %self.timestamp, + ttl= %self.ttl, + pricing_mode= %self.pricing_mode, + initiator_addr= %self.initiator_addr, + target= %self.target, + entry_point= %self.entry_point, + transaction_lane= %self.transaction_lane, + scheduling= %self.scheduling, + "invalid chain identifier" + ); + return Err(InvalidTransactionV1::InvalidChainName { + expected: chain_name, + got: self.chain_name.to_string(), + }); + } + + let price_handling = chainspec.core_config.pricing_handling; + let pricing_mode = &self.pricing_mode; + + match pricing_mode { + PricingMode::Classic { .. } => { + if let PricingHandling::Classic = price_handling { + } else { + return Err(InvalidTransactionV1::InvalidPricingMode { + price_mode: pricing_mode.clone(), + }); + } + } + PricingMode::Fixed { .. } => { + if let PricingHandling::Fixed = price_handling { + } else { + return Err(InvalidTransactionV1::InvalidPricingMode { + price_mode: pricing_mode.clone(), + }); + } + } + PricingMode::Reserved { .. } => { + if !chainspec.core_config.allow_reservations { + // Currently Reserved isn't implemented and we should + // not be accepting transactions with this mode. + return Err(InvalidTransactionV1::InvalidPricingMode { + price_mode: pricing_mode.clone(), + }); + } + } + } + + let min_gas_price = chainspec.vacancy_config.min_gas_price; + let gas_price_tolerance = self.gas_price_tolerance(); + if gas_price_tolerance < min_gas_price { + return Err(InvalidTransactionV1::GasPriceToleranceTooLow { + min_gas_price_tolerance: min_gas_price, + provided_gas_price_tolerance: gas_price_tolerance, + }); + } + + self.is_header_metadata_valid(&transaction_config, timestamp_leeway, at, &self.hash)?; + + let max_associated_keys = chainspec.core_config.max_associated_keys; + + if self.approvals.len() > max_associated_keys as usize { + debug!( + transaction_hash = %self.hash(), + number_of_approvals = %self.approvals.len(), + max_associated_keys = %max_associated_keys, + "number of transaction approvals exceeds the limit" + ); + return Err(InvalidTransactionV1::ExcessiveApprovals { + got: self.approvals.len() as u32, + max_associated_keys, + }); + } + + let gas_limit = self + .pricing_mode + .gas_limit(chainspec, &self.entry_point, self.transaction_lane as u8) + .map_err(Into::::into)?; + let block_gas_limit = Gas::new(U512::from(transaction_config.block_gas_limit)); + if gas_limit > block_gas_limit { + debug!( + amount = %gas_limit, + %block_gas_limit, + "transaction gas limit exceeds block gas limit" + ); + return Err(InvalidTransactionV1::ExceedsBlockGasLimit { + block_gas_limit: transaction_config.block_gas_limit, + got: Box::new(gas_limit.value()), + }); + } + + self.is_body_metadata_valid(&transaction_config) + } + + fn is_body_metadata_valid( + &self, + config: &TransactionConfig, + ) -> Result<(), InvalidTransactionV1> { + let lane_id = self.transaction_lane as u8; + if !config.transaction_v1_config.is_supported(lane_id) { + return Err(InvalidTransactionV1::InvalidTransactionLane(lane_id)); + } + + let max_serialized_length = config + .transaction_v1_config + .get_max_serialized_length(lane_id); + let actual_length = self.serialized_length; + if actual_length > max_serialized_length as usize { + return Err(InvalidTransactionV1::ExcessiveSize( + TransactionV1ExcessiveSizeError { + max_transaction_size: max_serialized_length as u32, + actual_transaction_size: actual_length, + }, + )); + } + + let max_args_length = config.transaction_v1_config.get_max_args_length(lane_id); + + let args_length = self.args.serialized_length(); + if args_length > max_args_length as usize { + debug!( + args_length, + max_args_length = max_args_length, + "transaction runtime args excessive size" + ); + return Err(InvalidTransactionV1::ExcessiveArgsLength { + max_length: max_args_length as usize, + got: args_length, + }); + } + + match &self.target { + TransactionTarget::Native => match self.entry_point { + TransactionEntryPoint::Call => { + debug!( + entry_point = %self.entry_point, + "native transaction cannot have call entry point" + ); + Err(InvalidTransactionV1::EntryPointCannotBeCall) + } + TransactionEntryPoint::Custom(_) => { + debug!( + entry_point = %self.entry_point, + "native transaction cannot have custom entry point" + ); + Err(InvalidTransactionV1::EntryPointCannotBeCustom { + entry_point: self.entry_point.clone(), + }) + } + TransactionEntryPoint::Transfer => arg_handling::has_valid_transfer_args( + &self.args, + config.native_transfer_minimum_motes, + ), + TransactionEntryPoint::AddBid => arg_handling::has_valid_add_bid_args(&self.args), + TransactionEntryPoint::WithdrawBid => { + arg_handling::has_valid_withdraw_bid_args(&self.args) + } + TransactionEntryPoint::Delegate => { + arg_handling::has_valid_delegate_args(&self.args) + } + TransactionEntryPoint::Undelegate => { + arg_handling::has_valid_undelegate_args(&self.args) + } + TransactionEntryPoint::Redelegate => { + arg_handling::has_valid_redelegate_args(&self.args) + } + TransactionEntryPoint::ActivateBid => { + arg_handling::has_valid_activate_bid_args(&self.args) + } + TransactionEntryPoint::ChangeBidPublicKey => { + arg_handling::has_valid_change_bid_public_key_args(&self.args) + } + TransactionEntryPoint::AddReservations => { + todo!() + } + TransactionEntryPoint::CancelReservations => { + todo!() + } + }, + TransactionTarget::Stored { .. } => match &self.entry_point { + TransactionEntryPoint::Custom(_) => Ok(()), + TransactionEntryPoint::Call + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + debug!( + entry_point = %self.entry_point, + "transaction targeting stored entity/package must have custom entry point" + ); + Err(InvalidTransactionV1::EntryPointMustBeCustom { + entry_point: self.entry_point.clone(), + }) + } + }, + TransactionTarget::Session { module_bytes, .. } => match &self.entry_point { + TransactionEntryPoint::Call | TransactionEntryPoint::Custom(_) => { + if module_bytes.is_empty() { + debug!("transaction with session code must not have empty module bytes"); + return Err(InvalidTransactionV1::EmptyModuleBytes); + } + Ok(()) + } + TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + debug!( + entry_point = %self.entry_point, + "transaction with session code must use custom or default 'call' entry point" + ); + Err(InvalidTransactionV1::EntryPointMustBeCustom { + entry_point: self.entry_point.clone(), + }) + } + }, + } + } + + fn is_header_metadata_valid( + &self, + config: &TransactionConfig, + timestamp_leeway: TimeDiff, + at: Timestamp, + transaction_hash: &TransactionV1Hash, + ) -> Result<(), InvalidTransactionV1> { + if self.ttl() > config.max_ttl { + debug!( + %transaction_hash, + transaction_header = %self, + max_ttl = %config.max_ttl, + "transaction ttl excessive" + ); + return Err(InvalidTransactionV1::ExcessiveTimeToLive { + max_ttl: config.max_ttl, + got: self.ttl(), + }); + } + + if self.timestamp() > at + timestamp_leeway { + debug!( + %transaction_hash, transaction_header = %self, %at, + "transaction timestamp in the future" + ); + return Err(InvalidTransactionV1::TimestampInFuture { + validation_timestamp: at, + timestamp_leeway, + got: self.timestamp(), + }); + } + + Ok(()) + } + + /// Returns the gas price tolerance for the given transaction. + pub fn gas_price_tolerance(&self) -> u8 { + match self.pricing_mode { + PricingMode::Classic { + gas_price_tolerance, + .. + } => gas_price_tolerance, + PricingMode::Fixed { + gas_price_tolerance, + .. + } => gas_price_tolerance, + PricingMode::Reserved { .. } => { + // TODO: Change this when reserve gets implemented. + 0u8 + } + } + } + + pub fn serialized_length(&self) -> usize { + self.serialized_length + } + + pub fn gas_limit(&self, chainspec: &Chainspec) -> Result { + self.pricing_mode() + .gas_limit(chainspec, self.entry_point(), self.transaction_lane as u8) + .map_err(Into::into) + } +} + +impl Display for MetaTransactionV1 { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "meta-transaction-v1[hash: {}, chain_name: {}, timestamp: {}, ttl: {}, pricing_mode: {}, initiator_addr: {}, target: {}, entry_point: {}, transaction_lane: {}, scheduling: {}, approvals: {}]", + self.hash, + self.chain_name, + self.timestamp, + self.ttl, + self.pricing_mode, + self.initiator_addr, + self.target, + self.entry_point, + self.transaction_lane, + self.scheduling, + DisplayIter::new(self.approvals.iter()) + ) + } +} diff --git a/node/src/types/transaction/meta_transaction/tranasction_lane.rs b/node/src/types/transaction/meta_transaction/tranasction_lane.rs new file mode 100644 index 0000000000..0d39ccf0f5 --- /dev/null +++ b/node/src/types/transaction/meta_transaction/tranasction_lane.rs @@ -0,0 +1,161 @@ +use core::{ + convert::TryFrom, + fmt::{self, Formatter}, +}; + +use casper_types::{ + InvalidTransaction, InvalidTransactionV1, TransactionConfig, TransactionEntryPoint, + TransactionTarget, TransactionV1Config, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, +}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +use serde::Serialize; + +/// The category of a Transaction. +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Serialize)] +#[repr(u8)] +pub enum TransactionLane { + /// Native mint interaction (the default). + Mint = 0, + /// Native auction interaction. + Auction = 1, + /// InstallUpgradeWasm + InstallUpgradeWasm = 2, + /// A large Wasm based transaction. + Large = 3, + /// A medium Wasm based transaction. + Medium = 4, + /// A small Wasm based transaction. + Small = 5, +} + +#[derive(Debug)] +pub struct TransactionLaneConversionError(u8); + +impl From for InvalidTransaction { + fn from(value: TransactionLaneConversionError) -> InvalidTransaction { + InvalidTransaction::V1(InvalidTransactionV1::InvalidTransactionLane(value.0)) + } +} + +impl TryFrom for TransactionLane { + type Error = TransactionLaneConversionError; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Mint), + 1 => Ok(Self::Auction), + 2 => Ok(Self::InstallUpgradeWasm), + 3 => Ok(Self::Large), + 4 => Ok(Self::Medium), + 5 => Ok(Self::Small), + _ => Err(TransactionLaneConversionError(value)), + } + } +} + +impl fmt::Display for TransactionLane { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + match self { + TransactionLane::Mint => write!(f, "Mint"), + TransactionLane::Auction => write!(f, "Auction"), + TransactionLane::Large => write!(f, "Large"), + TransactionLane::Medium => write!(f, "Medium"), + TransactionLane::Small => write!(f, "Small"), + TransactionLane::InstallUpgradeWasm => write!(f, "InstallUpgradeWASM"), + } + } +} + +/// Calculates the laned based on properties of the transaction +pub(crate) fn calculate_transaction_lane( + entry_point: &TransactionEntryPoint, + target: &TransactionTarget, + additional_computation_factor: u8, + transaction_config: &TransactionConfig, + transaction_size: u64, +) -> Result { + match target { + TransactionTarget::Native => match entry_point { + TransactionEntryPoint::Transfer => Ok(MINT_LANE_ID), + TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => Ok(AUCTION_LANE_ID), + TransactionEntryPoint::Call => Err(InvalidTransactionV1::EntryPointCannotBeCall), + TransactionEntryPoint::Custom(_) => { + Err(InvalidTransactionV1::EntryPointCannotBeCustom { + entry_point: entry_point.clone(), + }) + } + }, + TransactionTarget::Stored { .. } => match entry_point { + TransactionEntryPoint::Custom(_) => get_lane_for_non_install_wasm( + &transaction_config.transaction_v1_config, + transaction_size, + additional_computation_factor, + ), + TransactionEntryPoint::Call + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + Err(InvalidTransactionV1::EntryPointMustBeCustom { + entry_point: entry_point.clone(), + }) + } + }, + TransactionTarget::Session { + is_install_upgrade, .. + } => match entry_point { + TransactionEntryPoint::Call => { + if *is_install_upgrade { + Ok(INSTALL_UPGRADE_LANE_ID) + } else { + get_lane_for_non_install_wasm( + &transaction_config.transaction_v1_config, + transaction_size, + additional_computation_factor, + ) + } + } + TransactionEntryPoint::Custom(_) + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + Err(InvalidTransactionV1::EntryPointMustBeCall { + entry_point: entry_point.clone(), + }) + } + }, + } +} + +fn get_lane_for_non_install_wasm( + config: &TransactionV1Config, + transaction_size: u64, + additional_computation_factor: u8, +) -> Result { + config + .get_wasm_lane_id(transaction_size, additional_computation_factor) + .ok_or(InvalidTransactionV1::NoWasmLaneMatchesTransaction()) +} diff --git a/node/src/types/transaction/meta_transaction/transaction_header.rs b/node/src/types/transaction/meta_transaction/transaction_header.rs new file mode 100644 index 0000000000..fa0c6b0108 --- /dev/null +++ b/node/src/types/transaction/meta_transaction/transaction_header.rs @@ -0,0 +1,77 @@ +use casper_types::{DeployHeader, InitiatorAddr, TimeDiff, Timestamp, Transaction, TransactionV1}; +use core::fmt::{self, Display, Formatter}; +use datasize::DataSize; +use serde::Serialize; + +#[derive(Debug, Clone, DataSize, PartialEq, Eq, Serialize)] +pub(crate) struct TransactionV1Metadata { + initiator_addr: InitiatorAddr, + timestamp: Timestamp, + ttl: TimeDiff, +} + +impl TransactionV1Metadata { + pub(crate) fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + pub(crate) fn timestamp(&self) -> Timestamp { + self.timestamp + } + + pub(crate) fn ttl(&self) -> TimeDiff { + self.ttl + } +} + +impl Display for TransactionV1Metadata { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "transaction-v1-metadata[initiator_addr: {}]", + self.initiator_addr, + ) + } +} + +#[derive(Debug, Clone, DataSize, Serialize, PartialEq, Eq)] +/// A versioned wrapper for a transaction header or deploy header. +pub(crate) enum TransactionHeader { + Deploy(DeployHeader), + V1(TransactionV1Metadata), +} + +impl From for TransactionHeader { + fn from(header: DeployHeader) -> Self { + Self::Deploy(header) + } +} + +impl From<&TransactionV1> for TransactionHeader { + fn from(transaction_v1: &TransactionV1) -> Self { + let meta = TransactionV1Metadata { + initiator_addr: transaction_v1.initiator_addr().clone(), + timestamp: transaction_v1.timestamp(), + ttl: transaction_v1.ttl(), + }; + Self::V1(meta) + } +} + +impl From<&Transaction> for TransactionHeader { + fn from(transaction: &Transaction) -> Self { + match transaction { + Transaction::Deploy(deploy) => deploy.header().clone().into(), + Transaction::V1(v1) => v1.into(), + } + } +} + +impl Display for TransactionHeader { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + match self { + TransactionHeader::Deploy(header) => Display::fmt(header, formatter), + TransactionHeader::V1(meta) => Display::fmt(meta, formatter), + } + } +} diff --git a/node/src/types/transaction/transaction_footprint.rs b/node/src/types/transaction/transaction_footprint.rs index 163c6104b2..825fa57589 100644 --- a/node/src/types/transaction/transaction_footprint.rs +++ b/node/src/types/transaction/transaction_footprint.rs @@ -1,6 +1,7 @@ +use crate::types::MetaTransaction; use casper_types::{ - Approval, Chainspec, Digest, Gas, GasLimited, InvalidTransaction, InvalidTransactionV1, - TimeDiff, Timestamp, Transaction, TransactionHash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, + Approval, Chainspec, Digest, Gas, InvalidTransaction, InvalidTransactionV1, TimeDiff, + Timestamp, Transaction, TransactionHash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, }; use datasize::DataSize; @@ -13,16 +14,16 @@ use std::collections::BTreeSet; pub(crate) struct TransactionFootprint { /// The identifying hash. pub(crate) transaction_hash: TransactionHash, - /// Transaction body hash. - pub(crate) body_hash: Digest, + /// Transaction payload hash. + pub(crate) payload_hash: Digest, /// The estimated gas consumption. pub(crate) gas_limit: Gas, /// The gas tolerance. pub(crate) gas_price_tolerance: u8, /// The bytesrepr serialized length. pub(crate) size_estimate: usize, - /// The transaction category. - pub(crate) category: u8, + /// The transaction lane_id. + pub(crate) lane_id: u8, /// Timestamp of the transaction. pub(crate) timestamp: Timestamp, /// Time to live for the transaction. @@ -35,32 +36,40 @@ impl TransactionFootprint { pub(crate) fn new( chainspec: &Chainspec, transaction: &Transaction, + ) -> Result { + let transaction = MetaTransaction::from(transaction, &chainspec.transaction_config)?; + Self::new_from_meta_transaction(chainspec, &transaction) + } + + fn new_from_meta_transaction( + chainspec: &Chainspec, + transaction: &MetaTransaction, ) -> Result { let gas_price_tolerance = transaction.gas_price_tolerance()?; let gas_limit = transaction.gas_limit(chainspec)?; - let category = transaction.transaction_category(); + let lane_id = transaction.transaction_lane(); if !chainspec .transaction_config .transaction_v1_config - .is_supported(category) + .is_supported(lane_id) { return Err(InvalidTransaction::V1( - InvalidTransactionV1::InvalidTransactionKind(category), + InvalidTransactionV1::InvalidTransactionLane(lane_id), )); } let transaction_hash = transaction.hash(); - let body_hash = transaction.body_hash(); let size_estimate = transaction.size_estimate(); + let payload_hash = transaction.payload_hash(); let timestamp = transaction.timestamp(); let ttl = transaction.ttl(); let approvals = transaction.approvals(); Ok(TransactionFootprint { transaction_hash, - body_hash, + payload_hash, gas_limit, gas_price_tolerance, size_estimate, - category, + lane_id, timestamp, ttl, approvals, @@ -80,7 +89,7 @@ impl TransactionFootprint { /// Is mint interaction. pub(crate) fn is_mint(&self) -> bool { - if self.category == MINT_LANE_ID { + if self.lane_id == MINT_LANE_ID { return true; } @@ -89,7 +98,7 @@ impl TransactionFootprint { /// Is auction interaction. pub(crate) fn is_auction(&self) -> bool { - if self.category == AUCTION_LANE_ID { + if self.lane_id == AUCTION_LANE_ID { return true; } @@ -97,7 +106,7 @@ impl TransactionFootprint { } pub(crate) fn is_install_upgrade(&self) -> bool { - if self.category == INSTALL_UPGRADE_LANE_ID { + if self.lane_id == INSTALL_UPGRADE_LANE_ID { return true; } diff --git a/node/src/utils/specimen.rs b/node/src/utils/specimen.rs index d0a30fb897..cbf84eca64 100644 --- a/node/src/utils/specimen.rs +++ b/node/src/utils/specimen.rs @@ -28,7 +28,7 @@ use casper_types::{ ProtocolVersion, RewardedSignatures, RuntimeArgs, SecretKey, SemVer, SignedBlockHeader, SingleBlockRewardedSignatures, TimeDiff, Timestamp, Transaction, TransactionHash, TransactionId, TransactionV1, TransactionV1Builder, TransactionV1Hash, URef, AUCTION_LANE_ID, - INSTALL_UPGRADE_LANE_ID, KEY_HASH_LENGTH, MINT_LANE_ID, U512, + INSTALL_UPGRADE_LANE_ID, KEY_HASH_LENGTH, LARGE_WASM_LANE_ID, MINT_LANE_ID, U512, }; use crate::{ @@ -47,8 +47,6 @@ use casper_storage::block_store::types::ApprovalsHashes; /// The largest valid unicode codepoint that can be encoded to UTF-8. pub(crate) const HIGHEST_UNICODE_CODEPOINT: char = '\u{10FFFF}'; -const LARGE_LANE_ID: u8 = 3; - /// A cache used for memoization, typically on a single estimator. #[derive(Debug, Default)] pub(crate) struct Cache { @@ -860,7 +858,7 @@ impl LargestSpecimen for BlockPayload { ], ); transactions.insert( - LARGE_LANE_ID, + LARGE_WASM_LANE_ID, vec![ large_txn_hash_with_approvals.clone(); estimator.parameter::("max_standard_transactions_per_block") @@ -1014,9 +1012,8 @@ impl LargestSpecimen for TransactionV1 { // See comment in `impl LargestSpecimen for ExecutableDeployItem` below for rationale here. let max_size_with_margin = estimator.parameter::("max_transaction_size").max(0) as usize + 10 * 4; - TransactionV1Builder::new_session( - casper_types::TransactionCategory::InstallUpgrade, + true, Bytes::from(vec_of_largest_specimen( estimator, max_size_with_margin, diff --git a/resources/local/chainspec.toml.in b/resources/local/chainspec.toml.in index 69c5b92b2a..0680cd99aa 100644 --- a/resources/local/chainspec.toml.in +++ b/resources/local/chainspec.toml.in @@ -193,7 +193,8 @@ max_timestamp_leeway = '5 seconds' # [4] -> The maximum number of transactions the lane can contain native_mint_lane = [0, 1024, 1024, 65_000_000_000, 650] native_auction_lane = [1, 2048, 2048, 362_500_000_000, 145] -wasm_lanes = [[2, 1_048_576, 2048, 1_000_000_000_000, 1], [3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] +install_upgrade_lane = [2, 1_048_576, 2048, 1_000_000_000_000, 1] +wasm_lanes = [[3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] [transactions.deploy] # The maximum number of Motes allowed to be spent during payment. 0 means unlimited. diff --git a/resources/production/chainspec.toml b/resources/production/chainspec.toml index ad9bdcabf2..a084c37b9b 100644 --- a/resources/production/chainspec.toml +++ b/resources/production/chainspec.toml @@ -203,7 +203,8 @@ max_timestamp_leeway = '5 seconds' # [4] -> The maximum number of transactions the lane can contain native_mint_lane = [0, 1024, 1024, 65_000_000_000, 650] native_auction_lane = [1, 2048, 2048, 362_500_000_000, 145] -wasm_lanes = [[2, 1_048_576, 2048, 1_000_000_000_000, 1], [3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] +install_upgrade_lane = [2, 1_048_576, 2048, 1_000_000_000_000, 1] +wasm_lanes = [[3, 344_064, 1024, 500_000_000_000, 3], [4, 172_032, 1024, 50_000_000_000, 7], [5, 12_288, 512, 1_500_000_000, 15]] [transactions.deploy] # The maximum number of Motes allowed to be spent during payment. 0 means unlimited. diff --git a/resources/test/sse_data_schema.json b/resources/test/sse_data_schema.json index 23e740922a..789fca8aa8 100644 --- a/resources/test/sse_data_schema.json +++ b/resources/test/sse_data_schema.json @@ -1560,25 +1560,15 @@ "type": "object", "required": [ "approvals", - "body", "hash", - "header", - "serialization_version" + "payload" ], "properties": { - "serialization_version": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, "hash": { "$ref": "#/definitions/TransactionV1Hash" }, - "header": { - "$ref": "#/definitions/TransactionV1Header" - }, - "body": { - "$ref": "#/definitions/TransactionV1Body" + "payload": { + "$ref": "#/definitions/TransactionV1Payload" }, "approvals": { "type": "array", @@ -1590,18 +1580,24 @@ }, "additionalProperties": false }, - "TransactionV1Header": { - "description": "The header portion of a TransactionV1.", + "TransactionV1Payload": { + "description": "A unit of work sent by a client to the network, which when executed can cause global state to be altered.", "type": "object", "required": [ - "body_hash", "chain_name", + "fields", "initiator_addr", "pricing_mode", + "serialization_version", "timestamp", "ttl" ], "properties": { + "serialization_version": { + "type": "integer", + "format": "uint8", + "minimum": 0.0 + }, "chain_name": { "type": "string" }, @@ -1611,14 +1607,17 @@ "ttl": { "$ref": "#/definitions/TimeDiff" }, - "body_hash": { - "$ref": "#/definitions/Digest" - }, "pricing_mode": { "$ref": "#/definitions/PricingMode" }, "initiator_addr": { "$ref": "#/definitions/InitiatorAddr" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Bytes" + } } }, "additionalProperties": false @@ -1752,368 +1751,6 @@ "description": "Account hash as a formatted string.", "type": "string" }, - "TransactionV1Body": { - "description": "Body of a `TransactionV1`.", - "type": "object", - "required": [ - "args", - "entry_point", - "scheduling", - "target", - "transaction_category" - ], - "properties": { - "args": { - "$ref": "#/definitions/RuntimeArgs" - }, - "target": { - "$ref": "#/definitions/TransactionTarget" - }, - "entry_point": { - "$ref": "#/definitions/TransactionEntryPoint" - }, - "transaction_category": { - "type": "integer", - "format": "uint8", - "minimum": 0.0 - }, - "scheduling": { - "$ref": "#/definitions/TransactionScheduling" - } - }, - "additionalProperties": false - }, - "TransactionTarget": { - "description": "Execution target of a Transaction.", - "oneOf": [ - { - "description": "The execution target is a native operation (e.g. a transfer).", - "type": "string", - "enum": [ - "Native" - ] - }, - { - "description": "The execution target is a stored entity or package.", - "type": "object", - "required": [ - "Stored" - ], - "properties": { - "Stored": { - "type": "object", - "required": [ - "id", - "runtime" - ], - "properties": { - "id": { - "description": "The identifier of the stored execution target.", - "allOf": [ - { - "$ref": "#/definitions/TransactionInvocationTarget" - } - ] - }, - "runtime": { - "description": "The execution runtime to use.", - "allOf": [ - { - "$ref": "#/definitions/TransactionRuntime" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The execution target is the included module bytes, i.e. compiled Wasm.", - "type": "object", - "required": [ - "Session" - ], - "properties": { - "Session": { - "type": "object", - "required": [ - "module_bytes", - "runtime" - ], - "properties": { - "module_bytes": { - "description": "The compiled Wasm.", - "allOf": [ - { - "$ref": "#/definitions/Bytes" - } - ] - }, - "runtime": { - "description": "The execution runtime to use.", - "allOf": [ - { - "$ref": "#/definitions/TransactionRuntime" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "TransactionInvocationTarget": { - "description": "Identifier of a `Stored` transaction target.", - "oneOf": [ - { - "description": "Hex-encoded entity address identifying the invocable entity.", - "type": "object", - "required": [ - "ByHash" - ], - "properties": { - "ByHash": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The alias identifying the invocable entity.", - "type": "object", - "required": [ - "ByName" - ], - "properties": { - "ByName": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The address and optional version identifying the package.", - "type": "object", - "required": [ - "ByPackageHash" - ], - "properties": { - "ByPackageHash": { - "type": "object", - "required": [ - "addr" - ], - "properties": { - "addr": { - "description": "Hex-encoded address of the package.", - "type": "string" - }, - "version": { - "description": "The package version.\n\nIf `None`, the latest enabled version is implied.", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "The alias and optional version identifying the package.", - "type": "object", - "required": [ - "ByPackageName" - ], - "properties": { - "ByPackageName": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "name": { - "description": "The package name.", - "type": "string" - }, - "version": { - "description": "The package version.\n\nIf `None`, the latest enabled version is implied.", - "type": [ - "integer", - "null" - ], - "format": "uint32", - "minimum": 0.0 - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "TransactionRuntime": { - "description": "Runtime used to execute a Transaction.", - "oneOf": [ - { - "description": "The Casper Version 1 Virtual Machine.", - "type": "string", - "enum": [ - "VmCasperV1" - ] - }, - { - "description": "The Casper Version 2 Virtual Machine.", - "type": "string", - "enum": [ - "VmCasperV2" - ] - } - ] - }, - "TransactionEntryPoint": { - "description": "Entry point of a Transaction.", - "oneOf": [ - { - "description": "The standard `call` entry point used in session code.", - "type": "string", - "enum": [ - "Call" - ] - }, - { - "description": "A non-native, arbitrary entry point.", - "type": "object", - "required": [ - "Custom" - ], - "properties": { - "Custom": { - "type": "string" - } - }, - "additionalProperties": false - }, - { - "description": "The `transfer` native entry point, used to transfer `Motes` from a source purse to a target purse.", - "type": "string", - "enum": [ - "Transfer" - ] - }, - { - "description": "The `add_bid` native entry point, used to create or top off a bid purse.", - "type": "string", - "enum": [ - "AddBid" - ] - }, - { - "description": "The `withdraw_bid` native entry point, used to decrease a stake.", - "type": "string", - "enum": [ - "WithdrawBid" - ] - }, - { - "description": "The `delegate` native entry point, used to add a new delegator or increase an existing delegator's stake.", - "type": "string", - "enum": [ - "Delegate" - ] - }, - { - "description": "The `undelegate` native entry point, used to reduce a delegator's stake or remove the delegator if the remaining stake is 0.", - "type": "string", - "enum": [ - "Undelegate" - ] - }, - { - "description": "The `redelegate` native entry point, used to reduce a delegator's stake or remove the delegator if the remaining stake is 0, and after the unbonding delay, automatically delegate to a new validator.", - "type": "string", - "enum": [ - "Redelegate" - ] - }, - { - "description": "The `activate_bid` native entry point, used to used to reactivate an inactive bid.", - "type": "string", - "enum": [ - "ActivateBid" - ] - }, - { - "description": "The `change_bid_public_key` native entry point, used to change a bid's public key.", - "type": "string", - "enum": [ - "ChangeBidPublicKey" - ] - }, - { - "description": "The `add_reservations` native entry point, used to add delegator to validator's reserve list", - "type": "string", - "enum": [ - "AddReservations" - ] - }, - { - "description": "The `cancel_reservations` native entry point, used to remove delegator from validator's reserve list", - "type": "string", - "enum": [ - "CancelReservations" - ] - } - ] - }, - "TransactionScheduling": { - "description": "Scheduling mode of a Transaction.", - "oneOf": [ - { - "description": "No special scheduling applied.", - "type": "string", - "enum": [ - "Standard" - ] - }, - { - "description": "Execution should be scheduled for the specified era.", - "type": "object", - "required": [ - "FutureEra" - ], - "properties": { - "FutureEra": { - "$ref": "#/definitions/EraId" - } - }, - "additionalProperties": false - }, - { - "description": "Execution should be scheduled for the specified timestamp or later.", - "type": "object", - "required": [ - "FutureTimestamp" - ], - "properties": { - "FutureTimestamp": { - "$ref": "#/definitions/Timestamp" - } - }, - "additionalProperties": false - } - ] - }, "ExecutionResult": { "description": "The versioned result of executing a single deploy.", "oneOf": [ @@ -4661,6 +4298,25 @@ } ] }, + "TransactionRuntime": { + "description": "Runtime used to execute a Transaction.", + "oneOf": [ + { + "description": "The Casper Version 1 Virtual Machine.", + "type": "string", + "enum": [ + "VmCasperV1" + ] + }, + { + "description": "The Casper Version 2 Virtual Machine.", + "type": "string", + "enum": [ + "VmCasperV2" + ] + } + ] + }, "ByteCodeHash": { "description": "The hash address of the contract wasm", "type": "string" diff --git a/types/src/block.rs b/types/src/block.rs index 703c8dbf05..58a8048c31 100644 --- a/types/src/block.rs +++ b/types/src/block.rs @@ -444,25 +444,44 @@ impl Block { Block::V1(_) => false, Block::V2(block_v2) => { let mint_count = block_v2.mint().count(); - if mint_count as u64 >= transaction_config.transaction_v1_config.native_mint_lane[4] + if mint_count as u64 + >= transaction_config + .transaction_v1_config + .native_mint_lane + .max_transaction_count() { return true; } let auction_count = block_v2.auction().count(); if auction_count as u64 - >= transaction_config.transaction_v1_config.native_auction_lane[4] + >= transaction_config + .transaction_v1_config + .native_auction_lane + .max_transaction_count() { return true; } - for (category, transactions) in block_v2.body.transactions() { + + let install_upgrade_count = block_v2.install_upgrade().count(); + if install_upgrade_count as u64 + >= transaction_config + .transaction_v1_config + .install_upgrade_lane + .max_transaction_count() + { + return true; + } + + for (lane_id, transactions) in block_v2.body.transactions() { let transaction_count = transactions.len(); - if *category < 2 { + if *lane_id < 2 { continue; }; let max_transaction_count = transaction_config .transaction_v1_config - .get_max_transaction_count(*category); + .get_max_transaction_count(*lane_id); + if transaction_count as u64 >= max_transaction_count { return true; } diff --git a/types/src/block/block_body/block_body_v2.rs b/types/src/block/block_body/block_body_v2.rs index 028c63ef8e..8aae7f75d9 100644 --- a/types/src/block/block_body/block_body_v2.rs +++ b/types/src/block/block_body/block_body_v2.rs @@ -11,8 +11,8 @@ use serde::{Deserialize, Serialize}; use crate::{ block::RewardedSignatures, bytesrepr::{self, FromBytes, ToBytes}, - transaction::TransactionCategory, - Digest, TransactionHash, + Digest, TransactionHash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, LARGE_WASM_LANE_ID, + MEDIUM_WASM_LANE_ID, MINT_LANE_ID, SMALL_WASM_LANE_ID, }; /// The body portion of a block. Version 2. @@ -47,50 +47,33 @@ impl BlockBodyV2 { } } - fn transaction_by_category( - &self, - transaction_category: TransactionCategory, - ) -> Vec { - match self.transactions.get(&(transaction_category as u8)) { + /// Returns the hashes of the transactions within the block filtered by lane_id. + pub fn transaction_by_lane(&self, lane_id: u8) -> impl Iterator { + match self.transactions.get(&lane_id) { Some(transactions) => transactions.to_vec(), None => vec![], } + .into_iter() } /// Returns the hashes of the mint transactions within the block. pub fn mint(&self) -> impl Iterator { - self.transaction_by_category(TransactionCategory::Mint) - .into_iter() + self.transaction_by_lane(MINT_LANE_ID) } /// Returns the hashes of the auction transactions within the block. pub fn auction(&self) -> impl Iterator { - self.transaction_by_category(TransactionCategory::Auction) - .into_iter() + self.transaction_by_lane(AUCTION_LANE_ID) } /// Returns the hashes of the installer/upgrader transactions within the block. pub fn install_upgrade(&self) -> impl Iterator { - self.transaction_by_category(TransactionCategory::InstallUpgrade) - .into_iter() + self.transaction_by_lane(INSTALL_UPGRADE_LANE_ID) } - /// Returns the hashes of all other transactions within the block. - pub fn large(&self) -> impl Iterator { - self.transaction_by_category(TransactionCategory::Large) - .into_iter() - } - - /// Returns the hashes of all other transactions within the block. - pub fn medium(&self) -> impl Iterator { - self.transaction_by_category(TransactionCategory::Medium) - .into_iter() - } - - /// Returns the hashes of all other transactions within the block. - pub fn small(&self) -> impl Iterator { - self.transaction_by_category(TransactionCategory::Small) - .into_iter() + /// Returns the hashes of the transactions filtered by lane id within the block. + pub fn transactions_by_lane_id(&self, lane_id: u8) -> impl Iterator { + self.transaction_by_lane(lane_id) } /// Returns a reference to the collection of mapped transactions. @@ -147,14 +130,13 @@ impl Display for BlockBodyV2 { fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { write!( formatter, - "block body, {} mint, {} auction, {} \ - installer/upgraders, {} large, {} medium, {} small", + "block body, {} mint, {} auction, {} install_upgrade, {} large wasm, {} medium wasm, {} small wasm", self.mint().count(), self.auction().count(), self.install_upgrade().count(), - self.large().count(), - self.medium().count(), - self.small().count() + self.transaction_by_lane(LARGE_WASM_LANE_ID).count(), + self.transaction_by_lane(MEDIUM_WASM_LANE_ID).count(), + self.transaction_by_lane(SMALL_WASM_LANE_ID).count(), ) } } diff --git a/types/src/block/block_v2.rs b/types/src/block/block_v2.rs index 8da4783c67..d20e2bedcf 100644 --- a/types/src/block/block_v2.rs +++ b/types/src/block/block_v2.rs @@ -25,7 +25,7 @@ use crate::{ PublicKey, Timestamp, TransactionHash, }; #[cfg(feature = "json-schema")] -use crate::{transaction::TransactionCategory, TransactionV1Hash}; +use crate::{TransactionV1Hash, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID}; #[cfg(feature = "json-schema")] static BLOCK_V2: Lazy = Lazy::new(|| { @@ -49,18 +49,11 @@ static BLOCK_V2: Lazy = Lazy::new(|| { let installer_upgrader_hashes = vec![TransactionHash::V1(TransactionV1Hash::new( Digest::from([22; Digest::LENGTH]), ))]; - let standard = vec![TransactionHash::V1(TransactionV1Hash::new(Digest::from( - [23; Digest::LENGTH], - )))]; let transactions = { let mut ret = BTreeMap::new(); - ret.insert(TransactionCategory::Mint as u8, mint_hashes); - ret.insert(TransactionCategory::Auction as u8, auction_hashes); - ret.insert( - TransactionCategory::InstallUpgrade as u8, - installer_upgrader_hashes, - ); - ret.insert(TransactionCategory::Large as u8, standard); + ret.insert(MINT_LANE_ID, mint_hashes); + ret.insert(AUCTION_LANE_ID, auction_hashes); + ret.insert(INSTALL_UPGRADE_LANE_ID, installer_upgrader_hashes); ret }; let rewarded_signatures = RewardedSignatures::default(); @@ -254,24 +247,14 @@ impl BlockV2 { self.body.auction() } - /// Returns the hashes of the installer/upgrader transactions within the block. + /// Returns the hashes of the install/upgrade wasm transactions within the block. pub fn install_upgrade(&self) -> impl Iterator { self.body.install_upgrade() } - /// Returns the hashes of all other large transactions within the block. - pub fn large(&self) -> impl Iterator { - self.body.large() - } - - /// Returns the hashes of all other medium transactions within the block. - pub fn medium(&self) -> impl Iterator { - self.body.medium() - } - - /// Returns the hashes of all other small transactions within the block. - pub fn small(&self) -> impl Iterator { - self.body.small() + /// Returns the hashes of the transactions filtered by lane id within the block. + pub fn transactions_by_lane_id(&self, lane_id: u8) -> impl Iterator { + self.body.transaction_by_lane(lane_id) } /// Returns all of the transaction hashes in the order in which they were executed. diff --git a/types/src/block/test_block_builder/test_block_v2_builder.rs b/types/src/block/test_block_builder/test_block_v2_builder.rs index cc401aae80..d2c268a2f1 100644 --- a/types/src/block/test_block_builder/test_block_v2_builder.rs +++ b/types/src/block/test_block_builder/test_block_v2_builder.rs @@ -1,13 +1,13 @@ -use core::convert::TryInto; use std::iter; use alloc::collections::BTreeMap; use rand::Rng; use crate::{ - system::auction::ValidatorWeights, testing::TestRng, transaction::TransactionCategory, Block, - BlockHash, BlockV2, Digest, EraEndV2, EraId, ProtocolVersion, PublicKey, RewardedSignatures, - Timestamp, Transaction, U512, + system::auction::ValidatorWeights, testing::TestRng, Block, BlockHash, BlockV2, Digest, + EraEndV2, EraId, ProtocolVersion, PublicKey, RewardedSignatures, Timestamp, Transaction, + TransactionEntryPoint, TransactionTarget, AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, + LARGE_WASM_LANE_ID, MEDIUM_WASM_LANE_ID, MINT_LANE_ID, SMALL_WASM_LANE_ID, U512, }; /// A helper to build the blocks with various properties required for tests. @@ -180,30 +180,38 @@ impl TestBlockV2Builder { let mut small_hashes = vec![]; for txn in txns { let txn_hash = txn.hash(); - let category: TransactionCategory = txn - .transaction_category() - .try_into() - .expect("Expected a valid priority"); - match category { - TransactionCategory::Mint => mint_hashes.push(txn_hash), - TransactionCategory::Auction => auction_hashes.push(txn_hash), - TransactionCategory::InstallUpgrade => install_upgrade_hashes.push(txn_hash), - TransactionCategory::Large => large_hashes.push(txn_hash), - TransactionCategory::Medium => medium_hashes.push(txn_hash), - TransactionCategory::Small => small_hashes.push(txn_hash), + let lane_id = match txn { + Transaction::Deploy(deploy) => { + if deploy.is_transfer() { + MINT_LANE_ID + } else { + LARGE_WASM_LANE_ID + } + } + Transaction::V1(transaction_v1) => { + let entry_point = transaction_v1.get_transaction_entry_point().unwrap(); + let target = transaction_v1.get_transaction_target().unwrap(); + simplified_calculate_transaction_lane_from_values(&entry_point, &target) + } + }; + match lane_id { + MINT_LANE_ID => mint_hashes.push(txn_hash), + AUCTION_LANE_ID => auction_hashes.push(txn_hash), + INSTALL_UPGRADE_LANE_ID => install_upgrade_hashes.push(txn_hash), + LARGE_WASM_LANE_ID => large_hashes.push(txn_hash), + MEDIUM_WASM_LANE_ID => medium_hashes.push(txn_hash), + SMALL_WASM_LANE_ID => small_hashes.push(txn_hash), + _ => panic!("Invalid lane id"), } } let transactions = { let mut ret = BTreeMap::new(); - ret.insert(TransactionCategory::Mint as u8, mint_hashes); - ret.insert(TransactionCategory::Auction as u8, auction_hashes); - ret.insert( - TransactionCategory::InstallUpgrade as u8, - install_upgrade_hashes, - ); - ret.insert(TransactionCategory::Large as u8, large_hashes); - ret.insert(TransactionCategory::Medium as u8, medium_hashes); - ret.insert(TransactionCategory::Small as u8, small_hashes); + ret.insert(MINT_LANE_ID, mint_hashes); + ret.insert(AUCTION_LANE_ID, auction_hashes); + ret.insert(INSTALL_UPGRADE_LANE_ID, install_upgrade_hashes); + ret.insert(LARGE_WASM_LANE_ID, large_hashes); + ret.insert(MEDIUM_WASM_LANE_ID, medium_hashes); + ret.insert(SMALL_WASM_LANE_ID, small_hashes); ret }; let rewarded_signatures = rewarded_signatures.unwrap_or_default(); @@ -238,6 +246,72 @@ impl TestBlockV2Builder { } } +// A simplified way of calculating transaction lanes. It doesn't take +// into consideration the size of the transaction against the chainspec +// and doesn't take `additional_compufsdetation_factor` into consideration. +// This is only used for tests purposes. +fn simplified_calculate_transaction_lane_from_values( + entry_point: &TransactionEntryPoint, + target: &TransactionTarget, +) -> u8 { + match target { + TransactionTarget::Native => match entry_point { + TransactionEntryPoint::Transfer => MINT_LANE_ID, + TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => AUCTION_LANE_ID, + TransactionEntryPoint::Call => panic!("EntryPointCannotBeCall"), + TransactionEntryPoint::Custom(_) => panic!("EntryPointCannotBeCustom"), + }, + TransactionTarget::Stored { .. } => match entry_point { + TransactionEntryPoint::Custom(_) => LARGE_WASM_LANE_ID, + TransactionEntryPoint::Call + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + panic!("EntryPointMustBeCustom") + } + }, + TransactionTarget::Session { + is_install_upgrade, .. + } => match entry_point { + TransactionEntryPoint::Call => { + if *is_install_upgrade { + INSTALL_UPGRADE_LANE_ID + } else { + LARGE_WASM_LANE_ID + } + } + TransactionEntryPoint::Custom(_) + | TransactionEntryPoint::Transfer + | TransactionEntryPoint::AddBid + | TransactionEntryPoint::WithdrawBid + | TransactionEntryPoint::Delegate + | TransactionEntryPoint::Undelegate + | TransactionEntryPoint::Redelegate + | TransactionEntryPoint::ActivateBid + | TransactionEntryPoint::ChangeBidPublicKey + | TransactionEntryPoint::AddReservations + | TransactionEntryPoint::CancelReservations => { + panic!("EntryPointMustBeCall") + } + }, + } +} + fn gen_era_end_v2( rng: &mut TestRng, validator_weights: Option>, diff --git a/types/src/chainspec.rs b/types/src/chainspec.rs index f20d4908eb..298b847635 100644 --- a/types/src/chainspec.rs +++ b/types/src/chainspec.rs @@ -62,11 +62,12 @@ pub use next_upgrade::NextUpgrade; pub use pricing_handling::PricingHandling; pub use protocol_config::ProtocolConfig; pub use refund_handling::RefundHandling; -pub use transaction_config::{DeployConfig, TransactionConfig, TransactionV1Config}; +pub use transaction_config::{ + DeployConfig, TransactionConfig, TransactionLimitsDefinition, TransactionV1Config, +}; #[cfg(any(feature = "testing", test))] pub use transaction_config::{ - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, - DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES, + DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MIN_TRANSFER_MOTES, }; pub use upgrade_config::ProtocolUpgradeConfig; pub use vacancy_config::VacancyConfig; @@ -222,39 +223,39 @@ impl Chainspec { .saturating_sub(self.core_config.gas_hold_interval.millis()) } - /// Is the given transaction category supported. - pub fn is_supported(&self, category: u8) -> bool { + /// Is the given transaction lane supported. + pub fn is_supported(&self, lane: u8) -> bool { self.transaction_config .transaction_v1_config - .is_supported(category) + .is_supported(lane) } /// Returns the max serialized for the given category. - pub fn get_max_serialized_length_by_category(&self, category: u8) -> u64 { + pub fn get_max_serialized_length_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config - .get_max_serialized_length(category) + .get_max_serialized_length(lane) } /// Returns the max args length for the given category. - pub fn get_max_args_length_by_category(&self, category: u8) -> u64 { + pub fn get_max_args_length_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config - .get_max_args_length(category) + .get_max_args_length(lane) } /// Returns the max gas limit for the given category. - pub fn get_max_gas_limit_by_category(&self, category: u8) -> u64 { + pub fn get_max_gas_limit_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config - .get_max_gas_limit(category) + .get_max_transaction_gas_limit(lane) } /// Returns the max transaction count for the given category. - pub fn get_max_transaction_count_by_category(&self, category: u8) -> u64 { + pub fn get_max_transaction_count_by_category(&self, lane: u8) -> u64 { self.transaction_config .transaction_v1_config - .get_max_transaction_count(category) + .get_max_transaction_count(lane) } } diff --git a/types/src/chainspec/transaction_config.rs b/types/src/chainspec/transaction_config.rs index eef2f32f6f..7b2e584fd1 100644 --- a/types/src/chainspec/transaction_config.rs +++ b/types/src/chainspec/transaction_config.rs @@ -17,11 +17,9 @@ use crate::{ pub use deploy_config::DeployConfig; #[cfg(any(feature = "testing", test))] pub use deploy_config::DEFAULT_MAX_PAYMENT_MOTES; -pub use transaction_v1_config::TransactionV1Config; #[cfg(any(feature = "testing", test))] -pub use transaction_v1_config::{ - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, -}; +pub use transaction_v1_config::DEFAULT_LARGE_TRANSACTION_GAS_LIMIT; +pub use transaction_v1_config::{TransactionLimitsDefinition, TransactionV1Config}; /// The default minimum number of motes that can be transferred. pub const DEFAULT_MIN_TRANSFER_MOTES: u64 = 2_500_000_000; diff --git a/types/src/chainspec/transaction_config/transaction_v1_config.rs b/types/src/chainspec/transaction_config/transaction_v1_config.rs index 1523a61bad..d23b1215f1 100644 --- a/types/src/chainspec/transaction_config/transaction_v1_config.rs +++ b/types/src/chainspec/transaction_config/transaction_v1_config.rs @@ -1,53 +1,214 @@ +use core::cmp; + #[cfg(feature = "datasize")] use datasize::DataSize; +#[cfg(any(feature = "once_cell", test))] +use once_cell::sync::OnceCell; #[cfg(any(feature = "testing", test))] use rand::Rng; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{Error, Unexpected}, + ser::SerializeSeq, + Deserialize, Deserializer, Serialize, Serializer, +}; #[cfg(any(feature = "testing", test))] use crate::testing::TestRng; -#[cfg(any(feature = "testing", test))] -use crate::INSTALL_UPGRADE_LANE_ID; use crate::{ bytesrepr::{self, FromBytes, ToBytes}, - transaction::TransactionCategory, + AUCTION_LANE_ID, INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, }; -/// Default gas limit of install / upgrade contracts -pub const DEFAULT_INSTALL_UPGRADE_GAS_LIMIT: u64 = 3_500_000_000_000; - /// Default gas limit of standard transactions pub const DEFAULT_LARGE_TRANSACTION_GAS_LIMIT: u64 = 500_000_000_000; const DEFAULT_NATIVE_MINT_LANE: [u64; 5] = [0, 1_048_576, 1024, 2_500_000_000, 650]; const DEFAULT_NATIVE_AUCTION_LANE: [u64; 5] = [1, 1_048_576, 1024, 2_500_000_000, 145]; +const DEFAULT_INSTALL_UPGRADE_LANE: [u64; 5] = [2, 1_048_576, 2048, 3_500_000_000_000, 2]; -const KIND: usize = 0; -const MAX_TRANSACTION_LENGTH: usize = 1; -const MAX_TRANSACTION_ARGS_LENGTH: usize = 2; -const MAX_TRANSACTION_GAS_LIMIT: usize = 3; -const MAX_TRANSACTION_COUNT: usize = 4; +const TRANSACTION_ID_INDEX: usize = 0; +const TRANSACTION_LENGTH_INDEX: usize = 1; +const TRANSACTION_ARGS_LENGTH_INDEX: usize = 2; +const TRANSACTION_GAS_LIMIT_INDEX: usize = 3; +const TRANSACTION_COUNT_INDEX: usize = 4; -/// Configuration values associated with V1 Transactions. +/// Structured limits imposed on a transaction lane #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)] #[cfg_attr(feature = "datasize", derive(DataSize))] +pub struct TransactionLimitsDefinition { + /// The lane identifier + pub id: u8, + /// The maximum length of a transaction i bytes + pub max_transaction_length: u64, + /// The maximum number of runtime args + pub max_transaction_args_length: u64, + /// The maximum gas limit + pub max_transaction_gas_limit: u64, + /// The maximum number of transactions + pub max_transaction_count: u64, +} + +impl TryFrom> for TransactionLimitsDefinition { + type Error = TransactionConfigError; + + fn try_from(v: Vec) -> Result { + if v.len() != 5 { + return Err(TransactionConfigError::InvalidArgsProvided); + } + Ok(TransactionLimitsDefinition { + id: v[TRANSACTION_ID_INDEX] as u8, + max_transaction_length: v[TRANSACTION_LENGTH_INDEX], + max_transaction_args_length: v[TRANSACTION_ARGS_LENGTH_INDEX], + max_transaction_gas_limit: v[TRANSACTION_GAS_LIMIT_INDEX], + max_transaction_count: v[TRANSACTION_COUNT_INDEX], + }) + } +} + +impl TransactionLimitsDefinition { + /// Creates a new instance of TransactionLimitsDefinition + pub fn new( + id: u8, + max_transaction_length: u64, + max_transaction_args_length: u64, + max_transaction_gas_limit: u64, + max_transaction_count: u64, + ) -> Self { + Self { + id, + max_transaction_length, + max_transaction_args_length, + max_transaction_gas_limit, + max_transaction_count, + } + } + + fn as_vec(&self) -> Vec { + vec![ + self.id as u64, + self.max_transaction_length, + self.max_transaction_args_length, + self.max_transaction_gas_limit, + self.max_transaction_count, + ] + } + + /// Returns max_transaction_length + pub fn max_transaction_length(&self) -> u64 { + self.max_transaction_length + } + + /// Returns max_transaction_args_length + pub fn max_transaction_args_length(&self) -> u64 { + self.max_transaction_args_length + } + + /// Returns max_transaction_gas_limit + pub fn max_transaction_gas_limit(&self) -> u64 { + self.max_transaction_gas_limit + } + + /// Returns max_transaction_count + pub fn max_transaction_count(&self) -> u64 { + self.max_transaction_count + } + + /// Returns id + pub fn id(&self) -> u8 { + self.id + } +} + +#[derive(Debug, Clone)] +pub enum TransactionConfigError { + InvalidArgsProvided, +} + +/// Configuration values associated with V1 Transactions. +#[derive(Clone, Eq, Serialize, Deserialize, Debug)] +#[cfg_attr(feature = "datasize", derive(DataSize))] // Disallow unknown fields to ensure config files and command-line overrides contain valid keys. #[serde(deny_unknown_fields)] pub struct TransactionV1Config { + #[serde( + serialize_with = "limit_definition_to_vec", + deserialize_with = "vec_to_limit_definition" + )] /// Lane configuration of the native mint interaction. - pub native_mint_lane: Vec, + pub native_mint_lane: TransactionLimitsDefinition, + #[serde( + serialize_with = "limit_definition_to_vec", + deserialize_with = "vec_to_limit_definition" + )] /// Lane configuration for the native auction interaction. - pub native_auction_lane: Vec, - /// Lane configurations for the Wasm based lanes. - pub wasm_lanes: Vec>, + pub native_auction_lane: TransactionLimitsDefinition, + #[serde( + serialize_with = "limit_definition_to_vec", + deserialize_with = "vec_to_limit_definition" + )] + /// Lane configuration for the install/upgrade interaction. + pub install_upgrade_lane: TransactionLimitsDefinition, + #[serde( + serialize_with = "wasm_definitions_to_vec", + deserialize_with = "definition_to_wasms" + )] + /// Lane configurations for Wasm based lanes that are not declared as install/upgrade. + pub wasm_lanes: Vec, + #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))] + #[cfg_attr( + all(any(feature = "once_cell", test), feature = "datasize"), + data_size(skip) + )] + #[cfg(any(feature = "once_cell", test))] + wasm_lanes_ordered_by_transaction_size: OnceCell>, +} + +impl PartialEq for TransactionV1Config { + fn eq(&self, other: &TransactionV1Config) -> bool { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1Config { + native_mint_lane, + native_auction_lane, + install_upgrade_lane, + wasm_lanes, + #[cfg(any(feature = "once_cell", test))] + wasm_lanes_ordered_by_transaction_size: _, + } = self; + *native_mint_lane == other.native_mint_lane + && *native_auction_lane == other.native_auction_lane + && *install_upgrade_lane == other.install_upgrade_lane + && *wasm_lanes == other.wasm_lanes + } } impl TransactionV1Config { + /// Cretaes a new instance of TransactionV1Config + pub fn new( + native_mint_lane: TransactionLimitsDefinition, + native_auction_lane: TransactionLimitsDefinition, + install_upgrade_lane: TransactionLimitsDefinition, + wasm_lanes: Vec, + ) -> Self { + #[cfg(any(feature = "once_cell", test))] + let wasm_lanes_ordered_by_transaction_size = OnceCell::with_value( + Self::build_wasm_lanes_ordered_by_transaction_size(wasm_lanes.clone()), + ); + TransactionV1Config { + native_mint_lane, + native_auction_lane, + install_upgrade_lane, + wasm_lanes, + #[cfg(any(feature = "once_cell", test))] + wasm_lanes_ordered_by_transaction_size, + } + } + #[cfg(any(feature = "testing", test))] /// Generates a random instance using a `TestRng`. pub fn random(rng: &mut TestRng) -> Self { let native_mint_lane = DEFAULT_NATIVE_MINT_LANE.to_vec(); let native_auction_lane = DEFAULT_NATIVE_AUCTION_LANE.to_vec(); + let install_upgrade_lane = DEFAULT_INSTALL_UPGRADE_LANE.to_vec(); let mut wasm_lanes = vec![]; for kind in 2..7 { let lane = vec![ @@ -55,144 +216,93 @@ impl TransactionV1Config { rng.gen_range(0..=1_048_576), rng.gen_range(0..=1024), rng.gen_range(0..=2_500_000_000), + rng.gen_range(5..=150), ]; - wasm_lanes.push(lane) + wasm_lanes.push(lane.try_into().unwrap()) } - TransactionV1Config { - native_mint_lane, - native_auction_lane, + TransactionV1Config::new( + native_mint_lane.try_into().unwrap(), + native_auction_lane.try_into().unwrap(), + install_upgrade_lane.try_into().unwrap(), wasm_lanes, - } - } - - /// Returns true if the lane identifier is for either the mint or auction. - pub fn is_native_lane(&self, lane: u8) -> bool { - lane as u64 == DEFAULT_NATIVE_MINT_LANE[0] || lane as u64 == DEFAULT_NATIVE_AUCTION_LANE[0] + ) } - /// Returns the max serialized length of a transaction for the given category. - pub fn get_max_serialized_length(&self, category: u8) -> u64 { - if !self.is_supported(category) { - return 0; - } - match category { - 0 => self.native_mint_lane[MAX_TRANSACTION_LENGTH], - 1 => self.native_auction_lane[MAX_TRANSACTION_LENGTH], - _ => { - match self - .wasm_lanes - .iter() - .find(|lane| lane.first() == Some(&(category as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_LENGTH], - None => 0, - } - } + /// Returns the max serialized length of a transaction for the given lane. + pub fn get_max_serialized_length(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_length, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_length, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_length, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_length, + None => 0, + }, } } - /// Returns the max serialized args length of a transaction for the given category. - pub fn get_max_args_length(&self, category: u8) -> u64 { - if !self.is_supported(category) { - return 0; - } - match category { - 0 => self.native_mint_lane[MAX_TRANSACTION_ARGS_LENGTH], - 1 => self.native_auction_lane[MAX_TRANSACTION_ARGS_LENGTH], - _ => { - match self - .wasm_lanes - .iter() - .find(|lane| lane.first() == Some(&(category as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_ARGS_LENGTH], - None => 0, - } - } + /// Returns the max number of runtime args + pub fn get_max_args_length(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_args_length, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_args_length, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_args_length, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_args_length, + None => 0, + }, } } - /// Returns the max gas limit of a transaction for the given category. - pub fn get_max_gas_limit(&self, category: u8) -> u64 { - if !self.is_supported(category) { - return 0; - } - match category { - 0 => self.native_mint_lane[MAX_TRANSACTION_GAS_LIMIT], - 1 => self.native_auction_lane[MAX_TRANSACTION_GAS_LIMIT], - _ => { - match self - .wasm_lanes - .iter() - .find(|lane| lane.first() == Some(&(category as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_GAS_LIMIT], - None => 0, - } - } + /// Returns the max gas limit of a transaction for the given lane. + pub fn get_max_transaction_gas_limit(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_gas_limit, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_gas_limit, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_gas_limit, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_gas_limit, + None => 0, + }, } } - /// Returns the max gas limit of a transaction for the given category. - pub fn get_max_transaction_count(&self, category: u8) -> u64 { - if !self.is_supported(category) { - return 0; - } - match category { - 0 => self.native_mint_lane[MAX_TRANSACTION_COUNT], - 1 => self.native_auction_lane[MAX_TRANSACTION_COUNT], - _ => { - match self - .wasm_lanes - .iter() - .find(|lane| lane.first() == Some(&(category as u64))) - { - Some(wasm_lane) => wasm_lane[MAX_TRANSACTION_COUNT], - None => 0, - } - } - } - } - - /// Returns true if the given category is supported. - pub fn is_supported(&self, category: u8) -> bool { - if !self.is_native_lane(category) { - return self - .wasm_lanes - .iter() - .any(|lane| lane.first() == Some(&(category as u64))); + /// Returns the max transactions count for the given lane. + pub fn get_max_transaction_count(&self, lane_id: u8) -> u64 { + match lane_id { + MINT_LANE_ID => self.native_mint_lane.max_transaction_count, + AUCTION_LANE_ID => self.native_auction_lane.max_transaction_count, + INSTALL_UPGRADE_LANE_ID => self.install_upgrade_lane.max_transaction_count, + _ => match self.wasm_lanes.iter().find(|lane| lane.id == lane_id) { + Some(wasm_lane) => wasm_lane.max_transaction_count, + None => 0, + }, } - - true - } - - /// Returns the max total count for all transactions across all lanes allowed in a block. - pub fn get_max_block_count(&self) -> u64 { - self.native_mint_lane[MAX_TRANSACTION_COUNT] - + self.native_auction_lane[MAX_TRANSACTION_COUNT] - + self - .wasm_lanes - .iter() - .map(|lane| lane[MAX_TRANSACTION_COUNT]) - .sum::() } /// Returns the maximum number of Wasm based transactions across wasm lanes. pub fn get_max_wasm_transaction_count(&self) -> u64 { let mut ret = 0; for lane in self.wasm_lanes.iter() { - ret += lane[MAX_TRANSACTION_COUNT]; + ret += lane.max_transaction_count; } ret } + /// Are the given transaction parameters supported. + pub fn is_supported(&self, lane_id: u8) -> bool { + if !self.is_predefined_lane(lane_id) { + return self.wasm_lanes.iter().any(|lane| lane.id == lane_id); + } + true + } + /// Returns the list of currently supported lane identifiers. - pub fn get_supported_categories(&self) -> Vec { - let mut ret = vec![0, 1]; + pub fn get_supported_lanes(&self) -> Vec { + let mut ret = vec![0, 1, 2]; for lane in self.wasm_lanes.iter() { - let lane_id = lane[KIND] as u8; - ret.push(lane_id); + ret.push(lane.id); } ret } @@ -207,75 +317,126 @@ impl TransactionV1Config { large: Option, ) -> Self { if let Some(mint_count) = mint { - self.native_mint_lane[MAX_TRANSACTION_COUNT] = mint_count; + self.native_mint_lane.max_transaction_count = mint_count; } if let Some(auction_count) = auction { - self.native_auction_lane[MAX_TRANSACTION_COUNT] = auction_count; + self.native_auction_lane.max_transaction_count = auction_count; } - if let Some(install_upgrade_count) = install { - let (index, lane) = self - .wasm_lanes - .iter() - .enumerate() - .find(|(_, lane)| lane.first() == Some(&(INSTALL_UPGRADE_LANE_ID as u64))) - .expect("must get install upgrade lane"); - let mut updated_lane = lane.clone(); - self.wasm_lanes.remove(index); - updated_lane[MAX_TRANSACTION_COUNT] = install_upgrade_count; - self.wasm_lanes.push(updated_lane); + if let Some(install_upgrade) = install { + self.native_auction_lane.max_transaction_count = install_upgrade; } if let Some(large_limit) = large { - let (index, lane) = self + for lane in self.wasm_lanes.iter_mut() { + if lane.id == 3 { + lane.max_transaction_count = large_limit; + } + } + } + self + } + + /// Returns the max total count for all transactions across all lanes allowed in a block. + pub fn get_max_block_count(&self) -> u64 { + self.native_mint_lane.max_transaction_count + + self.native_auction_lane.max_transaction_count + + self.install_upgrade_lane.max_transaction_count + + self .wasm_lanes .iter() - .enumerate() - .find(|(_, lane)| lane.first() == Some(&3)) - .expect("must get install upgrade lane"); - let mut updated_lane = lane.clone(); - self.wasm_lanes.remove(index); - updated_lane[MAX_TRANSACTION_COUNT] = large_limit; - self.wasm_lanes.push(updated_lane); + .map(|lane| lane.max_transaction_count) + .sum::() + } + + /// Returns true if the lane identifier is for one of the predefined lanes. + pub fn is_predefined_lane(&self, lane: u8) -> bool { + lane == AUCTION_LANE_ID || lane == MINT_LANE_ID || lane == INSTALL_UPGRADE_LANE_ID + } + + /// Returns a wasm lane id based on the transaction size adjusted by + /// maybe_additional_computation_factor if necessary. + pub fn get_wasm_lane_id( + &self, + transaction_size: u64, + additional_computation_factor: u8, + ) -> Option { + let mut maybe_adequate_lane_index = None; + let buckets = self.get_wasm_lanes_ordered(); + let number_of_lanes = buckets.len(); + for (i, lane) in buckets.iter().enumerate() { + let lane_size = lane.max_transaction_length; + if lane_size >= transaction_size { + maybe_adequate_lane_index = Some(i); + break; + } } - self + if let Some(adequate_lane_index) = maybe_adequate_lane_index { + maybe_adequate_lane_index = Some(cmp::min( + adequate_lane_index + additional_computation_factor as usize, + number_of_lanes - 1, + )); + } + maybe_adequate_lane_index.map(|index| buckets[index].id) + } + + #[allow(unreachable_code)] + //We're allowing unreachable code here because there's a possibility that someone might + // want to use the types crate without once_cell + fn get_wasm_lanes_ordered(&self) -> &Vec { + #[cfg(any(feature = "once_cell", test))] + return self.wasm_lanes_ordered_by_transaction_size.get_or_init(|| { + Self::build_wasm_lanes_ordered_by_transaction_size(self.wasm_lanes.clone()) + }); + &Self::build_wasm_lanes_ordered_by_transaction_size(self.wasm_lanes.clone()) + } + + fn build_wasm_lanes_ordered_by_transaction_size( + wasm_lanes: Vec, + ) -> Vec { + let mut wasm_lanes_ordered_by_transaction_size = wasm_lanes; + wasm_lanes_ordered_by_transaction_size + .sort_by(|a, b| a.max_transaction_length.cmp(&b.max_transaction_length)); + wasm_lanes_ordered_by_transaction_size } } #[cfg(any(feature = "std", test))] impl Default for TransactionV1Config { fn default() -> Self { - let large_lane = vec![ - TransactionCategory::Large as u64, + let wasm_lane = vec![ + 3_u64, //large lane id 1_048_576, 1024, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, 10, ]; - let install_upgrade_lane = vec![ - TransactionCategory::InstallUpgrade as u64, - 1_048_576, - 2048, - DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, - 2, - ]; - let native_mint_lane = DEFAULT_NATIVE_MINT_LANE.to_vec(); let native_auction_lane = DEFAULT_NATIVE_AUCTION_LANE.to_vec(); - let wasm_lanes = vec![large_lane, install_upgrade_lane]; + let install_upgrade_lane = DEFAULT_INSTALL_UPGRADE_LANE.to_vec(); + let raw_wasm_lanes = vec![wasm_lane]; + let wasm_lanes: Result, _> = + raw_wasm_lanes.into_iter().map(|v| v.try_into()).collect(); - TransactionV1Config { - native_mint_lane, - native_auction_lane, - wasm_lanes, - } + TransactionV1Config::new( + native_mint_lane.try_into().unwrap(), + native_auction_lane.try_into().unwrap(), + install_upgrade_lane.try_into().unwrap(), + wasm_lanes.unwrap(), + ) } } impl ToBytes for TransactionV1Config { fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - self.native_mint_lane.write_bytes(writer)?; - self.native_auction_lane.write_bytes(writer)?; - self.wasm_lanes.write_bytes(writer) + self.native_mint_lane.as_vec().write_bytes(writer)?; + self.native_auction_lane.as_vec().write_bytes(writer)?; + self.install_upgrade_lane.as_vec().write_bytes(writer)?; + let wasm_lanes_as_vecs: Vec> = self + .wasm_lanes + .iter() + .map(TransactionLimitsDefinition::as_vec) + .collect(); + wasm_lanes_as_vecs.write_bytes(writer) } fn to_bytes(&self) -> Result, bytesrepr::Error> { @@ -285,30 +446,119 @@ impl ToBytes for TransactionV1Config { } fn serialized_length(&self) -> usize { - self.native_mint_lane.serialized_length() - + self.native_auction_lane.serialized_length() - + self.wasm_lanes.serialized_length() + let wasm_lanes_as_vecs: Vec> = self + .wasm_lanes + .iter() + .map(TransactionLimitsDefinition::as_vec) + .collect(); + self.native_mint_lane.as_vec().serialized_length() + + self.native_auction_lane.as_vec().serialized_length() + + self.install_upgrade_lane.as_vec().serialized_length() + + wasm_lanes_as_vecs.serialized_length() } } impl FromBytes for TransactionV1Config { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (native_mint_lane, remainder) = FromBytes::from_bytes(bytes)?; - let (native_auction_lane, remainder) = FromBytes::from_bytes(remainder)?; - let (wasm_lanes, remainder) = FromBytes::from_bytes(remainder)?; - let config = TransactionV1Config { + let (raw_native_mint_lane, remainder): (Vec, &[u8]) = FromBytes::from_bytes(bytes)?; + let (raw_native_auction_lane, remainder): (Vec, &[u8]) = + FromBytes::from_bytes(remainder)?; + let (raw_install_upgrade_lane, remainder): (Vec, &[u8]) = + FromBytes::from_bytes(remainder)?; + let (raw_wasm_lanes, remainder): (Vec>, &[u8]) = FromBytes::from_bytes(remainder)?; + let native_mint_lane = raw_native_mint_lane + .try_into() + .map_err(|_| bytesrepr::Error::Formatting)?; + let native_auction_lane = raw_native_auction_lane + .try_into() + .map_err(|_| bytesrepr::Error::Formatting)?; + let install_upgrade_lane = raw_install_upgrade_lane + .try_into() + .map_err(|_| bytesrepr::Error::Formatting)?; + let wasm_lanes: Result, _> = + raw_wasm_lanes.into_iter().map(|v| v.try_into()).collect(); + let config = TransactionV1Config::new( native_mint_lane, native_auction_lane, - wasm_lanes, - }; + install_upgrade_lane, + wasm_lanes.map_err(|_| bytesrepr::Error::Formatting)?, + ); Ok((config, remainder)) } } +fn vec_to_limit_definition<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let vec = Vec::::deserialize(deserializer)?; + let limits = TransactionLimitsDefinition::try_from(vec).map_err(|_| { + D::Error::invalid_value( + Unexpected::Seq, + &"expected 5 u64 compliant numbers to create a TransactionLimitsDefinition", + ) + })?; + Ok(limits) +} + +fn limit_definition_to_vec( + limits: &TransactionLimitsDefinition, + serializer: S, +) -> Result +where + S: Serializer, +{ + let vec = limits.as_vec(); + let mut seq = serializer.serialize_seq(Some(vec.len()))?; + for element in vec { + seq.serialize_element(&element)?; + } + seq.end() +} + +fn definition_to_wasms<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let vec = Vec::>::deserialize(deserializer)?; + let result: Result, TransactionConfigError> = + vec.into_iter().map(|v| v.try_into()).collect(); + result.map_err(|_| { + D::Error::invalid_value( + Unexpected::Seq, + &"sequence of sequences to assemble wasm definitions", + ) + }) +} + +fn wasm_definitions_to_vec( + limits: &[TransactionLimitsDefinition], + serializer: S, +) -> Result +where + S: Serializer, +{ + let vec_of_vecs: Vec> = limits.iter().map(|v| v.as_vec()).collect(); + let mut seq = serializer.serialize_seq(Some(vec_of_vecs.len()))?; + for element in vec_of_vecs { + seq.serialize_element(&element)?; + } + seq.end() +} + #[cfg(test)] mod tests { - use super::*; + use serde_json::Value; + use super::*; + const EXAMPLE_JSON: &str = r#"{ + "native_mint_lane": [0,1,2,3,4], + "native_auction_lane": [1,5,6,7,8], + "install_upgrade_lane": [2,9,10,11,12], + "wasm_lanes": [[3,13,14,15,16], [4,17,18,19,20], [5,21,22,23,24]] + }"#; #[test] fn bytesrepr_roundtrip() { let mut rng = TestRng::new(); @@ -321,7 +571,183 @@ mod tests { let config = TransactionV1Config::default(); assert!(config.is_supported(0)); assert!(config.is_supported(1)); + assert!(config.is_supported(2)); assert!(config.is_supported(3)); assert!(!config.is_supported(10)); } + + #[test] + fn should_get_configuration_for_wasm() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(100, 0); + assert_eq!(got, Some(3)); + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(100, 0); + assert_eq!(got, Some(5)); + } + + #[test] + fn given_too_big_transaction_should_return_none() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(100000000, 0); + assert!(got.is_none()); + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(100000000, 0); + assert!(got.is_none()); + } + + #[test] + fn given_wasm_should_return_first_fit() { + let config = build_example_transaction_config(); + + let got = config.get_wasm_lane_id(660, 0); + assert_eq!(got, Some(4)); + + let got = config.get_wasm_lane_id(800, 0); + assert_eq!(got, Some(5)); + + let got = config.get_wasm_lane_id(1, 0); + assert_eq!(got, Some(3)); + + let config = build_example_transaction_config_reverse_wasm_ids(); + + let got = config.get_wasm_lane_id(660, 0); + assert_eq!(got, Some(4)); + + let got = config.get_wasm_lane_id(800, 0); + assert_eq!(got, Some(3)); + + let got = config.get_wasm_lane_id(1, 0); + assert_eq!(got, Some(5)); + } + + #[test] + fn given_additional_computation_factor_should_be_applied() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(660, 1); + assert_eq!(got, Some(5)); + + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(660, 1); + assert_eq!(got, Some(3)); + } + + #[test] + fn given_additional_computation_factor_should_not_overflow() { + let config = build_example_transaction_config(); + let got = config.get_wasm_lane_id(660, 2); + assert_eq!(got, Some(5)); + let got_2 = config.get_wasm_lane_id(660, 20); + assert_eq!(got_2, Some(5)); + + let config = build_example_transaction_config_reverse_wasm_ids(); + let got = config.get_wasm_lane_id(660, 2); + assert_eq!(got, Some(3)); + let got_2 = config.get_wasm_lane_id(660, 20); + assert_eq!(got_2, Some(3)); + } + + #[test] + fn given_no_wasm_lanes_should_return_none() { + let config = build_example_transaction_config_no_wasms(); + let got = config.get_wasm_lane_id(660, 2); + assert!(got.is_none()); + let got = config.get_wasm_lane_id(660, 0); + assert!(got.is_none()); + let got = config.get_wasm_lane_id(660, 20); + assert!(got.is_none()); + } + + #[test] + fn should_deserialize() { + let got: TransactionV1Config = serde_json::from_str(EXAMPLE_JSON).unwrap(); + let expected = TransactionV1Config::new( + TransactionLimitsDefinition::new(0, 1, 2, 3, 4), + TransactionLimitsDefinition::new(1, 5, 6, 7, 8), + TransactionLimitsDefinition::new(2, 9, 10, 11, 12), + vec![ + TransactionLimitsDefinition::new(3, 13, 14, 15, 16), + TransactionLimitsDefinition::new(4, 17, 18, 19, 20), + TransactionLimitsDefinition::new(5, 21, 22, 23, 24), + ], + ); + assert_eq!(got, expected); + } + + #[test] + fn should_serialize() { + let input = TransactionV1Config::new( + TransactionLimitsDefinition::new(0, 1, 2, 3, 4), + TransactionLimitsDefinition::new(1, 5, 6, 7, 8), + TransactionLimitsDefinition::new(2, 9, 10, 11, 12), + vec![ + TransactionLimitsDefinition::new(3, 13, 14, 15, 16), + TransactionLimitsDefinition::new(4, 17, 18, 19, 20), + TransactionLimitsDefinition::new(5, 21, 22, 23, 24), + ], + ); + let raw = serde_json::to_string(&input).unwrap(); + let got = serde_json::from_str::(&raw).unwrap(); + let expected: Value = serde_json::from_str::(EXAMPLE_JSON).unwrap(); + assert_eq!(got, expected); + } + + fn example_native() -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(0, 1500, 1024, 1_500_000_000, 150) + } + + fn example_auction() -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(1, 500, 3024, 3_500_000_000, 350) + } + + fn example_install_upgrade() -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(2, 10000, 2024, 2_500_000_000, 250) + } + + fn wasm_small(id: u8) -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(id, 600, 4024, 4_500_000_000, 450) + } + + fn wasm_medium(id: u8) -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(id, 700, 5024, 5_500_000_000, 550) + } + + fn wasm_large(id: u8) -> TransactionLimitsDefinition { + TransactionLimitsDefinition::new(id, 800, 6024, 6_500_000_000, 650) + } + + fn example_wasm() -> Vec { + vec![wasm_small(3), wasm_medium(4), wasm_large(5)] + } + + fn example_wasm_reversed_ids() -> Vec { + vec![wasm_small(5), wasm_medium(4), wasm_large(3)] + } + + fn build_example_transaction_config_no_wasms() -> TransactionV1Config { + TransactionV1Config::new( + example_native(), + example_auction(), + example_install_upgrade(), + vec![], + ) + } + + fn build_example_transaction_config() -> TransactionV1Config { + TransactionV1Config::new( + example_native(), + example_auction(), + example_install_upgrade(), + example_wasm(), + ) + } + + fn build_example_transaction_config_reverse_wasm_ids() -> TransactionV1Config { + TransactionV1Config::new( + example_native(), + example_auction(), + example_install_upgrade(), + example_wasm_reversed_ids(), + ) + } } diff --git a/types/src/deploy_info.rs b/types/src/deploy_info.rs index a741cf96fd..6ba9436580 100644 --- a/types/src/deploy_info.rs +++ b/types/src/deploy_info.rs @@ -107,14 +107,13 @@ impl ToBytes for DeployInfo { /// Generators for a `DeployInfo` #[cfg(any(feature = "testing", feature = "gens", test))] pub(crate) mod gens { - use proptest::{collection, prelude::Strategy}; - use crate::{ gens::{account_hash_arb, u512_arb, uref_arb}, transaction::gens::deploy_hash_arb, transfer::gens::transfer_v1_addr_arb, DeployInfo, }; + use proptest::{collection, prelude::Strategy}; pub fn deploy_info_arb() -> impl Strategy { let transfers_length_range = 0..5; diff --git a/types/src/gens.rs b/types/src/gens.rs index d8f7b63897..577da144d8 100644 --- a/types/src/gens.rs +++ b/types/src/gens.rs @@ -9,14 +9,6 @@ use alloc::{ vec, }; -use proptest::{ - array, bits, bool, - collection::{self, vec, SizeRange}, - option, - prelude::*, - result, -}; - use crate::{ account::{ self, action_thresholds::gens::account_action_thresholds_arb, @@ -34,7 +26,10 @@ use crate::{ Contract, ContractHash, ContractPackage, ContractPackageStatus, ContractVersionKey, ContractVersions, EntryPoint as ContractEntryPoint, EntryPoints as ContractEntryPoints, }, - crypto::{self, gens::public_key_arb_no_system}, + crypto::{ + self, + gens::{public_key_arb_no_system, secret_key_arb_no_system}, + }, deploy_info::gens::deploy_info_arb, global_state::{Pointer, TrieMerkleProof, TrieMerkleProofStep}, package::{EntityVersionKey, EntityVersions, Groups, PackageStatus}, @@ -47,7 +42,9 @@ use crate::{ mint::BalanceHoldAddr, SystemEntityType, }, - transaction::gens::deploy_hash_arb, + transaction::{ + gens::deploy_hash_arb, FieldsContainer, InitiatorAddrAndSecretKey, TransactionV1Payload, + }, transfer::{ gens::{transfer_v1_addr_arb, transfer_v1_arb}, TransferAddr, @@ -55,9 +52,16 @@ use crate::{ AccessRights, AddressableEntity, AddressableEntityHash, BlockTime, ByteCode, CLType, CLValue, Digest, EntityAddr, EntityKind, EntryPoint, EntryPointAccess, EntryPointPayment, EntryPointType, EntryPoints, EraId, Group, InitiatorAddr, Key, NamedArg, Package, Parameter, - Phase, PricingMode, ProtocolVersion, RuntimeArgs, SemVer, StoredValue, Timestamp, - TransactionCategory, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, - TransactionScheduling, TransactionTarget, TransactionV1Body, URef, U128, U256, U512, + Phase, PricingMode, ProtocolVersion, PublicKey, RuntimeArgs, SemVer, StoredValue, TimeDiff, + Timestamp, Transaction, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, + TransactionScheduling, TransactionTarget, TransactionV1, URef, U128, U256, U512, +}; +use proptest::{ + array, bits, bool, + collection::{self, vec, SizeRange}, + option, + prelude::*, + result, }; pub fn u8_slice_32() -> impl Strategy { @@ -919,17 +923,6 @@ pub fn trie_merkle_proof_arb() -> impl Strategy impl Strategy { - prop_oneof![ - Just(TransactionCategory::Mint), - Just(TransactionCategory::Auction), - Just(TransactionCategory::InstallUpgrade), - Just(TransactionCategory::Large), - Just(TransactionCategory::Medium), - Just(TransactionCategory::Small), - ] -} - pub fn transaction_scheduling_arb() -> impl Strategy { prop_oneof![ Just(TransactionScheduling::Standard), @@ -1001,27 +994,45 @@ pub fn runtime_args_arb() -> impl Strategy { prop_oneof![Just(runtime_args_1)] } -pub fn v1_transaction_body_arb() -> impl Strategy { +pub fn fields_arb() -> impl Strategy> { + collection::btree_map( + any::(), + any::().prop_map(|s| Bytes::from(s.as_bytes())), + 3..30, + ) +} +pub fn v1_transaction_payload_arb() -> impl Strategy { ( - runtime_args_arb(), - transaction_target_arb(), - transaction_entry_point_arb(), - transaction_category_arb(), - transaction_scheduling_arb(), + any::(), + Just(1_u64).prop_map(Timestamp::from), + any::(), + pricing_mode_arb(), + initiator_addr_arb(), + fields_arb(), ) .prop_map( - |(args, target, entry_point, transaction_category, scheduling)| { - TransactionV1Body::new( - args, - target, - entry_point, - transaction_category as u8, - scheduling, + |(chain_name, timestamp, ttl_millis, pricing_mode, initiator_addr, fields)| { + TransactionV1Payload::new( + chain_name, + timestamp, + TimeDiff::from_millis(ttl_millis), + pricing_mode, + initiator_addr, + fields, ) }, ) } +pub fn fixed_pricing_mode_arb() -> impl Strategy { + (any::(), any::()).prop_map(|(gas_price_tolerance, additional_computation_factor)| { + PricingMode::Fixed { + gas_price_tolerance, + additional_computation_factor, + } + }) +} + pub fn pricing_mode_arb() -> impl Strategy { prop_oneof![ (any::(), any::(), any::()).prop_map( @@ -1033,11 +1044,7 @@ pub fn pricing_mode_arb() -> impl Strategy { } } ), - any::().prop_map(|gas_price_tolerance| { - PricingMode::Fixed { - gas_price_tolerance, - } - }), + fixed_pricing_mode_arb(), u8_slice_32().prop_map(|receipt| { PricingMode::Reserved { receipt: receipt.into(), @@ -1052,3 +1059,50 @@ pub fn initiator_addr_arb() -> impl Strategy { u2_slice_32().prop_map(|hash| InitiatorAddr::AccountHash(AccountHash::new(hash))), ] } + +pub fn v1_transaction_arb() -> impl Strategy { + ( + any::(), + any::(), + any::(), + pricing_mode_arb(), + secret_key_arb_no_system(), + runtime_args_arb(), + transaction_target_arb(), + transaction_entry_point_arb(), + transaction_scheduling_arb(), + ) + .prop_map( + |( + chain_name, + timestamp, + ttl, + pricing_mode, + secret_key, + args, + target, + entry_point, + scheduling, + )| { + let public_key = PublicKey::from(&secret_key); + let initiator_addr = InitiatorAddr::PublicKey(public_key); + let initiator_addr_with_secret = InitiatorAddrAndSecretKey::Both { + initiator_addr, + secret_key: &secret_key, + }; + let container = FieldsContainer::new(args, target, entry_point, scheduling); + TransactionV1::build( + chain_name, + Timestamp::from(timestamp), + TimeDiff::from_seconds(ttl), + pricing_mode, + container.to_map().unwrap(), + initiator_addr_with_secret, + ) + }, + ) +} + +pub fn transaction_arb() -> impl Strategy { + (v1_transaction_arb()).prop_map(Transaction::V1) +} diff --git a/types/src/lib.rs b/types/src/lib.rs index 2607e9f45b..2c93f1f5a2 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -127,9 +127,9 @@ pub use chainspec::{ HoldBalanceHandling, HostFunction, HostFunctionCost, HostFunctionCosts, LegacyRequiredFinality, MessageLimits, MintCosts, NetworkConfig, NextUpgrade, OpcodeCosts, PricingHandling, ProtocolConfig, ProtocolUpgradeConfig, RefundHandling, StandardPaymentCosts, StorageCosts, - SystemConfig, TransactionConfig, TransactionV1Config, VacancyConfig, ValidatorConfig, - WasmConfig, DEFAULT_GAS_HOLD_INTERVAL, DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, - DEFAULT_REFUND_HANDLING, + SystemConfig, TransactionConfig, TransactionLimitsDefinition, TransactionV1Config, + VacancyConfig, ValidatorConfig, WasmConfig, DEFAULT_GAS_HOLD_INTERVAL, + DEFAULT_HOST_FUNCTION_NEW_DICTIONARY, DEFAULT_REFUND_HANDLING, }; #[cfg(any(all(feature = "std", feature = "testing"), test))] pub use chainspec::{ @@ -143,11 +143,10 @@ pub use chainspec::{ DEFAULT_CONTROL_FLOW_RETURN_OPCODE, DEFAULT_CONTROL_FLOW_SELECT_OPCODE, DEFAULT_CONVERSION_COST, DEFAULT_CURRENT_MEMORY_COST, DEFAULT_DELEGATE_COST, DEFAULT_DIV_COST, DEFAULT_FEE_HANDLING, DEFAULT_GAS_HOLD_BALANCE_HANDLING, DEFAULT_GLOBAL_COST, - DEFAULT_GROW_MEMORY_COST, DEFAULT_INSTALL_UPGRADE_GAS_LIMIT, DEFAULT_INTEGER_COMPARISON_COST, - DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, - DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MAX_STACK_HEIGHT, DEFAULT_MIN_TRANSFER_MOTES, - DEFAULT_MUL_COST, DEFAULT_NEW_DICTIONARY_COST, DEFAULT_NOP_COST, DEFAULT_STORE_COST, - DEFAULT_TRANSFER_COST, DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, + DEFAULT_GROW_MEMORY_COST, DEFAULT_INTEGER_COMPARISON_COST, DEFAULT_LARGE_TRANSACTION_GAS_LIMIT, + DEFAULT_LOAD_COST, DEFAULT_LOCAL_COST, DEFAULT_MAX_PAYMENT_MOTES, DEFAULT_MAX_STACK_HEIGHT, + DEFAULT_MIN_TRANSFER_MOTES, DEFAULT_MUL_COST, DEFAULT_NEW_DICTIONARY_COST, DEFAULT_NOP_COST, + DEFAULT_STORE_COST, DEFAULT_TRANSFER_COST, DEFAULT_UNREACHABLE_COST, DEFAULT_WASM_MAX_MEMORY, }; pub use contract_wasm::{ContractWasm, ContractWasmHash}; #[doc(inline)] @@ -187,15 +186,14 @@ pub use timestamp::{TimeDiff, Timestamp}; #[cfg(any(feature = "std", test))] pub use transaction::GasLimited; pub use transaction::{ - AddressableEntityIdentifier, Approval, ApprovalsHash, Deploy, DeployDecodeFromJsonError, - DeployError, DeployExcessiveSizeError, DeployHash, DeployHeader, DeployId, - ExecutableDeployItem, ExecutableDeployItemIdentifier, ExecutionInfo, InitiatorAddr, + arg_handling, AddressableEntityIdentifier, Approval, ApprovalsHash, Deploy, + DeployDecodeFromJsonError, DeployError, DeployExcessiveSizeError, DeployHash, DeployHeader, + DeployId, ExecutableDeployItem, ExecutableDeployItemIdentifier, ExecutionInfo, InitiatorAddr, InvalidDeploy, InvalidTransaction, InvalidTransactionV1, NamedArg, PackageIdentifier, - PricingMode, RuntimeArgs, Transaction, TransactionCategory, TransactionEntryPoint, - TransactionHash, TransactionHeader, TransactionId, TransactionInvocationTarget, - TransactionRuntime, TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1Body, - TransactionV1DecodeFromJsonError, TransactionV1Error, TransactionV1ExcessiveSizeError, - TransactionV1Hash, TransactionV1Header, TransferTarget, + PricingMode, PricingModeError, RuntimeArgs, Transaction, TransactionEntryPoint, + TransactionHash, TransactionId, TransactionInvocationTarget, TransactionRuntime, + TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1DecodeFromJsonError, + TransactionV1Error, TransactionV1ExcessiveSizeError, TransactionV1Hash, TransferTarget, }; #[cfg(any(feature = "std", test))] pub use transaction::{ @@ -214,8 +212,14 @@ pub use validator_change::ValidatorChange; pub const MINT_LANE_ID: u8 = 0; /// The lane identifier for the native auction interaction. pub const AUCTION_LANE_ID: u8 = 1; -/// The lane identifier for the special Wasm `install_upgrade` lane. +/// The lane identifier for the install/upgrade auction interaction. pub const INSTALL_UPGRADE_LANE_ID: u8 = 2; +/// The lane identifier for large wasms. +pub const LARGE_WASM_LANE_ID: u8 = 3; +/// The lane identifier for medium wasms. +pub const MEDIUM_WASM_LANE_ID: u8 = 4; +/// The lane identifier for small wasms. +pub const SMALL_WASM_LANE_ID: u8 = 5; /// OS page size. #[cfg(feature = "std")] diff --git a/types/src/transaction.rs b/types/src/transaction.rs index 7e11215eba..68f12aaaf8 100644 --- a/types/src/transaction.rs +++ b/types/src/transaction.rs @@ -11,10 +11,8 @@ mod package_identifier; mod pricing_mode; mod runtime_args; mod serialization; -mod transaction_category; mod transaction_entry_point; mod transaction_hash; -mod transaction_header; mod transaction_id; mod transaction_invocation_target; mod transaction_runtime; @@ -28,6 +26,8 @@ use core::fmt::{self, Debug, Display, Formatter}; #[cfg(any(feature = "std", test))] use std::hash::Hash; +#[cfg(feature = "json-schema")] +use crate::URef; #[cfg(feature = "datasize")] use datasize::DataSize; #[cfg(feature = "json-schema")] @@ -42,12 +42,10 @@ use tracing::error; #[cfg(any(all(feature = "std", feature = "testing"), test))] use crate::testing::TestRng; -#[cfg(feature = "json-schema")] -use crate::URef; use crate::{ account::AccountHash, bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - Digest, Phase, SecretKey, TimeDiff, Timestamp, + Digest, SecretKey, TimeDiff, Timestamp, }; #[cfg(any(feature = "std", test))] use crate::{Chainspec, Gas, Motes}; @@ -64,22 +62,24 @@ pub use error::InvalidTransaction; pub use execution_info::ExecutionInfo; pub use initiator_addr::InitiatorAddr; #[cfg(any(feature = "std", test))] -use initiator_addr_and_secret_key::InitiatorAddrAndSecretKey; +pub(crate) use initiator_addr_and_secret_key::InitiatorAddrAndSecretKey; pub use package_identifier::PackageIdentifier; -pub use pricing_mode::PricingMode; +pub use pricing_mode::{PricingMode, PricingModeError}; pub use runtime_args::{NamedArg, RuntimeArgs}; pub use transaction_entry_point::TransactionEntryPoint; pub use transaction_hash::TransactionHash; -pub use transaction_header::TransactionHeader; pub use transaction_id::TransactionId; pub use transaction_invocation_target::TransactionInvocationTarget; pub use transaction_runtime::TransactionRuntime; pub use transaction_scheduling::TransactionScheduling; pub use transaction_target::TransactionTarget; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +pub(crate) use transaction_v1::FieldsContainer; +#[cfg(any(feature = "testing", feature = "gens", test))] +pub use transaction_v1::TransactionV1Payload; pub use transaction_v1::{ - InvalidTransactionV1, TransactionCategory, TransactionV1, TransactionV1Body, - TransactionV1DecodeFromJsonError, TransactionV1Error, TransactionV1ExcessiveSizeError, - TransactionV1Hash, TransactionV1Header, + arg_handling, InvalidTransactionV1, TransactionV1, TransactionV1DecodeFromJsonError, + TransactionV1Error, TransactionV1ExcessiveSizeError, TransactionV1Hash, }; #[cfg(any(feature = "std", test))] pub use transaction_v1::{TransactionV1Builder, TransactionV1BuilderError}; @@ -138,14 +138,6 @@ impl Transaction { } } - /// Body hash. - pub fn body_hash(&self) -> Digest { - match self { - Transaction::Deploy(deploy) => *deploy.header().body_hash(), - Transaction::V1(v1) => *v1.header().body_hash(), - } - } - /// Size estimate. pub fn size_estimate(&self) -> usize { match self { @@ -158,7 +150,7 @@ impl Transaction { pub fn timestamp(&self) -> Timestamp { match self { Transaction::Deploy(deploy) => deploy.header().timestamp(), - Transaction::V1(v1) => v1.header().timestamp(), + Transaction::V1(v1) => v1.payload().timestamp(), } } @@ -166,7 +158,7 @@ impl Transaction { pub fn ttl(&self) -> TimeDiff { match self { Transaction::Deploy(deploy) => deploy.header().ttl(), - Transaction::V1(v1) => v1.header().ttl(), + Transaction::V1(v1) => v1.payload().ttl(), } } @@ -194,15 +186,15 @@ impl Transaction { Transaction::V1(v1) => v1.approvals().clone(), } } - - /// Returns the header. - pub fn header(&self) -> TransactionHeader { - match self { - Transaction::Deploy(deploy) => TransactionHeader::Deploy(deploy.header().clone()), - Transaction::V1(transaction) => TransactionHeader::V1(transaction.header().clone()), + /* + /// Returns the header. + pub fn header(&self) -> TransactionHeader { + match self { + Transaction::Deploy(deploy) => TransactionHeader::Deploy(deploy.header().clone()), + Transaction::V1(transaction) => TransactionHeader::V1(transaction.header().clone()), + } } - } - + */ /// Returns the computed approvals hash identifying this transaction's approvals. pub fn compute_approvals_hash(&self) -> Result { let approvals_hash = match self { @@ -212,6 +204,7 @@ impl Transaction { Ok(approvals_hash) } + /* TODO remove /// Turns `self` into an invalid `Transaction` by clearing the `chain_name`, invalidating the /// transaction hash. #[cfg(any(all(feature = "std", feature = "testing"), test))] @@ -221,6 +214,7 @@ impl Transaction { Transaction::V1(v1) => v1.invalidate(), } } + */ /// Returns the computed `TransactionId` uniquely identifying this transaction and its /// approvals. @@ -265,7 +259,7 @@ impl Transaction { pub fn expires(&self) -> Timestamp { match self { Transaction::Deploy(deploy) => deploy.header().expires(), - Transaction::V1(txn) => txn.header().expires(), + Transaction::V1(txn) => txn.payload().expires(), } } @@ -299,98 +293,55 @@ impl Transaction { } } - /// Returns `true` if `self` represents a native transfer deploy or a native V1 transaction. - pub fn is_native(&self) -> bool { - match self { - Transaction::Deploy(deploy) => deploy.is_transfer(), - Transaction::V1(v1_txn) => *v1_txn.target() == TransactionTarget::Native, - } - } - - /// Is this a transaction that should be sent to the v1 execution engine? - pub fn is_v1_wasm(&self) -> bool { + /// Is the transaction the legacy deploy variant. + pub fn is_legacy_transaction(&self) -> bool { match self { - Transaction::Deploy(deploy) => !deploy.is_transfer(), - Transaction::V1(v1) => v1.is_v1_wasm(), + Transaction::Deploy(_) => true, + Transaction::V1(_) => false, } } - /// Should this transaction use standard payment processing? - pub fn is_standard_payment(&self) -> bool { + #[cfg(any(all(feature = "std", feature = "testing"), test))] + /// Calcualates the gas limit for the transaction. + pub fn gas_limit(&self, chainspec: &Chainspec, lane_id: u8) -> Result { match self { - Transaction::Deploy(deploy) => deploy.payment().is_standard_payment(Phase::Payment), + Transaction::Deploy(deploy) => deploy + .gas_limit(chainspec) + .map_err(InvalidTransaction::from), Transaction::V1(v1) => { - if let PricingMode::Classic { - standard_payment, .. - } = v1.pricing_mode() - { - *standard_payment - } else { - true - } + let pricing_mode = v1.pricing_mode(); + let entry_point = v1 + .get_transaction_entry_point() + .map_err(InvalidTransaction::from)?; + pricing_mode + .gas_limit(chainspec, &entry_point, lane_id) + .map_err(InvalidTransaction::from) } } } - /// Should this transaction start in the initiating accounts context? - pub fn is_account_session(&self) -> bool { - match self { - Transaction::Deploy(deploy) => deploy.is_account_session(), - Transaction::V1(v1) => v1.is_account_session(), - } - } - - /// Authorization keys. - pub fn authorization_keys(&self) -> BTreeSet { + #[cfg(any(all(feature = "std", feature = "testing"), test))] + /// Returns a gas cost based upon the gas_limit, the gas price, + /// and the chainspec settings. + pub fn gas_cost( + &self, + chainspec: &Chainspec, + lane_id: u8, + gas_price: u8, + ) -> Result { match self { Transaction::Deploy(deploy) => deploy - .approvals() - .iter() - .map(|approval| approval.signer().to_account_hash()) - .collect(), - Transaction::V1(transaction_v1) => transaction_v1 - .approvals() - .iter() - .map(|approval| approval.signer().to_account_hash()) - .collect(), - } - } - - /// The session args. - pub fn session_args(&self) -> &RuntimeArgs { - match self { - Transaction::Deploy(deploy) => deploy.session().args(), - Transaction::V1(transaction_v1) => transaction_v1.body().args(), - } - } - - /// The entry point. - pub fn entry_point(&self) -> TransactionEntryPoint { - match self { - Transaction::Deploy(deploy) => deploy.session().entry_point_name().into(), - Transaction::V1(transaction_v1) => transaction_v1.entry_point().clone(), - } - } - - /// The transaction category. - pub fn transaction_category(&self) -> u8 { - match self { - Transaction::Deploy(deploy) => { - if deploy.is_transfer() { - TransactionCategory::Mint as u8 - } else { - TransactionCategory::Large as u8 - } + .gas_cost(chainspec, gas_price) + .map_err(InvalidTransaction::from), + Transaction::V1(v1) => { + let pricing_mode = v1.pricing_mode(); + let entry_point = v1 + .get_transaction_entry_point() + .map_err(InvalidTransaction::from)?; + pricing_mode + .gas_cost(chainspec, &entry_point, lane_id, gas_price) + .map_err(InvalidTransaction::from) } - Transaction::V1(v1) => v1.transaction_category(), - } - } - - /// Is the transaction the legacy deploy variant. - pub fn is_legacy_transaction(&self) -> bool { - match self { - Transaction::Deploy(_) => true, - Transaction::V1(_) => false, } } @@ -432,40 +383,6 @@ pub trait GasLimited { fn gas_price_tolerance(&self) -> Result; } -#[cfg(any(feature = "std", test))] -impl GasLimited for Transaction { - type Error = InvalidTransaction; - - fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result { - match self { - Transaction::Deploy(deploy) => deploy - .gas_cost(chainspec, gas_price) - .map_err(InvalidTransaction::from), - Transaction::V1(v1) => v1 - .gas_cost(chainspec, gas_price) - .map_err(InvalidTransaction::from), - } - } - - fn gas_limit(&self, chainspec: &Chainspec) -> Result { - match self { - Transaction::Deploy(deploy) => deploy - .gas_limit(chainspec) - .map_err(InvalidTransaction::from), - Transaction::V1(v1) => v1.gas_limit(chainspec).map_err(InvalidTransaction::from), - } - } - - fn gas_price_tolerance(&self) -> Result { - match self { - Transaction::Deploy(deploy) => deploy - .gas_price_tolerance() - .map_err(InvalidTransaction::from), - Transaction::V1(v1) => v1.gas_price_tolerance().map_err(InvalidTransaction::from), - } - } -} - impl From for Transaction { fn from(deploy: Deploy) -> Self { Self::Deploy(deploy) @@ -536,13 +453,12 @@ impl Display for Transaction { /// Proptest generators for [`Transaction`]. #[cfg(any(feature = "testing", feature = "gens", test))] pub mod gens { + use super::*; use proptest::{ array, prelude::{Arbitrary, Strategy}, }; - use super::*; - pub fn deploy_hash_arb() -> impl Strategy { array::uniform32(::arbitrary()).prop_map(DeployHash::from_raw) } @@ -594,3 +510,24 @@ mod tests { bytesrepr::test_serialization_roundtrip(&transaction); } } + +#[cfg(test)] +mod proptests { + use super::*; + use crate::{bytesrepr, gens::transaction_arb}; + use proptest::prelude::*; + + proptest! { + #[test] + fn bytesrepr_roundtrip(transaction in transaction_arb()) { + bytesrepr::test_serialization_roundtrip(&transaction); + } + + #[test] + fn json_roundtrip(transaction in transaction_arb()) { + let json_string = serde_json::to_string_pretty(&transaction).unwrap(); + let decoded = serde_json::from_str::(&json_string).unwrap(); + assert_eq!(transaction, decoded); + } + } +} diff --git a/types/src/transaction/deploy.rs b/types/src/transaction/deploy.rs index 74a99c821f..3177dfa052 100644 --- a/types/src/transaction/deploy.rs +++ b/types/src/transaction/deploy.rs @@ -62,12 +62,11 @@ use crate::{ }; #[cfg(any(feature = "std", test))] -use crate::{chainspec::PricingHandling, transaction::TransactionCategory, Chainspec}; +use crate::{chainspec::PricingHandling, Chainspec, LARGE_WASM_LANE_ID}; #[cfg(any(feature = "std", test))] use crate::{system::auction::ARG_AMOUNT, transaction::GasLimited, Gas, Motes, U512}; #[cfg(any(feature = "std", test))] pub use deploy_builder::{DeployBuilder, DeployBuilderError}; -pub use deploy_category::DeployCategory; pub use deploy_hash::DeployHash; pub use deploy_header::DeployHeader; pub use deploy_id::DeployId; @@ -404,9 +403,12 @@ impl Deploy { at: Timestamp, ) -> Result<(), InvalidDeploy> { let config = &chainspec.transaction_config; + // We're assuming that Deploy can have a maximum size of an InstallUpgrade transaction. + // We're passing 0 as transaction size since determining max transaction size for + // InstallUpgrade doesn't rely on the size of transaction let max_transaction_size = config .transaction_v1_config - .get_max_serialized_length(TransactionCategory::Large as u8); + .get_max_serialized_length(LARGE_WASM_LANE_ID); self.is_valid_size(max_transaction_size as u32)?; let header = self.header(); @@ -1319,7 +1321,7 @@ impl GasLimited for Deploy { let computation_limit = if self.is_transfer() { costs.mint_costs().transfer as u64 } else { - chainspec.get_max_gas_limit_by_category(TransactionCategory::Large as u8) + chainspec.get_max_gas_limit_by_category(LARGE_WASM_LANE_ID) }; Gas::new(computation_limit) } // legacy deploys do not support reservations diff --git a/types/src/transaction/initiator_addr_and_secret_key.rs b/types/src/transaction/initiator_addr_and_secret_key.rs index 20b565b401..d174a81d87 100644 --- a/types/src/transaction/initiator_addr_and_secret_key.rs +++ b/types/src/transaction/initiator_addr_and_secret_key.rs @@ -2,7 +2,7 @@ use crate::{InitiatorAddr, PublicKey, SecretKey}; /// Used when constructing a deploy or transaction. #[derive(Debug)] -pub(super) enum InitiatorAddrAndSecretKey<'a> { +pub(crate) enum InitiatorAddrAndSecretKey<'a> { /// Provides both the initiator address and the secret key (not necessarily for the same /// initiator address) used to sign the deploy or transaction. Both { diff --git a/types/src/transaction/pricing_mode.rs b/types/src/transaction/pricing_mode.rs index 159f523eac..5036d9a88e 100644 --- a/types/src/transaction/pricing_mode.rs +++ b/types/src/transaction/pricing_mode.rs @@ -9,9 +9,12 @@ use rand::Rng; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use super::serialization::CalltableSerializationEnvelope; #[cfg(doc)] use super::Transaction; +use super::{ + serialization::CalltableSerializationEnvelope, InvalidTransaction, InvalidTransactionV1, + TransactionEntryPoint, +}; #[cfg(any(feature = "testing", test))] use crate::testing::TestRng; use crate::{ @@ -22,6 +25,8 @@ use crate::{ transaction::serialization::CalltableSerializationEnvelopeBuilder, Digest, }; +#[cfg(any(feature = "std", test))] +use crate::{Chainspec, Gas, Motes, AUCTION_LANE_ID, MINT_LANE_ID, U512}; /// The pricing mode of a [`Transaction`]. #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)] @@ -48,6 +53,14 @@ pub enum PricingMode { /// The cost of the transaction is determined by the cost table, per the /// transaction category. Fixed { + /// User-specified additional computation factor (minimum 0). If "0" is provided, + /// no additional logic is applied to the computation limit. Each value above "0" + /// tells the node that it needs to treat the transaction as if it uses more gas + /// than it's serialized size indicates. Each "1" will increase the "wasm lane" + /// size bucket for this transaction by 1. So if the size of the transaction + /// indicates bucket "0" and "additional_computation_factor = 2", the transaction + /// will be treated as a "2". + additional_computation_factor: u8, /// User-specified gas_price tolerance (minimum 1). /// This is interpreted to mean "do not include this transaction in a block /// if the current gas price is greater than this number" @@ -73,6 +86,7 @@ impl PricingMode { }, 1 => PricingMode::Fixed { gas_price_tolerance: rng.gen(), + additional_computation_factor: 1, }, 2 => PricingMode::Reserved { receipt: rng.gen() }, _ => unreachable!(), @@ -95,10 +109,12 @@ impl PricingMode { } PricingMode::Fixed { gas_price_tolerance, + additional_computation_factor, } => { vec![ crate::bytesrepr::U8_SERIALIZED_LENGTH, gas_price_tolerance.serialized_length(), + additional_computation_factor.serialized_length(), ] } PricingMode::Reserved { receipt } => { @@ -109,6 +125,155 @@ impl PricingMode { } } } + + #[cfg(any(feature = "std", test))] + /// Returns the gas limit. + pub fn gas_limit( + &self, + chainspec: &Chainspec, + entry_point: &TransactionEntryPoint, + lane_id: u8, + ) -> Result { + let costs = chainspec.system_costs_config; + let gas = match self { + PricingMode::Classic { payment_amount, .. } => Gas::new(*payment_amount), + PricingMode::Fixed { .. } => { + let computation_limit = { + if lane_id == MINT_LANE_ID { + // Because we currently only support one native mint interaction, + // native transfer, we can short circuit to return that value. + // However if other direct mint interactions are supported + // in the future (such as the upcoming burn feature), + // this logic will need to be expanded to self.mint_costs().field? + // for the value for each verb...see how auction is set up below. + costs.mint_costs().transfer as u64 + } else if lane_id == AUCTION_LANE_ID { + let amount = match entry_point { + TransactionEntryPoint::Call => { + return Err(PricingModeError::EntryPointCannotBeCall) + } + TransactionEntryPoint::Custom(_) | TransactionEntryPoint::Transfer => { + return Err(PricingModeError::EntryPointCannotBeCustom { + entry_point: entry_point.clone(), + }); + } + TransactionEntryPoint::AddBid | TransactionEntryPoint::ActivateBid => { + costs.auction_costs().add_bid.into() + } + TransactionEntryPoint::WithdrawBid => { + costs.auction_costs().withdraw_bid.into() + } + TransactionEntryPoint::Delegate => { + costs.auction_costs().delegate.into() + } + TransactionEntryPoint::Undelegate => { + costs.auction_costs().undelegate.into() + } + TransactionEntryPoint::Redelegate => { + costs.auction_costs().redelegate.into() + } + TransactionEntryPoint::ChangeBidPublicKey => { + costs.auction_costs().change_bid_public_key + } + TransactionEntryPoint::AddReservations => { + costs.auction_costs().add_reservations.into() + } + TransactionEntryPoint::CancelReservations => { + costs.auction_costs().cancel_reservations.into() + } + }; + amount + } else { + chainspec.get_max_gas_limit_by_category(lane_id) + } + }; + Gas::new(U512::from(computation_limit)) + } + PricingMode::Reserved { receipt } => { + return Err(PricingModeError::InvalidPricingMode { + price_mode: PricingMode::Reserved { receipt: *receipt }, + }); + } + }; + Ok(gas) + } + + #[cfg(any(feature = "std", test))] + /// Returns gas cost. + pub fn gas_cost( + &self, + chainspec: &Chainspec, + entry_point: &TransactionEntryPoint, + lane_id: u8, + gas_price: u8, + ) -> Result { + let gas_limit = self.gas_limit(chainspec, entry_point, lane_id)?; + let motes = match self { + PricingMode::Classic { .. } | PricingMode::Fixed { .. } => { + Motes::from_gas(gas_limit, gas_price) + .ok_or(PricingModeError::UnableToCalculateGasCost)? + } + PricingMode::Reserved { .. } => { + Motes::zero() // prepaid + } + }; + Ok(motes) + } + + /// Returns gas cost. + pub fn additional_computation_factor(&self) -> u8 { + match self { + PricingMode::Classic { .. } => 0, + PricingMode::Fixed { + additional_computation_factor, + .. + } => *additional_computation_factor, + PricingMode::Reserved { .. } => 0, + } + } +} + +///Errors that can occur when calling PricingMode functions +pub enum PricingModeError { + /// The entry point for this transaction target cannot be `call`. + EntryPointCannotBeCall, + /// The entry point for this transaction target cannot be `TransactionEntryPoint::Custom`. + EntryPointCannotBeCustom { + /// The invalid entry point. + entry_point: TransactionEntryPoint, + }, + /// Invalid combination of pricing handling and pricing mode. + InvalidPricingMode { + /// The pricing mode as specified by the transaction. + price_mode: PricingMode, + }, + /// Unable to calculate gas cost. + UnableToCalculateGasCost, +} + +impl From for InvalidTransaction { + fn from(err: PricingModeError) -> Self { + InvalidTransaction::V1(err.into()) + } +} + +impl From for InvalidTransactionV1 { + fn from(err: PricingModeError) -> Self { + match err { + PricingModeError::EntryPointCannotBeCall => { + InvalidTransactionV1::EntryPointCannotBeCall + } + PricingModeError::EntryPointCannotBeCustom { entry_point } => { + InvalidTransactionV1::EntryPointCannotBeCustom { entry_point } + } + PricingModeError::InvalidPricingMode { price_mode } => { + InvalidTransactionV1::InvalidPricingMode { price_mode } + } + PricingModeError::UnableToCalculateGasCost => { + InvalidTransactionV1::UnableToCalculateGasCost + } + } + } } const TAG_FIELD_INDEX: u16 = 0; @@ -119,6 +284,7 @@ const CLASSIC_STANDARD_PAYMENT_INDEX: u16 = 3; const FIXED_VARIANT_TAG: u8 = 1; const FIXED_GAS_PRICE_TOLERANCE_INDEX: u16 = 1; +const FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX: u16 = 2; const RESERVED_VARIANT_TAG: u8 = 2; const RESERVED_RECEIPT_INDEX: u16 = 1; @@ -138,9 +304,14 @@ impl ToBytes for PricingMode { .binary_payload_bytes(), PricingMode::Fixed { gas_price_tolerance, + additional_computation_factor, } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? .add_field(TAG_FIELD_INDEX, &FIXED_VARIANT_TAG)? .add_field(FIXED_GAS_PRICE_TOLERANCE_INDEX, &gas_price_tolerance)? + .add_field( + FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX, + &additional_computation_factor, + )? .binary_payload_bytes(), PricingMode::Reserved { receipt } => { CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? @@ -185,11 +356,16 @@ impl FromBytes for PricingMode { let window = window.ok_or(Formatting)?; window.verify_index(FIXED_GAS_PRICE_TOLERANCE_INDEX)?; let (gas_price_tolerance, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX)?; + let (additional_computation_factor, window) = + window.deserialize_and_maybe_next::()?; if window.is_some() { return Err(Formatting); } Ok(PricingMode::Fixed { gas_price_tolerance, + additional_computation_factor, }) } RESERVED_VARIANT_TAG => { @@ -224,7 +400,12 @@ impl Display for PricingMode { PricingMode::Reserved { receipt } => write!(formatter, "reserved: {}", receipt), PricingMode::Fixed { gas_price_tolerance, - } => write!(formatter, "fixed pricing {}", gas_price_tolerance), + additional_computation_factor, + } => write!( + formatter, + "fixed pricing {} {}", + gas_price_tolerance, additional_computation_factor + ), } } } diff --git a/types/src/transaction/transaction_category.rs b/types/src/transaction/transaction_category.rs deleted file mode 100644 index c3ce0a678e..0000000000 --- a/types/src/transaction/transaction_category.rs +++ /dev/null @@ -1,71 +0,0 @@ -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -// 1.x of the protocol had two implicit categories...standard and native transfer / mint -// 2.x and onwards the protocol has explicit categories. -// For legacy deploy support purposes, 1.x native transfers map to Mint, and all other deploys -// map to Standard. - -// NOTE: there is a direct correlation between the block body structure and the transaction -// categories. A given block structure defines some number of lanes for transactions. -// Thus, a given transaction explicitly specifies which lane within the block -// structure it is intended to go into. - -// Conceptually, the enum could just as easily be flipped around to be defined by the Block -// variant and be called something like BlockLane or BlockTransactionCategory, etc. It's only -// a matter of perspective. - -use crate::{ - transaction::{deploy::DeployCategory, transaction_v1::TransactionCategory as V1}, - Deploy, -}; - -/// The category of a [`Transaction`]. -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Session kind of a Transaction.") -)] -#[serde(deny_unknown_fields)] -#[repr(u8)] -pub(crate) enum TransactionCategory { - /// The supported categories of transactions. This was not explicit in protocol 1.x - /// but was made explicit in protocol 2.x. Thus V1 is introduced in protocol 2.0 - /// Older deploys are retroactively mapped into the corresponding variants to - /// allow retro-compatibility. Think of it as a retcon. - V1(V1), -} - -impl From for TransactionCategory { - fn from(value: Deploy) -> Self { - // To hand waive away legacy issues, we just curry the implicit categories from protocol 1.x - // forward to the corresponding protocol 2.x explicit categories. - if value.is_transfer() { - TransactionCategory::V1(V1::Mint) - } else { - TransactionCategory::V1(V1::Large) - } - } -} - -impl From for TransactionCategory { - fn from(value: DeployCategory) -> Self { - // To hand waive away legacy issues, we just curry the implicit categories from protocol 1.x - // forward to the corresponding protocol 2.x explicit categories. - match value { - DeployCategory::Standard => TransactionCategory::V1(V1::Large), - DeployCategory::Transfer => TransactionCategory::V1(V1::Mint), - } - } -} - -impl From for TransactionCategory { - fn from(value: V1) -> Self { - TransactionCategory::V1(value) - } -} diff --git a/types/src/transaction/transaction_header.rs b/types/src/transaction/transaction_header.rs deleted file mode 100644 index 68b5d7f3bd..0000000000 --- a/types/src/transaction/transaction_header.rs +++ /dev/null @@ -1,129 +0,0 @@ -use alloc::vec::Vec; -use core::fmt::{self, Display, Formatter}; - -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; - -use super::{DeployHeader, TransactionV1Header}; -use crate::{ - bytesrepr::{self, FromBytes, ToBytes, U8_SERIALIZED_LENGTH}, - InitiatorAddr, -}; - -const DEPLOY_TAG: u8 = 0; -const V1_TAG: u8 = 1; - -/// A versioned wrapper for a transaction header or deploy header. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr(feature = "json-schema", derive(JsonSchema))] -pub enum TransactionHeader { - /// A deploy header. - Deploy(DeployHeader), - /// A version 1 transaction header. - #[cfg_attr(any(feature = "std", test), serde(rename = "Version1"))] - V1(TransactionV1Header), -} - -impl TransactionHeader { - /// Return the initiator addr of this transaction. - pub fn initiator_addr(&self) -> InitiatorAddr { - match self { - TransactionHeader::Deploy(header) => header.account().clone().into(), - TransactionHeader::V1(header) => header.initiator_addr().clone(), - } - } -} - -impl From for TransactionHeader { - fn from(header: DeployHeader) -> Self { - Self::Deploy(header) - } -} - -impl From for TransactionHeader { - fn from(header: TransactionV1Header) -> Self { - Self::V1(header) - } -} - -impl Display for TransactionHeader { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - match self { - TransactionHeader::Deploy(hash) => Display::fmt(hash, formatter), - TransactionHeader::V1(hash) => Display::fmt(hash, formatter), - } - } -} - -impl ToBytes for TransactionHeader { - fn write_bytes(&self, writer: &mut Vec) -> Result<(), bytesrepr::Error> { - match self { - TransactionHeader::Deploy(header) => { - DEPLOY_TAG.write_bytes(writer)?; - header.write_bytes(writer) - } - TransactionHeader::V1(header) => { - V1_TAG.write_bytes(writer)?; - header.write_bytes(writer) - } - } - } - - fn to_bytes(&self) -> Result, bytesrepr::Error> { - let mut buffer = bytesrepr::allocate_buffer(self)?; - self.write_bytes(&mut buffer)?; - Ok(buffer) - } - - fn serialized_length(&self) -> usize { - U8_SERIALIZED_LENGTH - + match self { - TransactionHeader::Deploy(header) => header.serialized_length(), - TransactionHeader::V1(header) => header.serialized_length(), - } - } -} - -impl FromBytes for TransactionHeader { - fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> { - let (tag, remainder) = u8::from_bytes(bytes)?; - match tag { - DEPLOY_TAG => { - let (header, remainder) = DeployHeader::from_bytes(remainder)?; - Ok((TransactionHeader::Deploy(header), remainder)) - } - V1_TAG => { - let (header, remainder) = TransactionV1Header::from_bytes(remainder)?; - Ok((TransactionHeader::V1(header), remainder)) - } - _ => Err(bytesrepr::Error::Formatting), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{testing::TestRng, Deploy, TransactionV1}; - - #[test] - fn bytesrepr_roundtrip() { - let rng = &mut TestRng::new(); - - let header = TransactionHeader::from(Deploy::random(rng).take_header()); - bytesrepr::test_serialization_roundtrip(&header); - - let header = TransactionHeader::from(TransactionV1::random(rng).take_header()); - bytesrepr::test_serialization_roundtrip(&header); - } -} diff --git a/types/src/transaction/transaction_target.rs b/types/src/transaction/transaction_target.rs index e1e8d4f284..f8d83a4c73 100644 --- a/types/src/transaction/transaction_target.rs +++ b/types/src/transaction/transaction_target.rs @@ -43,10 +43,12 @@ pub enum TransactionTarget { }, /// The execution target is the included module bytes, i.e. compiled Wasm. Session { - /// The compiled Wasm. - module_bytes: Bytes, + /// Flag determining if the Wasm is an install/upgrade. + is_install_upgrade: bool, /// The execution runtime to use. runtime: TransactionRuntime, + /// The compiled Wasm. + module_bytes: Bytes, }, } @@ -62,8 +64,13 @@ impl TransactionTarget { } /// Returns a new `TransactionTarget::Session`. - pub fn new_session(module_bytes: Bytes, runtime: TransactionRuntime) -> Self { + pub fn new_session( + is_install_upgrade: bool, + module_bytes: Bytes, + runtime: TransactionRuntime, + ) -> Self { TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, } @@ -82,13 +89,15 @@ impl TransactionTarget { ] } TransactionTarget::Session { - module_bytes, + is_install_upgrade, runtime, + module_bytes, } => { vec![ crate::bytesrepr::U8_SERIALIZED_LENGTH, - module_bytes.serialized_length(), + is_install_upgrade.serialized_length(), runtime.serialized_length(), + module_bytes.serialized_length(), ] } } @@ -106,7 +115,12 @@ impl TransactionTarget { 2 => { let mut buffer = vec![0u8; rng.gen_range(0..100)]; rng.fill_bytes(buffer.as_mut()); - TransactionTarget::new_session(Bytes::from(buffer), TransactionRuntime::VmCasperV1) + let is_install_upgrade = rng.gen(); + TransactionTarget::new_session( + is_install_upgrade, + Bytes::from(buffer), + TransactionRuntime::VmCasperV1, + ) } _ => unreachable!(), } @@ -122,8 +136,9 @@ const STORED_ID_INDEX: u16 = 1; const STORED_RUNTIME_INDEX: u16 = 2; const SESSION_VARIANT: u8 = 2; -const SESSION_MODULE_BYTES_INDEX: u16 = 1; +const SESSION_IS_INSTALL_INDEX: u16 = 1; const SESSION_RUNTIME_INDEX: u16 = 2; +const SESSION_MODULE_BYTES_INDEX: u16 = 3; impl ToBytes for TransactionTarget { fn to_bytes(&self) -> Result, Error> { @@ -141,12 +156,14 @@ impl ToBytes for TransactionTarget { .binary_payload_bytes() } TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, } => CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? .add_field(TAG_FIELD_INDEX, &SESSION_VARIANT)? - .add_field(SESSION_MODULE_BYTES_INDEX, &module_bytes)? + .add_field(SESSION_IS_INSTALL_INDEX, &is_install_upgrade)? .add_field(SESSION_RUNTIME_INDEX, &runtime)? + .add_field(SESSION_MODULE_BYTES_INDEX, &module_bytes)? .binary_payload_bytes(), } } @@ -158,7 +175,7 @@ impl ToBytes for TransactionTarget { impl FromBytes for TransactionTarget { fn from_bytes(bytes: &[u8]) -> Result<(TransactionTarget, &[u8]), Error> { - let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(3, bytes)?; + let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(4, bytes)?; let window = binary_payload.start_consuming()?.ok_or(Formatting)?; window.verify_index(TAG_FIELD_INDEX)?; let (tag, window) = window.deserialize_and_maybe_next::()?; @@ -185,16 +202,20 @@ impl FromBytes for TransactionTarget { } SESSION_VARIANT => { let window = window.ok_or(Formatting)?; - window.verify_index(SESSION_MODULE_BYTES_INDEX)?; - let (module_bytes, window) = window.deserialize_and_maybe_next::()?; + window.verify_index(SESSION_IS_INSTALL_INDEX)?; + let (is_install_upgrade, window) = window.deserialize_and_maybe_next::()?; let window = window.ok_or(Formatting)?; window.verify_index(SESSION_RUNTIME_INDEX)?; let (runtime, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(SESSION_MODULE_BYTES_INDEX)?; + let (module_bytes, window) = window.deserialize_and_maybe_next::()?; if window.is_some() { return Err(Formatting); } Ok(TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, }) @@ -213,13 +234,15 @@ impl Display for TransactionTarget { write!(formatter, "stored({}, {})", id, runtime) } TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, } => write!( formatter, - "session({} module bytes, {})", + "session({} module bytes, runtime: {}, is_install_upgrade: {})", module_bytes.len(), - runtime + runtime, + is_install_upgrade, ), } } @@ -235,6 +258,7 @@ impl Debug for TransactionTarget { .field("runtime", runtime) .finish(), TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime, } => { @@ -249,6 +273,7 @@ impl Debug for TransactionTarget { .debug_struct("Session") .field("module_bytes", &BytesLen(module_bytes.len())) .field("runtime", runtime) + .field("is_install_upgrade", is_install_upgrade) .finish() } } diff --git a/types/src/transaction/transaction_v1.rs b/types/src/transaction/transaction_v1.rs index ddbf53c2ee..41741afadf 100644 --- a/types/src/transaction/transaction_v1.rs +++ b/types/src/transaction/transaction_v1.rs @@ -1,20 +1,27 @@ +pub mod arg_handling; mod errors_v1; -mod transaction_v1_body; #[cfg(any(feature = "std", test))] mod transaction_v1_builder; -mod transaction_v1_category; mod transaction_v1_hash; -mod transaction_v1_header; +pub mod transaction_v1_payload; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use alloc::string::ToString; -use alloc::{collections::BTreeSet, vec::Vec}; -use core::{ - cmp, - fmt::{self, Debug, Display, Formatter}, - hash, +use crate::{ + bytesrepr::{self, Error, FromBytes, ToBytes}, + crypto, }; +#[cfg(any(feature = "testing", test))] +use crate::{testing::TestRng, AUCTION_LANE_ID, LARGE_WASM_LANE_ID, MINT_LANE_ID}; +#[cfg(any(feature = "std", test))] +use alloc::collections::BTreeMap; +use alloc::{collections::BTreeSet, vec::Vec}; +use errors_v1::FieldDeserializationError; +use tracing::debug; +pub use transaction_v1_payload::TransactionV1Payload; +#[cfg(any(feature = "std", test))] +use super::InitiatorAddrAndSecretKey; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use super::{TransactionEntryPoint, TransactionTarget}; #[cfg(feature = "datasize")] use datasize::DataSize; #[cfg(any(feature = "once_cell", test))] @@ -23,58 +30,47 @@ use once_cell::sync::OnceCell; use schemars::JsonSchema; #[cfg(any(feature = "std", test))] use serde::{Deserialize, Serialize}; -use tracing::debug; use super::{ serialization::{CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder}, - Approval, ApprovalsHash, InitiatorAddr, PricingMode, TransactionEntryPoint, - TransactionScheduling, TransactionTarget, + Approval, ApprovalsHash, InitiatorAddr, PricingMode, }; -#[cfg(any(feature = "std", test))] -use super::{GasLimited, InitiatorAddrAndSecretKey}; -#[cfg(any(feature = "std", test))] -use crate::chainspec::Chainspec; +#[cfg(any(feature = "std", feature = "testing", test))] +use crate::bytesrepr::Bytes; +use crate::{Digest, DisplayIter, SecretKey, TimeDiff, Timestamp}; #[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::chainspec::PricingHandling; -use crate::{ - bytesrepr::{ - Error::{self, Formatting}, - FromBytes, ToBytes, - }, - crypto, Digest, DisplayIter, RuntimeArgs, SecretKey, TimeDiff, Timestamp, TransactionRuntime, -}; +pub(crate) use transaction_v1_builder::FieldsContainer; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::TransactionConfig; -#[cfg(any(feature = "std", test))] -use crate::{Gas, Motes, U512}; pub use errors_v1::{ DecodeFromJsonErrorV1 as TransactionV1DecodeFromJsonError, ErrorV1 as TransactionV1Error, ExcessiveSizeErrorV1 as TransactionV1ExcessiveSizeError, InvalidTransaction as InvalidTransactionV1, }; -pub use transaction_v1_body::TransactionV1Body; #[cfg(any(feature = "std", test))] pub use transaction_v1_builder::{TransactionV1Builder, TransactionV1BuilderError}; -pub use transaction_v1_category::TransactionCategory; pub use transaction_v1_hash::TransactionV1Hash; -pub use transaction_v1_header::TransactionV1Header; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::testing::TestRng; +use core::{ + cmp, + fmt::{self, Debug, Display, Formatter}, + hash, +}; -const TRANSACTION_V1_SERIALIZATION_VERSION: u8 = 1; +#[cfg(any(feature = "std", test))] +pub(crate) const ARGS_MAP_KEY: u16 = 0; +#[cfg(any(feature = "std", test))] +pub(crate) const TARGET_MAP_KEY: u16 = 1; +#[cfg(any(feature = "std", test))] +pub(crate) const ENTRY_POINT_MAP_KEY: u16 = 2; +#[cfg(any(feature = "std", test))] +pub(crate) const SCHEDULING_MAP_KEY: u16 = 3; -const SERIALIZATION_VERSION_INDEX: u16 = 0; -const HASH_FIELD_META_INDEX: u16 = 1; -const HEADER_FIELD_META_INDEX: u16 = 2; -const BODY_FIELD_META_INDEX: u16 = 3; -const APPROVALS_FIELD_META_INDEX: u16 = 4; +const HASH_FIELD_INDEX: u16 = 0; +const PAYLOAD_FIELD_INDEX: u16 = 1; +const APPROVALS_FIELD_INDEX: u16 = 2; /// A unit of work sent by a client to the network, which when executed can cause global state to /// be altered. -/// -/// To construct a new `TransactionV1`, use a [`TransactionV1Builder`]. #[derive(Clone, Eq, Debug)] #[cfg_attr( any(feature = "std", test), @@ -91,10 +87,8 @@ const APPROVALS_FIELD_META_INDEX: u16 = 4; ) )] pub struct TransactionV1 { - serialization_version: u8, hash: TransactionV1Hash, - header: TransactionV1Header, - body: TransactionV1Body, + payload: TransactionV1Payload, approvals: BTreeSet, #[cfg_attr(any(all(feature = "std", feature = "once_cell"), test), serde(skip))] #[cfg_attr( @@ -106,50 +100,32 @@ pub struct TransactionV1 { } impl TransactionV1 { - fn serialized_field_lengths(&self) -> Vec { - let serialization_version_len = TRANSACTION_V1_SERIALIZATION_VERSION.serialized_length(); - let approvals_len = self.approvals.serialized_length(); - let hash_len = self.hash.serialized_length(); - let header_len = self.header.serialized_length(); - let body_len = self.body.serialized_length(); - vec![ - serialization_version_len, - hash_len, - header_len, - body_len, - approvals_len, - ] - } - /// Called by the `TransactionV1Builder` to construct a new `TransactionV1`. #[cfg(any(feature = "std", test))] - pub(super) fn build( + pub(crate) fn build( chain_name: String, timestamp: Timestamp, ttl: TimeDiff, - body: TransactionV1Body, pricing_mode: PricingMode, + fields: BTreeMap, initiator_addr_and_secret_key: InitiatorAddrAndSecretKey, ) -> TransactionV1 { let initiator_addr = initiator_addr_and_secret_key.initiator_addr(); - let body_hash = Digest::hash( - body.to_bytes() - .unwrap_or_else(|error| panic!("should serialize body: {}", error)), - ); - let header = TransactionV1Header::new( + let transaction_v1_payload = TransactionV1Payload::new( chain_name, timestamp, ttl, - body_hash, pricing_mode, initiator_addr, + fields, + ); + let hash = Digest::hash( + transaction_v1_payload + .to_bytes() + .unwrap_or_else(|error| panic!("should serialize body: {}", error)), ); - - let hash = header.compute_hash(); let mut transaction = TransactionV1 { - serialization_version: TRANSACTION_V1_SERIALIZATION_VERSION, - hash, - header, - body, + hash: hash.into(), + payload: transaction_v1_payload, approvals: BTreeSet::new(), #[cfg(any(feature = "once_cell", test))] is_verified: OnceCell::new(), @@ -161,341 +137,76 @@ impl TransactionV1 { transaction } - /// Returns the hash identifying this transaction. + /// Adds a signature of this transaction's hash to its approvals. + pub fn sign(&mut self, secret_key: &SecretKey) { + let approval = Approval::create(&self.hash.into(), secret_key); + self.approvals.insert(approval); + } + + /// Returns the `ApprovalsHash` of this transaction's approvals. pub fn hash(&self) -> &TransactionV1Hash { &self.hash } + /// Returns the internal payload of this transaction. + pub fn payload(&self) -> &TransactionV1Payload { + &self.payload + } + + /// Returns transactions approvals. + pub fn approvals(&self) -> &BTreeSet { + &self.approvals + } + + /// Returns the address of the initiator of the transaction. + pub fn initiator_addr(&self) -> &InitiatorAddr { + self.payload.initiator_addr() + } + /// Returns the name of the chain the transaction should be executed on. pub fn chain_name(&self) -> &str { - self.header.chain_name() + self.payload.chain_name() } /// Returns the creation timestamp of the transaction. pub fn timestamp(&self) -> Timestamp { - self.header.timestamp() + self.payload.timestamp() } /// Returns the duration after the creation timestamp for which the transaction will stay valid. /// /// After this duration has ended, the transaction will be considered expired. pub fn ttl(&self) -> TimeDiff { - self.header.ttl() + self.payload.ttl() } /// Returns `true` if the transaction has expired. pub fn expired(&self, current_instant: Timestamp) -> bool { - self.header.expired(current_instant) + self.payload.expired(current_instant) } /// Returns the pricing mode for the transaction. pub fn pricing_mode(&self) -> &PricingMode { - self.header.pricing_mode() - } - - /// Returns the address of the initiator of the transaction. - pub fn initiator_addr(&self) -> &InitiatorAddr { - self.header.initiator_addr() - } - - /// Returns a reference to the header of this transaction. - pub fn header(&self) -> &TransactionV1Header { - &self.header - } - - /// Consumes `self`, returning the header of this transaction. - pub fn take_header(self) -> TransactionV1Header { - self.header - } - - /// Returns the runtime args of the transaction. - pub fn args(&self) -> &RuntimeArgs { - self.body.args() - } - - /// Consumes `self`, returning the runtime args of the transaction. - pub fn take_args(self) -> RuntimeArgs { - self.body.take_args() - } - - /// Returns the target of the transaction. - pub fn target(&self) -> &TransactionTarget { - self.body.target() - } - - /// Returns the entry point of the transaction. - pub fn entry_point(&self) -> &TransactionEntryPoint { - self.body.entry_point() - } - - /// Returns the scheduling kind of the transaction. - pub fn scheduling(&self) -> &TransactionScheduling { - self.body.scheduling() - } - - /// Returns the body of this transaction. - pub fn body(&self) -> &TransactionV1Body { - &self.body - } - - /// Returns true if this transaction is a native mint interaction. - pub fn is_native_mint(&self) -> bool { - self.body().is_native_mint() - } - - /// Returns true if this transaction is a native auction interaction. - pub fn is_native_auction(&self) -> bool { - self.body().is_native_auction() - } - - /// Returns true if this transaction is a smart contract installer or upgrader. - pub fn is_install_or_upgrade(&self) -> bool { - self.body().is_install_or_upgrade() - } - - /// Returns the transaction category. - pub fn transaction_category(&self) -> u8 { - self.body.transaction_category() - } - - /// Does this transaction have wasm targeting the v1 vm. - pub fn is_v1_wasm(&self) -> bool { - match self.target() { - TransactionTarget::Native => false, - TransactionTarget::Stored { runtime, .. } - | TransactionTarget::Session { runtime, .. } => { - matches!(runtime, TransactionRuntime::VmCasperV1) - && (!self.is_native_mint() && !self.is_native_auction()) - } - } - } - - /// Should this transaction start in the initiating accounts context? - pub fn is_account_session(&self) -> bool { - let target_is_stored_contract = matches!(self.target(), TransactionTarget::Stored { .. }); - !target_is_stored_contract - } - - /// Returns the approvals for this transaction. - pub fn approvals(&self) -> &BTreeSet { - &self.approvals - } - - /// Consumes `self`, returning a tuple of its constituent parts. - pub fn destructure( - self, - ) -> ( - TransactionV1Hash, - TransactionV1Header, - TransactionV1Body, - BTreeSet, - ) { - (self.hash, self.header, self.body, self.approvals) - } - - /// Adds a signature of this transaction's hash to its approvals. - pub fn sign(&mut self, secret_key: &SecretKey) { - let approval = Approval::create(&self.hash.into(), secret_key); - self.approvals.insert(approval); + self.payload.pricing_mode() } /// Returns the `ApprovalsHash` of this transaction's approvals. - pub fn compute_approvals_hash(&self) -> Result { + pub fn compute_approvals_hash(&self) -> Result { ApprovalsHash::compute(&self.approvals) } - /// Returns `true` if the serialized size of the transaction is not greater than - /// `max_transaction_size`. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn is_valid_size( - &self, - max_transaction_size: u32, - ) -> Result<(), TransactionV1ExcessiveSizeError> { - let actual_transaction_size = self.serialized_length(); - if actual_transaction_size > max_transaction_size as usize { - return Err(TransactionV1ExcessiveSizeError { - max_transaction_size, - actual_transaction_size, - }); - } - Ok(()) - } - - /// Returns `Ok` if and only if this transaction's body hashes to the value of `body_hash()`, - /// and if this transaction's header hashes to the value claimed as the transaction hash. - pub fn has_valid_hash(&self) -> Result<(), InvalidTransactionV1> { - let body_hash = Digest::hash( - self.body - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize body: {}", error)), - ); - if body_hash != *self.header.body_hash() { - debug!(?self, ?body_hash, "invalid transaction body hash"); - return Err(InvalidTransactionV1::InvalidBodyHash); - } - - let hash = TransactionV1Hash::new(Digest::hash( - self.header - .to_bytes() - .unwrap_or_else(|error| panic!("should serialize header: {}", error)), - )); - if hash != self.hash { - debug!(?self, ?hash, "invalid transaction hash"); - return Err(InvalidTransactionV1::InvalidTransactionHash); - } - Ok(()) - } - - /// Returns `Ok` if and only if: - /// * the transaction hash is correct (see [`TransactionV1::has_valid_hash`] for details) - /// * approvals are non empty, and - /// * all approvals are valid signatures of the signed hash - pub fn verify(&self) -> Result<(), InvalidTransactionV1> { - #[cfg(any(feature = "once_cell", test))] - return self.is_verified.get_or_init(|| self.do_verify()).clone(); - - #[cfg(not(any(feature = "once_cell", test)))] - self.do_verify() - } - - fn do_verify(&self) -> Result<(), InvalidTransactionV1> { - if self.approvals.is_empty() { - debug!(?self, "transaction has no approvals"); - return Err(InvalidTransactionV1::EmptyApprovals); - } - - self.has_valid_hash()?; - - for (index, approval) in self.approvals.iter().enumerate() { - if let Err(error) = crypto::verify(self.hash, approval.signature(), approval.signer()) { - debug!( - ?self, - "failed to verify transaction approval {}: {}", index, error - ); - return Err(InvalidTransactionV1::InvalidApproval { index, error }); - } - } - - Ok(()) - } - - /// Returns `Ok` if and only if: - /// * the chain_name is correct, - /// * the configured parameters are complied with at the given timestamp - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn is_config_compliant( - &self, - chainspec: &Chainspec, - timestamp_leeway: TimeDiff, - at: Timestamp, - ) -> Result<(), InvalidTransactionV1> { - let transaction_config = chainspec.transaction_config.clone(); - self.is_valid_size( - transaction_config - .transaction_v1_config - .get_max_serialized_length(self.body.transaction_category) as u32, - )?; - - let chain_name = chainspec.network_config.name.clone(); - - let header = self.header(); - if header.chain_name() != chain_name { - debug!( - transaction_hash = %self.hash(), - transaction_header = %header, - chain_name = %header.chain_name(), - "invalid chain identifier" - ); - return Err(InvalidTransactionV1::InvalidChainName { - expected: chain_name, - got: header.chain_name().to_string(), - }); - } - - let price_handling = chainspec.core_config.pricing_handling; - let price_mode = header.pricing_mode(); - - match price_mode { - PricingMode::Classic { .. } => { - if let PricingHandling::Classic = price_handling { - } else { - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: price_mode.clone(), - }); - } - } - PricingMode::Fixed { .. } => { - if let PricingHandling::Fixed = price_handling { - } else { - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: price_mode.clone(), - }); - } - } - PricingMode::Reserved { .. } => { - if !chainspec.core_config.allow_reservations { - // Currently Reserved isn't implemented and we should - // not be accepting transactions with this mode. - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: price_mode.clone(), - }); - } - } - } - - let min_gas_price = chainspec.vacancy_config.min_gas_price; - let gas_price_tolerance = self.header.gas_price_tolerance(); - if gas_price_tolerance < min_gas_price { - return Err(InvalidTransactionV1::GasPriceToleranceTooLow { - min_gas_price_tolerance: min_gas_price, - provided_gas_price_tolerance: gas_price_tolerance, - }); - } - - header.is_valid(&transaction_config, timestamp_leeway, at, &self.hash)?; - - let max_associated_keys = chainspec.core_config.max_associated_keys; - - if self.approvals.len() > max_associated_keys as usize { - debug!( - transaction_hash = %self.hash(), - number_of_approvals = %self.approvals.len(), - max_associated_keys = %max_associated_keys, - "number of transaction approvals exceeds the limit" - ); - return Err(InvalidTransactionV1::ExcessiveApprovals { - got: self.approvals.len() as u32, - max_associated_keys, - }); - } - - let gas_limit = self.gas_limit(chainspec)?; - let block_gas_limit = Gas::new(U512::from(transaction_config.block_gas_limit)); - if gas_limit > block_gas_limit { - debug!( - amount = %gas_limit, - %block_gas_limit, - "transaction gas limit exceeds block gas limit" - ); - return Err(InvalidTransactionV1::ExceedsBlockGasLimit { - block_gas_limit: transaction_config.block_gas_limit, - got: Box::new(gas_limit.value()), - }); - } - - self.body.is_valid(&transaction_config) - } - - // This method is not intended to be used by third party crates. - // - // It is required to allow finalized approvals to be injected after reading a transaction from - // storage. #[doc(hidden)] pub fn with_approvals(mut self, approvals: BTreeSet) -> Self { self.approvals = approvals; self } + /// Used by the `TestTransactionV1Builder` to inject invalid approvals for testing purposes. + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(super) fn apply_approvals(&mut self, approvals: Vec) { + self.approvals.extend(approvals); + } + /// Returns a random, valid but possibly expired transaction. /// /// Note that the [`TransactionV1Builder`] can be used to create a random transaction with @@ -515,20 +226,15 @@ impl TransactionV1 { timestamp: Option, ttl: Option, ) -> Self { - let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( + let transaction_v1 = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionCategory::Mint as u8, + MINT_LANE_ID, timestamp, ttl, ) .build() .unwrap(); - assert_eq!( - transaction.transaction_category(), - TransactionCategory::Mint as u8, - "Required mint, incorrect category" - ); - transaction + transaction_v1 } /// Returns a random transaction with "standard" category. @@ -543,17 +249,12 @@ impl TransactionV1 { ) -> Self { let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionCategory::Large as u8, + LARGE_WASM_LANE_ID, timestamp, ttl, ) .build() .unwrap(); - assert_eq!( - transaction.transaction_category(), - TransactionCategory::Large as u8, - "Required large, incorrect category" - ); transaction } @@ -562,25 +263,19 @@ impl TransactionV1 { /// Note that the [`TransactionV1Builder`] can be used to create a random transaction with /// more specific values. #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random_install_upgrade( + pub fn random_auction( rng: &mut TestRng, timestamp: Option, ttl: Option, ) -> Self { - let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( + TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionCategory::InstallUpgrade as u8, + AUCTION_LANE_ID, timestamp, ttl, ) .build() - .unwrap(); - assert_eq!( - transaction.transaction_category(), - TransactionCategory::InstallUpgrade as u8, - "Required install/upgrade, incorrect category" - ); - transaction + .unwrap() } /// Returns a random transaction with "install/upgrade" category. @@ -588,194 +283,137 @@ impl TransactionV1 { /// Note that the [`TransactionV1Builder`] can be used to create a random transaction with /// more specific values. #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random_auction( + pub fn random_install_upgrade( rng: &mut TestRng, timestamp: Option, ttl: Option, ) -> Self { let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( rng, - TransactionCategory::Auction as u8, + LARGE_WASM_LANE_ID, timestamp, ttl, ) .build() .unwrap(); - assert_eq!( - transaction.transaction_category(), - TransactionCategory::Auction as u8, - "Required auction, incorrect category" - ); transaction } - /// Turns `self` into an invalid transaction by clearing the `chain_name`, invalidating the - /// transaction header hash. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn invalidate(&mut self) { - self.header.invalidate(); + /// Returns result of attempting to deserailize a field from the amorphic `fields` container. + pub fn deserialize_field( + &self, + index: u16, + ) -> Result { + self.payload.deserialize_field(index) } - /// Used by the `TestTransactionV1Builder` to inject invalid approvals for testing purposes. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub(super) fn apply_approvals(&mut self, approvals: Vec) { - self.approvals.extend(approvals); + /// Returns number of fields in the amorphic `fields` container. + pub fn number_of_fields(&self) -> usize { + self.payload.number_of_fields() } -} -#[cfg(any(feature = "std", test))] -impl GasLimited for TransactionV1 { - type Error = InvalidTransactionV1; + /// Checks if the declared hash of the transaction matches calculated hash. + pub fn has_valid_hash(&self) -> Result<(), InvalidTransactionV1> { + let computed_hash = Digest::hash( + self.payload + .to_bytes() + .unwrap_or_else(|error| panic!("should serialize body: {}", error)), + ); + if TransactionV1Hash::new(computed_hash) != self.hash { + debug!(?self, ?computed_hash, "invalid transaction hash"); + return Err(InvalidTransactionV1::InvalidTransactionHash); + } + Ok(()) + } - fn gas_cost(&self, chainspec: &Chainspec, gas_price: u8) -> Result { - let gas_limit = self.gas_limit(chainspec)?; - let motes = match self.header().pricing_mode() { - PricingMode::Classic { .. } | PricingMode::Fixed { .. } => { - Motes::from_gas(gas_limit, gas_price) - .ok_or(InvalidTransactionV1::UnableToCalculateGasCost)? - } - PricingMode::Reserved { .. } => { - Motes::zero() // prepaid - } - }; - Ok(motes) + /// Returns `Ok` if and only if: + /// * the transaction hash is correct (see [`TransactionV1::has_valid_hash`] for details) + /// * approvals are non empty, and + /// * all approvals are valid signatures of the signed hash + pub fn verify(&self) -> Result<(), InvalidTransactionV1> { + #[cfg(any(feature = "once_cell", test))] + return self.is_verified.get_or_init(|| self.do_verify()).clone(); + + #[cfg(not(any(feature = "once_cell", test)))] + self.do_verify() } - fn gas_limit(&self, chainspec: &Chainspec) -> Result { - let costs = chainspec.system_costs_config; - let gas = match self.header().pricing_mode() { - PricingMode::Classic { payment_amount, .. } => Gas::new(*payment_amount), - PricingMode::Fixed { .. } => { - let computation_limit = { - if self.is_native_mint() { - // Because we currently only support one native mint interaction, - // native transfer, we can short circuit to return that value. - // However if other direct mint interactions are supported - // in the future (such as the upcoming burn feature), - // this logic will need to be expanded to self.mint_costs().field? - // for the value for each verb...see how auction is set up below. - costs.mint_costs().transfer as u64 - } else if self.is_native_auction() { - let entry_point = self.body().entry_point(); - let amount = match entry_point { - TransactionEntryPoint::Call => { - return Err(InvalidTransactionV1::EntryPointCannotBeCall) - } - TransactionEntryPoint::Custom(_) | TransactionEntryPoint::Transfer => { - return Err(InvalidTransactionV1::EntryPointCannotBeCustom { - entry_point: entry_point.clone(), - }); - } - TransactionEntryPoint::AddBid | TransactionEntryPoint::ActivateBid => { - costs.auction_costs().add_bid.into() - } - TransactionEntryPoint::WithdrawBid => { - costs.auction_costs().withdraw_bid.into() - } - TransactionEntryPoint::Delegate => { - costs.auction_costs().delegate.into() - } - TransactionEntryPoint::Undelegate => { - costs.auction_costs().undelegate.into() - } - TransactionEntryPoint::Redelegate => { - costs.auction_costs().redelegate.into() - } - TransactionEntryPoint::ChangeBidPublicKey => { - costs.auction_costs().change_bid_public_key - } - TransactionEntryPoint::AddReservations => { - costs.auction_costs().add_reservations.into() - } - TransactionEntryPoint::CancelReservations => { - costs.auction_costs().cancel_reservations.into() - } - }; - amount - } else { - chainspec.get_max_gas_limit_by_category(self.body.transaction_category) - } - }; - Gas::new(U512::from(computation_limit)) - } - PricingMode::Reserved { receipt } => { - return Err(InvalidTransactionV1::InvalidPricingMode { - price_mode: PricingMode::Reserved { receipt: *receipt }, - }); + fn do_verify(&self) -> Result<(), InvalidTransactionV1> { + if self.approvals.is_empty() { + debug!(?self, "transaction has no approvals"); + return Err(InvalidTransactionV1::EmptyApprovals); + } + + self.has_valid_hash()?; + + for (index, approval) in self.approvals.iter().enumerate() { + if let Err(error) = crypto::verify(self.hash, approval.signature(), approval.signer()) { + debug!( + ?self, + "failed to verify transaction approval {}: {}", index, error + ); + return Err(InvalidTransactionV1::InvalidApproval { index, error }); } - }; - Ok(gas) + } + + Ok(()) } - fn gas_price_tolerance(&self) -> Result { - Ok(self.header.gas_price_tolerance()) + /// Returns the hash of the transaction's payload. + pub fn payload_hash(&self) -> Result { + let bytes = self + .payload + .fields() + .to_bytes() + .map_err(|_| InvalidTransactionV1::CannotCalculateFieldsHash)?; + Ok(Digest::hash(bytes)) } -} -impl hash::Hash for TransactionV1 { - fn hash(&self, state: &mut H) { - // Destructure to make sure we don't accidentally omit fields. - let TransactionV1 { - serialization_version, - hash, - header, - body, - approvals, - #[cfg(any(feature = "once_cell", test))] - is_verified: _, - } = self; - serialization_version.hash(state); - hash.hash(state); - header.hash(state); - body.hash(state); - approvals.hash(state); + fn serialized_field_lengths(&self) -> Vec { + vec![ + self.hash.serialized_length(), + self.payload.serialized_length(), + self.approvals.serialized_length(), + ] } -} -impl PartialEq for TransactionV1 { - fn eq(&self, other: &TransactionV1) -> bool { - // Destructure to make sure we don't accidentally omit fields. - let TransactionV1 { - serialization_version, - hash, - header, - body, - approvals, - #[cfg(any(feature = "once_cell", test))] - is_verified: _, - } = self; - *serialization_version == other.serialization_version - && *hash == other.hash - && *header == other.header - && *body == other.body - && *approvals == other.approvals + /// Turns `self` into an invalid `TransactionV1` by clearing the `chain_name`, invalidating the + /// transaction hash + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn invalidate(&mut self) { + self.payload.invalidate(); } -} -impl Ord for TransactionV1 { - fn cmp(&self, other: &TransactionV1) -> cmp::Ordering { - // Destructure to make sure we don't accidentally omit fields. - let TransactionV1 { - serialization_version, - hash, - header, - body, - approvals, - #[cfg(any(feature = "once_cell", test))] - is_verified: _, - } = self; - serialization_version - .cmp(&other.serialization_version) - .then_with(|| hash.cmp(&other.hash)) - .then_with(|| header.cmp(&other.header)) - .then_with(|| body.cmp(&other.body)) - .then_with(|| approvals.cmp(&other.approvals)) + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(crate) fn get_transaction_target(&self) -> Result { + self.deserialize_field::(TARGET_MAP_KEY) + .map_err(|error| InvalidTransactionV1::CouldNotDeserializeField { error }) } -} -impl PartialOrd for TransactionV1 { - fn partial_cmp(&self, other: &TransactionV1) -> Option { - Some(self.cmp(other)) + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(crate) fn get_transaction_entry_point( + &self, + ) -> Result { + self.deserialize_field::(ENTRY_POINT_MAP_KEY) + .map_err(|error| InvalidTransactionV1::CouldNotDeserializeField { error }) + } + + /// Returns the gas price tolerance for the given transaction. + pub fn gas_price_tolerance(&self) -> u8 { + match self.pricing_mode() { + PricingMode::Classic { + gas_price_tolerance, + .. + } => *gas_price_tolerance, + PricingMode::Fixed { + gas_price_tolerance, + .. + } => *gas_price_tolerance, + PricingMode::Reserved { .. } => { + // TODO: Change this when reserve gets implemented. + 0u8 + } + } } } @@ -783,11 +421,9 @@ impl ToBytes for TransactionV1 { fn to_bytes(&self) -> Result, crate::bytesrepr::Error> { let expected_payload_sizes = self.serialized_field_lengths(); CalltableSerializationEnvelopeBuilder::new(expected_payload_sizes)? - .add_field(SERIALIZATION_VERSION_INDEX, &self.serialization_version)? - .add_field(HASH_FIELD_META_INDEX, &self.hash)? - .add_field(HEADER_FIELD_META_INDEX, &self.header)? - .add_field(BODY_FIELD_META_INDEX, &self.body)? - .add_field(APPROVALS_FIELD_META_INDEX, &self.approvals)? + .add_field(HASH_FIELD_INDEX, &self.hash)? + .add_field(PAYLOAD_FIELD_INDEX, &self.payload)? + .add_field(APPROVALS_FIELD_INDEX, &self.approvals)? .binary_payload_bytes() } @@ -798,39 +434,26 @@ impl ToBytes for TransactionV1 { impl FromBytes for TransactionV1 { fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { - let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(5, bytes)?; - let window = binary_payload.start_consuming()?.ok_or(Formatting)?; - window.verify_index(SERIALIZATION_VERSION_INDEX)?; - - let (serialization_version, window) = window.deserialize_and_maybe_next::()?; - if serialization_version != TRANSACTION_V1_SERIALIZATION_VERSION { - return Err(Formatting); - } - let window = window.ok_or(Formatting)?; - window.verify_index(HASH_FIELD_META_INDEX)?; + let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(3, bytes)?; + let window = binary_payload.start_consuming()?.ok_or(Error::Formatting)?; + window.verify_index(HASH_FIELD_INDEX)?; let (hash, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(HEADER_FIELD_META_INDEX)?; - let (header, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(BODY_FIELD_META_INDEX)?; - let (body, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(APPROVALS_FIELD_META_INDEX)?; + let window = window.ok_or(Error::Formatting)?; + window.verify_index(PAYLOAD_FIELD_INDEX)?; + let (payload, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Error::Formatting)?; + window.verify_index(APPROVALS_FIELD_INDEX)?; let (approvals, window) = window.deserialize_and_maybe_next::>()?; if window.is_some() { - return Err(Formatting); + return Err(Error::Formatting); } let from_bytes = TransactionV1 { - serialization_version, hash, - header, - body, + payload, approvals, #[cfg(any(feature = "once_cell", test))] is_verified: OnceCell::new(), }; - Ok((from_bytes, remainder)) } } @@ -840,565 +463,61 @@ impl Display for TransactionV1 { write!( formatter, "transaction-v1[{}, {}, approvals: {}]", - self.header, - self.body, + self.hash, + self.payload, DisplayIter::new(self.approvals.iter()) ) } } -#[cfg(test)] -mod tests { - use std::time::Duration; - - use crate::bytesrepr; - - use super::*; - - const MAX_ASSOCIATED_KEYS: u32 = 5; - - #[test] - fn json_roundtrip() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - let json_string = serde_json::to_string_pretty(&transaction).unwrap(); - let decoded = serde_json::from_str(&json_string).unwrap(); - assert_eq!(transaction, decoded); - } - - #[test] - fn bincode_roundtrip() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - let serialized = bincode::serialize(&transaction).unwrap(); - let deserialized = bincode::deserialize(&serialized).unwrap(); - assert_eq!(transaction, deserialized); - } - - #[test] - fn bytesrepr_roundtrip() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - bytesrepr::test_serialization_roundtrip(transaction.header()); - bytesrepr::test_serialization_roundtrip(&transaction); - } - - #[test] - fn is_valid() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1::random(rng); - assert_eq!( - transaction.is_verified.get(), - None, - "is_verified should initially be None" - ); - transaction.verify().expect("should verify"); - assert_eq!( - transaction.is_verified.get(), - Some(&Ok(())), - "is_verified should be true" - ); - } - - fn check_is_not_valid( - invalid_transaction: TransactionV1, - expected_error: InvalidTransactionV1, - ) { - assert!( - invalid_transaction.is_verified.get().is_none(), - "is_verified should initially be None" - ); - let actual_error = invalid_transaction.verify().unwrap_err(); - - // Ignore the `error_msg` field of `InvalidApproval` when comparing to expected error, as - // this makes the test too fragile. Otherwise expect the actual error should exactly match - // the expected error. - match expected_error { - InvalidTransactionV1::InvalidApproval { - index: expected_index, - .. - } => match actual_error { - InvalidTransactionV1::InvalidApproval { - index: actual_index, - .. - } => { - assert_eq!(actual_index, expected_index); - } - _ => panic!("expected {}, got: {}", expected_error, actual_error), - }, - _ => { - assert_eq!(actual_error, expected_error,); - } - } - - // The actual error should have been lazily initialized correctly. - assert_eq!( - invalid_transaction.is_verified.get(), - Some(&Err(actual_error)), - "is_verified should now be Some" - ); - } - - #[test] - fn not_valid_due_to_invalid_transaction_hash() { - let rng = &mut TestRng::new(); - let mut transaction = TransactionV1::random(rng); - - transaction.invalidate(); - check_is_not_valid(transaction, InvalidTransactionV1::InvalidTransactionHash); - } - - #[test] - fn not_valid_due_to_empty_approvals() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1Builder::new_random(rng) - .with_no_secret_key() - .build() - .unwrap(); - assert!(transaction.approvals.is_empty()); - check_is_not_valid(transaction, InvalidTransactionV1::EmptyApprovals) - } - - #[test] - fn not_valid_due_to_invalid_approval() { - let rng = &mut TestRng::new(); - let transaction = TransactionV1Builder::new_random(rng) - .with_invalid_approval(rng) - .build() - .unwrap(); - - // The expected index for the invalid approval will be the first index at which there is an - // approval where the signer is not the account holder. - let account_holder = match transaction.initiator_addr() { - InitiatorAddr::PublicKey(public_key) => public_key.clone(), - InitiatorAddr::AccountHash(_) => unreachable!(), - }; - let expected_index = transaction - .approvals - .iter() - .enumerate() - .find(|(_, approval)| approval.signer() != &account_holder) - .map(|(index, _)| index) - .unwrap(); - check_is_not_valid( - transaction, - InvalidTransactionV1::InvalidApproval { - index: expected_index, - error: crypto::Error::SignatureError, // This field is ignored in the check. - }, - ); - } - - #[test] - fn is_config_compliant() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let transaction = TransactionV1Builder::new_random_with_category_and_timestamp_and_ttl( - rng, - TransactionCategory::Large as u8, - None, - None, - ) - .with_chain_name(chain_name) - .build() - .unwrap(); - let current_timestamp = transaction.timestamp(); - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - transaction - .is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp) - .expect("should be acceptable"); - } - - #[test] - fn not_acceptable_due_to_invalid_chain_name() { - let rng = &mut TestRng::new(); - let expected_chain_name = "net-1"; - let wrong_chain_name = "net-2"; - let transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(wrong_chain_name) - .build() - .unwrap(); - - let expected_error = InvalidTransactionV1::InvalidChainName { - expected: expected_chain_name.to_string(), - got: wrong_chain_name.to_string(), - }; - - let current_timestamp = transaction.timestamp(); - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = expected_chain_name.to_string(); - ret - }; - assert_eq!( - transaction.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_excessive_ttl() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let transaction_config = TransactionConfig::default(); - let ttl = transaction_config.max_ttl + TimeDiff::from(Duration::from_secs(1)); - let transaction = TransactionV1Builder::new_random(rng) - .with_ttl(ttl) - .with_chain_name(chain_name) - .build() - .unwrap(); - - let expected_error = InvalidTransactionV1::ExcessiveTimeToLive { - max_ttl: transaction_config.max_ttl, - got: ttl, - }; - - let current_timestamp = transaction.timestamp(); - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - assert_eq!( - transaction.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_timestamp_in_future() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let leeway = TimeDiff::from_seconds(2); - - let transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .build() - .unwrap(); - let current_timestamp = transaction.timestamp() - leeway - TimeDiff::from_seconds(1); - - let expected_error = InvalidTransactionV1::TimestampInFuture { - validation_timestamp: current_timestamp, - timestamp_leeway: leeway, - got: transaction.timestamp(), - }; - - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - - assert_eq!( - transaction.is_config_compliant(&chainspec, leeway, current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_excessive_approvals() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - let mut transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .build() - .unwrap(); - - for _ in 0..MAX_ASSOCIATED_KEYS { - transaction.sign(&SecretKey::random(rng)); - } - - let current_timestamp = transaction.timestamp(); - - let expected_error = InvalidTransactionV1::ExcessiveApprovals { - got: MAX_ASSOCIATED_KEYS + 1, - max_associated_keys: MAX_ASSOCIATED_KEYS, - }; - - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret.core_config.max_associated_keys = MAX_ASSOCIATED_KEYS; - ret - }; - - assert_eq!( - transaction.is_config_compliant(&chainspec, TimeDiff::default(), current_timestamp), - Err(expected_error) - ); - assert!( - transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - } - - #[test] - fn not_acceptable_due_to_invalid_pricing_modes() { - let rng = &mut TestRng::new(); - let chain_name = "net-1"; - - let reserved_mode = PricingMode::Reserved { - receipt: Default::default(), - }; - - let reserved_transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(reserved_mode.clone()) - .build() - .expect("must be able to create a reserved transaction"); - - let chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret - }; - - let current_timestamp = reserved_transaction.timestamp(); - let expected_error = InvalidTransactionV1::InvalidPricingMode { - price_mode: reserved_mode, - }; - assert_eq!( - reserved_transaction.is_config_compliant( - &chainspec, - TimeDiff::default(), - current_timestamp, - ), - Err(expected_error) - ); - assert!( - reserved_transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - - let fixed_mode_transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::Fixed { - gas_price_tolerance: 1u8, - }) - .build() - .expect("must create fixed mode transaction"); - - let fixed_handling_chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret.core_config.pricing_handling = PricingHandling::Fixed; - ret - }; - - let classic_handling_chainspec = { - let mut ret = Chainspec::default(); - ret.network_config.name = chain_name.to_string(); - ret.core_config.pricing_handling = PricingHandling::Classic; - ret - }; - - let current_timestamp = fixed_mode_transaction.timestamp(); - let expected_error = InvalidTransactionV1::InvalidPricingMode { - price_mode: fixed_mode_transaction.pricing_mode().clone(), - }; - - assert_eq!( - fixed_mode_transaction.is_config_compliant( - &classic_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ), - Err(expected_error) - ); - assert!( - fixed_mode_transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - - assert!(fixed_mode_transaction - .is_config_compliant( - &fixed_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ) - .is_ok()); - - let classic_mode_transaction = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::Classic { - payment_amount: 100000, - gas_price_tolerance: 1, - standard_payment: true, - }) - .build() - .expect("must create classic transaction"); - - let current_timestamp = classic_mode_transaction.timestamp(); - let expected_error = InvalidTransactionV1::InvalidPricingMode { - price_mode: classic_mode_transaction.pricing_mode().clone(), - }; - - assert_eq!( - classic_mode_transaction.is_config_compliant( - &fixed_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ), - Err(expected_error) - ); - assert!( - classic_mode_transaction.is_verified.get().is_none(), - "transaction should not have run expensive `is_verified` call" - ); - - assert!(classic_mode_transaction - .is_config_compliant( - &classic_handling_chainspec, - TimeDiff::default(), - current_timestamp, - ) - .is_ok()); - } - - #[test] - fn should_use_payment_amount_for_classic_payment() { - let payment_amount = 500u64; - let mut chainspec = Chainspec::default(); - let chain_name = "net-1"; - chainspec - .with_chain_name(chain_name.to_string()) - .with_pricing_handling(PricingHandling::Classic); - - let rng = &mut TestRng::new(); - let builder = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::Classic { - payment_amount, - gas_price_tolerance: 1, - standard_payment: true, - }); - let transaction = builder.build().expect("should build"); - let mut gas_price = 1; - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, - U512::from(payment_amount), - "in classic pricing, the user selected amount should be the cost if gas price is 1" - ); - gas_price += 1; - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, - U512::from(payment_amount) * gas_price, - "in classic pricing, the cost should == user selected amount * gas_price" - ); +impl hash::Hash for TransactionV1 { + fn hash(&self, state: &mut H) { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1 { + hash, + payload, + approvals, + #[cfg(any(feature = "once_cell", test))] + is_verified: _, + } = self; + hash.hash(state); + payload.hash(state); + approvals.hash(state); } +} - #[test] - fn should_use_cost_table_for_fixed_payment() { - let mut chainspec = Chainspec::default(); - let chain_name = "net-1"; - chainspec - .with_chain_name(chain_name.to_string()) - .with_pricing_handling(PricingHandling::Fixed); - - let rng = &mut TestRng::new(); - let builder = TransactionV1Builder::new_random(rng) - .with_chain_name(chain_name) - .with_pricing_mode(PricingMode::Fixed { - gas_price_tolerance: 5, - }); - let transaction = builder.build().expect("should build"); - let mut gas_price = 1; - let limit = transaction - .gas_limit(&chainspec) - .expect("should limit") - .value(); - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, limit, - "in fixed pricing, the cost & limit should == if gas price is 1" - ); - gas_price += 1; - let cost = transaction - .gas_cost(&chainspec, gas_price) - .expect("should cost") - .value(); - assert_eq!( - cost, - limit * gas_price, - "in fixed pricing, the cost should == limit * gas_price" - ); +impl PartialEq for TransactionV1 { + fn eq(&self, other: &TransactionV1) -> bool { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1 { + hash, + payload, + approvals, + #[cfg(any(feature = "once_cell", test))] + is_verified: _, + } = self; + *hash == other.hash && *payload == other.payload && *approvals == other.approvals } } -#[cfg(test)] -mod proptests { - use super::gens::v1_transaction_arb; - use crate::bytesrepr::test_serialization_roundtrip; - use proptest::prelude::*; - - proptest! { - #[test] - fn bytesrepr_roundtrip(v1_transaction in v1_transaction_arb()) { - test_serialization_roundtrip(&v1_transaction); - } +impl Ord for TransactionV1 { + fn cmp(&self, other: &TransactionV1) -> cmp::Ordering { + // Destructure to make sure we don't accidentally omit fields. + let TransactionV1 { + hash, + payload, + approvals, + #[cfg(any(feature = "once_cell", test))] + is_verified: _, + } = self; + hash.cmp(&other.hash) + .then_with(|| payload.cmp(&other.payload)) + .then_with(|| approvals.cmp(&other.approvals)) } } -#[cfg(test)] -pub(crate) mod gens { - use super::*; - use crate::{gens::*, PublicKey}; - use crypto::gens::secret_key_arb_no_system; - use proptest::prelude::*; - - pub fn v1_transaction_arb() -> impl Strategy { - ( - any::(), - any::(), - any::(), - v1_transaction_body_arb(), - pricing_mode_arb(), - secret_key_arb_no_system(), - ) - .prop_map( - |(chain_name, timestamp, ttl, body, pricing_mode, secret_key)| { - let public_key = PublicKey::from(&secret_key); - let initiator_addr = InitiatorAddr::PublicKey(public_key); - let initiator_addr_with_secret = InitiatorAddrAndSecretKey::Both { - initiator_addr, - secret_key: &secret_key, - }; - TransactionV1::build( - chain_name, - Timestamp::from(timestamp), - TimeDiff::from_seconds(ttl), - body, - pricing_mode, - initiator_addr_with_secret, - ) - }, - ) +impl PartialOrd for TransactionV1 { + fn partial_cmp(&self, other: &TransactionV1) -> Option { + Some(self.cmp(other)) } } diff --git a/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs b/types/src/transaction/transaction_v1/arg_handling.rs similarity index 94% rename from types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs rename to types/src/transaction/transaction_v1/arg_handling.rs index a136ce3cae..2936b6ee1b 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_body/arg_handling.rs +++ b/types/src/transaction/transaction_v1/arg_handling.rs @@ -1,14 +1,19 @@ +//! Collection of helper functions and structures to reason about amorphic RuntimeArgs. use core::marker::PhantomData; +#[cfg(any(all(feature = "std", feature = "testing"), test))] use tracing::debug; #[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::{account::AccountHash, system::auction::ARG_VALIDATOR, CLType}; use crate::{ - bytesrepr::{FromBytes, ToBytes}, - CLTyped, CLValue, CLValueError, InvalidTransactionV1, PublicKey, RuntimeArgs, TransferTarget, - URef, U512, + account::AccountHash, bytesrepr::FromBytes, system::auction::ARG_VALIDATOR, CLType, CLValue, + InvalidTransactionV1, +}; +use crate::{ + bytesrepr::ToBytes, CLTyped, CLValueError, PublicKey, RuntimeArgs, TransferTarget, URef, U512, }; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use alloc::string::ToString; const TRANSFER_ARG_AMOUNT: RequiredArg = RequiredArg::new("amount"); const TRANSFER_ARG_SOURCE: OptionalArg = OptionalArg::new("source"); @@ -42,7 +47,9 @@ const REDELEGATE_ARG_NEW_VALIDATOR: RequiredArg = RequiredArg::new("n #[cfg(any(all(feature = "std", feature = "testing"), test))] const ACTIVATE_BID_ARG_VALIDATOR: RequiredArg = RequiredArg::new(ARG_VALIDATOR); +#[cfg(any(all(feature = "std", feature = "testing"), test))] const CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY: RequiredArg = RequiredArg::new("public_key"); +#[cfg(any(all(feature = "std", feature = "testing"), test))] const CHANGE_BID_PUBLIC_KEY_ARG_NEW_PUBLIC_KEY: RequiredArg = RequiredArg::new("new_public_key"); @@ -59,6 +66,7 @@ impl RequiredArg { } } + #[cfg(any(all(feature = "std", feature = "testing"), test))] fn get(&self, args: &RuntimeArgs) -> Result where T: CLTyped + FromBytes, @@ -114,6 +122,7 @@ impl OptionalArg { } } +#[cfg(any(all(feature = "std", feature = "testing"), test))] fn parse_cl_value( cl_value: &CLValue, arg_name: &str, @@ -136,10 +145,7 @@ fn parse_cl_value( } /// Creates a `RuntimeArgs` suitable for use in a transfer transaction. -pub(in crate::transaction::transaction_v1) fn new_transfer_args< - A: Into, - T: Into, ->( +pub fn new_transfer_args, T: Into>( amount: A, maybe_source: Option, target: T, @@ -165,7 +171,7 @@ pub(in crate::transaction::transaction_v1) fn new_transfer_args< /// Checks the given `RuntimeArgs` are suitable for use in a transfer transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_transfer_args( +pub fn has_valid_transfer_args( args: &RuntimeArgs, native_transfer_minimum_motes: u64, ) -> Result<(), InvalidTransactionV1> { @@ -221,7 +227,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_transfer_args( } /// Creates a `RuntimeArgs` suitable for use in an add_bid transaction. -pub(in crate::transaction::transaction_v1) fn new_add_bid_args>( +pub fn new_add_bid_args>( public_key: PublicKey, delegation_rate: u8, amount: A, @@ -239,9 +245,7 @@ pub(in crate::transaction::transaction_v1) fn new_add_bid_args>( /// Checks the given `RuntimeArgs` are suitable for use in an add_bid transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_add_bid_args( - args: &RuntimeArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_add_bid_args(args: &RuntimeArgs) -> Result<(), InvalidTransactionV1> { let _public_key = ADD_BID_ARG_PUBLIC_KEY.get(args)?; let _delegation_rate = ADD_BID_ARG_DELEGATION_RATE.get(args)?; let _amount = ADD_BID_ARG_AMOUNT.get(args)?; @@ -249,7 +253,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_add_bid_args( } /// Creates a `RuntimeArgs` suitable for use in a withdraw_bid transaction. -pub(in crate::transaction::transaction_v1) fn new_withdraw_bid_args>( +pub fn new_withdraw_bid_args>( public_key: PublicKey, amount: A, ) -> Result { @@ -261,16 +265,14 @@ pub(in crate::transaction::transaction_v1) fn new_withdraw_bid_args Result<(), InvalidTransactionV1> { +pub fn has_valid_withdraw_bid_args(args: &RuntimeArgs) -> Result<(), InvalidTransactionV1> { let _public_key = WITHDRAW_BID_ARG_PUBLIC_KEY.get(args)?; let _amount = WITHDRAW_BID_ARG_AMOUNT.get(args)?; Ok(()) } /// Creates a `RuntimeArgs` suitable for use in a delegate transaction. -pub(in crate::transaction::transaction_v1) fn new_delegate_args>( +pub fn new_delegate_args>( delegator: PublicKey, validator: PublicKey, amount: A, @@ -284,9 +286,7 @@ pub(in crate::transaction::transaction_v1) fn new_delegate_args>( /// Checks the given `RuntimeArgs` are suitable for use in a delegate transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_delegate_args( - args: &RuntimeArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_delegate_args(args: &RuntimeArgs) -> Result<(), InvalidTransactionV1> { let _delegator = DELEGATE_ARG_DELEGATOR.get(args)?; let _validator = DELEGATE_ARG_VALIDATOR.get(args)?; let _amount = DELEGATE_ARG_AMOUNT.get(args)?; @@ -294,7 +294,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_delegate_args( } /// Creates a `RuntimeArgs` suitable for use in an undelegate transaction. -pub(in crate::transaction::transaction_v1) fn new_undelegate_args>( +pub fn new_undelegate_args>( delegator: PublicKey, validator: PublicKey, amount: A, @@ -308,9 +308,7 @@ pub(in crate::transaction::transaction_v1) fn new_undelegate_args> /// Checks the given `RuntimeArgs` are suitable for use in an undelegate transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_undelegate_args( - args: &RuntimeArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_undelegate_args(args: &RuntimeArgs) -> Result<(), InvalidTransactionV1> { let _delegator = UNDELEGATE_ARG_DELEGATOR.get(args)?; let _validator = UNDELEGATE_ARG_VALIDATOR.get(args)?; let _amount = UNDELEGATE_ARG_AMOUNT.get(args)?; @@ -318,7 +316,7 @@ pub(in crate::transaction::transaction_v1) fn has_valid_undelegate_args( } /// Creates a `RuntimeArgs` suitable for use in a redelegate transaction. -pub(in crate::transaction::transaction_v1) fn new_redelegate_args>( +pub fn new_redelegate_args>( delegator: PublicKey, validator: PublicKey, amount: A, @@ -334,9 +332,7 @@ pub(in crate::transaction::transaction_v1) fn new_redelegate_args> /// Checks the given `RuntimeArgs` are suitable for use in a redelegate transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_redelegate_args( - args: &RuntimeArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_redelegate_args(args: &RuntimeArgs) -> Result<(), InvalidTransactionV1> { let _delegator = REDELEGATE_ARG_DELEGATOR.get(args)?; let _validator = REDELEGATE_ARG_VALIDATOR.get(args)?; let _amount = REDELEGATE_ARG_AMOUNT.get(args)?; @@ -346,16 +342,15 @@ pub(in crate::transaction::transaction_v1) fn has_valid_redelegate_args( /// Checks the given `RuntimeArgs` are suitable for use in an activate bid transaction. #[cfg(any(all(feature = "std", feature = "testing"), test))] -pub(in crate::transaction::transaction_v1) fn has_valid_activate_bid_args( - args: &RuntimeArgs, -) -> Result<(), InvalidTransactionV1> { +pub fn has_valid_activate_bid_args(args: &RuntimeArgs) -> Result<(), InvalidTransactionV1> { let _validator = ACTIVATE_BID_ARG_VALIDATOR.get(args)?; Ok(()) } /// Checks the given `RuntimeArgs` are suitable for use in a change bid public key transaction. #[allow(dead_code)] -pub(super) fn has_valid_change_bid_public_key_args( +#[cfg(any(all(feature = "std", feature = "testing"), test))] +pub fn has_valid_change_bid_public_key_args( args: &RuntimeArgs, ) -> Result<(), InvalidTransactionV1> { let _public_key = CHANGE_BID_PUBLIC_KEY_ARG_PUBLIC_KEY.get(args)?; diff --git a/types/src/transaction/transaction_v1/errors_v1.rs b/types/src/transaction/transaction_v1/errors_v1.rs index bc0ef906e2..2c19de43fb 100644 --- a/types/src/transaction/transaction_v1/errors_v1.rs +++ b/types/src/transaction/transaction_v1/errors_v1.rs @@ -10,10 +10,21 @@ use std::error::Error as StdError; use datasize::DataSize; use serde::Serialize; -use super::super::TransactionEntryPoint; #[cfg(doc)] use super::TransactionV1; -use crate::{bytesrepr, crypto, CLType, DisplayIter, PricingMode, TimeDiff, Timestamp, U512}; +use crate::{ + bytesrepr, crypto, CLType, DisplayIter, PricingMode, TimeDiff, Timestamp, + TransactionEntryPoint, U512, +}; + +#[derive(Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "std", derive(Serialize))] +#[cfg_attr(feature = "datasize", derive(DataSize))] +pub enum FieldDeserializationError { + IndexNotExists { index: u16 }, + FromBytesError { index: u16, error: bytesrepr::Error }, + LingeringBytesInField { index: u16 }, +} /// Returned when a [`TransactionV1`] fails validation. #[derive(Clone, Eq, PartialEq, Debug)] @@ -130,12 +141,16 @@ pub enum InvalidTransaction { /// The invalid entry point. entry_point: TransactionEntryPoint, }, - /// The entry point for this transaction target must be `TransactionEntryPoint::Custom`. EntryPointMustBeCustom { /// The invalid entry point. entry_point: TransactionEntryPoint, }, + /// The entry point for this transaction target must be `TransactionEntryPoint::Call`. + EntryPointMustBeCall { + /// The invalid entry point. + entry_point: TransactionEntryPoint, + }, /// The transaction has empty module bytes. EmptyModuleBytes, /// Attempt to factor the amount over the gas_price failed. @@ -155,7 +170,9 @@ pub enum InvalidTransaction { price_mode: PricingMode, }, /// The transaction provided is not supported. - InvalidTransactionKind(u8), + InvalidTransactionLane(u8), + /// No wasm lane matches transaction + NoWasmLaneMatchesTransaction(), /// Gas price tolerance too low. GasPriceToleranceTooLow { /// The minimum gas price tolerance. @@ -163,6 +180,14 @@ pub enum InvalidTransaction { /// The provided gas price tolerance. provided_gas_price_tolerance: u8, }, + /// Error when trying to deserialize one of the transactionV1 payload fields. + CouldNotDeserializeField { + /// Underlying reason why the deserialization failed + error: FieldDeserializationError, + }, + + /// Unable to calculate hash for payloads transaction. + CannotCalculateFieldsHash, } impl Display for InvalidTransaction { @@ -295,7 +320,7 @@ impl Display for InvalidTransaction { "received a transaction with an invalid mode {price_mode}" ) } - InvalidTransaction::InvalidTransactionKind(kind) => { + InvalidTransaction::InvalidTransactionLane(kind) => { write!( formatter, "received a transaction with an invalid kind {kind}" @@ -311,6 +336,34 @@ impl Display for InvalidTransaction { provided_gas_price_tolerance, min_gas_price_tolerance ) } + InvalidTransaction::CouldNotDeserializeField { error } => { + match error { + FieldDeserializationError::IndexNotExists { index } => write!( + formatter, + "tried to deserialize a field under index {} but it is not present in the payload", + index + ), + FieldDeserializationError::FromBytesError { index, error } => write!( + formatter, + "tried to deserialize a field under index {} but it failed with error: {}", + index, + error + ), + FieldDeserializationError::LingeringBytesInField { index } => write!( + formatter, + "tried to deserialize a field under index {} but after deserialization there were still bytes left", + index, + ), + } + }, + InvalidTransaction::CannotCalculateFieldsHash => write!( + formatter, + "cannot calculate a hash digest for the transaction" + ), + InvalidTransaction::EntryPointMustBeCall { entry_point } => { + write!(formatter, "entry point must be call: {entry_point}") + }, + InvalidTransaction::NoWasmLaneMatchesTransaction() => write!(formatter, "Could not match any generic wasm lane to the specified transaction"), } } } @@ -343,13 +396,21 @@ impl StdError for InvalidTransaction { | InvalidTransaction::EntryPointCannotBeCall | InvalidTransaction::EntryPointCannotBeCustom { .. } | InvalidTransaction::EntryPointMustBeCustom { .. } + | InvalidTransaction::EntryPointMustBeCall { .. } | InvalidTransaction::EmptyModuleBytes | InvalidTransaction::GasPriceConversion { .. } | InvalidTransaction::UnableToCalculateGasLimit | InvalidTransaction::UnableToCalculateGasCost | InvalidTransaction::InvalidPricingMode { .. } | InvalidTransaction::GasPriceToleranceTooLow { .. } - | InvalidTransaction::InvalidTransactionKind(_) => None, + | InvalidTransaction::InvalidTransactionLane(_) + | InvalidTransaction::CannotCalculateFieldsHash + | InvalidTransaction::NoWasmLaneMatchesTransaction() => None, + InvalidTransaction::CouldNotDeserializeField { error } => match error { + FieldDeserializationError::IndexNotExists { .. } + | FieldDeserializationError::LingeringBytesInField { .. } => None, + FieldDeserializationError::FromBytesError { error, .. } => Some(error), + }, } } } diff --git a/types/src/transaction/transaction_v1/transaction_v1_body.rs b/types/src/transaction/transaction_v1/transaction_v1_body.rs deleted file mode 100644 index fec2259311..0000000000 --- a/types/src/transaction/transaction_v1/transaction_v1_body.rs +++ /dev/null @@ -1,661 +0,0 @@ -#[cfg(any(feature = "std", test))] -pub(super) mod arg_handling; - -use alloc::vec::Vec; -use core::fmt::{self, Display, Formatter}; - -use super::super::{RuntimeArgs, TransactionEntryPoint, TransactionScheduling, TransactionTarget}; -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use rand::{Rng, RngCore}; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use tracing::debug; - -use super::TransactionCategory; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use super::TransactionConfig; -#[cfg(doc)] -use super::TransactionV1; -#[cfg(any(feature = "std", test))] -use crate::InvalidTransactionV1; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::TransactionV1ExcessiveSizeError; -#[cfg(any(all(feature = "std", feature = "testing"), test))] -use crate::{ - bytesrepr::Bytes, testing::TestRng, PublicKey, TransactionInvocationTarget, TransactionRuntime, - TransferTarget, -}; -use crate::{ - bytesrepr::{Error, FromBytes, ToBytes}, - transaction::serialization::{ - CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder, - }, -}; -/// The body of a [`TransactionV1`]. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Body of a `TransactionV1`.") -)] -pub struct TransactionV1Body { - pub(crate) args: RuntimeArgs, - pub(crate) target: TransactionTarget, - pub(crate) entry_point: TransactionEntryPoint, - pub(crate) transaction_category: u8, - pub(crate) scheduling: TransactionScheduling, -} - -impl TransactionV1Body { - /// Returns a new `TransactionV1Body`. - pub fn new( - args: RuntimeArgs, - target: TransactionTarget, - entry_point: TransactionEntryPoint, - transaction_category: u8, - scheduling: TransactionScheduling, - ) -> Self { - TransactionV1Body { - args, - target, - entry_point, - transaction_category, - scheduling, - } - } - - /// Returns the runtime args of the transaction. - pub fn args(&self) -> &RuntimeArgs { - &self.args - } - - /// Consumes `self`, returning the runtime args of the transaction. - pub fn take_args(self) -> RuntimeArgs { - self.args - } - - /// Returns the target of the transaction. - pub fn target(&self) -> &TransactionTarget { - &self.target - } - - /// Returns the entry point of the transaction. - pub fn entry_point(&self) -> &TransactionEntryPoint { - &self.entry_point - } - - /// Returns the scheduling kind of the transaction. - pub fn scheduling(&self) -> &TransactionScheduling { - &self.scheduling - } - - /// Returns true if this transaction is a native mint interaction. - pub fn is_native_mint(&self) -> bool { - self.transaction_category == TransactionCategory::Mint as u8 - } - - /// Returns true if this transaction is a native auction interaction. - pub fn is_native_auction(&self) -> bool { - self.transaction_category == TransactionCategory::Auction as u8 - } - - /// Returns true if this transaction is a smart contract installer or upgrader. - pub fn is_install_or_upgrade(&self) -> bool { - self.transaction_category == TransactionCategory::InstallUpgrade as u8 - } - - /// Returns the transaction category. - pub fn transaction_category(&self) -> u8 { - self.transaction_category - } - - /// Consumes `self`, returning its constituent parts. - pub fn destructure( - self, - ) -> ( - RuntimeArgs, - TransactionTarget, - TransactionEntryPoint, - TransactionScheduling, - ) { - (self.args, self.target, self.entry_point, self.scheduling) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub(super) fn is_valid(&self, config: &TransactionConfig) -> Result<(), InvalidTransactionV1> { - let kind = self.transaction_category; - if !config.transaction_v1_config.is_supported(kind) { - return Err(InvalidTransactionV1::InvalidTransactionKind( - self.transaction_category, - )); - } - - let max_serialized_length = config.transaction_v1_config.get_max_serialized_length(kind); - let actual_length = self.serialized_length(); - if actual_length > max_serialized_length as usize { - return Err(InvalidTransactionV1::ExcessiveSize( - TransactionV1ExcessiveSizeError { - max_transaction_size: max_serialized_length as u32, - actual_transaction_size: actual_length, - }, - )); - } - - let max_args_length = config.transaction_v1_config.get_max_args_length(kind); - - let args_length = self.args.serialized_length(); - if args_length > max_args_length as usize { - debug!( - args_length, - max_args_length = max_args_length, - "transaction runtime args excessive size" - ); - return Err(InvalidTransactionV1::ExcessiveArgsLength { - max_length: max_args_length as usize, - got: args_length, - }); - } - - match &self.target { - TransactionTarget::Native => match self.entry_point { - TransactionEntryPoint::Call => { - debug!( - entry_point = %self.entry_point, - "native transaction cannot have call entry point" - ); - Err(InvalidTransactionV1::EntryPointCannotBeCall) - } - TransactionEntryPoint::Custom(_) => { - debug!( - entry_point = %self.entry_point, - "native transaction cannot have custom entry point" - ); - Err(InvalidTransactionV1::EntryPointCannotBeCustom { - entry_point: self.entry_point.clone(), - }) - } - TransactionEntryPoint::Transfer => arg_handling::has_valid_transfer_args( - &self.args, - config.native_transfer_minimum_motes, - ), - TransactionEntryPoint::AddBid => arg_handling::has_valid_add_bid_args(&self.args), - TransactionEntryPoint::WithdrawBid => { - arg_handling::has_valid_withdraw_bid_args(&self.args) - } - TransactionEntryPoint::Delegate => { - arg_handling::has_valid_delegate_args(&self.args) - } - TransactionEntryPoint::Undelegate => { - arg_handling::has_valid_undelegate_args(&self.args) - } - TransactionEntryPoint::Redelegate => { - arg_handling::has_valid_redelegate_args(&self.args) - } - TransactionEntryPoint::ActivateBid => { - arg_handling::has_valid_activate_bid_args(&self.args) - } - TransactionEntryPoint::ChangeBidPublicKey => { - arg_handling::has_valid_change_bid_public_key_args(&self.args) - } - TransactionEntryPoint::AddReservations => { - todo!() - } - TransactionEntryPoint::CancelReservations => { - todo!() - } - }, - TransactionTarget::Stored { .. } => match &self.entry_point { - TransactionEntryPoint::Custom(_) => Ok(()), - TransactionEntryPoint::Call - | TransactionEntryPoint::Transfer - | TransactionEntryPoint::AddBid - | TransactionEntryPoint::WithdrawBid - | TransactionEntryPoint::Delegate - | TransactionEntryPoint::Undelegate - | TransactionEntryPoint::Redelegate - | TransactionEntryPoint::ActivateBid - | TransactionEntryPoint::ChangeBidPublicKey - | TransactionEntryPoint::AddReservations - | TransactionEntryPoint::CancelReservations => { - debug!( - entry_point = %self.entry_point, - "transaction targeting stored entity/package must have custom entry point" - ); - Err(InvalidTransactionV1::EntryPointMustBeCustom { - entry_point: self.entry_point.clone(), - }) - } - }, - TransactionTarget::Session { module_bytes, .. } => match &self.entry_point { - TransactionEntryPoint::Call | TransactionEntryPoint::Custom(_) => { - if module_bytes.is_empty() { - debug!("transaction with session code must not have empty module bytes"); - return Err(InvalidTransactionV1::EmptyModuleBytes); - } - Ok(()) - } - TransactionEntryPoint::Transfer - | TransactionEntryPoint::AddBid - | TransactionEntryPoint::WithdrawBid - | TransactionEntryPoint::Delegate - | TransactionEntryPoint::Undelegate - | TransactionEntryPoint::Redelegate - | TransactionEntryPoint::ActivateBid - | TransactionEntryPoint::ChangeBidPublicKey - | TransactionEntryPoint::AddReservations - | TransactionEntryPoint::CancelReservations => { - debug!( - entry_point = %self.entry_point, - "transaction with session code must use custom or default 'call' entry point" - ); - Err(InvalidTransactionV1::EntryPointMustBeCustom { - entry_point: self.entry_point.clone(), - }) - } - }, - } - } - - fn serialized_field_lengths(&self) -> Vec { - vec![ - self.args.serialized_length(), - self.target.serialized_length(), - self.entry_point.serialized_length(), - self.transaction_category.serialized_length(), - self.scheduling.serialized_length(), - ] - } - - /// Returns a random `TransactionV1Body`. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random_of_category(rng: &mut TestRng, category: u8) -> Self { - match category { - 0 => Self::random_transfer(rng), - 1 => Self::random_staking(rng), - 2 => Self::random_install_upgrade(rng), - _ => Self::random_standard(rng), - } - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_transfer(rng: &mut TestRng) -> Self { - let amount = - rng.gen_range(TransactionConfig::default().native_transfer_minimum_motes..=u64::MAX); - let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; - let target = TransferTarget::random(rng); - let maybe_id = rng.gen::().then(|| rng.gen()); - let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Transfer, - TransactionCategory::Mint as u8, - TransactionScheduling::random(rng), - ) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_standard(rng: &mut TestRng) -> Self { - let target = TransactionTarget::Stored { - id: TransactionInvocationTarget::random(rng), - runtime: TransactionRuntime::VmCasperV1, - }; - TransactionV1Body::new( - RuntimeArgs::random(rng), - target, - TransactionEntryPoint::Custom(rng.random_string(1..11)), - TransactionCategory::Large as u8, - TransactionScheduling::random(rng), - ) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_install_upgrade(rng: &mut TestRng) -> Self { - let target = TransactionTarget::Session { - module_bytes: Bytes::from(rng.random_vec(0..100)), - runtime: TransactionRuntime::VmCasperV1, - }; - TransactionV1Body::new( - RuntimeArgs::random(rng), - target, - TransactionEntryPoint::Custom(rng.random_string(1..11)), - TransactionCategory::InstallUpgrade as u8, - TransactionScheduling::random(rng), - ) - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - fn random_staking(rng: &mut TestRng) -> Self { - let public_key = PublicKey::random(rng); - let delegation_rate = rng.gen(); - let amount = rng.gen::(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; - let args = arg_handling::new_add_bid_args( - public_key, - delegation_rate, - amount, - minimum_delegation_amount, - maximum_delegation_amount, - ) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::AddBid, - TransactionCategory::Auction as u8, - TransactionScheduling::random(rng), - ) - } - - /// Returns a random `TransactionV1Body`. - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub fn random(rng: &mut TestRng) -> Self { - match rng.gen_range(0..8) { - 0 => { - let amount = rng.gen_range( - TransactionConfig::default().native_transfer_minimum_motes..=u64::MAX, - ); - let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; - let target = TransferTarget::random(rng); - let maybe_id = rng.gen::().then(|| rng.gen()); - let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Transfer, - TransactionCategory::Mint as u8, - TransactionScheduling::random(rng), - ) - } - 1 => { - let public_key = PublicKey::random(rng); - let delegation_rate = rng.gen(); - let amount = rng.gen::(); - let minimum_delegation_amount = rng.gen::() as u64; - let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; - let args = arg_handling::new_add_bid_args( - public_key, - delegation_rate, - amount, - minimum_delegation_amount, - maximum_delegation_amount, - ) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::AddBid, - TransactionCategory::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 2 => { - let public_key = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::WithdrawBid, - TransactionCategory::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 3 => { - let delegator = PublicKey::random(rng); - let validator = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_delegate_args(delegator, validator, amount).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Delegate, - TransactionCategory::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 4 => { - let delegator = PublicKey::random(rng); - let validator = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_undelegate_args(delegator, validator, amount).unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Undelegate, - TransactionCategory::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 5 => { - let delegator = PublicKey::random(rng); - let validator = PublicKey::random(rng); - let amount = rng.gen::(); - let new_validator = PublicKey::random(rng); - let args = - arg_handling::new_redelegate_args(delegator, validator, amount, new_validator) - .unwrap(); - TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Redelegate, - TransactionCategory::Auction as u8, - TransactionScheduling::random(rng), - ) - } - 6 => Self::random_standard(rng), - 7 => { - let mut buffer = vec![0u8; rng.gen_range(1..100)]; - rng.fill_bytes(buffer.as_mut()); - let target = TransactionTarget::Session { - module_bytes: Bytes::from(buffer), - runtime: TransactionRuntime::VmCasperV1, - }; - TransactionV1Body::new( - RuntimeArgs::random(rng), - target, - TransactionEntryPoint::Custom(rng.random_string(1..11)), - TransactionCategory::Large as u8, - TransactionScheduling::random(rng), - ) - } - _ => unreachable!(), - } - } -} - -const ARGS_INDEX: u16 = 0; -const TARGET_INDEX: u16 = 1; -const ENTRY_POINT_INDEX: u16 = 2; -const TRANSACTION_CATEGORY_INDEX: u16 = 3; -const SCHEDULING_INDEX: u16 = 4; - -impl FromBytes for TransactionV1Body { - fn from_bytes(bytes: &[u8]) -> Result<(TransactionV1Body, &[u8]), Error> { - let (binary_payload, remainder) = - crate::transaction::serialization::CalltableSerializationEnvelope::from_bytes( - 5, bytes, - )?; - let window = binary_payload.start_consuming()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(ARGS_INDEX)?; - let (args, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(TARGET_INDEX)?; - let (target, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(ENTRY_POINT_INDEX)?; - let (entry_point, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(TRANSACTION_CATEGORY_INDEX)?; - let (transaction_category, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Error::Formatting)?; - window.verify_index(SCHEDULING_INDEX)?; - let (scheduling, window) = window.deserialize_and_maybe_next::()?; - if window.is_some() { - return Err(Error::Formatting); - } - let from_bytes = TransactionV1Body { - args, - target, - entry_point, - transaction_category, - scheduling, - }; - Ok((from_bytes, remainder)) - } -} - -impl ToBytes for TransactionV1Body { - fn to_bytes(&self) -> Result, Error> { - CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? - .add_field(ARGS_INDEX, &self.args)? - .add_field(TARGET_INDEX, &self.target)? - .add_field(ENTRY_POINT_INDEX, &self.entry_point)? - .add_field(TRANSACTION_CATEGORY_INDEX, &self.transaction_category)? - .add_field(SCHEDULING_INDEX, &self.scheduling)? - .binary_payload_bytes() - } - fn serialized_length(&self) -> usize { - CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths()) - } -} - -impl Display for TransactionV1Body { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - write!( - formatter, - "v1-body({} {} {})", - self.target, self.entry_point, self.scheduling - ) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{bytesrepr, runtime_args}; - - #[test] - fn bytesrepr_roundtrip() { - let rng = &mut TestRng::new(); - let body = TransactionV1Body::random(rng); - bytesrepr::test_serialization_roundtrip(&body); - } - - #[test] - fn not_acceptable_due_to_excessive_args_length() { - let rng = &mut TestRng::new(); - let mut config = TransactionConfig::default(); - let mut body = TransactionV1Body::random_standard(rng); - config.transaction_v1_config.wasm_lanes = - vec![vec![body.transaction_category as u64, 1_048_576, 10, 0]]; - body.args = runtime_args! {"a" => 1_u8}; - - let expected_error = InvalidTransactionV1::ExcessiveArgsLength { - max_length: 10, - got: 15, - }; - - assert_eq!(body.is_valid(&config), Err(expected_error)); - } - - #[test] - fn not_acceptable_due_to_custom_entry_point_in_native() { - let rng = &mut TestRng::new(); - let public_key = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); - let entry_point = TransactionEntryPoint::Custom("custom".to_string()); - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - entry_point.clone(), - TransactionCategory::Mint as u8, - TransactionScheduling::random(rng), - ); - - let expected_error = InvalidTransactionV1::EntryPointCannotBeCustom { entry_point }; - - let config = TransactionConfig::default(); - assert_eq!(body.is_valid(&config), Err(expected_error)); - } - - #[test] - fn not_acceptable_due_to_call_entry_point_in_native() { - let rng = &mut TestRng::new(); - let public_key = PublicKey::random(rng); - let amount = rng.gen::(); - let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); - let entry_point = TransactionEntryPoint::Call; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - entry_point, - TransactionCategory::Mint as u8, - TransactionScheduling::random(rng), - ); - - let expected_error = InvalidTransactionV1::EntryPointCannotBeCall; - - let config = TransactionConfig::default(); - assert_eq!(body.is_valid(&config,), Err(expected_error)); - } - - #[test] - fn not_acceptable_due_to_non_custom_entry_point_in_stored_or_session() { - let rng = &mut TestRng::new(); - let config = TransactionConfig::default(); - - let mut check = |entry_point: TransactionEntryPoint| { - let stored_target = TransactionTarget::new_stored( - TransactionInvocationTarget::ByHash([0; 32]), - TransactionRuntime::VmCasperV1, - ); - let session_target = TransactionTarget::new_session( - Bytes::from(vec![1]), - TransactionRuntime::VmCasperV1, - ); - - let stored_body = TransactionV1Body::new( - RuntimeArgs::new(), - stored_target, - entry_point.clone(), - TransactionCategory::Large as u8, - TransactionScheduling::random(rng), - ); - let session_body = TransactionV1Body::new( - RuntimeArgs::new(), - session_target, - entry_point.clone(), - TransactionCategory::Large as u8, - TransactionScheduling::random(rng), - ); - - let expected_error = InvalidTransactionV1::EntryPointMustBeCustom { entry_point }; - - assert_eq!(stored_body.is_valid(&config), Err(expected_error.clone())); - assert_eq!(session_body.is_valid(&config), Err(expected_error)); - }; - - check(TransactionEntryPoint::Transfer); - check(TransactionEntryPoint::AddBid); - check(TransactionEntryPoint::WithdrawBid); - check(TransactionEntryPoint::Delegate); - check(TransactionEntryPoint::Undelegate); - check(TransactionEntryPoint::Redelegate); - } -} diff --git a/types/src/transaction/transaction_v1/transaction_v1_builder.rs b/types/src/transaction/transaction_v1/transaction_v1_builder.rs index aa36231934..889a6ad48d 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_builder.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_builder.rs @@ -2,25 +2,28 @@ mod error; use core::marker::PhantomData; -#[cfg(any(feature = "testing", test))] -use rand::Rng; - use super::{ - super::{ - InitiatorAddr, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntime, - TransactionScheduling, TransactionTarget, - }, - transaction_v1_body::arg_handling, - InitiatorAddrAndSecretKey, PricingMode, TransactionV1, TransactionV1Body, + super::{InitiatorAddr, TransactionRuntime, TransactionScheduling, TransactionTarget}, + arg_handling, InitiatorAddrAndSecretKey, PricingMode, TransactionV1, }; use crate::{ - bytesrepr::Bytes, transaction::TransactionCategory, AddressableEntityHash, CLValue, - CLValueError, EntityVersion, PackageHash, PublicKey, RuntimeArgs, SecretKey, TimeDiff, - Timestamp, TransferTarget, URef, U512, + bytesrepr::{Bytes, ToBytes}, + transaction::transaction_v1::*, + AddressableEntityHash, CLValue, CLValueError, EntityVersion, PackageHash, PublicKey, + RuntimeArgs, SecretKey, TimeDiff, Timestamp, TransactionEntryPoint, + TransactionInvocationTarget, TransferTarget, URef, U512, }; #[cfg(any(feature = "testing", test))] -use crate::{testing::TestRng, transaction::Approval, TransactionConfig, TransactionV1Hash}; +use crate::{ + testing::TestRng, transaction::Approval, TransactionConfig, TransactionV1Hash, AUCTION_LANE_ID, + INSTALL_UPGRADE_LANE_ID, MINT_LANE_ID, +}; +use alloc::collections::BTreeMap; pub use error::TransactionV1BuilderError; +#[cfg(any(feature = "testing", test))] +use rand::Rng; +#[cfg(any(all(feature = "std", feature = "testing"), test))] +use rand::RngCore; /// A builder for constructing a [`TransactionV1`]. /// @@ -36,10 +39,13 @@ pub use error::TransactionV1BuilderError; /// It can be signed later (multiple times if desired) to make it valid before sending to the /// network for execution. pub struct TransactionV1Builder<'a> { + args: RuntimeArgs, + target: TransactionTarget, + scheduling: TransactionScheduling, + entry_point: TransactionEntryPoint, chain_name: Option, timestamp: Timestamp, ttl: TimeDiff, - body: TransactionV1Body, pricing_mode: PricingMode, initiator_addr: Option, #[cfg(not(any(feature = "testing", test)))] @@ -57,18 +63,22 @@ impl<'a> TransactionV1Builder<'a> { /// The default pricing mode for v1 transactions, ie FIXED cost. pub const DEFAULT_PRICING_MODE: PricingMode = PricingMode::Fixed { gas_price_tolerance: 5, + additional_computation_factor: 0, }; /// The default runtime for transactions, i.e. Casper Version 1 Virtual Machine. pub const DEFAULT_RUNTIME: TransactionRuntime = TransactionRuntime::VmCasperV1; /// The default scheduling for transactions, i.e. `Standard`. pub const DEFAULT_SCHEDULING: TransactionScheduling = TransactionScheduling::Standard; - pub(super) fn new(body: TransactionV1Body) -> Self { + pub(super) fn new() -> Self { TransactionV1Builder { + args: RuntimeArgs::new(), + entry_point: TransactionEntryPoint::Transfer, + target: TransactionTarget::Native, + scheduling: TransactionScheduling::Standard, chain_name: None, timestamp: Timestamp::now(), ttl: Self::DEFAULT_TTL, - body, pricing_mode: Self::DEFAULT_PRICING_MODE, initiator_addr: None, secret_key: None, @@ -86,14 +96,12 @@ impl<'a> TransactionV1Builder<'a> { maybe_id: Option, ) -> Result { let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Transfer, - TransactionCategory::Mint as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = args; + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Transfer; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native add_bid transaction. @@ -111,14 +119,12 @@ impl<'a> TransactionV1Builder<'a> { minimum_delegation_amount, maximum_delegation_amount, )?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::AddBid, - TransactionCategory::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = args; + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::AddBid; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native withdraw_bid @@ -128,14 +134,12 @@ impl<'a> TransactionV1Builder<'a> { amount: A, ) -> Result { let args = arg_handling::new_withdraw_bid_args(public_key, amount)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::WithdrawBid, - TransactionCategory::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = args; + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::WithdrawBid; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native delegate transaction. @@ -145,14 +149,12 @@ impl<'a> TransactionV1Builder<'a> { amount: A, ) -> Result { let args = arg_handling::new_delegate_args(delegator, validator, amount)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Delegate, - TransactionCategory::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = args; + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Delegate; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native undelegate transaction. @@ -162,14 +164,12 @@ impl<'a> TransactionV1Builder<'a> { amount: A, ) -> Result { let args = arg_handling::new_undelegate_args(delegator, validator, amount)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Undelegate, - TransactionCategory::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = args; + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Undelegate; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } /// Returns a new `TransactionV1Builder` suitable for building a native redelegate transaction. @@ -180,14 +180,12 @@ impl<'a> TransactionV1Builder<'a> { new_validator: PublicKey, ) -> Result { let args = arg_handling::new_redelegate_args(delegator, validator, amount, new_validator)?; - let body = TransactionV1Body::new( - args, - TransactionTarget::Native, - TransactionEntryPoint::Redelegate, - TransactionCategory::Auction as u8, - Self::DEFAULT_SCHEDULING, - ); - Ok(TransactionV1Builder::new(body)) + let mut builder = TransactionV1Builder::new(); + builder.args = args; + builder.target = TransactionTarget::Native; + builder.entry_point = TransactionEntryPoint::Redelegate; + builder.scheduling = Self::DEFAULT_SCHEDULING; + Ok(builder) } fn new_targeting_stored>( @@ -198,14 +196,12 @@ impl<'a> TransactionV1Builder<'a> { id, runtime: Self::DEFAULT_RUNTIME, }; - let body = TransactionV1Body::new( - RuntimeArgs::new(), - target, - TransactionEntryPoint::Custom(entry_point.into()), - TransactionCategory::Large as u8, - Self::DEFAULT_SCHEDULING, - ); - TransactionV1Builder::new(body) + let mut builder = TransactionV1Builder::new(); + builder.args = RuntimeArgs::new(); + builder.target = target; + builder.entry_point = TransactionEntryPoint::Custom(entry_point.into()); + builder.scheduling = Self::DEFAULT_SCHEDULING; + builder } /// Returns a new `TransactionV1Builder` suitable for building a transaction targeting a stored @@ -252,19 +248,18 @@ impl<'a> TransactionV1Builder<'a> { /// Returns a new `TransactionV1Builder` suitable for building a transaction for running session /// logic, i.e. compiled Wasm. - pub fn new_session(category: TransactionCategory, module_bytes: Bytes) -> Self { + pub fn new_session(is_install_upgrade: bool, module_bytes: Bytes) -> Self { let target = TransactionTarget::Session { + is_install_upgrade, module_bytes, runtime: Self::DEFAULT_RUNTIME, }; - let body = TransactionV1Body::new( - RuntimeArgs::new(), - target, - TransactionEntryPoint::Call, - category as u8, - Self::DEFAULT_SCHEDULING, - ); - TransactionV1Builder::new(body) + let mut builder = TransactionV1Builder::new(); + builder.args = RuntimeArgs::new(); + builder.target = target; + builder.entry_point = TransactionEntryPoint::Call; + builder.scheduling = Self::DEFAULT_SCHEDULING; + builder } /// Returns a new `TransactionV1Builder` which will build a random, valid but possibly expired @@ -277,14 +272,18 @@ impl<'a> TransactionV1Builder<'a> { pub fn new_random(rng: &mut TestRng) -> Self { let secret_key = SecretKey::random(rng); let ttl_millis = rng.gen_range(60_000..TransactionConfig::default().max_ttl.millis()); - let body = TransactionV1Body::random(rng); + let fields = FieldsContainer::random(rng); TransactionV1Builder { chain_name: Some(rng.random_string(5..10)), timestamp: Timestamp::random(rng), ttl: TimeDiff::from_millis(ttl_millis), - body, + args: RuntimeArgs::random(rng), + target: fields.target, + entry_point: fields.entry_point, + scheduling: fields.scheduling, pricing_mode: PricingMode::Fixed { gas_price_tolerance: 5, + additional_computation_factor: 0, }, initiator_addr: Some(InitiatorAddr::PublicKey(PublicKey::from(&secret_key))), secret_key: Some(secret_key), @@ -302,7 +301,7 @@ impl<'a> TransactionV1Builder<'a> { #[cfg(any(feature = "testing", test))] pub fn new_random_with_category_and_timestamp_and_ttl( rng: &mut TestRng, - category: u8, + lane: u8, timestamp: Option, ttl: Option, ) -> Self { @@ -311,14 +310,23 @@ impl<'a> TransactionV1Builder<'a> { rng.gen_range(60_000..TransactionConfig::default().max_ttl.millis()), |ttl| ttl.millis(), ); - let body = TransactionV1Body::random_of_category(rng, category); + let FieldsContainer { + args, + target, + entry_point, + scheduling, + } = FieldsContainer::random_of_lane(rng, lane); TransactionV1Builder { chain_name: Some(rng.random_string(5..10)), timestamp: timestamp.unwrap_or(Timestamp::now()), ttl: TimeDiff::from_millis(ttl_millis), - body, + args, + target, + entry_point, + scheduling, pricing_mode: PricingMode::Fixed { gas_price_tolerance: 5, + additional_computation_factor: 0, }, initiator_addr: Some(InitiatorAddr::PublicKey(PublicKey::from(&secret_key))), secret_key: Some(secret_key), @@ -389,7 +397,7 @@ impl<'a> TransactionV1Builder<'a> { /// Appends the given runtime arg into the body's `args`. pub fn with_runtime_arg>(mut self, key: K, cl_value: CLValue) -> Self { - self.body.args.insert_cl_value(key, cl_value); + self.args.insert_cl_value(key, cl_value); self } @@ -398,7 +406,7 @@ impl<'a> TransactionV1Builder<'a> { /// NOTE: this overwrites any existing runtime args. To append to existing args, use /// [`TransactionV1Builder::with_runtime_arg`]. pub fn with_runtime_args(mut self, args: RuntimeArgs) -> Self { - self.body.args = args; + self.args = args; self } @@ -409,7 +417,7 @@ impl<'a> TransactionV1Builder<'a> { /// NOTE: This has no effect for native transactions, i.e. where the `body.target` is /// `TransactionTarget::Native`. pub fn with_runtime(mut self, runtime: TransactionRuntime) -> Self { - match &mut self.body.target { + match &mut self.target { TransactionTarget::Native => {} TransactionTarget::Stored { runtime: existing_runtime, @@ -431,7 +439,7 @@ impl<'a> TransactionV1Builder<'a> { /// /// If not provided, the scheduling will be set to [`Self::DEFAULT_SCHEDULING`]. pub fn with_scheduling(mut self, scheduling: TransactionScheduling) -> Self { - self.body.scheduling = scheduling; + self.scheduling = scheduling; self } @@ -478,12 +486,15 @@ impl<'a> TransactionV1Builder<'a> { .chain_name .ok_or(TransactionV1BuilderError::MissingChainName)?; + let map = FieldsContainer::new(self.args, self.target, self.entry_point, self.scheduling) + .to_map()?; + let transaction = TransactionV1::build( chain_name, self.timestamp, self.ttl, - self.body, self.pricing_mode, + map, initiator_addr_and_secret_key, ); @@ -507,13 +518,15 @@ impl<'a> TransactionV1Builder<'a> { let chain_name = self .chain_name .ok_or(TransactionV1BuilderError::MissingChainName)?; + let map = FieldsContainer::new(self.args, self.target, self.entry_point, self.scheduling) + .to_map()?; let mut transaction = TransactionV1::build( chain_name, self.timestamp, self.ttl, - self.body, self.pricing_mode, + map, initiator_addr_and_secret_key, ); @@ -522,3 +535,254 @@ impl<'a> TransactionV1Builder<'a> { Ok(transaction) } } + +pub(crate) struct FieldsContainer { + args: RuntimeArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + scheduling: TransactionScheduling, +} + +impl FieldsContainer { + pub(crate) fn new( + args: RuntimeArgs, + target: TransactionTarget, + entry_point: TransactionEntryPoint, + scheduling: TransactionScheduling, + ) -> Self { + FieldsContainer { + args, + target, + entry_point, + scheduling, + } + } + + pub(crate) fn to_map(&self) -> Result, TransactionV1BuilderError> { + let mut map: BTreeMap = BTreeMap::new(); + map.insert( + ARGS_MAP_KEY, + self.args.to_bytes().map(Into::into).map_err(|_| { + TransactionV1BuilderError::CouldNotSerializeField { + field_index: ARGS_MAP_KEY, + } + })?, + ); + map.insert( + TARGET_MAP_KEY, + self.target.to_bytes().map(Into::into).map_err(|_| { + TransactionV1BuilderError::CouldNotSerializeField { + field_index: TARGET_MAP_KEY, + } + })?, + ); + map.insert( + ENTRY_POINT_MAP_KEY, + self.entry_point.to_bytes().map(Into::into).map_err(|_| { + TransactionV1BuilderError::CouldNotSerializeField { + field_index: ENTRY_POINT_MAP_KEY, + } + })?, + ); + map.insert( + SCHEDULING_MAP_KEY, + self.scheduling.to_bytes().map(Into::into).map_err(|_| { + TransactionV1BuilderError::CouldNotSerializeField { + field_index: SCHEDULING_MAP_KEY, + } + })?, + ); + Ok(map) + } + + /// Returns a random `FieldsContainer`. + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub(crate) fn random(rng: &mut TestRng) -> Self { + match rng.gen_range(0..8) { + 0 => { + let amount = rng.gen_range( + TransactionConfig::default().native_transfer_minimum_motes..=u64::MAX, + ); + let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; + let target = TransferTarget::random(rng); + let maybe_id = rng.gen::().then(|| rng.gen()); + let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id) + .unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::Transfer, + TransactionScheduling::random(rng), + ) + } + 1 => { + let public_key = PublicKey::random(rng); + let delegation_rate = rng.gen(); + let amount = rng.gen::(); + let minimum_delegation_amount = rng.gen::() as u64; + let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + minimum_delegation_amount, + maximum_delegation_amount, + ) + .unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::AddBid, + TransactionScheduling::random(rng), + ) + } + 2 => { + let public_key = PublicKey::random(rng); + let amount = rng.gen::(); + let args = arg_handling::new_withdraw_bid_args(public_key, amount).unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::WithdrawBid, + TransactionScheduling::random(rng), + ) + } + 3 => { + let delegator = PublicKey::random(rng); + let validator = PublicKey::random(rng); + let amount = rng.gen::(); + let args = arg_handling::new_delegate_args(delegator, validator, amount).unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::Delegate, + TransactionScheduling::random(rng), + ) + } + 4 => { + let delegator = PublicKey::random(rng); + let validator = PublicKey::random(rng); + let amount = rng.gen::(); + let args = arg_handling::new_undelegate_args(delegator, validator, amount).unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::Undelegate, + TransactionScheduling::random(rng), + ) + } + 5 => { + let delegator = PublicKey::random(rng); + let validator = PublicKey::random(rng); + let amount = rng.gen::(); + let new_validator = PublicKey::random(rng); + let args = + arg_handling::new_redelegate_args(delegator, validator, amount, new_validator) + .unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::Redelegate, + TransactionScheduling::random(rng), + ) + } + 6 => Self::random_standard(rng), + 7 => { + let mut buffer = vec![0u8; rng.gen_range(1..100)]; + rng.fill_bytes(buffer.as_mut()); + let is_install_upgrade = rng.gen(); + let target = TransactionTarget::Session { + is_install_upgrade, + module_bytes: Bytes::from(buffer), + runtime: TransactionRuntime::VmCasperV1, + }; + FieldsContainer::new( + RuntimeArgs::random(rng), + target, + TransactionEntryPoint::Call, + TransactionScheduling::random(rng), + ) + } + _ => unreachable!(), + } + } + + /// Returns a random `FieldsContainer`. + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn random_of_lane(rng: &mut TestRng, lane_id: u8) -> Self { + match lane_id { + MINT_LANE_ID => Self::random_transfer(rng), + AUCTION_LANE_ID => Self::random_staking(rng), + INSTALL_UPGRADE_LANE_ID => Self::random_install_upgrade(rng), + _ => Self::random_standard(rng), + } + } + + #[cfg(any(all(feature = "std", feature = "testing"), test))] + fn random_transfer(rng: &mut TestRng) -> Self { + let amount = + rng.gen_range(TransactionConfig::default().native_transfer_minimum_motes..=u64::MAX); + let maybe_source = if rng.gen() { Some(rng.gen()) } else { None }; + let target = TransferTarget::random(rng); + let maybe_id = rng.gen::().then(|| rng.gen()); + let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id).unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::Transfer, + TransactionScheduling::random(rng), + ) + } + + #[cfg(any(all(feature = "std", feature = "testing"), test))] + fn random_install_upgrade(rng: &mut TestRng) -> Self { + let target = TransactionTarget::Session { + module_bytes: Bytes::from(rng.random_vec(0..100)), + runtime: TransactionRuntime::VmCasperV1, + is_install_upgrade: true, + }; + FieldsContainer::new( + RuntimeArgs::random(rng), + target, + TransactionEntryPoint::Call, + TransactionScheduling::random(rng), + ) + } + + #[cfg(any(all(feature = "std", feature = "testing"), test))] + fn random_staking(rng: &mut TestRng) -> Self { + let public_key = PublicKey::random(rng); + let delegation_rate = rng.gen(); + let amount = rng.gen::(); + let minimum_delegation_amount = rng.gen::() as u64; + let maximum_delegation_amount = minimum_delegation_amount + rng.gen::() as u64; + let args = arg_handling::new_add_bid_args( + public_key, + delegation_rate, + amount, + minimum_delegation_amount, + maximum_delegation_amount, + ) + .unwrap(); + FieldsContainer::new( + args, + TransactionTarget::Native, + TransactionEntryPoint::AddBid, + TransactionScheduling::random(rng), + ) + } + + #[cfg(any(all(feature = "std", feature = "testing"), test))] + fn random_standard(rng: &mut TestRng) -> Self { + let target = TransactionTarget::Stored { + id: TransactionInvocationTarget::random(rng), + runtime: TransactionRuntime::VmCasperV1, + }; + FieldsContainer::new( + RuntimeArgs::random(rng), + target, + TransactionEntryPoint::Custom(rng.random_string(1..11)), + TransactionScheduling::random(rng), + ) + } +} diff --git a/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs b/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs index f92121003f..8ce1196f62 100644 --- a/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs +++ b/types/src/transaction/transaction_v1/transaction_v1_builder/error.rs @@ -19,6 +19,12 @@ pub enum TransactionV1BuilderError { /// Call [`TransactionV1Builder::with_chain_name`] before calling /// [`TransactionV1Builder::build`]. MissingChainName, + /// Failed to build transaction due to an error when calling `to_bytes` on one of the payload + /// `field`. + CouldNotSerializeField { + /// The field index that failed to serialize. + field_index: u16, + }, } impl Display for TransactionV1BuilderError { @@ -36,6 +42,9 @@ impl Display for TransactionV1BuilderError { "transaction requires chain name - use `with_chain_name`" ) } + TransactionV1BuilderError::CouldNotSerializeField { field_index } => { + write!(formatter, "Cannot serialize field at index {}", field_index) + } } } } diff --git a/types/src/transaction/transaction_v1/transaction_v1_category.rs b/types/src/transaction/transaction_v1/transaction_v1_category.rs deleted file mode 100644 index e8b2d8b1bb..0000000000 --- a/types/src/transaction/transaction_v1/transaction_v1_category.rs +++ /dev/null @@ -1,76 +0,0 @@ -use core::{ - convert::TryFrom, - fmt::{self, Formatter}, -}; - -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; - -/// The category of a Transaction. -#[derive( - Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, Default, -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "Session kind of a V1 Transaction.") -)] -#[serde(deny_unknown_fields)] -#[repr(u8)] -pub enum TransactionCategory { - /// Native mint interaction (the default). - #[default] - Mint = 0, - /// Native auction interaction. - Auction = 1, - /// Install or Upgrade. - InstallUpgrade = 2, - /// A large Wasm based transaction. - Large = 3, - /// A medium Wasm based transaction. - Medium = 4, - /// A small Wasm based transaction. - Small = 5, -} - -impl fmt::Display for TransactionCategory { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - TransactionCategory::Mint => write!(f, "Mint"), - TransactionCategory::Auction => write!(f, "Auction"), - TransactionCategory::InstallUpgrade => write!(f, "InstallUpgrade"), - TransactionCategory::Large => write!(f, "Large"), - TransactionCategory::Medium => write!(f, "Medium"), - TransactionCategory::Small => write!(f, "Small"), - } - } -} - -#[derive(Debug)] -pub struct TransactionCategoryConversionError(u8); - -impl fmt::Display for TransactionCategoryConversionError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Failed to convert {} into a TransactionCategory", self.0) - } -} - -impl TryFrom for TransactionCategory { - type Error = TransactionCategoryConversionError; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Mint), - 1 => Ok(Self::Auction), - 2 => Ok(Self::InstallUpgrade), - 3 => Ok(Self::Large), - 4 => Ok(Self::Medium), - 5 => Ok(Self::Small), - _ => Err(TransactionCategoryConversionError(value)), - } - } -} diff --git a/types/src/transaction/transaction_v1/transaction_v1_header.rs b/types/src/transaction/transaction_v1/transaction_v1_header.rs deleted file mode 100644 index 9755f4f00b..0000000000 --- a/types/src/transaction/transaction_v1/transaction_v1_header.rs +++ /dev/null @@ -1,271 +0,0 @@ -use alloc::{string::String, vec::Vec}; -use core::fmt::{self, Display, Formatter}; - -#[cfg(feature = "datasize")] -use datasize::DataSize; -#[cfg(feature = "json-schema")] -use schemars::JsonSchema; -#[cfg(any(feature = "std", test))] -use serde::{Deserialize, Serialize}; -#[cfg(any(feature = "std", test))] -use tracing::debug; - -#[cfg(doc)] -use super::TransactionV1; -use super::{InitiatorAddr, PricingMode}; -use crate::{ - bytesrepr::{ - Error::{self, Formatting}, - FromBytes, ToBytes, - }, - transaction::serialization::{ - CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder, - }, - Digest, TimeDiff, Timestamp, -}; -#[cfg(any(feature = "std", test))] -use crate::{InvalidTransactionV1, TransactionConfig, TransactionV1Hash}; - -const CHAIN_NAME_INDEX: u16 = 0; -const TIMESTAMP_INDEX: u16 = 1; -const TTL_INDEX: u16 = 2; -const BODY_HASH_INDEX: u16 = 3; -const PRICING_MODE_INDEX: u16 = 4; -const INITIATOR_ADDR_INDEX: u16 = 5; - -/// The header portion of a [`TransactionV1`]. -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] -#[cfg_attr( - any(feature = "std", test), - derive(Serialize, Deserialize), - serde(deny_unknown_fields) -)] -#[cfg_attr(feature = "datasize", derive(DataSize))] -#[cfg_attr( - feature = "json-schema", - derive(JsonSchema), - schemars(description = "The header portion of a TransactionV1.") -)] -pub struct TransactionV1Header { - chain_name: String, - timestamp: Timestamp, - ttl: TimeDiff, - body_hash: Digest, - pricing_mode: PricingMode, - initiator_addr: InitiatorAddr, -} - -impl TransactionV1Header { - fn serialized_field_lengths(&self) -> Vec { - vec![ - self.chain_name.serialized_length(), - self.timestamp.serialized_length(), - self.ttl.serialized_length(), - self.body_hash.serialized_length(), - self.pricing_mode.serialized_length(), - self.initiator_addr.serialized_length(), - ] - } - - #[cfg(any(feature = "std", feature = "json-schema", test))] - pub(super) fn new( - chain_name: String, - timestamp: Timestamp, - ttl: TimeDiff, - body_hash: Digest, - pricing_mode: PricingMode, - initiator_addr: InitiatorAddr, - ) -> Self { - TransactionV1Header { - chain_name, - timestamp, - ttl, - body_hash, - pricing_mode, - initiator_addr, - } - } - - /// Computes the hash identifying this transaction. - #[cfg(any(feature = "std", test))] - pub fn compute_hash(&self) -> TransactionV1Hash { - TransactionV1Hash::new(Digest::hash( - self.to_bytes() - .unwrap_or_else(|error| panic!("should serialize header: {}", error)), - )) - } - - /// Returns the name of the chain the transaction should be executed on. - pub fn chain_name(&self) -> &str { - &self.chain_name - } - - /// Returns the creation timestamp of the transaction. - pub fn timestamp(&self) -> Timestamp { - self.timestamp - } - - /// Returns the duration after the creation timestamp for which the transaction will stay valid. - /// - /// After this duration has ended, the transaction will be considered expired. - pub fn ttl(&self) -> TimeDiff { - self.ttl - } - - /// Returns `true` if the transaction has expired. - pub fn expired(&self, current_instant: Timestamp) -> bool { - self.expires() < current_instant - } - - /// Returns the hash of the body of the transaction. - pub fn body_hash(&self) -> &Digest { - &self.body_hash - } - - /// Returns the pricing mode for the transaction. - pub fn pricing_mode(&self) -> &PricingMode { - &self.pricing_mode - } - - /// Returns the address of the initiator of the transaction. - pub fn initiator_addr(&self) -> &InitiatorAddr { - &self.initiator_addr - } - - /// Returns `Ok` if and only if the TTL is within limits, and the timestamp is not later than - /// `at + timestamp_leeway`. Does NOT check for expiry. - #[cfg(any(feature = "std", test))] - pub fn is_valid( - &self, - config: &TransactionConfig, - timestamp_leeway: TimeDiff, - at: Timestamp, - transaction_hash: &TransactionV1Hash, - ) -> Result<(), InvalidTransactionV1> { - if self.ttl() > config.max_ttl { - debug!( - %transaction_hash, - transaction_header = %self, - max_ttl = %config.max_ttl, - "transaction ttl excessive" - ); - return Err(InvalidTransactionV1::ExcessiveTimeToLive { - max_ttl: config.max_ttl, - got: self.ttl(), - }); - } - - if self.timestamp() > at + timestamp_leeway { - debug!( - %transaction_hash, transaction_header = %self, %at, - "transaction timestamp in the future" - ); - return Err(InvalidTransactionV1::TimestampInFuture { - validation_timestamp: at, - timestamp_leeway, - got: self.timestamp(), - }); - } - - Ok(()) - } - - /// Returns the timestamp of when the transaction expires, i.e. `self.timestamp + self.ttl`. - pub fn expires(&self) -> Timestamp { - self.timestamp.saturating_add(self.ttl) - } - - /// Returns the gas price tolerance for the given transaction. - pub fn gas_price_tolerance(&self) -> u8 { - match self.pricing_mode { - PricingMode::Classic { - gas_price_tolerance, - .. - } => gas_price_tolerance, - PricingMode::Fixed { - gas_price_tolerance, - .. - } => gas_price_tolerance, - PricingMode::Reserved { .. } => { - // TODO: Change this when reserve gets implemented. - 0u8 - } - } - } - - #[cfg(any(all(feature = "std", feature = "testing"), test))] - pub(super) fn invalidate(&mut self) { - self.chain_name.clear(); - } -} - -impl ToBytes for TransactionV1Header { - fn to_bytes(&self) -> Result, Error> { - CalltableSerializationEnvelopeBuilder::new(self.serialized_field_lengths())? - .add_field(CHAIN_NAME_INDEX, &self.chain_name)? - .add_field(TIMESTAMP_INDEX, &self.timestamp)? - .add_field(TTL_INDEX, &self.ttl)? - .add_field(BODY_HASH_INDEX, &self.body_hash)? - .add_field(PRICING_MODE_INDEX, &self.pricing_mode)? - .add_field(INITIATOR_ADDR_INDEX, &self.initiator_addr)? - .binary_payload_bytes() - } - fn serialized_length(&self) -> usize { - CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths()) - } -} - -impl FromBytes for TransactionV1Header { - fn from_bytes(bytes: &[u8]) -> Result<(TransactionV1Header, &[u8]), Error> { - let (binary_payload, remainder) = - crate::transaction::serialization::CalltableSerializationEnvelope::from_bytes( - 6u32, bytes, - )?; - let window = binary_payload.start_consuming()?; - let window = window.ok_or(Formatting)?; - window.verify_index(CHAIN_NAME_INDEX)?; - let (chain_name, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(TIMESTAMP_INDEX)?; - let (timestamp, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(TTL_INDEX)?; - let (ttl, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(BODY_HASH_INDEX)?; - let (body_hash, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(PRICING_MODE_INDEX)?; - let (pricing_mode, window) = window.deserialize_and_maybe_next::()?; - let window = window.ok_or(Formatting)?; - window.verify_index(INITIATOR_ADDR_INDEX)?; - let (initiator_addr, window) = window.deserialize_and_maybe_next::()?; - if window.is_some() { - return Err(Formatting); - } - let from_bytes = TransactionV1Header { - chain_name, - timestamp, - ttl, - body_hash, - pricing_mode, - initiator_addr, - }; - Ok((from_bytes, remainder)) - } -} - -impl Display for TransactionV1Header { - fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { - #[cfg(any(feature = "std", test))] - let hash = self.compute_hash(); - #[cfg(not(any(feature = "std", test)))] - let hash = "unknown"; - write!( - formatter, - "transaction-v1-header[{}, chain_name: {}, timestamp: {}, ttl: {}, pricing mode: {}, \ - initiator: {}]", - hash, self.chain_name, self.timestamp, self.ttl, self.pricing_mode, self.initiator_addr - ) - } -} diff --git a/types/src/transaction/transaction_v1/transaction_v1_payload.rs b/types/src/transaction/transaction_v1/transaction_v1_payload.rs new file mode 100644 index 0000000000..b370d777fe --- /dev/null +++ b/types/src/transaction/transaction_v1/transaction_v1_payload.rs @@ -0,0 +1,296 @@ +use core::fmt::{self, Debug, Display, Formatter}; + +use super::{errors_v1::FieldDeserializationError, PricingMode}; +use crate::{ + bytesrepr::{ + Bytes, + Error::{self, Formatting}, + FromBytes, ToBytes, + }, + transaction::serialization::{ + CalltableSerializationEnvelope, CalltableSerializationEnvelopeBuilder, + }, + DisplayIter, InitiatorAddr, TimeDiff, Timestamp, +}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; +#[cfg(feature = "datasize")] +use datasize::DataSize; +#[cfg(feature = "json-schema")] +use schemars::JsonSchema; +#[cfg(any(feature = "std", test))] +use serde::{Deserialize, Serialize}; + +const INITIATOR_ADDR_FIELD_INDEX: u16 = 0; +const TIMESTAMP_FIELD_INDEX: u16 = 1; +const TTL_FIELD_INDEX: u16 = 2; +const CHAIN_NAME_FIELD_INDEX: u16 = 3; +const PRICING_MODE_FIELD_INDEX: u16 = 4; +const FIELDS_FIELD_INDEX: u16 = 5; + +const ARGS_MAP_KEY: u16 = 0; +const TARGET_MAP_KEY: u16 = 1; +const ENTRY_POINT_MAP_KEY: u16 = 2; +const SCHEDULING_MAP_KEY: u16 = 3; +const EXPECTED_FIELD_KEYS: [u16; 4] = [ + ARGS_MAP_KEY, + TARGET_MAP_KEY, + ENTRY_POINT_MAP_KEY, + SCHEDULING_MAP_KEY, +]; + +#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[cfg_attr( + any(feature = "std", test), + derive(Serialize, Deserialize), + serde(deny_unknown_fields) +)] +#[cfg_attr(feature = "datasize", derive(DataSize))] +#[cfg_attr( + feature = "json-schema", + derive(JsonSchema), + schemars( + description = "A unit of work sent by a client to the network, which when executed can \ + cause global state to be altered." + ) +)] +pub struct TransactionV1Payload { + initiator_addr: InitiatorAddr, + timestamp: Timestamp, + ttl: TimeDiff, + chain_name: String, + pricing_mode: PricingMode, + fields: BTreeMap, +} + +impl TransactionV1Payload { + pub fn new( + chain_name: String, + timestamp: Timestamp, + ttl: TimeDiff, + pricing_mode: PricingMode, + initiator_addr: InitiatorAddr, + fields: BTreeMap, + ) -> TransactionV1Payload { + TransactionV1Payload { + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + fields, + } + } + + fn serialized_field_lengths(&self) -> Vec { + vec![ + self.initiator_addr.serialized_length(), + self.timestamp.serialized_length(), + self.ttl.serialized_length(), + self.chain_name.serialized_length(), + self.pricing_mode.serialized_length(), + self.fields.serialized_length(), + ] + } + + pub fn chain_name(&self) -> &str { + &self.chain_name + } + + pub fn timestamp(&self) -> Timestamp { + self.timestamp + } + + pub fn ttl(&self) -> TimeDiff { + self.ttl + } + + pub fn pricing_mode(&self) -> &PricingMode { + &self.pricing_mode + } + + pub fn initiator_addr(&self) -> &InitiatorAddr { + &self.initiator_addr + } + + pub fn fields(&self) -> &BTreeMap { + &self.fields + } + + /// Returns the timestamp of when the transaction expires, i.e. `self.timestamp + self.ttl`. + pub fn expires(&self) -> Timestamp { + self.timestamp.saturating_add(self.ttl) + } + + /// Returns `true` if the transaction has expired. + pub fn expired(&self, current_instant: Timestamp) -> bool { + self.expires() < current_instant + } + + pub fn deserialize_field( + &self, + index: u16, + ) -> Result { + let field = self + .fields + .get(&index) + .ok_or(FieldDeserializationError::IndexNotExists { index })?; + let (value, remainder) = T::from_bytes(field) + .map_err(|error| FieldDeserializationError::FromBytesError { index, error })?; + if !remainder.is_empty() { + return Err(FieldDeserializationError::LingeringBytesInField { index }); + } + Ok(value) + } + + pub fn number_of_fields(&self) -> usize { + self.fields.len() + } + + #[cfg(any(all(feature = "std", feature = "testing"), test))] + pub fn invalidate(&mut self) { + self.chain_name.clear(); + } +} + +impl ToBytes for TransactionV1Payload { + fn to_bytes(&self) -> Result, crate::bytesrepr::Error> { + let expected_payload_sizes = self.serialized_field_lengths(); + CalltableSerializationEnvelopeBuilder::new(expected_payload_sizes)? + .add_field(INITIATOR_ADDR_FIELD_INDEX, &self.initiator_addr)? + .add_field(TIMESTAMP_FIELD_INDEX, &self.timestamp)? + .add_field(TTL_FIELD_INDEX, &self.ttl)? + .add_field(CHAIN_NAME_FIELD_INDEX, &self.chain_name)? + .add_field(PRICING_MODE_FIELD_INDEX, &self.pricing_mode)? + .add_field(FIELDS_FIELD_INDEX, &self.fields)? + .binary_payload_bytes() + } + + fn serialized_length(&self) -> usize { + CalltableSerializationEnvelope::estimate_size(self.serialized_field_lengths()) + } +} + +impl FromBytes for TransactionV1Payload { + fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), Error> { + let (binary_payload, remainder) = CalltableSerializationEnvelope::from_bytes(6, bytes)?; + let window = binary_payload.start_consuming()?.ok_or(Formatting)?; + + window.verify_index(INITIATOR_ADDR_FIELD_INDEX)?; + let (initiator_addr, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(TIMESTAMP_FIELD_INDEX)?; + let (timestamp, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(TTL_FIELD_INDEX)?; + let (ttl, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(CHAIN_NAME_FIELD_INDEX)?; + let (chain_name, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(PRICING_MODE_FIELD_INDEX)?; + let (pricing_mode, window) = window.deserialize_and_maybe_next::()?; + let window = window.ok_or(Formatting)?; + window.verify_index(FIELDS_FIELD_INDEX)?; + let (fields, window) = window.deserialize_and_maybe_next::>()?; + if window.is_some() { + return Err(Formatting); + } + if fields.len() != EXPECTED_FIELD_KEYS.len() + || EXPECTED_FIELD_KEYS + .iter() + .any(|expected_key| !fields.contains_key(expected_key)) + { + return Err(Formatting); + } + let from_bytes = TransactionV1Payload { + chain_name, + timestamp, + ttl, + pricing_mode, + initiator_addr, + fields, + }; + + Ok((from_bytes, remainder)) + } +} + +impl Display for TransactionV1Payload { + fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { + write!( + formatter, + "transaction-v1-payload[{}, {}, {}, {}, {}, fields: {}]", + self.chain_name, + self.timestamp, + self.ttl, + self.pricing_mode, + self.initiator_addr, + DisplayIter::new(self.fields.keys()) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + testing::TestRng, RuntimeArgs, TransactionEntryPoint, TransactionScheduling, + TransactionTarget, + }; + use std::collections::BTreeMap; + + #[test] + fn should_fail_if_deserialized_payload_has_too_many_fields() { + let rng = &mut TestRng::new(); + let mut fields = BTreeMap::new(); + let args = RuntimeArgs::random(rng); + let target = TransactionTarget::random(rng); + let entry_point = TransactionEntryPoint::random(rng); + let scheduling = TransactionScheduling::random(rng); + fields.insert(ARGS_MAP_KEY, args.to_bytes().unwrap().into()); + fields.insert(TARGET_MAP_KEY, target.to_bytes().unwrap().into()); + fields.insert(ENTRY_POINT_MAP_KEY, entry_point.to_bytes().unwrap().into()); + fields.insert(SCHEDULING_MAP_KEY, scheduling.to_bytes().unwrap().into()); + fields.insert(4, 111_u64.to_bytes().unwrap().into()); + + let bytes = TransactionV1Payload::new( + "chain-name".to_string(), + Timestamp::now(), + TimeDiff::from_millis(1000), + PricingMode::random(rng), + InitiatorAddr::random(rng), + fields, + ) + .to_bytes() + .unwrap(); + let result = TransactionV1Payload::from_bytes(&bytes); + assert!(result.is_err()); + } + + #[test] + fn should_fail_if_deserialized_payload_has_unrecognized_fields() { + let rng = &mut TestRng::new(); + let mut fields = BTreeMap::new(); + let args = RuntimeArgs::random(rng); + let target = TransactionTarget::random(rng); + let entry_point = TransactionEntryPoint::random(rng); + let scheduling = TransactionScheduling::random(rng); + fields.insert(ARGS_MAP_KEY, args.to_bytes().unwrap().into()); + fields.insert(TARGET_MAP_KEY, target.to_bytes().unwrap().into()); + fields.insert(100, entry_point.to_bytes().unwrap().into()); + fields.insert(SCHEDULING_MAP_KEY, scheduling.to_bytes().unwrap().into()); + + let bytes = TransactionV1Payload::new( + "chain-name".to_string(), + Timestamp::now(), + TimeDiff::from_millis(1000), + PricingMode::random(rng), + InitiatorAddr::random(rng), + fields, + ) + .to_bytes() + .unwrap(); + let result = TransactionV1Payload::from_bytes(&bytes); + assert!(result.is_err()); + } +}