diff --git a/binary_port/src/speculative_execution_result.rs b/binary_port/src/speculative_execution_result.rs index cd06312f78..4fb68c90c6 100644 --- a/binary_port/src/speculative_execution_result.rs +++ b/binary_port/src/speculative_execution_result.rs @@ -66,6 +66,10 @@ impl SpeculativeExecutionResult { } } + pub fn error(&self) -> Option<&str> { + self.error.as_deref() + } + // This method is not intended to be used by third party crates. #[doc(hidden)] pub fn example() -> &'static Self { diff --git a/execution_engine/src/engine_state/mod.rs b/execution_engine/src/engine_state/mod.rs index fd8fa3e661..b0b201afcb 100644 --- a/execution_engine/src/engine_state/mod.rs +++ b/execution_engine/src/engine_state/mod.rs @@ -68,6 +68,7 @@ impl ExecutionEngineV1 { args, authorization_keys, phase, + is_speculative, } = wasm_v1_request; // NOTE to core engineers: it is intended for the EE to ONLY execute wasm targeting the // casper v1 virtual machine. it should not handle native behavior, database / global state @@ -94,6 +95,7 @@ impl ExecutionEngineV1 { account_hash, &authorization_keys, &self.config().administrative_accounts, + is_speculative, ) { Ok((runtime_footprint, entity_hash)) => (runtime_footprint, entity_hash), Err(tce) => { @@ -142,6 +144,7 @@ impl ExecutionEngineV1 { args: RuntimeArgs, authorization_keys: BTreeSet, phase: Phase, + is_speculative: bool, ) -> WasmV1Result where R: StateReader, @@ -161,6 +164,7 @@ impl ExecutionEngineV1 { account_hash, &authorization_keys, &self.config().administrative_accounts, + is_speculative, ) { Ok((addressable_entity, entity_hash)) => (addressable_entity, entity_hash), Err(tce) => { diff --git a/execution_engine/src/engine_state/wasm_v1.rs b/execution_engine/src/engine_state/wasm_v1.rs index 7d4fcdcbb6..19cb5c91fe 100644 --- a/execution_engine/src/engine_state/wasm_v1.rs +++ b/execution_engine/src/engine_state/wasm_v1.rs @@ -350,6 +350,8 @@ pub struct WasmV1Request { pub authorization_keys: BTreeSet, /// Execution phase. pub phase: Phase, + /// Is speculative execution + pub is_speculative: bool, } impl WasmV1Request { @@ -414,6 +416,7 @@ impl WasmV1Request { entry_point: executable_info.entry_point().clone(), args: executable_info.args().clone(), phase: executable_info.phase(), + is_speculative: false, } } @@ -437,6 +440,22 @@ impl WasmV1Request { )) } + /// Creates a new request from a transaction for use as the session code in speculative + /// execution flows. + pub fn new_session_speculative( + block_info: BlockInfo, + gas_limit: Gas, + session_input_data: &SessionInputData, + ) -> Result { + match Self::new_session(block_info, gas_limit, session_input_data) { + Ok(mut request) => { + request.is_speculative = true; + Ok(request) + } + Err(err) => Err(err), + } + } + /// Creates a new request from a transaction for use as custom payment. pub fn new_custom_payment( block_info: BlockInfo, 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 55fba7eded..c8e371c95d 100644 --- a/execution_engine_testing/test_support/src/execute_request_builder.rs +++ b/execution_engine_testing/test_support/src/execute_request_builder.rs @@ -52,6 +52,7 @@ pub struct ExecuteRequestBuilder { session_entry_point: String, session_args: RuntimeArgs, authorization_keys: BTreeSet, + is_speculative: bool, } const DEFAULT_GAS_LIMIT: u64 = 5_000_u64 * 10u64.pow(9); @@ -130,6 +131,7 @@ impl ExecuteRequestBuilder { session_entry_point: session.entry_point, session_args: session.args, authorization_keys, + is_speculative: false, } } @@ -206,6 +208,7 @@ impl ExecuteRequestBuilder { session_entry_point: session.entry_point, session_args: session.args, authorization_keys, + is_speculative: false, } } @@ -476,6 +479,7 @@ impl ExecuteRequestBuilder { session_entry_point, session_args, authorization_keys, + is_speculative, } = self; let block_info = BlockInfo::new( @@ -495,6 +499,7 @@ impl ExecuteRequestBuilder { args: payment_args, authorization_keys: authorization_keys.clone(), phase: Phase::Payment, + is_speculative, }); let session = WasmV1Request { @@ -507,6 +512,7 @@ impl ExecuteRequestBuilder { args: session_args, authorization_keys, phase: Phase::Session, + is_speculative, }; ExecuteRequest { diff --git a/executor/wasm/src/lib.rs b/executor/wasm/src/lib.rs index cc8e7cd3eb..f70fa0342b 100644 --- a/executor/wasm/src/lib.rs +++ b/executor/wasm/src/lib.rs @@ -696,6 +696,7 @@ impl ExecutorV2 { args, authorization_keys, phase, + false, ) }; diff --git a/node/src/components/binary_port.rs b/node/src/components/binary_port.rs index 8f6c0dfdb1..45f1eca4fa 100644 --- a/node/src/components/binary_port.rs +++ b/node/src/components/binary_port.rs @@ -209,7 +209,7 @@ where match req { Command::TryAcceptTransaction { transaction } => { metrics.binary_port_try_accept_transaction_count.inc(); - try_accept_transaction(effect_builder, transaction, false).await + try_accept_transaction(effect_builder, transaction).await } Command::TrySpeculativeExec { transaction } => { metrics.binary_port_try_speculative_exec_count.inc(); @@ -220,10 +220,6 @@ where ); return BinaryResponse::new_error(ErrorCode::FunctionDisabled); } - let response = try_accept_transaction(effect_builder, transaction.clone(), true).await; - if !response.is_success() { - return response; - } try_speculative_execution(effect_builder, transaction).await } Command::Get(get_req) => { @@ -1343,13 +1339,12 @@ where async fn try_accept_transaction( effect_builder: EffectBuilder, transaction: Transaction, - is_speculative: bool, ) -> BinaryResponse where REv: From, { effect_builder - .try_accept_transaction(transaction, is_speculative) + .try_accept_transaction(transaction) .await .map_or_else( |err| BinaryResponse::new_error(err.into()), @@ -1384,9 +1379,6 @@ where SpeculativeExecutionResult::WasmV1(spec_exec_result) => { BinaryResponse::from_value(spec_exec_result) } - SpeculativeExecutionResult::ReceivedV1Transaction => { - BinaryResponse::new_error(ErrorCode::ReceivedV1Transaction) - } } } diff --git a/node/src/components/contract_runtime/operations.rs b/node/src/components/contract_runtime/operations.rs index 4cf456c6d0..4dfb6a2d2e 100644 --- a/node/src/components/contract_runtime/operations.rs +++ b/node/src/components/contract_runtime/operations.rs @@ -35,7 +35,7 @@ use casper_types::{ system::handle_payment::ARG_AMOUNT, BlockHash, BlockHeader, BlockTime, BlockV2, CLValue, Chainspec, ChecksumRegistry, Digest, EntityAddr, EraEndV2, EraId, FeeHandling, Gas, InvalidTransaction, InvalidTransactionV1, Key, - ProtocolVersion, PublicKey, RefundHandling, Transaction, TransactionEntryPoint, + ProtocolVersion, PublicKey, RefundHandling, TimeDiff, Transaction, TransactionEntryPoint, AUCTION_LANE_ID, MINT_LANE_ID, U512, }; @@ -1353,6 +1353,11 @@ where return SpeculativeExecutionResult::invalid_transaction(error); } let transaction = maybe_transaction.unwrap(); + if let Err(error) = + transaction.is_config_compliant(chainspec, TimeDiff::ZERO, transaction.timestamp()) + { + return SpeculativeExecutionResult::invalid_transaction(error); + } let state_root_hash = block_header.state_root_hash(); let parent_block_hash = block_header.block_hash(); let block_height = block_header.height(); @@ -1406,20 +1411,45 @@ where execution_engine_v1.config().protocol_version(), ); let session_input_data = transaction.to_session_input_data(); - let wasm_v1_result = - match WasmV1Request::new_session(block_info, gas_limit, &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), - }; + let wasm_v1_result = match WasmV1Request::new_session_speculative( + block_info, + gas_limit, + &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), + }; SpeculativeExecutionResult::WasmV1(Box::new(utils::spec_exec_from_wasm_v1_result( wasm_v1_result, block_header.block_hash(), ))) } + } else if transaction.is_wasm() { + let block_info = BlockInfo::new( + *state_root_hash, + block_time.into(), + parent_block_hash, + block_height, + execution_engine_v1.config().protocol_version(), + ); + let session_input_data = transaction.to_session_input_data(); + let wasm_v1_result = match WasmV1Request::new_session_speculative( + block_info, + gas_limit, + &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), + }; + SpeculativeExecutionResult::WasmV1(Box::new(utils::spec_exec_from_wasm_v1_result( + wasm_v1_result, + block_header.block_hash(), + ))) } else { - SpeculativeExecutionResult::ReceivedV1Transaction + // TODO: placeholder error + SpeculativeExecutionResult::InvalidTransaction(InvalidTransaction::V1( + InvalidTransactionV1::CannotCalculateFieldsHash, + )) } } diff --git a/node/src/components/contract_runtime/types.rs b/node/src/components/contract_runtime/types.rs index 5551ed19c7..5af037dc8f 100644 --- a/node/src/components/contract_runtime/types.rs +++ b/node/src/components/contract_runtime/types.rs @@ -566,7 +566,6 @@ pub struct BlockAndExecutionArtifacts { pub enum SpeculativeExecutionResult { InvalidTransaction(InvalidTransaction), WasmV1(Box), - ReceivedV1Transaction, } impl SpeculativeExecutionResult { diff --git a/node/src/components/fetcher.rs b/node/src/components/fetcher.rs index 4f84f78273..26c6921e9f 100644 --- a/node/src/components/fetcher.rs +++ b/node/src/components/fetcher.rs @@ -134,7 +134,7 @@ where Source::PeerGossiped(peer) | Source::Peer(peer) => { self.got_from_peer(effect_builder, peer, item) } - Source::Client | Source::SpeculativeExec | Source::Ourself => Effects::new(), + Source::Client | Source::Ourself => Effects::new(), }, Event::GotInvalidRemotely { .. } => Effects::new(), Event::AbsentRemotely { id, peer } => { diff --git a/node/src/components/fetcher/tests.rs b/node/src/components/fetcher/tests.rs index d45c1c2e19..4807d80a9a 100644 --- a/node/src/components/fetcher/tests.rs +++ b/node/src/components/fetcher/tests.rs @@ -223,10 +223,8 @@ impl ReactorTrait for Reactor { } Event::AcceptTransactionRequest(AcceptTransactionRequest { transaction, - is_speculative, responder, }) => { - assert!(!is_speculative); let event = transaction_acceptor::Event::Accept { transaction, source: Source::Client, @@ -383,9 +381,7 @@ impl NetworkedReactor for Reactor { fn announce_transaction_received( txn: Transaction, ) -> impl FnOnce(EffectBuilder) -> Effects { - |effect_builder: EffectBuilder| { - effect_builder.try_accept_transaction(txn, false).ignore() - } + |effect_builder: EffectBuilder| effect_builder.try_accept_transaction(txn).ignore() } type FetchedTransactionResult = Arc>)>>; diff --git a/node/src/components/gossiper/tests.rs b/node/src/components/gossiper/tests.rs index 78c657bbe6..55cfbffa19 100644 --- a/node/src/components/gossiper/tests.rs +++ b/node/src/components/gossiper/tests.rs @@ -267,10 +267,8 @@ impl reactor::Reactor for Reactor { ), Event::AcceptTransactionRequest(AcceptTransactionRequest { transaction, - is_speculative, responder, }) => { - assert!(!is_speculative); let event = transaction_acceptor::Event::Accept { transaction, source: Source::Client, @@ -336,9 +334,7 @@ fn announce_transaction_received( transaction: &Transaction, ) -> impl FnOnce(EffectBuilder) -> Effects { let txn = transaction.clone(); - |effect_builder: EffectBuilder| { - effect_builder.try_accept_transaction(txn, false).ignore() - } + |effect_builder: EffectBuilder| effect_builder.try_accept_transaction(txn).ignore() } async fn run_gossip(rng: &mut TestRng, network_size: usize, txn_count: usize) { diff --git a/node/src/components/transaction_acceptor.rs b/node/src/components/transaction_acceptor.rs index 37caf3d38d..1fc6ad6010 100644 --- a/node/src/components/transaction_acceptor.rs +++ b/node/src/components/transaction_acceptor.rs @@ -856,16 +856,6 @@ impl TransactionAcceptor { return self.reject_transaction(effect_builder, *event_metadata, error); } - // If this has been received from the speculative exec server, we just want to call the - // responder and finish. Otherwise store the transaction and announce it if required. - if let Source::SpeculativeExec = event_metadata.source { - if let Some(responder) = event_metadata.maybe_responder { - return responder.respond(Ok(())).ignore(); - } - error!("speculative exec source should always have a responder"); - return Effects::new(); - } - effect_builder .put_transaction_to_storage(event_metadata.transaction.clone()) .event(move |is_new| Event::PutToStorageResult { diff --git a/node/src/effect.rs b/node/src/effect.rs index 08e3c73273..6004d0efaa 100644 --- a/node/src/effect.rs +++ b/node/src/effect.rs @@ -857,7 +857,6 @@ impl EffectBuilder { pub(crate) async fn try_accept_transaction( self, transaction: Transaction, - is_speculative: bool, ) -> Result<(), transaction_acceptor::Error> where REv: From, @@ -865,7 +864,6 @@ impl EffectBuilder { self.make_request( |responder| AcceptTransactionRequest { transaction, - is_speculative, responder, }, QueueKind::Api, diff --git a/node/src/effect/requests.rs b/node/src/effect/requests.rs index a0f8c52869..cf497e00f6 100644 --- a/node/src/effect/requests.rs +++ b/node/src/effect/requests.rs @@ -1229,17 +1229,11 @@ impl Display for SetNodeStopRequest { #[derive(DataSize, Debug, Serialize)] pub(crate) struct AcceptTransactionRequest { pub(crate) transaction: Transaction, - pub(crate) is_speculative: bool, pub(crate) responder: Responder>, } impl Display for AcceptTransactionRequest { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!( - f, - "accept transaction {} is_speculative: {}", - self.transaction.hash(), - self.is_speculative - ) + write!(f, "accept transaction {}", self.transaction.hash(),) } } diff --git a/node/src/reactor/main_reactor.rs b/node/src/reactor/main_reactor.rs index 4e91d452d7..9375fde4dd 100644 --- a/node/src/reactor/main_reactor.rs +++ b/node/src/reactor/main_reactor.rs @@ -733,14 +733,9 @@ impl reactor::Reactor for MainReactor { ), MainEvent::AcceptTransactionRequest(AcceptTransactionRequest { transaction, - is_speculative, responder, }) => { - let source = if is_speculative { - Source::SpeculativeExec - } else { - Source::Client - }; + let source = Source::Client; let event = transaction_acceptor::Event::Accept { transaction, source, @@ -797,12 +792,6 @@ impl reactor::Reactor for MainReactor { ), )); } - Source::SpeculativeExec => { - error!( - %transaction, - "transaction acceptor should not announce speculative exec transactions" - ); - } } effects diff --git a/node/src/reactor/main_reactor/tests/binary_port.rs b/node/src/reactor/main_reactor/tests/binary_port.rs index 92d18f917e..33bc51b5d8 100644 --- a/node/src/reactor/main_reactor/tests/binary_port.rs +++ b/node/src/reactor/main_reactor/tests/binary_port.rs @@ -14,7 +14,7 @@ use casper_binary_port::{ GetTrieFullResult, GlobalStateEntityQualifier, GlobalStateQueryResult, GlobalStateRequest, InformationRequest, InformationRequestTag, KeyPrefix, LastProgress, NetworkName, NodeStatus, PackageIdentifier, PurseIdentifier, ReactorStateName, RecordId, ResponseType, RewardResponse, - Uptime, ValueWithProof, + SpeculativeExecutionResult, Uptime, ValueWithProof, }; use casper_storage::global_state::state::CommitProvider; use casper_types::{ @@ -25,13 +25,13 @@ use casper_types::{ execution::{Effects, TransformKindV2, TransformV2}, system::auction::DelegatorKind, testing::TestRng, - Account, AddressableEntity, AvailableBlockRange, Block, BlockHash, BlockHeader, - BlockIdentifier, BlockSynchronizerStatus, BlockWithSignatures, ByteCode, ByteCodeAddr, - ByteCodeHash, ByteCodeKind, CLValue, CLValueDictionary, ChainspecRawBytes, Contract, - ContractRuntimeTag, ContractWasm, ContractWasmHash, DictionaryAddr, Digest, EntityAddr, - EntityKind, EntityVersions, GlobalStateIdentifier, Key, KeyTag, NextUpgrade, Package, - PackageAddr, PackageHash, Peers, ProtocolVersion, PublicKey, Rewards, SecretKey, StoredValue, - Transaction, Transfer, URef, U512, + Account, AddressableEntity, AddressableEntityHash, AvailableBlockRange, Block, BlockHash, + BlockHeader, BlockIdentifier, BlockSynchronizerStatus, BlockWithSignatures, ByteCode, + ByteCodeAddr, ByteCodeHash, ByteCodeKind, CLValue, CLValueDictionary, ChainspecRawBytes, + Contract, ContractRuntimeTag, ContractWasm, ContractWasmHash, DictionaryAddr, Digest, + EntityAddr, EntityKind, EntityVersions, GlobalStateIdentifier, Key, KeyTag, NextUpgrade, + Package, PackageAddr, PackageHash, Peers, ProtocolVersion, PublicKey, Rewards, SecretKey, + StoredValue, Transaction, Transfer, URef, U512, }; use futures::{SinkExt, StreamExt}; use rand::Rng; @@ -463,6 +463,13 @@ async fn binary_port_component_handles_all_requests() { TEST_DICT_ITEM_KEY.to_owned(), ), try_spec_exec_invalid(&mut rng), + spec_exec_v1_session_signed(&secret_signing_key), + spec_exec_v1_session_garbage_bytes(&secret_signing_key), + spec_exec_v1_session_install_upgrade(&secret_signing_key), + spec_exec_v1_native_rejected(&secret_signing_key), + spec_exec_v1_stored_not_found(&secret_signing_key, &mut rng), + spec_exec_v1_wrong_chain_name(&mut rng), + spec_exec_v1_unsigned_executed(&mut rng), try_accept_transaction_invalid(&mut rng), try_accept_transaction(&secret_signing_key), get_balance(state_root_hash, effects.pre_migration_account_hash), @@ -1356,6 +1363,197 @@ fn try_spec_exec_invalid(rng: &mut TestRng) -> TestCase { } } +fn assert_spec_exec_result(response: &BinaryResponse, check: F) -> bool +where + F: FnOnce(SpeculativeExecutionResult) -> bool, +{ + assert_response::( + response, + Some(ResponseType::SpeculativeExecutionResult), + check, + ) +} + +// Minimal valid WASM module `(module)` — parses but has no "call" entry point. +const MINIMAL_WASM: &[u8] = &[0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]; +// Bytes that are not valid WASM at all. +const GARBAGE_BYTES: &[u8] = &[0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe]; + +/// V1 Session wasm, signed, valid wasm bytes — routes to the EE; the wasm has +/// no "call" entry point so execution fails, but a SpeculativeExecutionResult +/// is returned rather than an error code. +fn spec_exec_v1_session_signed(key: &SecretKey) -> TestCase { + let transaction = Transaction::V1( + TransactionV1Builder::new_session( + false, + Bytes::from(MINIMAL_WASM), + casper_types::TransactionRuntimeParams::VmCasperV1, + ) + .with_chain_name("casper-example") + .with_secret_key(key) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_session_signed", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + assert_spec_exec_result(response, |result| result.error().is_some()) + }), + } +} + +/// V1 Session wasm, signed, garbage bytes — EE rejects the wasm but still +/// returns a SpeculativeExecutionResult. +fn spec_exec_v1_session_garbage_bytes(key: &SecretKey) -> TestCase { + let transaction = Transaction::V1( + TransactionV1Builder::new_session( + false, + Bytes::from(GARBAGE_BYTES), + casper_types::TransactionRuntimeParams::VmCasperV1, + ) + .with_chain_name("casper-example") + .with_secret_key(key) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_session_garbage_bytes", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + assert_spec_exec_result(response, |result| result.error().is_some()) + }), + } +} + +/// V1 Session wasm with is_install_upgrade=true — same routing as a regular +/// session; must return SpeculativeExecutionResult. +fn spec_exec_v1_session_install_upgrade(key: &SecretKey) -> TestCase { + let transaction = Transaction::V1( + TransactionV1Builder::new_session( + true, + Bytes::from(MINIMAL_WASM), + casper_types::TransactionRuntimeParams::VmCasperV1, + ) + .with_chain_name("casper-example") + .with_secret_key(key) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_session_install_upgrade", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + assert_spec_exec_result(response, |result| result.error().is_some()) + }), + } +} + +/// V1 Native transfer — is_wasm() == false, so speculatively_execute returns +/// InvalidTransaction(CannotCalculateFieldsHash); the response must carry an +/// error code and no SpeculativeExecutionResult payload. +fn spec_exec_v1_native_rejected(key: &SecretKey) -> TestCase { + let transaction = Transaction::V1( + TransactionV1Builder::new_transfer( + U512::from(1_000_000_000u64), + None, + casper_types::TransferTarget::AccountHash(AccountHash([1u8; 32])), + None::, + ) + .unwrap() + .with_chain_name("casper-example") + .with_secret_key(key) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_native_rejected", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + !validate_metadata(response, Some(ResponseType::SpeculativeExecutionResult)) + && response.error_code() != ErrorCode::NoError as u16 + }), + } +} + +/// V1 Stored target pointing at a non-existent entity — is_wasm() == true, so +/// the request reaches the EE; the EE fails when the entity is not found, but +/// the response is still a SpeculativeExecutionResult. +fn spec_exec_v1_stored_not_found(key: &SecretKey, rng: &mut TestRng) -> TestCase { + let missing_hash: [u8; 32] = rng.gen(); + let transaction = Transaction::V1( + TransactionV1Builder::new_targeting_invocable_entity( + AddressableEntityHash::new(missing_hash), + "call", + casper_types::TransactionRuntimeParams::VmCasperV1, + ) + .with_chain_name("casper-example") + .with_secret_key(key) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_stored_not_found", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + assert_spec_exec_result(response, |result| result.error().is_some()) + }), + } +} + +/// V1 Session wasm with the wrong chain name, signed with a fresh key. +/// The pre-flight acceptor call was removed, so chain name is no longer +/// validated before execution. The transaction reaches the EE (where it fails +/// because the account is absent from state) and returns SpeculativeExecutionResult. +fn spec_exec_v1_wrong_chain_name(rng: &mut TestRng) -> TestCase { + let key = SecretKey::random(rng); + let transaction = Transaction::V1( + TransactionV1Builder::new_session( + false, + Bytes::from(MINIMAL_WASM), + casper_types::TransactionRuntimeParams::VmCasperV1, + ) + .with_chain_name("wrong-chain") + .with_secret_key(&key) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_wrong_chain_name", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + !validate_metadata(response, Some(ResponseType::SpeculativeExecutionResult)) + && response.error_code() != ErrorCode::NoError as u16 + }), + } +} + +/// V1 Session wasm, unsigned — speculative execution skips approval verification, +/// so the transaction reaches the EE and returns a SpeculativeExecutionResult +/// (with an execution error, same as the signed variant). +fn spec_exec_v1_unsigned_executed(rng: &mut TestRng) -> TestCase { + let key = SecretKey::random(rng); + let initiator = casper_types::InitiatorAddr::PublicKey(PublicKey::from(&key)); + let transaction = Transaction::V1( + TransactionV1Builder::new_session( + false, + Bytes::from(MINIMAL_WASM), + casper_types::TransactionRuntimeParams::VmCasperV1, + ) + .with_chain_name("casper-example") + .with_initiator_addr(initiator) + .build() + .unwrap(), + ); + TestCase { + name: "spec_exec_v1_unsigned_executed", + request: Command::TrySpeculativeExec { transaction }, + asserter: Box::new(|response| { + assert_spec_exec_result(response, |result| result.error().is_some()) + }), + } +} + #[tokio::test] async fn binary_port_component_rejects_requests_with_invalid_header_version() { testing::init_logging(); diff --git a/node/src/types/transaction/meta_transaction.rs b/node/src/types/transaction/meta_transaction.rs index 75e5f7ed6d..0cc81cbaa6 100644 --- a/node/src/types/transaction/meta_transaction.rs +++ b/node/src/types/transaction/meta_transaction.rs @@ -94,6 +94,13 @@ impl MetaTransaction { } } + pub(crate) fn is_wasm(&self) -> bool { + match self { + MetaTransaction::Deploy(meta_deploy) => !meta_deploy.deploy().is_transfer(), + MetaTransaction::V1(v1_txn) => *v1_txn.target() != TransactionTarget::Native, + } + } + /// Should this transaction use standard payment processing? pub(crate) fn is_standard_payment(&self) -> bool { match self { diff --git a/node/src/utils.rs b/node/src/utils.rs index 9a6d060f3a..f33eb41a8d 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -260,8 +260,6 @@ pub(crate) enum Source { Peer(NodeId), /// A client. Client, - /// A client via the speculative_exec server. - SpeculativeExec, /// This node. Ourself, } @@ -270,7 +268,7 @@ impl Source { #[allow(clippy::wrong_self_convention)] pub(crate) fn is_client(&self) -> bool { match self { - Source::Client | Source::SpeculativeExec => true, + Source::Client => true, Source::PeerGossiped(_) | Source::Peer(_) | Source::Ourself => false, } } @@ -279,7 +277,7 @@ impl Source { pub(crate) fn node_id(&self) -> Option { match self { Source::Peer(node_id) | Source::PeerGossiped(node_id) => Some(*node_id), - Source::Client | Source::SpeculativeExec | Source::Ourself => None, + Source::Client | Source::Ourself => None, } } } @@ -291,7 +289,6 @@ impl Display for Source { Display::fmt(node_id, formatter) } Source::Client => write!(formatter, "client"), - Source::SpeculativeExec => write!(formatter, "client (speculative exec)"), Source::Ourself => write!(formatter, "ourself"), } } diff --git a/storage/src/global_state/state/mod.rs b/storage/src/global_state/state/mod.rs index 004ed73102..a4fb2870fe 100644 --- a/storage/src/global_state/state/mod.rs +++ b/storage/src/global_state/state/mod.rs @@ -1178,6 +1178,7 @@ pub trait StateProvider: Send + Sync + Sized { source_account_hash, &authorization_keys, &BTreeSet::default(), + false, ) { Ok(ret) => ret, Err(tce) => { @@ -2179,6 +2180,7 @@ pub trait StateProvider: Send + Sync + Sized { source_account_hash, authorization_keys, &administrative_accounts, + false, ) { Ok(ret) => ret, Err(tce) => { @@ -2307,6 +2309,7 @@ pub trait StateProvider: Send + Sync + Sized { source_account_hash, authorization_keys, &BTreeSet::default(), + false, ) { Ok(ret) => ret, Err(tce) => { diff --git a/storage/src/tracking_copy/ext_entity.rs b/storage/src/tracking_copy/ext_entity.rs index 3191d46c91..b46ffed634 100644 --- a/storage/src/tracking_copy/ext_entity.rs +++ b/storage/src/tracking_copy/ext_entity.rs @@ -59,12 +59,14 @@ pub trait TrackingCopyEntityExt { ) -> Result<(EntityAddr, RuntimeFootprint), Self::Error>; /// Get runtime information for an account if authorized, else error. + /// When `is_speculative` is true the authorization key checks are skipped fn authorized_runtime_footprint_by_account( &mut self, protocol_version: ProtocolVersion, account_hash: AccountHash, authorization_keys: &BTreeSet, administrative_accounts: &BTreeSet, + is_speculative: bool, ) -> Result<(RuntimeFootprint, EntityAddr), Self::Error>; /// Returns runtime information and access rights if authorized, else error. @@ -74,6 +76,7 @@ pub trait TrackingCopyEntityExt { initiating_address: AccountHash, authorization_keys: &BTreeSet, administrative_accounts: &BTreeSet, + is_speculative: bool, ) -> Result<(EntityAddr, RuntimeFootprint, ContextAccessRights), TrackingCopyError>; /// Returns runtime information for systemic functionality. @@ -387,10 +390,15 @@ where account_hash: AccountHash, authorization_keys: &BTreeSet, administrative_accounts: &BTreeSet, + is_speculative: bool, ) -> Result<(RuntimeFootprint, EntityAddr), Self::Error> { let (entity_addr, footprint) = self.runtime_footprint_by_account_hash(protocol_version, account_hash)?; + if is_speculative { + return Ok((footprint, entity_addr)); + } + if !administrative_accounts.is_empty() && administrative_accounts .intersection(authorization_keys) @@ -420,6 +428,7 @@ where initiating_address: AccountHash, authorization_keys: &BTreeSet, administrative_accounts: &BTreeSet, + is_speculative: bool, ) -> Result<(EntityAddr, RuntimeFootprint, ContextAccessRights), TrackingCopyError> { if initiating_address == PublicKey::System.to_account_hash() { return self.system_entity_runtime_footprint(protocol_version); @@ -430,6 +439,7 @@ where initiating_address, authorization_keys, administrative_accounts, + is_speculative, )?; let access_rights = footprint.extract_access_rights(entity_addr.value()); Ok((entity_addr, footprint, access_rights))