diff --git a/core-rust/Cargo.lock b/core-rust/Cargo.lock index 63d19eb980..d43d5bdb02 100644 --- a/core-rust/Cargo.lock +++ b/core-rust/Cargo.lock @@ -1764,7 +1764,7 @@ dependencies = [ [[package]] name = "radix-blueprint-schema-init" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "bitflags 1.3.2", "radix-common", @@ -1775,7 +1775,7 @@ dependencies = [ [[package]] name = "radix-common" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "bech32", "blake2", @@ -1801,7 +1801,7 @@ dependencies = [ [[package]] name = "radix-common-derive" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "paste", "proc-macro2", @@ -1813,7 +1813,7 @@ dependencies = [ [[package]] name = "radix-engine" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "bitflags 1.3.2", "colored", @@ -1844,7 +1844,7 @@ dependencies = [ [[package]] name = "radix-engine-interface" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "bitflags 1.3.2", "const-sha1", @@ -1865,7 +1865,7 @@ dependencies = [ [[package]] name = "radix-engine-profiling" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "fixedstr", ] @@ -1873,7 +1873,7 @@ dependencies = [ [[package]] name = "radix-engine-profiling-derive" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "proc-macro2", "quote", @@ -1884,7 +1884,7 @@ dependencies = [ [[package]] name = "radix-engine-toolkit-common" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "radix-common", "radix-engine", @@ -1898,7 +1898,7 @@ dependencies = [ [[package]] name = "radix-native-sdk" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "radix-common", "radix-engine-interface", @@ -1909,7 +1909,7 @@ dependencies = [ [[package]] name = "radix-rust" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "indexmap 2.2.6", "serde", @@ -1918,7 +1918,7 @@ dependencies = [ [[package]] name = "radix-sbor-derive" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "proc-macro2", "quote", @@ -1929,7 +1929,7 @@ dependencies = [ [[package]] name = "radix-substate-store-impls" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "hex", "itertools", @@ -1943,7 +1943,7 @@ dependencies = [ [[package]] name = "radix-substate-store-interface" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "hex", "itertools", @@ -1955,7 +1955,7 @@ dependencies = [ [[package]] name = "radix-substate-store-queries" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "hex", "itertools", @@ -1972,7 +1972,7 @@ dependencies = [ [[package]] name = "radix-transaction-scenarios" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "hex", "itertools", @@ -1992,7 +1992,7 @@ dependencies = [ [[package]] name = "radix-transactions" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "annotate-snippets", "bech32", @@ -2165,7 +2165,7 @@ dependencies = [ [[package]] name = "sbor" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "const-sha1", "hex", @@ -2179,7 +2179,7 @@ dependencies = [ [[package]] name = "sbor-derive" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "proc-macro2", "sbor-derive-common", @@ -2189,7 +2189,7 @@ dependencies = [ [[package]] name = "sbor-derive-common" version = "1.3.0-dev" -source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-39a21e3a#39a21e3aa0794cebfef4db8fce90d4cfbb0d3976" +source = "git+https://github.com/radixdlt/radixdlt-scrypto?tag=cuttlefish-6130d5b2#6130d5b2afab3bc8a1984c639ebfb5b076987301" dependencies = [ "const-sha1", "indexmap 2.2.6", diff --git a/core-rust/Cargo.toml b/core-rust/Cargo.toml index 791c028722..f251eb491c 100644 --- a/core-rust/Cargo.toml +++ b/core-rust/Cargo.toml @@ -23,18 +23,18 @@ resolver = "2" # $ git push origin "release_name-BLAH" # * Then use tag="release_name-BLAH" in the below dependencies. # -sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a", features = ["serde"] } -radix-transactions = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-transaction-scenarios = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a", features = ["serde"] } -radix-engine-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-substate-store-impls = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-substate-store-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-substate-store-queries = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } -radix-rust = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a", features = ["serde"] } -radix-blueprint-schema-init = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a", features = ["serde"] } -radix-engine-toolkit-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-39a21e3a" } +sbor = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2", features = ["serde"] } +radix-transactions = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-transaction-scenarios = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2", features = ["serde"] } +radix-engine-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-engine = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-substate-store-impls = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-substate-store-interface = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-substate-store-queries = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } +radix-rust = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2", features = ["serde"] } +radix-blueprint-schema-init = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2", features = ["serde"] } +radix-engine-toolkit-common = { git = "https://github.com/radixdlt/radixdlt-scrypto", tag = "cuttlefish-6130d5b2" } itertools = { version = "=0.10.5" } jni = { version = "=0.19.0" } diff --git a/core-rust/core-api-server/src/core_api/conversions/receipt.rs b/core-rust/core-api-server/src/core_api/conversions/receipt.rs index a3fc1ec1ab..34f10d647f 100644 --- a/core-rust/core-api-server/src/core_api/conversions/receipt.rs +++ b/core-rust/core-api-server/src/core_api/conversions/receipt.rs @@ -158,6 +158,8 @@ pub fn to_api_substate_system_structure( SystemFieldKind::VmBoot => models::SystemFieldKind::VmBoot, SystemFieldKind::SystemBoot => models::SystemFieldKind::SystemBoot, SystemFieldKind::KernelBoot => models::SystemFieldKind::KernelBoot, + SystemFieldKind::TransactionValidationConfiguration => todo!(), + SystemFieldKind::ProtocolUpdateStatusSummary => todo!(), }, } } diff --git a/core-rust/core-api-server/src/core_api/handlers/state_component.rs b/core-rust/core-api-server/src/core_api/handlers/state_component.rs index ac5e98575c..a69c8ffc90 100644 --- a/core-rust/core-api-server/src/core_api/handlers/state_component.rs +++ b/core-rust/core-api-server/src/core_api/handlers/state_component.rs @@ -1,7 +1,7 @@ use crate::core_api::*; use crate::engine_prelude::*; -use state_manager::query::{dump_component_state, ComponentStateDump, DescendantParentOpt}; +use state_manager::query::*; use std::ops::Deref; diff --git a/core-rust/core-api-server/src/core_api/handlers/status_scenarios.rs b/core-rust/core-api-server/src/core_api/handlers/status_scenarios.rs index 7227fabcd9..08a443aa0d 100644 --- a/core-rust/core-api-server/src/core_api/handlers/status_scenarios.rs +++ b/core-rust/core-api-server/src/core_api/handlers/status_scenarios.rs @@ -58,7 +58,7 @@ pub fn to_api_scenario_transaction( Ok(models::ExecutedScenarioTransaction { logical_name: transaction.logical_name.clone(), state_version: to_api_state_version(transaction.state_version)?, - intent_hash: to_api_intent_hash(&transaction.intent_hash), - intent_hash_bech32m: to_api_hash_bech32m(context, &transaction.intent_hash)?, + intent_hash: to_api_intent_hash(&transaction.transaction_intent_hash), + intent_hash_bech32m: to_api_hash_bech32m(context, &transaction.transaction_intent_hash)?, }) } diff --git a/core-rust/core-api-server/src/core_api/handlers/stream_transactions.rs b/core-rust/core-api-server/src/core_api/handlers/stream_transactions.rs index 66a56a87c2..8d66ad57c4 100644 --- a/core-rust/core-api-server/src/core_api/handlers/stream_transactions.rs +++ b/core-rust/core-api-server/src/core_api/handlers/stream_transactions.rs @@ -310,7 +310,7 @@ pub fn to_api_ledger_transaction( context: &MappingContext, raw_ledger_transaction: &RawLedgerTransaction, ledger_transaction: &LedgerTransaction, - payload_identifiers: &PayloadIdentifiers, + hashes: &LedgerTransactionHashes, ) -> Result { let payload_hex = if context.transaction_options.include_raw_ledger { Some(to_hex(raw_ledger_transaction.as_slice())) @@ -320,7 +320,7 @@ pub fn to_api_ledger_transaction( Ok(match ledger_transaction { LedgerTransaction::UserV1(tx) => { - let user_identifiers = payload_identifiers.typed.user().ok_or_else(|| { + let user_hashes = hashes.as_user().ok_or_else(|| { MappingError::MismatchedTransactionIdentifiers { message: "Transaction hashes for notarized transaction were not user" .to_string(), @@ -331,12 +331,11 @@ pub fn to_api_ledger_transaction( notarized_transaction: Box::new(to_api_notarized_transaction( context, tx, - user_identifiers.intent_hash, - user_identifiers.signed_intent_hash, - user_identifiers.notarized_transaction_hash, + &user_hashes, )?), } } + LedgerTransaction::UserV2(tx) => todo!(), LedgerTransaction::RoundUpdateV1(tx) => { models::LedgerTransaction::RoundUpdateLedgerTransaction { payload_hex, @@ -372,9 +371,7 @@ pub fn to_api_ledger_transaction( pub fn to_api_notarized_transaction( context: &MappingContext, notarized: &NotarizedTransactionV1, - intent_hash: &IntentHash, - signed_intent_hash: &SignedIntentHash, - notarized_transaction_hash: &NotarizedTransactionHash, + user_hashes: &UserTransactionHashes, ) -> Result { let payload_hex = if context.transaction_options.include_raw_notarized { Some(to_hex(notarized.to_payload_bytes().map_err(|err| { @@ -394,8 +391,8 @@ pub fn to_api_notarized_transaction( signed_intent: Box::new(to_api_signed_intent( context, ¬arized.signed_intent, - intent_hash, - signed_intent_hash, + &user_hashes.transaction_intent_hash, + &user_hashes.signed_transaction_intent_hash, )?), notary_signature: Some(to_api_signature(¬arized.notary_signature.0)), }) @@ -405,13 +402,13 @@ pub fn to_api_notarized_transaction( pub fn to_api_signed_intent( context: &MappingContext, signed_intent: &SignedIntentV1, - intent_hash: &IntentHash, - signed_intent_hash: &SignedIntentHash, + transaction_intent_hash: &TransactionIntentHash, + signed_transaction_intent_hash: &SignedTransactionIntentHash, ) -> Result { Ok(models::SignedTransactionIntent { - hash: to_api_signed_intent_hash(signed_intent_hash), - hash_bech32m: to_api_hash_bech32m(context, signed_intent_hash)?, - intent: Box::new(to_api_intent(context, &signed_intent.intent, intent_hash)?), + hash: to_api_signed_intent_hash(signed_transaction_intent_hash), + hash_bech32m: to_api_hash_bech32m(context, signed_transaction_intent_hash)?, + intent: Box::new(to_api_intent(context, &signed_intent.intent, transaction_intent_hash)?), intent_signatures: signed_intent .intent_signatures .signatures diff --git a/core-rust/core-api-server/src/core_api/handlers/transaction_callpreview.rs b/core-rust/core-api-server/src/core_api/handlers/transaction_callpreview.rs index ee746ddc1e..3bc83b7115 100644 --- a/core-rust/core-api-server/src/core_api/handlers/transaction_callpreview.rs +++ b/core-rust/core-api-server/src/core_api/handlers/transaction_callpreview.rs @@ -49,12 +49,12 @@ pub(crate) async fn handle_transaction_callpreview( extract_package_address(&extraction_context, package_address.as_str()) .map_err(|err| err.into_response_error("target.package_address"))?; - InstructionV1::CallFunction { + InstructionV1::CallFunction(CallFunction { blueprint_name, function_name, package_address: package_address.into(), args: args_from_bytes_vec!(args), - } + }) } models::TargetIdentifier::ComponentMethodTargetIdentifier { component_address, @@ -64,11 +64,11 @@ pub(crate) async fn handle_transaction_callpreview( extract_component_address(&extraction_context, component_address.as_str()) .map_err(|err| err.into_response_error("target.component_address"))?; - InstructionV1::CallMethod { + InstructionV1::CallMethod(CallMethod { address: component_address.into(), method_name, args: args_from_bytes_vec!(args), - } + }) } }; @@ -77,6 +77,7 @@ pub(crate) async fn handle_transaction_callpreview( manifest: TransactionManifestV1 { instructions: vec![requested_call], blobs: index_map_new(), + object_names: Default::default(), }, start_epoch_inclusive: None, end_epoch_exclusive: None, diff --git a/core-rust/core-api-server/src/core_api/handlers/transaction_parse.rs b/core-rust/core-api-server/src/core_api/handlers/transaction_parse.rs index 1357638dc7..462841b7b5 100644 --- a/core-rust/core-api-server/src/core_api/handlers/transaction_parse.rs +++ b/core-rust/core-api-server/src/core_api/handlers/transaction_parse.rs @@ -191,9 +191,11 @@ fn to_api_parsed_notarized_transaction( ResponseMode::Full => Some(Box::new(to_api_notarized_transaction( &context.mapping_context, &parsed.model, - &intent_hash, - &signed_intent_hash, - ¬arized_transaction_hash, + &UserTransactionHashes { + transaction_intent_hash, + signed_transaction_intent_hash, + notarized_transaction_hash, + }, )?)), }; diff --git a/core-rust/node-common/src/utils.rs b/core-rust/node-common/src/utils.rs index c0b21a4db9..a675713fb7 100644 --- a/core-rust/node-common/src/utils.rs +++ b/core-rust/node-common/src/utils.rs @@ -109,6 +109,15 @@ impl CaptureSupport { self.capture_value(value.clone()); } + /// Calls [`Self::capture_value()`] with the constructed value, or does nothing if + /// not expecting any value at the moment. + pub fn capture_if_required(&mut self, constructor: impl FnOnce() -> T){ + if matches!(self, CaptureSupport::NotExpecting) { + return; // deliberately do nothing + } + self.capture_value(constructor()); + } + /// Returns the currently captured value and resets the instance to "not expecting" state. /// The [`Self::capture_value()`] must have been called before this method. pub fn retrieve_captured(&mut self) -> T { @@ -119,6 +128,15 @@ impl CaptureSupport { }; value } + + /// Extracts the captured value as an [`Option`]. + pub fn into_option(self) -> Option { + match self { + CaptureSupport::NotExpecting => None, + CaptureSupport::Expecting => None, + CaptureSupport::Captured(x) => Some(x), + } + } } pub trait IsAccountExt { diff --git a/core-rust/state-manager/src/commit_bundle.rs b/core-rust/state-manager/src/commit_bundle.rs index 1acc812857..94d56fd3ed 100644 --- a/core-rust/state-manager/src/commit_bundle.rs +++ b/core-rust/state-manager/src/commit_bundle.rs @@ -100,7 +100,7 @@ impl CommitBundleBuilder { state_version: StateVersion, proposer_timestamp_ms: i64, raw: RawLedgerTransaction, - validated: ValidatedLedgerTransaction, + hashes: LedgerTransactionHashes, result: ProcessedCommitResult, ) { self.substate_store_update.apply(result.database_updates); @@ -122,7 +122,7 @@ impl CommitBundleBuilder { raw, receipt: result.local_receipt, identifiers: CommittedTransactionIdentifiers { - payload: validated.create_identifiers(), + transaction_hashes: hashes, resultant_ledger_hashes: hash_structures_diff.ledger_hashes, proposer_timestamp_ms, }, diff --git a/core-rust/state-manager/src/committer.rs b/core-rust/state-manager/src/committer.rs index 675e0c84c4..7aad831be4 100644 --- a/core-rust/state-manager/src/committer.rs +++ b/core-rust/state-manager/src/committer.rs @@ -218,15 +218,18 @@ impl Committer { panic!("cannot validate transaction to be committed: {error:?}"); }); + let hashes = validated.create_hashes(); + let executable = validated.create_ledger_executable(); + let commit = series_executor - .execute_and_update_state(&validated, "prepared") + .execute_and_update_state(&executable, &hashes, "prepared") .expect("cannot execute transaction to be committed"); - if let ValidatedLedgerTransactionInner::User(user_transaction) = &validated.inner { + if let Some(user_hashes) = hashes.as_user() { committed_user_transactions.push(CommittedUserTransactionIdentifiers { state_version: series_executor.latest_state_version(), - intent_hash: user_transaction.transaction_intent_hash(), - notarized_transaction_hash: user_transaction.notarized_transaction_hash(), + transaction_intent_hash: user_hashes.transaction_intent_hash, + notarized_transaction_hash: user_hashes.notarized_transaction_hash, }); } transactions_metrics_data.push(TransactionMetricsData::new(&raw, &commit)); @@ -235,7 +238,7 @@ impl Committer { series_executor.latest_state_version(), proposer_timestamp_ms, raw, - validated, + hashes, commit, ); } @@ -265,7 +268,7 @@ impl Committer { self.mempool_manager.remove_committed( committed_user_transactions .iter() - .map(|txn| &txn.intent_hash), + .map(|txn| &txn.transaction_intent_hash), ); if let Some(epoch_change) = epoch_change { @@ -313,9 +316,9 @@ impl Committer { let mut commit_bundle_builder = series_executor.start_commit_builder(); let mut transactions_metrics_data = Vec::new(); - for RawAndValidatedTransaction { raw, validated } in transactions { + for ProcessedLedgerTransaction { raw, executable, hashes } in transactions { let mut commit = series_executor - .execute_and_update_state(&validated, "system transaction") + .execute_and_update_state(&executable, &hashes, "system transaction") .expect("cannot execute system transaction"); if require_committed_successes { commit = commit.expect_success("system transaction not successful"); @@ -326,7 +329,7 @@ impl Committer { series_executor.latest_state_version(), proposer_timestamp_ms, raw, - validated, + hashes, commit, ); } @@ -481,6 +484,6 @@ impl Committer { pub struct CommittedUserTransactionIdentifiers { pub state_version: StateVersion, - pub intent_hash: TransactionIntentHash, + pub transaction_intent_hash: TransactionIntentHash, pub notarized_transaction_hash: NotarizedTransactionHash, } diff --git a/core-rust/state-manager/src/jni/mempool.rs b/core-rust/state-manager/src/jni/mempool.rs index 805510c0b5..633d261a35 100644 --- a/core-rust/state-manager/src/jni/mempool.rs +++ b/core-rust/state-manager/src/jni/mempool.rs @@ -204,8 +204,8 @@ impl From for MempoolAddErrorJava { fn from(err: MempoolAddError) -> Self { match err { MempoolAddError::PriorityThresholdNotMet { - min_tip_percentage_required, - tip_percentage, + min_tip_basis_points_required: min_tip_percentage_required, + tip_basis_points: tip_percentage, } => MempoolAddErrorJava::PriorityThresholdNotMet { min_tip_percentage_required: min_tip_percentage_required.map(|x| x as u32), tip_percentage: tip_percentage as u32, diff --git a/core-rust/state-manager/src/jni/test_state_reader.rs b/core-rust/state-manager/src/jni/test_state_reader.rs index 34b0c91502..e5bc77a654 100644 --- a/core-rust/state-manager/src/jni/test_state_reader.rs +++ b/core-rust/state-manager/src/jni/test_state_reader.rs @@ -129,7 +129,7 @@ extern "system" fn Java_com_radixdlt_testutil_TestStateReader_getTransactionAtSt database.get_committed_local_transaction_execution(state_version)?; Some(ExecutedTransaction { - ledger_transaction_hash: committed_identifiers.payload.ledger_transaction_hash, + ledger_transaction_hash: committed_identifiers.transaction_hashes.ledger_transaction_hash, outcome: match committed_ledger_transaction_receipt.outcome { LedgerTransactionOutcome::Success => TransactionOutcomeJava::Success, LedgerTransactionOutcome::Failure => TransactionOutcomeJava::Failure, diff --git a/core-rust/state-manager/src/lib.rs b/core-rust/state-manager/src/lib.rs index 12773d4792..ec79bb2dc0 100644 --- a/core-rust/state-manager/src/lib.rs +++ b/core-rust/state-manager/src/lib.rs @@ -72,7 +72,7 @@ mod limits; mod mempool; mod metrics; mod protocol; -mod query; +pub mod query; mod receipt; mod staging; mod state_manager; diff --git a/core-rust/state-manager/src/mempool/mempool_manager.rs b/core-rust/state-manager/src/mempool/mempool_manager.rs index 8376a632a3..ca392aa8f4 100644 --- a/core-rust/state-manager/src/mempool/mempool_manager.rs +++ b/core-rust/state-manager/src/mempool/mempool_manager.rs @@ -164,7 +164,10 @@ impl MempoolManager { for candidate_transaction in candidate_transactions { // invoking the check automatically removes the transaction when rejected self.cached_committability_validator - .check_for_rejection_validated(&candidate_transaction.transaction.validated); + .check_for_rejection_validated( + &candidate_transaction.transaction.executable, + &candidate_transaction.transaction.hashes, + ); } } @@ -257,14 +260,16 @@ impl MempoolManager { // STEP 4 - We check if the result should mean we add the transaction to our mempool let PendingExecutedTransaction { - transaction, + executable, + user_hashes, latest_attempt_against_state, } = record .should_accept_into_mempool(check_result) .map_err(MempoolAddError::Rejected)?; let mempool_transaction = Arc::new(MempoolTransaction { - validated: transaction, + executable, + hashes: user_hashes, raw: raw_transaction, }); match self.mempool.write().add_transaction_if_not_present( diff --git a/core-rust/state-manager/src/mempool/mod.rs b/core-rust/state-manager/src/mempool/mod.rs index a9dcefe139..29cb3167eb 100644 --- a/core-rust/state-manager/src/mempool/mod.rs +++ b/core-rust/state-manager/src/mempool/mod.rs @@ -85,8 +85,8 @@ pub enum MempoolAddSource { #[derive(Debug, Clone)] pub enum MempoolAddError { PriorityThresholdNotMet { - min_tip_percentage_required: Option, - tip_percentage: u16, + min_tip_basis_points_required: Option, + tip_basis_points: u32, }, Duplicate(NotarizedTransactionHash), Rejected(MempoolAddRejection), @@ -150,7 +150,7 @@ impl MempoolAddRejection { impl ToString for MempoolAddError { fn to_string(&self) -> String { match self { - MempoolAddError::PriorityThresholdNotMet {min_tip_percentage_required, tip_percentage} => { + MempoolAddError::PriorityThresholdNotMet {min_tip_basis_points_required: min_tip_percentage_required, tip_basis_points: tip_percentage} => { match min_tip_percentage_required { None => { "Priority Threshold not met. There is no known tip to guarantee mempool submission.".to_string() diff --git a/core-rust/state-manager/src/mempool/pending_transaction_result_cache.rs b/core-rust/state-manager/src/mempool/pending_transaction_result_cache.rs index 5f0c235352..253da7d17b 100644 --- a/core-rust/state-manager/src/mempool/pending_transaction_result_cache.rs +++ b/core-rust/state-manager/src/mempool/pending_transaction_result_cache.rs @@ -133,11 +133,6 @@ impl MempoolRejectionReason { TransactionValidationError::SubintentError(_) => { RejectionPermanence::PermanentForAnyPayloadWithThisIntent } - // This only occurs when validating ledger transactions against a particular kind - // So we can't actually get this here. Let's just use temporary as a fallback. - TransactionValidationError::Other(_) => { - RejectionPermanence::default_temporary() - }, // Total signature count is a property of the payload, not intent TransactionValidationError::TooManySignatures { .. } => { RejectionPermanence::PermanentForPayload @@ -319,17 +314,23 @@ pub enum RetryFrom { #[derive(Debug, Clone)] pub struct PendingExecutedTransaction { - pub transaction: Box, + pub executable: ExecutableTransaction, + pub user_hashes: UserTransactionHashes, pub latest_attempt_against_state: AtSpecificState, } impl PendingExecutedTransaction { - pub fn new(transaction: Box, against_state: AtState) -> Self { + pub fn new( + executable: ExecutableTransaction, + user_hashes: UserTransactionHashes, + against_state: AtState, + ) -> Self { let AtState::Specific(latest_attempt_against_state) = against_state else { panic!("transaction must have been executed against some state") }; Self { - transaction, + executable, + user_hashes, latest_attempt_against_state, } } @@ -431,8 +432,11 @@ impl PendingTransactionRecord { CheckMetadata::Cached => { panic!("Precondition was not met - the result was cached, but the latest attempt was not a rejection") } - CheckMetadata::Fresh(StaticValidation::Valid(transaction)) => Ok( - PendingExecutedTransaction::new(transaction, self.latest_attempt.against_state), + CheckMetadata::Fresh(StaticValidation::Valid { + executable, + user_hashes, + }) => Ok( + PendingExecutedTransaction::new(executable, user_hashes, self.latest_attempt.against_state), ), CheckMetadata::Fresh(StaticValidation::Invalid) => { panic!("A statically invalid transaction should already have been handled in the above") @@ -567,7 +571,7 @@ impl PendingTransactionResultCache { committed_transactions: Vec, ) { for committed_transaction in committed_transactions { - let committed_intent_hash = committed_transaction.intent_hash; + let committed_intent_hash = committed_transaction.transaction_intent_hash; let committed_notarized_transaction_hash = committed_transaction.notarized_transaction_hash; // Note - we keep the relevant statuses of all known payloads for the intent in the cache @@ -892,7 +896,7 @@ mod tests { now, vec![CommittedUserTransactionIdentifiers { state_version: StateVersion::of(1), - intent_hash: intent_hash_1, + transaction_intent_hash: intent_hash_1, notarized_transaction_hash: payload_hash_1, }], ); diff --git a/core-rust/state-manager/src/mempool/priority_mempool.rs b/core-rust/state-manager/src/mempool/priority_mempool.rs index 1c62991369..230b78313b 100644 --- a/core-rust/state-manager/src/mempool/priority_mempool.rs +++ b/core-rust/state-manager/src/mempool/priority_mempool.rs @@ -96,41 +96,38 @@ pub struct MempoolData { #[derive(Debug, Clone, Eq, PartialEq)] // (`Eq` for tests only) pub struct MempoolTransaction { - pub validated: Box, + pub executable: ExecutableTransaction, + pub hashes: UserTransactionHashes, pub raw: RawNotarizedTransaction, } impl MempoolTransaction { - pub fn tip_percentage(&self) -> u16 { - self.validated - .prepared - .signed_intent - .intent - .header - .inner - .tip_percentage + pub fn tip_basis_points(&self) -> u32 { + self.executable.costing_parameters().tip.basis_points() } pub fn end_epoch_exclusive(&self) -> Epoch { - self.validated.end_epoch_exclusive() + self.executable.overall_epoch_range() + .expect("User tranasctions must have an epoch validity range") + .end_epoch_exclusive } } impl HasTransactionIntentHash for MempoolTransaction { fn transaction_intent_hash(&self) -> TransactionIntentHash { - self.validated.transaction_intent_hash() + self.hashes.transaction_intent_hash } } impl HasSignedTransactionIntentHash for MempoolTransaction { fn signed_transaction_intent_hash(&self) -> SignedTransactionIntentHash { - self.validated.signed_transaction_intent_hash() + self.hashes.signed_transaction_intent_hash } } impl HasNotarizedTransactionHash for MempoolTransaction { fn notarized_transaction_hash(&self) -> NotarizedTransactionHash { - self.validated.notarized_transaction_hash() + self.hashes.notarized_transaction_hash } } @@ -152,18 +149,18 @@ impl MempoolData { /// A transaction's proposal priority. /// -/// The greatest element (i.e. the one with the *highest* `tip_percentage`, and then the *oldest* -/// `added_at`) marks the "best" transaction (i.e. the one to be executed first). +/// The greatest element (i.e. the one with the *highest* [`tip_basis_points`][Self::tip_basis_points], +/// and then the *oldest* `added_at`) marks the "best" transaction (i.e. the one to be executed first). #[derive(Clone, Eq, PartialEq)] struct ProposalPriority { - tip_percentage: u16, + tip_basis_points: u32, added_at: StdInstant, } impl Ord for ProposalPriority { fn cmp(&self, other: &Self) -> Ordering { - self.tip_percentage - .cmp(&other.tip_percentage) + self.tip_basis_points + .cmp(&other.tip_basis_points) // Note: the `reverse()` below is deliberate; this is why we cannot do `#[derive(Ord)]` .then_with(|| self.added_at.cmp(&other.added_at).reverse()) } @@ -178,7 +175,7 @@ impl PartialOrd for ProposalPriority { impl ProposalPriority { pub fn new(mempool_data: &MempoolData) -> Self { Self { - tip_percentage: mempool_data.transaction.tip_percentage(), + tip_basis_points: mempool_data.transaction.tip_basis_points(), added_at: mempool_data.added_at, } } @@ -284,8 +281,8 @@ impl PriorityMempool { // Even with an empty mempool we are not able to fulfill the request. warn!("Impossible to add new transaction. Mempool max size lower than transaction size!"); return Err(MempoolAddError::PriorityThresholdNotMet { - min_tip_percentage_required: None, - tip_percentage: transaction_data.transaction.tip_percentage(), + min_tip_basis_points_required: None, + tip_basis_points: transaction_data.transaction.tip_basis_points(), }); } Some(mempool_data) => { @@ -304,11 +301,11 @@ impl PriorityMempool { if new_proposal_priority < ProposalPriority::new(best_to_be_removed) { let min_tip_percentage_required = best_to_be_removed .transaction - .tip_percentage() + .tip_basis_points() .checked_add(1); return Err(MempoolAddError::PriorityThresholdNotMet { - min_tip_percentage_required, - tip_percentage: transaction_data.transaction.tip_percentage(), + min_tip_basis_points_required: min_tip_percentage_required, + tip_basis_points: transaction_data.transaction.tip_basis_points(), }); } } @@ -587,78 +584,44 @@ mod tests { use crate::mempool::priority_mempool::*; - fn create_fake_pub_key() -> PublicKey { - PublicKey::Secp256k1(Secp256k1PublicKey([0; Secp256k1PublicKey::LENGTH])) - } - - fn create_fake_signature() -> NotarySignatureV1 { - NotarySignatureV1(SignatureV1::Secp256k1(Secp256k1Signature( - [0; Secp256k1Signature::LENGTH], - ))) - } - - fn create_fake_signature_with_public_key() -> IntentSignatureV1 { - IntentSignatureV1(SignatureWithPublicKeyV1::Secp256k1 { - signature: Secp256k1Signature([0; Secp256k1Signature::LENGTH]), - }) - } - - fn create_fake_notarized_transaction( - nonce: u32, - sigs_count: usize, - tip_percentage: u16, - ) -> PreparedNotarizedTransactionV1 { - NotarizedTransactionV1 { - signed_intent: SignedIntentV1 { - intent: IntentV1 { - header: TransactionHeaderV1 { - network_id: 1, - start_epoch_inclusive: Epoch::of(1), - end_epoch_exclusive: Epoch::of(2), - nonce, - notary_public_key: create_fake_pub_key(), - notary_is_signatory: false, - tip_percentage, - }, - instructions: InstructionsV1(vec![]), - blobs: BlobsV1 { blobs: vec![] }, - message: MessageV1::None, - }, - intent_signatures: IntentSignaturesV1 { - signatures: vec![0; sigs_count] - .into_iter() - .map(|_| create_fake_signature_with_public_key()) - .collect(), - }, - }, - notary_signature: create_fake_signature(), - } - .prepare(&PreparationSettings::latest()) - .expect("Expected that it could be prepared") - } - - fn create_fake_pending_transaction( - nonce: u32, - sigs_count: usize, + fn create_mempool_transaction( + intent_discriminator: u32, + signer_discriminator: u64, tip_percentage: u16, ) -> Arc { + let notary = Ed25519PrivateKey::from_u64(999).unwrap(); + let raw = TransactionV1Builder::new() + .header(TransactionHeaderV1 { + network_id: 1, + start_epoch_inclusive: Epoch::of(1), + end_epoch_exclusive: Epoch::of(2), + nonce: intent_discriminator, + notary_public_key: notary.public_key().into(), + notary_is_signatory: false, + tip_percentage, + }) + .manifest(ManifestBuilder::new_v1().build()) + .sign(&Ed25519PrivateKey::from_u64(signer_discriminator).unwrap()) + .notarize(¬ary) + .build() + .to_raw() + .unwrap(); + let validated = raw.validate(&TransactionValidator::new_with_latest_config_network_agnostic()).unwrap(); + let hashes = validated.hashes(); + let executable = validated.create_executable(); + Arc::new(MempoolTransaction { - validated: Box::new(ValidatedNotarizedTransactionV1 { - prepared: create_fake_notarized_transaction(nonce, sigs_count, tip_percentage), - // Fake these - encoded_instructions: vec![], - signer_keys: vec![], - num_of_signature_validations: 0, - }), - raw: RawNotarizedTransaction::from_vec(vec![]), + executable, + hashes, + raw, }) } #[test] fn add_and_get_test() { - let mt1 = create_fake_pending_transaction(1, 0, 0); - let mt2 = create_fake_pending_transaction(2, 0, 0); - let mt3 = create_fake_pending_transaction(3, 0, 0); + let mt1 = create_mempool_transaction(1, 0, 0); + let mt2 = create_mempool_transaction(2, 0, 0); + let mt3 = create_mempool_transaction(3, 0, 0); let v1 = StateVersion::of(1); let registry = MetricRegistry::new(); @@ -718,11 +681,11 @@ mod tests { #[test] fn test_intent_lookup() { - let intent_1_payload_1 = create_fake_pending_transaction(1, 1, 0); - let intent_1_payload_2 = create_fake_pending_transaction(1, 2, 0); - let intent_1_payload_3 = create_fake_pending_transaction(1, 3, 0); - let intent_2_payload_1 = create_fake_pending_transaction(2, 1, 0); - let intent_2_payload_2 = create_fake_pending_transaction(2, 2, 0); + let intent_1_payload_1 = create_mempool_transaction(1, 1, 0); + let intent_1_payload_2 = create_mempool_transaction(1, 2, 0); + let intent_1_payload_3 = create_mempool_transaction(1, 3, 0); + let intent_2_payload_1 = create_mempool_transaction(2, 1, 0); + let intent_2_payload_2 = create_mempool_transaction(2, 2, 0); let v1 = StateVersion::of(1); let registry = MetricRegistry::new(); @@ -841,8 +804,8 @@ mod tests { #[test] fn test_proposal_priority_ordering() { - let mt1 = create_fake_pending_transaction(1, 0, 10); - let mt2 = create_fake_pending_transaction(2, 0, 20); + let mt1 = create_mempool_transaction(1, 0, 10); + let mt2 = create_mempool_transaction(2, 0, 20); let now = StdInstant::now(); let time_point = [now + Duration::from_secs(1), now + Duration::from_secs(2)]; @@ -878,15 +841,15 @@ mod tests { #[test] fn test_proposal_priority_add_eviction() { - let mt1 = create_fake_pending_transaction(1, 0, 10); - let mt2 = create_fake_pending_transaction(1, 0, 20); - let mt3 = create_fake_pending_transaction(2, 0, 20); - let mt4 = create_fake_pending_transaction(1, 0, 30); - let mt5 = create_fake_pending_transaction(1, 0, 40); - let mt6 = create_fake_pending_transaction(2, 0, 40); - let mt7 = create_fake_pending_transaction(3, 0, 40); - let mt8 = create_fake_pending_transaction(4, 0, 40); - let mt9 = create_fake_pending_transaction(5, 0, 40); + let mt1 = create_mempool_transaction(1, 0, 10); + let mt2 = create_mempool_transaction(1, 0, 20); + let mt3 = create_mempool_transaction(2, 0, 20); + let mt4 = create_mempool_transaction(1, 0, 30); + let mt5 = create_mempool_transaction(1, 0, 40); + let mt6 = create_mempool_transaction(2, 0, 40); + let mt7 = create_mempool_transaction(3, 0, 40); + let mt8 = create_mempool_transaction(4, 0, 40); + let mt9 = create_mempool_transaction(5, 0, 40); let now = StdInstant::now(); let v1 = StateVersion::of(1); @@ -975,7 +938,7 @@ mod tests { #[test] fn test_duplicate_txn_not_inserted() { - let mempool_txn = create_fake_pending_transaction(1, 0, 10); + let mempool_txn = create_mempool_transaction(1, 0, 10); let now = StdInstant::now(); let v1 = StateVersion::of(1); diff --git a/core-rust/state-manager/src/protocol/protocol_config.rs b/core-rust/state-manager/src/protocol/protocol_config.rs index bd4c0785c7..2c54602ee0 100644 --- a/core-rust/state-manager/src/protocol/protocol_config.rs +++ b/core-rust/state-manager/src/protocol/protocol_config.rs @@ -10,6 +10,7 @@ const MAX_PROTOCOL_VERSION_NAME_LEN: usize = 16; pub const GENESIS_PROTOCOL_VERSION: &str = "babylon-genesis"; pub const ANEMONE_PROTOCOL_VERSION: &str = "anemone"; pub const BOTTLENOSE_PROTOCOL_VERSION: &str = "bottlenose"; +pub const CUTTLEFISH_PROTOCOL_VERSION: &str = "cuttlefish"; pub fn resolve_update_definition_for_version( protocol_version_name: &ProtocolVersionName, @@ -20,6 +21,7 @@ pub fn resolve_update_definition_for_version( GENESIS_PROTOCOL_VERSION => Some(Box::new(NoOpProtocolDefinition)), ANEMONE_PROTOCOL_VERSION => Some(Box::new(AnemoneProtocolUpdateDefinition)), BOTTLENOSE_PROTOCOL_VERSION => Some(Box::new(BottlenoseProtocolUpdateDefinition)), + CUTTLEFISH_PROTOCOL_VERSION => Some(Box::new(CuttlefishProtocolUpdateDefinition)), // Updates starting "custom-" are intended for use with tests, where the thresholds and config are injected on all nodes name_string if CustomProtocolUpdateDefinition::matches(name_string) => { Some(Box::new(CustomProtocolUpdateDefinition)) @@ -260,6 +262,9 @@ impl ProtocolUpdateTrigger { ProtocolUpdateEnactmentCondition::EnactAtStartOfEpochUnconditionally(_) => { // Nothing to check here } + ProtocolUpdateEnactmentCondition::EnactStraightAfterEndOfPrevious { .. } => { + // Nothing to check here + }, } Ok(()) } @@ -299,6 +304,7 @@ pub enum ProtocolUpdateEnactmentCondition { /// The enactment proceeds unconditionally /// at the start of specified epoch. EnactAtStartOfEpochUnconditionally(Epoch), + EnactImmediatelyAfterEndOfPreviousProtocolUpdate, } #[derive(Clone, Debug, Eq, PartialEq, ScryptoSbor)] diff --git a/core-rust/state-manager/src/protocol/protocol_configs/testnet_protocol_config.rs b/core-rust/state-manager/src/protocol/protocol_configs/testnet_protocol_config.rs index 5c650dcaad..58aba024b2 100644 --- a/core-rust/state-manager/src/protocol/protocol_configs/testnet_protocol_config.rs +++ b/core-rust/state-manager/src/protocol/protocol_configs/testnet_protocol_config.rs @@ -14,6 +14,7 @@ pub fn testnet_protocol_config() -> ProtocolConfig { // So we should target applying protocol updates from 3 onwards (1 per epoch) ProtocolConfig::new_with_triggers(hashmap! { ANEMONE_PROTOCOL_VERSION => EnactAtStartOfEpochUnconditionally(Epoch::of(3)), - BOTTLENOSE_PROTOCOL_VERSION => EnactAtStartOfEpochUnconditionally(Epoch::of(4)) + BOTTLENOSE_PROTOCOL_VERSION => EnactImmediatelyAfterEndOfPreviousProtocolUpdate, + CUTTLEFISH_PROTOCOL_VERSION => EnactImmediatelyAfterEndOfPreviousProtocolUpdate, }) } diff --git a/core-rust/state-manager/src/protocol/protocol_updates/definitions/anemone_definition.rs b/core-rust/state-manager/src/protocol/protocol_updates/definitions/anemone_definition.rs index 9b36eb988b..fea082f8a6 100644 --- a/core-rust/state-manager/src/protocol/protocol_updates/definitions/anemone_definition.rs +++ b/core-rust/state-manager/src/protocol/protocol_updates/definitions/anemone_definition.rs @@ -1,8 +1,4 @@ -use crate::engine_prelude::*; -use crate::protocol::*; -use crate::store::rocks_db::ActualStateManagerDatabase; -use node_common::locks::DbLock; -use std::sync::Arc; +use crate::prelude::*; pub struct AnemoneProtocolUpdateDefinition; diff --git a/core-rust/state-manager/src/protocol/protocol_updates/definitions/bottlenose_definition.rs b/core-rust/state-manager/src/protocol/protocol_updates/definitions/bottlenose_definition.rs index 48530042a7..396ede2f4e 100644 --- a/core-rust/state-manager/src/protocol/protocol_updates/definitions/bottlenose_definition.rs +++ b/core-rust/state-manager/src/protocol/protocol_updates/definitions/bottlenose_definition.rs @@ -1,8 +1,4 @@ -use crate::engine_prelude::*; -use crate::protocol::*; -use crate::store::rocks_db::ActualStateManagerDatabase; -use node_common::locks::DbLock; -use std::sync::Arc; +use crate::prelude::*; pub struct BottlenoseProtocolUpdateDefinition; diff --git a/core-rust/state-manager/src/protocol/protocol_updates/definitions/custom_definition.rs b/core-rust/state-manager/src/protocol/protocol_updates/definitions/custom_definition.rs index 8a7fbff9d9..ee3faa04c5 100644 --- a/core-rust/state-manager/src/protocol/protocol_updates/definitions/custom_definition.rs +++ b/core-rust/state-manager/src/protocol/protocol_updates/definitions/custom_definition.rs @@ -1,8 +1,4 @@ -use crate::engine_prelude::*; -use crate::protocol::*; -use crate::store::rocks_db::ActualStateManagerDatabase; -use node_common::locks::DbLock; -use std::sync::Arc; +use crate::prelude::*; /// Any protocol update beginning `custom-` can have content injected via config. pub struct CustomProtocolUpdateDefinition; diff --git a/core-rust/state-manager/src/protocol/protocol_updates/definitions/cuttlefish_definition.rs b/core-rust/state-manager/src/protocol/protocol_updates/definitions/cuttlefish_definition.rs new file mode 100644 index 0000000000..7c4d75454e --- /dev/null +++ b/core-rust/state-manager/src/protocol/protocol_updates/definitions/cuttlefish_definition.rs @@ -0,0 +1,18 @@ +use crate::prelude::*; + +pub struct CuttlefishProtocolUpdateDefinition; + +impl ProtocolUpdateDefinition for CuttlefishProtocolUpdateDefinition { + type Overrides = (); + + fn create_batch_generator( + &self, + network: &NetworkDefinition, + database: Arc>, + _overrides: Option, + ) -> Box { + Box::new(engine_default_for_network::( + network, database, + )) + } +} diff --git a/core-rust/state-manager/src/protocol/protocol_updates/definitions/default_definition.rs b/core-rust/state-manager/src/protocol/protocol_updates/definitions/default_definition.rs index 8491f2fe7e..d43e6d1e3b 100644 --- a/core-rust/state-manager/src/protocol/protocol_updates/definitions/default_definition.rs +++ b/core-rust/state-manager/src/protocol/protocol_updates/definitions/default_definition.rs @@ -1,8 +1,4 @@ -use crate::engine_prelude::*; -use crate::protocol::*; -use crate::store::rocks_db::ActualStateManagerDatabase; -use node_common::locks::DbLock; -use std::sync::Arc; +use crate::prelude::*; pub struct NoOpProtocolDefinition; diff --git a/core-rust/state-manager/src/protocol/protocol_updates/definitions/mod.rs b/core-rust/state-manager/src/protocol/protocol_updates/definitions/mod.rs index 8e0b29d54b..7a6e786920 100644 --- a/core-rust/state-manager/src/protocol/protocol_updates/definitions/mod.rs +++ b/core-rust/state-manager/src/protocol/protocol_updates/definitions/mod.rs @@ -1,64 +1,13 @@ -use crate::prelude::*; - mod anemone_definition; mod bottlenose_definition; +mod cuttlefish_definition; mod custom_definition; mod default_definition; mod test_definition; pub use anemone_definition::*; pub use bottlenose_definition::*; +pub use cuttlefish_definition::*; pub use custom_definition::*; pub use default_definition::*; pub use test_definition::*; - -/// A [`ProtocolUpdateNodeBatchGenerator`] implementation for the actual Engine's protocol updates. -pub struct EngineBatchGenerator { - database: Arc>, - engine_batch_generator: G, -} - -/// Creates an [`EngineBatchGenerator`] for the given [`UpdateSettings`], with all -/// the features that Engine wants enabled by default. -pub fn engine_default_for_network( - network: &NetworkDefinition, - database: Arc>, -) -> EngineBatchGenerator { - EngineBatchGenerator { - database, - engine_batch_generator: U::all_enabled_as_default_for_network(network) - .create_batch_generator(), - } -} - -impl ProtocolUpdateNodeBatchGenerator for EngineBatchGenerator { - fn generate_batch(&self, batch_idx: u32) -> ProtocolUpdateNodeBatch { - let ProtocolUpdateBatch { transactions } = self - .engine_batch_generator - .generate_batch(self.database.lock().deref(), batch_idx); - ProtocolUpdateNodeBatch::FlashTransactions( - transactions - .into_iter() - .map(FlashTransactionV1::from) - .collect(), - ) - } - - fn batch_count(&self) -> u32 { - self.engine_batch_generator.batch_count() - } -} - -impl From for FlashTransactionV1 { - fn from(value: ProtocolUpdateTransactionDetails) -> Self { - let ProtocolUpdateTransactionDetails::FlashV1Transaction(flash) = value; - let FlashProtocolUpdateTransactionDetails { - name, - state_updates, - } = flash; - FlashTransactionV1 { - name, - state_updates, - } - } -} diff --git a/core-rust/state-manager/src/protocol/protocol_updates/protocol_updaters.rs b/core-rust/state-manager/src/protocol/protocol_updates/protocol_updaters.rs index d449269b97..37cd22a95c 100644 --- a/core-rust/state-manager/src/protocol/protocol_updates/protocol_updaters.rs +++ b/core-rust/state-manager/src/protocol/protocol_updates/protocol_updaters.rs @@ -25,6 +25,57 @@ pub trait ProtocolUpdateNodeBatchGenerator { fn batch_count(&self) -> u32; } +/// A [`ProtocolUpdateNodeBatchGenerator`] implementation for the actual Engine's protocol updates. +pub struct EngineBatchGenerator { + database: Arc>, + engine_batch_generator: G, +} + +/// Creates an [`EngineBatchGenerator`] for the given [`UpdateSettings`], with all +/// the features that Engine wants enabled by default. +pub fn engine_default_for_network( + network: &NetworkDefinition, + database: Arc>, +) -> EngineBatchGenerator { + EngineBatchGenerator { + database, + engine_batch_generator: U::all_enabled_as_default_for_network(network) + .create_batch_generator(), + } +} + +impl ProtocolUpdateNodeBatchGenerator for EngineBatchGenerator { + fn generate_batch(&self, batch_idx: u32) -> ProtocolUpdateNodeBatch { + let ProtocolUpdateBatch { transactions } = self + .engine_batch_generator + .generate_batch(self.database.lock().deref(), batch_idx); + ProtocolUpdateNodeBatch::FlashTransactions( + transactions + .into_iter() + .map(FlashTransactionV1::from) + .collect(), + ) + } + + fn batch_count(&self) -> u32 { + self.engine_batch_generator.batch_count() + } +} + +impl From for FlashTransactionV1 { + fn from(value: ProtocolUpdateTransactionDetails) -> Self { + let ProtocolUpdateTransactionDetails::FlashV1Transaction(flash) = value; + let FlashProtocolUpdateTransactionDetails { + name, + state_updates, + } = flash; + FlashTransactionV1 { + name, + state_updates, + } + } +} + /// A [`ProtocolUpdateNodeBatchGenerator`] decorator which additionally executes post-update Scenarios. pub struct WithScenariosNodeBatchGenerator<'b, B: ProtocolUpdateNodeBatchGenerator + ?Sized> { pub base_batch_generator: &'b B, diff --git a/core-rust/state-manager/src/receipt.rs b/core-rust/state-manager/src/receipt.rs index a7b8cc285a..5c839be4ef 100644 --- a/core-rust/state-manager/src/receipt.rs +++ b/core-rust/state-manager/src/receipt.rs @@ -7,7 +7,7 @@ define_single_versioned! { #[derive(Debug, Clone, Sbor)] pub struct CommittedTransactionIdentifiersV1 { - pub payload: PayloadIdentifiers, + pub transaction_hashes: LedgerTransactionHashes, pub resultant_ledger_hashes: LedgerHashes, pub proposer_timestamp_ms: i64, } @@ -216,11 +216,17 @@ impl LedgerStateChanges { } } -/// A committed transaction (success or failure), extracted from the Engine's `TransactionReceipt` +/// A committed transaction (success or failure), extracted from the Engine's [`TransactionReceipt`] /// of any locally-executed transaction (slightly post-processed). +/// /// It contains all the critical, deterministic pieces of the Engine's receipt, but also some of its -/// other parts - for this reason, it is very clearly split into 2 parts (on-ledger vs off-ledger). -#[derive(Debug, Clone, ScryptoSbor)] +/// other parts - for this reason, it is very clearly split into 2 parts: +/// +/// * The [`LedgerTransactionReceipt`] contains the parts of the receipt which are validated at +/// consensus (success/failure, state changes and emitted events). +/// * The [`LocalTransactionExecution`] contains other information which is useful for transaction +/// analysis and investigation. +#[derive(Debug, Clone)] pub struct LocalTransactionReceipt { pub on_ledger: LedgerTransactionReceipt, pub local_execution: LocalTransactionExecution, @@ -233,8 +239,10 @@ define_single_versioned! { /// A part of the [`LocalTransactionReceipt`] which is completely stored on ledger. It contains only /// the critical, deterministic pieces of the original Engine's `TransactionReceipt`. +/// /// All these pieces can be verified against the Receipt Root hash (found in the Ledger Proof). -/// Note: the Ledger Receipt is still a pretty large structure (i.e. containing entire collections, +/// +/// Note: the [`LedgerTransactionReceipt`] is still a pretty large structure (i.e. containing entire collections, /// like substate changes) and is not supposed to be hashed directly - it should instead go through /// a [`ConsensusReceipt`]. #[derive(Debug, Clone, ScryptoSbor)] @@ -248,14 +256,21 @@ pub struct LedgerTransactionReceiptV1 { pub application_events: Vec, } -define_single_versioned! { +define_versioned! { + /// A computable/non-critical/non-deterministic part of the `LocalTransactionReceipt` + /// (e.g. logs, summaries). + /// It is not verifiable against ledger, but is still be useful for debugging. #[derive(Debug, Clone, ScryptoSbor)] - pub VersionedLocalTransactionExecution(LocalTransactionExecutionVersions) => LocalTransactionExecution = LocalTransactionExecutionV1 + pub VersionedLocalTransactionExecution(LocalTransactionExecutionVersions) { + previous_versions: [ + 1 => LocalTransactionExecutionV1: { updates_to: 2 }, + ], + latest_version: { + 2 => LocalTransactionExecution = LocalTransactionExecutionV2, + }, + } } -/// A computable/non-critical/non-deterministic part of the `LocalTransactionReceipt` (e.g. logs, -/// summaries). -/// It is not verifiable against ledger, but may still be useful for debugging. #[derive(Debug, Clone, ScryptoSbor)] pub struct LocalTransactionExecutionV1 { pub outcome: DetailedTransactionOutcome, @@ -272,6 +287,42 @@ pub struct LocalTransactionExecutionV1 { pub next_epoch: Option, } + +impl From for LocalTransactionExecutionV2 { + fn from(value: LocalTransactionExecutionV1) -> Self { + LocalTransactionExecutionV2 { + outcome: value.outcome, + fee_summary: value.fee_summary, + fee_source: value.fee_source, + fee_destination: value.fee_destination, + engine_costing_parameters: value.engine_costing_parameters, + transaction_costing_parameters: value.transaction_costing_parameters.into(), + application_logs: value.application_logs, + state_update_summary: value.state_update_summary, + global_balance_summary: value.global_balance_summary, + substates_system_structure: value.substates_system_structure, + events_system_structure: value.events_system_structure, + next_epoch: value.next_epoch, + } + } +} + +#[derive(Debug, Clone, ScryptoSbor)] +pub struct LocalTransactionExecutionV2 { + pub outcome: DetailedTransactionOutcome, + pub fee_summary: TransactionFeeSummary, + pub fee_source: FeeSource, + pub fee_destination: FeeDestination, + pub engine_costing_parameters: CostingParameters, + pub transaction_costing_parameters: TransactionCostingParametersReceiptV2, + pub application_logs: Vec<(crate::engine_prelude::Level, String)>, + pub state_update_summary: StateUpdateSummary, + pub global_balance_summary: GlobalBalanceSummary, + pub substates_system_structure: BySubstate, + pub events_system_structure: IndexMap, + pub next_epoch: Option, +} + impl LedgerTransactionReceipt { pub fn get_consensus_receipt(&self) -> ConsensusReceipt { let LedgerTransactionReceipt { diff --git a/core-rust/state-manager/src/store/rocks_db.rs b/core-rust/state-manager/src/store/rocks_db.rs index a9d70e55ee..5fce4c143e 100644 --- a/core-rust/state-manager/src/store/rocks_db.rs +++ b/core-rust/state-manager/src/store/rocks_db.rs @@ -497,14 +497,12 @@ impl CommitStore for StateManagerDatabase { let commit_state_version = commit_ledger_header.state_version; for transaction_bundle in commit_bundle.transactions { - let payload_identifiers = &transaction_bundle.identifiers.payload; - if let TypedTransactionIdentifiers::User { intent_hash, .. } = - &payload_identifiers.typed - { - processed_intent_hashes.insert(*intent_hash); + let hashes = &transaction_bundle.identifiers.transaction_hashes; + if let Some(user_hashes) = hashes.as_user() { + processed_intent_hashes.insert(user_hashes.transaction_intent_hash); user_transactions_count += 1; } - processed_ledger_transaction_hashes.insert(payload_identifiers.ledger_transaction_hash); + processed_ledger_transaction_hashes.insert(hashes.ledger_transaction_hash); self.add_transaction_to_write_batch(&db_context, transaction_bundle); } @@ -640,29 +638,28 @@ impl StateManagerDatabase { receipt, identifiers, } = transaction_bundle; - let ledger_transaction_hash = identifiers.payload.ledger_transaction_hash; + let ledger_transaction_hash = identifiers.transaction_hashes.ledger_transaction_hash; // TEMPORARY until this is handled in the engine: we store both an intent lookup and the transaction itself - if let TypedTransactionIdentifiers::User { - intent_hash, + if let Some(UserTransactionHashes { + transaction_intent_hash, notarized_transaction_hash, .. - } = &identifiers.payload.typed - { + }) = identifiers.transaction_hashes.as_user().as_ref() { /* For user transactions we only need to check for duplicate intent hashes to know that user payload hash and ledger payload hash are also unique. */ - let maybe_existing_state_version = db_context.cf(IntentHashesCf).get(intent_hash); + let maybe_existing_state_version = db_context.cf(IntentHashesCf).get(transaction_intent_hash); if let Some(existing_state_version) = maybe_existing_state_version { panic!( "Attempted to save intent hash {:?} which already exists at state version {:?}", - intent_hash, existing_state_version + transaction_intent_hash, existing_state_version ); } db_context .cf(IntentHashesCf) - .put(intent_hash, &state_version); + .put(transaction_intent_hash, &state_version); db_context .cf(NotarizedTransactionHashesCf) .put(notarized_transaction_hash, &state_version); diff --git a/core-rust/state-manager/src/store/traits.rs b/core-rust/state-manager/src/store/traits.rs index 114951b9e3..69bbfc935c 100644 --- a/core-rust/state-manager/src/store/traits.rs +++ b/core-rust/state-manager/src/store/traits.rs @@ -589,7 +589,7 @@ pub mod scenario { pub struct ExecutedScenarioTransaction { pub logical_name: String, pub state_version: StateVersion, - pub intent_hash: TransactionIntentHash, + pub transaction_intent_hash: TransactionIntentHash, } /// A store of testing-specific [`ExecutedScenario`], meant to be as separated as possible from diff --git a/core-rust/state-manager/src/system_commits.rs b/core-rust/state-manager/src/system_commits.rs index 7c860bb0cf..e70d96567f 100644 --- a/core-rust/state-manager/src/system_commits.rs +++ b/core-rust/state-manager/src/system_commits.rs @@ -115,7 +115,7 @@ impl SystemCommitRequestFactory { /// An input to [`SystemCommitRequestFactory::create()`]. pub struct SystemPrepareResult { - pub committed_transactions: Vec, + pub committed_transactions: Vec, pub ledger_hashes: LedgerHashes, pub next_epoch: Option, } @@ -124,7 +124,7 @@ impl SystemPrepareResult { /// Creates an instance for committing the given pre-validated transactions, using the current /// end-state of the given series executor. pub fn from_committed_series( - committed_transactions: Vec, + committed_transactions: Vec, series_executor: TransactionSeriesExecutor, ) -> Self { Self { @@ -137,7 +137,7 @@ impl SystemPrepareResult { /// An output from [`SystemCommitRequestFactory::create()`]. pub struct SystemCommitRequest { - pub transactions: Vec, + pub transactions: Vec, pub proof: LedgerProof, pub require_committed_successes: bool, } @@ -150,7 +150,8 @@ impl SystemCommitRequest { } } -pub struct RawAndValidatedTransaction { +pub struct ProcessedLedgerTransaction { pub raw: RawLedgerTransaction, - pub validated: ValidatedLedgerTransaction, + pub executable: LedgerExecutable, + pub hashes: LedgerTransactionHashes, } diff --git a/core-rust/state-manager/src/system_executor.rs b/core-rust/state-manager/src/system_executor.rs index 1517888922..3d8320d0e7 100644 --- a/core-rust/state-manager/src/system_executor.rs +++ b/core-rust/state-manager/src/system_executor.rs @@ -351,7 +351,7 @@ impl SystemExecutor { fn create_executed_scenario_entry( &self, logical_name: &str, - committed_transactions: &[RawAndValidatedTransaction], + committed_transactions: &[ProcessedLedgerTransaction], committed_transaction_names: Vec<(StateVersion, String)>, output: ScenarioOutput, ) -> ExecutedScenario { @@ -365,7 +365,7 @@ impl SystemExecutor { |(transaction, (state_version, logical_name))| ExecutedScenarioTransaction { logical_name, state_version, - intent_hash: transaction.validated.intent_hash_if_user().unwrap(), + transaction_intent_hash: transaction.hashes.as_user().unwrap().transaction_intent_hash, }, ) .collect(), @@ -398,7 +398,7 @@ fn log_executed_scenario_details(executed_scenario: &ExecutedScenario) { let ExecutedScenarioTransaction { logical_name, state_version, - intent_hash, + transaction_intent_hash: intent_hash, } = committed_transaction; info!( "Committed {} at state version {} ({:?})", @@ -614,34 +614,30 @@ mod tests { &prepare_request.round_history, ); let ledger_round_update = LedgerTransaction::RoundUpdateV1(Box::new(round_update)); - let validated_round_update = ledger_round_update + let round_update_executable = ledger_round_update .to_raw() .unwrap() - .validate(state_manager.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::UserOrValidator) + .create_identifiable_ledger_executable(state_manager.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::UserOrValidator) .expect("expected to be able to prepare the round update transaction"); let round_update_result = series_executor - .execute_and_update_state(&validated_round_update, "cost computation - round update") + .execute_and_update_state( + &round_update_executable.executable, + &round_update_executable.hashes, + "cost computation - round update", + ) .expect("round update rejected"); prepare_request .proposed_transactions .iter() .map(|raw_user_transaction| { - let (_, prepared_transaction) = state_manager + let (_, executable, hashes) = state_manager .preparator - .try_prepare_ledger_transaction_from_user_transaction(raw_user_transaction) - .unwrap(); - - let validated = prepared_transaction - .validate( - state_manager.transaction_validator.read().deref(), - AcceptedLedgerTransactionKind::UserOrValidator, - ) - .unwrap(); + .prepare_known_valid_raw_user_transaction(raw_user_transaction); let execute_result = - series_executor.execute_and_update_state(&validated, "cost computation"); + series_executor.execute_and_update_state(&executable, &hashes, "cost computation"); match execute_result { Ok(commit) => { diff --git a/core-rust/state-manager/src/transaction/executable_logic.rs b/core-rust/state-manager/src/transaction/executable_logic.rs index d7f375c6db..a7ded71cf0 100644 --- a/core-rust/state-manager/src/transaction/executable_logic.rs +++ b/core-rust/state-manager/src/transaction/executable_logic.rs @@ -13,10 +13,12 @@ pub enum ConfigType { Genesis, /// A (non-genesis) system protocol update transaction. ProtocolUpdate, + /// A validator transaction (e.g. round update, which sometimes becomes a much larger epoch change). + Validator, /// A user transaction during regular execution (e.g. prepare or commit). - Regular, + User, /// A user transaction during "committability check" execution (e.g. in mempool). - Pending, + UserAbortingRejectionCheck, /// A user transaction during preview execution. Preview, /// A user transaction during preview execution with auth module disabled. @@ -31,8 +33,8 @@ const PREVIEW_RUNTIME_WARN_THRESHOLD: Duration = Duration::from_millis(500); impl ConfigType { pub fn get_transaction_runtime_warn_threshold(&self) -> Duration { match self { - ConfigType::Genesis | ConfigType::ProtocolUpdate => GENESIS_TRANSACTION_RUNTIME_WARN_THRESHOLD, - ConfigType::Pending => PENDING_UP_TO_FEE_LOAN_RUNTIME_WARN_THRESHOLD, + ConfigType::Genesis | ConfigType::ProtocolUpdate | ConfigType::Validator => GENESIS_TRANSACTION_RUNTIME_WARN_THRESHOLD, + ConfigType::UserAbortingRejectionCheck => PENDING_UP_TO_FEE_LOAN_RUNTIME_WARN_THRESHOLD, ConfigType::Preview | ConfigType::PreviewNoAuth => PREVIEW_RUNTIME_WARN_THRESHOLD, _ => TRANSACTION_RUNTIME_WARN_THRESHOLD, } @@ -64,17 +66,23 @@ impl ExecutionConfigurator { .with_kernel_trace(engine_trace), ), ( - ConfigType::Regular, - ExecutionConfig::for_notarized_transaction(network.clone()) + ConfigType::Validator, + ExecutionConfig::for_validator_transaction(network.clone()) .with_no_fees(no_fees) .with_kernel_trace(engine_trace), ), ( - ConfigType::Pending, + ConfigType::User, ExecutionConfig::for_notarized_transaction(network.clone()) .with_no_fees(no_fees) .with_kernel_trace(engine_trace), ), + ( + ConfigType::UserAbortingRejectionCheck, + ExecutionConfig::for_notarized_transaction_rejection_check(network.clone()) + .with_no_fees(no_fees) + .with_kernel_trace(engine_trace), + ), ( ConfigType::Preview, ExecutionConfig::for_preview(network.clone()).with_no_fees(no_fees), @@ -88,74 +96,71 @@ impl ExecutionConfigurator { } /// Wraps the given `Executable` with a configuration resolved from its `ConfigType`. - pub fn wrap_ledger_transaction( - &self, - transaction: &ValidatedLedgerTransaction, + pub fn wrap_ledger_transaction<'a>( + &'a self, + transaction_hashes: &LedgerTransactionHashes, + ledger_executable: &'a LedgerExecutable, description: impl ToString, ) -> ConfiguredExecutable { + match ledger_executable { + LedgerExecutable::GenesisFlash => ConfiguredExecutable::SystemFlash { + state_updates: create_system_bootstrap_flash_state_updates(), + }, + LedgerExecutable::Flash { updates } => ConfiguredExecutable::SystemFlash { + state_updates: updates.clone(), + }, + LedgerExecutable::Transaction { executable } => { + let config_type = match &transaction_hashes.kinded { + KindedTransactionHashes::Genesis { .. } => ConfigType::Genesis, + KindedTransactionHashes::User(..) => ConfigType::User, + KindedTransactionHashes::RoundUpdateV1 { .. } => ConfigType::Validator, + KindedTransactionHashes::FlashV1 { .. } => ConfigType::ProtocolUpdate, + }; - match &transaction.inner { - ValidatedLedgerTransactionInner::Genesis(PreparedGenesisTransaction::Flash(..)) => { - return ConfiguredExecutable::GenesisFlash { - flash_receipt: create_substate_flash_for_genesis(), - } + self.wrap_transaction( + executable, + config_type, + description.to_string(), + ) }, - ValidatedLedgerTransactionInner::ProtocolUpdate(flash) => { - return ConfiguredExecutable::SystemFlash { - state_updates: flash.state_updates.clone(), - } - } - _ => {}, } - - let config_type = match &transaction.inner { - ValidatedLedgerTransactionInner::Genesis(_) => ConfigType::Genesis, - ValidatedLedgerTransactionInner::User(_) => ConfigType::Regular, - ValidatedLedgerTransactionInner::Validator(_) => ConfigType::Regular, - ValidatedLedgerTransactionInner::ProtocolUpdate(_) => ConfigType::ProtocolUpdate, - }; - - self.wrap_transaction( - transaction.clone().create_executable().expect("Should not be a flash transaction"), - config_type, - description.to_string(), - ) } pub fn wrap_pending_transaction<'a>( &'a self, - transaction: &ValidatedUserTransaction, + executable: &'a ExecutableTransaction, + user_hashes: &UserTransactionHashes, ) -> ConfiguredExecutable<'a> { - let intent_hash = transaction.transaction_intent_hash(); self.wrap_transaction( - transaction.clone().create_executable().abort_when_loan_repaid(), - ConfigType::Pending, + executable, + ConfigType::UserAbortingRejectionCheck, format!( "pending intent hash {:?}, up to fee loan", - intent_hash, + &user_hashes.transaction_intent_hash, ), ) } - pub fn wrap_preview_transaction( - &self, - validated_preview_intent: ValidatedPreviewIntent, - ) -> ConfiguredExecutable { - let config_type = if validated_preview_intent.flags.disable_auth { + pub fn wrap_preview_transaction<'a>( + &'a self, + executable: &'a ExecutableTransaction, + disable_auth: bool, + ) -> ConfiguredExecutable<'a> { + let config_type = if disable_auth { ConfigType::PreviewNoAuth } else { ConfigType::Preview }; self.wrap_transaction( - validated_preview_intent.into_executable_transaction(), + executable, config_type, "preview".to_string(), ) } - fn wrap_transaction( - &self, - executable: ExecutableTransaction, + fn wrap_transaction<'a>( + &'a self, + executable: &'a ExecutableTransaction, config_type: ConfigType, description: String, ) -> ConfiguredExecutable { @@ -171,14 +176,11 @@ impl ExecutionConfigurator { /// An `Executable` transaction bound to a specific execution configuration. pub enum ConfiguredExecutable<'a> { - GenesisFlash { - flash_receipt: FlashReceipt, - }, SystemFlash { state_updates: StateUpdates, }, Transaction { - executable: ExecutableTransaction, + executable: &'a ExecutableTransaction, vm_modules: &'a DefaultVmModules, execution_config: &'a ExecutionConfig, threshold: Duration, @@ -189,28 +191,8 @@ pub enum ConfiguredExecutable<'a> { impl<'a, S: SubstateDatabase> TransactionLogic for ConfiguredExecutable<'a> { fn execute_on(self, store: &S) -> TransactionReceipt { match self { - ConfiguredExecutable::GenesisFlash { flash_receipt } => flash_receipt.into(), ConfiguredExecutable::SystemFlash { state_updates } => { - let application_events = Vec::new(); - let system_structure = - SystemStructure::resolve(store, &state_updates, &application_events); - let new_node_ids = collect_new_node_ids(&state_updates); - let state_update_summary = - StateUpdateSummary::new(store, new_node_ids, &state_updates); - - let commit_result = CommitResult { - state_updates, - state_update_summary, - fee_source: Default::default(), - fee_destination: Default::default(), - outcome: TransactionOutcome::Success(vec![]), - application_events, - application_logs: vec![], - system_structure, - execution_trace: None, - }; - - TransactionReceipt::empty_with_commit(commit_result) + FlashReceipt::from_state_updates(state_updates, store).into() } ConfiguredExecutable::Transaction { executable, diff --git a/core-rust/state-manager/src/transaction/preparation.rs b/core-rust/state-manager/src/transaction/preparation.rs index 83281d5263..75ba057c33 100644 --- a/core-rust/state-manager/src/transaction/preparation.rs +++ b/core-rust/state-manager/src/transaction/preparation.rs @@ -96,7 +96,7 @@ impl Preparator { let raw = LedgerTransaction::Genesis(Box::new(genesis_transaction)) .to_raw() .expect("Could not encode genesis transaction"); - let validated = raw.validate( + let IdentifiedLedgerExecutable { executable, hashes, } = raw.create_identifiable_ledger_executable( self.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::GenesisOnly, ) @@ -108,12 +108,12 @@ impl Preparator { .start_series_execution(database.deref()); series_executor - .execute_and_update_state(&validated, "genesis") + .execute_and_update_state(&executable, &hashes, "genesis") .expect("genesis not committable") .expect_success("genesis"); SystemPrepareResult::from_committed_series( - vec![RawAndValidatedTransaction { raw, validated }], + vec![ProcessedLedgerTransaction { raw, executable, hashes, }], series_executor, ) } @@ -132,18 +132,18 @@ impl Preparator { let raw = LedgerTransaction::FlashV1(Box::new(flash_transaction)) .to_raw() .expect("Could not encode protocol update transaction"); - let validated = raw.validate( + let IdentifiedLedgerExecutable { executable, hashes } = raw.create_identifiable_ledger_executable( self.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::ProtocolUpdateOnly, ) .expect("Could not prepare and validate protocol update transaction"); series_executor - .execute_and_update_state(&validated, "protocol update") + .execute_and_update_state(&executable, &hashes, "protocol update") .expect("protocol update not committable") .expect_success("protocol update"); - committed_transactions.push(RawAndValidatedTransaction { raw, validated }); + committed_transactions.push(ProcessedLedgerTransaction { raw, executable, hashes }); } SystemPrepareResult::from_committed_series(committed_transactions, series_executor) @@ -206,28 +206,23 @@ impl Preparator { series_executor: &mut TransactionSeriesExecutor, scenario_name: &str, next: &NextTransaction, - ) -> (RawAndValidatedTransaction, TransactionReceipt) { + ) -> (ProcessedLedgerTransaction, TransactionReceipt) { let qualified_name = format!( "{} scenario - {} transaction", scenario_name, &next.logical_name ); - let (raw, prepared) = self - .try_prepare_ledger_transaction_from_user_transaction(&next.raw_transaction) - .unwrap_or_else(|_| panic!("cannot prepare {}", qualified_name)); - - let validated = prepared - .validate(self.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::UserOrValidator) - .unwrap_or_else(|_| panic!("{} not valid", qualified_name)); + let (raw, executable, hashes) = self + .prepare_known_valid_raw_user_transaction(&next.raw_transaction); series_executor .capture_next_engine_receipt() - .execute_and_update_state(&validated, qualified_name.as_str()) + .execute_and_update_state(&executable, &hashes, qualified_name.as_str()) .ok(); // we need to consume the `Result<>`, but we actually only care about the receipt let engine_receipt = series_executor.retrieve_captured_engine_receipt(); ( - RawAndValidatedTransaction { raw, validated }, + ProcessedLedgerTransaction { raw, executable, hashes }, engine_receipt, ) } @@ -269,14 +264,14 @@ impl Preparator { // TODO(optimization-only): We could avoid the hashing, decoding, signature verification // and executable creation by accessing the execution cache in a more clever way. let validated = raw_ancestor - .validate( + .create_identifiable_ledger_executable( self.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::UserOrValidator, ) .expect("Ancestor transactions should be valid"); series_executor - .execute_and_update_state(&validated, "ancestor") + .execute_and_update_state(&validated.executable, &validated.hashes, "ancestor") .expect("ancestor transaction rejected"); } @@ -308,7 +303,7 @@ impl Preparator { .expect("Expected round update to be encodable"); let validated_round_update = raw_ledger_round_update - .validate( + .create_identifiable_ledger_executable( self.transaction_validator.read().deref(), AcceptedLedgerTransactionKind::ValidatorOnly, ) @@ -320,7 +315,11 @@ impl Preparator { .expect("round update transaction should fit inside of empty vertex"); let round_update_result = series_executor - .execute_and_update_state(&validated_round_update, "round update") + .execute_and_update_state( + &validated_round_update.executable, + &validated_round_update.hashes, + "round update", + ) .expect("round update rejected"); vertex_limits_tracker @@ -338,9 +337,9 @@ impl Preparator { committable_transactions.push(CommittableTransaction { index: None, raw: raw_ledger_round_update, - intent_hash: None, + transaction_intent_hash: None, notarized_transaction_hash: None, - ledger_transaction_hash: validated_round_update.ledger_transaction_hash(), + ledger_transaction_hash: validated_round_update.hashes.ledger_transaction_hash, }); //======================================================================================== @@ -388,61 +387,38 @@ impl Preparator { continue; } - let try_prepare_result = - self.try_prepare_ledger_transaction_from_user_transaction(&raw_user_transaction); + let mut prepared_details = CaptureSupport::Expecting; + let handle_result = + self.prepare_raw_user_transaction(&raw_user_transaction, &mut prepared_details); - let (raw_ledger_transaction, prepared_transaction) = match try_prepare_result { + let (raw_ledger_transaction, executable) = match handle_result { Ok(results) => results, Err(error) => { - rejected_transactions.push(RejectedTransaction { - index: index as u32, - intent_hash: None, - notarized_transaction_hash: None, - ledger_transaction_hash: None, - error: format!("{error:?}"), - }); + let error_message = format!("{error:?}"); + match prepared_details.into_option() { + Some(prepared_details) => { + let ledger_hash = prepared_details.hashes.ledger_transaction_hash; + let user_hashes = prepared_details.hashes.as_user().unwrap(); + rejected_transactions.push(RejectedTransaction::new(index, error_message, ledger_hash, user_hashes)); + pending_transaction_results.push(PendingTransactionResult { + transaction_intent_hash: user_hashes.transaction_intent_hash, + notarized_transaction_hash: user_hashes.notarized_transaction_hash, + invalid_at_epoch: prepared_details.end_epoch_exclusive, + rejection_reason: Some(error.into()), + }); + }, + None => { + rejected_transactions.push(RejectedTransaction::failed_before_prepare(index, error_message)) + }, + }; continue; } }; - let prepared_user_transaction = prepared_transaction - .as_user() - .expect("Proposed was created from user"); - - let intent_hash = prepared_user_transaction.transaction_intent_hash(); - let notarized_transaction_hash = prepared_user_transaction.notarized_transaction_hash(); - let ledger_transaction_hash = prepared_transaction.ledger_transaction_hash(); - let invalid_at_epoch = prepared_user_transaction.end_epoch_exclusive(); - - // TODO(optimization-only): We could avoid signature verification by re-using the - // validated transaction from the mempool. - let validate_result = prepared_transaction - .validate( - self.transaction_validator.read().deref(), - AcceptedLedgerTransactionKind::UserOrValidator, - ); - - let validated = match validate_result { - Ok(validated) => validated, - Err(error) => { - rejected_transactions.push(RejectedTransaction { - index: index as u32, - intent_hash: Some(intent_hash), - notarized_transaction_hash: Some(notarized_transaction_hash), - ledger_transaction_hash: Some(ledger_transaction_hash), - error: format!("{:?}", &error), - }); - pending_transaction_results.push(PendingTransactionResult { - intent_hash, - notarized_transaction_hash, - invalid_at_epoch, - rejection_reason: Some(MempoolRejectionReason::ValidationError( - error.into_user_validation_error(), - )), - }); - continue; - } - }; + let prepared_details = prepared_details.retrieve_captured(); + let user_hashes = prepared_details.hashes.as_user().unwrap(); + let ledger_transaction_hash = prepared_details.hashes.ledger_transaction_hash; + let invalid_at_epoch = prepared_details.end_epoch_exclusive; // Note that we're using a "_no_state_update" variant here, because // we may still reject some *committable* transactions if they exceed @@ -450,7 +426,11 @@ impl Preparator { // So it's important to manually update the state if the transaction // is to be included (that's the `series_executor.update_state(...)` call below). let execute_result = - series_executor.execute_no_state_update(&validated, "newly proposed"); + series_executor.execute_no_state_update( + &executable, + &prepared_details.hashes, + "newly proposed", + ); match execute_result { Ok(processed_commit_result) => { match vertex_limits_tracker.try_next_transaction( @@ -464,16 +444,10 @@ impl Preparator { // We're including the transaction, so updating the executor state series_executor.update_state(&processed_commit_result); committed_proposal_size += transaction_size; - committable_transactions.push(CommittableTransaction { - index: Some(index as u32), - raw: raw_ledger_transaction, - intent_hash: Some(intent_hash), - notarized_transaction_hash: Some(notarized_transaction_hash), - ledger_transaction_hash, - }); + committable_transactions.push(CommittableTransaction::new(index, raw_ledger_transaction, ledger_transaction_hash, user_hashes)); pending_transaction_results.push(PendingTransactionResult { - intent_hash, - notarized_transaction_hash, + transaction_intent_hash: user_hashes.transaction_intent_hash, + notarized_transaction_hash: user_hashes.notarized_transaction_hash, invalid_at_epoch, rejection_reason: None, }); @@ -487,13 +461,12 @@ impl Preparator { } } Err(error) => { - rejected_transactions.push(RejectedTransaction { - index: index as u32, - intent_hash: Some(intent_hash), - notarized_transaction_hash: Some(notarized_transaction_hash), - ledger_transaction_hash: Some(ledger_transaction_hash), - error: format!("{:?}", &error), - }); + rejected_transactions.push(RejectedTransaction::new( + index, + format!("{:?}", &error), + ledger_transaction_hash, + user_hashes + )); // In order to mitigate the worst-case scenario where the proposal contains lots of small // transactions that take maximum amount of time to execute, we stop right after first // exceeded vertex limit. @@ -508,21 +481,21 @@ impl Preparator { result, fee_summary, }) => { - rejected_transactions.push(RejectedTransaction { - index: index as u32, - intent_hash: Some(intent_hash), - notarized_transaction_hash: Some(notarized_transaction_hash), - ledger_transaction_hash: Some(ledger_transaction_hash), - error: format!("{:?}", &result.reason), - }); + let error_message = format!("{:?}", &result.reason); pending_transaction_results.push(PendingTransactionResult { - intent_hash, - notarized_transaction_hash, + transaction_intent_hash: user_hashes.transaction_intent_hash, + notarized_transaction_hash: user_hashes.notarized_transaction_hash, invalid_at_epoch, rejection_reason: Some(MempoolRejectionReason::FromExecution(Box::new( result.reason, ))), }); + rejected_transactions.push(RejectedTransaction::new( + index, + error_message, + ledger_transaction_hash, + user_hashes, + )); // We want to account for rejected execution costs too and stop accordingly since // executing the maximum number of (rejected) transactions in a proposal for the @@ -551,7 +524,7 @@ impl Preparator { timestamp: pending_transaction_timestamp, }; write_pending_transaction_result_cache.track_transaction_result( - pending_transaction_result.intent_hash, + pending_transaction_result.transaction_intent_hash, pending_transaction_result.notarized_transaction_hash, Some(pending_transaction_result.invalid_at_epoch), attempt, @@ -574,27 +547,56 @@ impl Preparator { } } - // only public for test purposes - pub fn try_prepare_ledger_transaction_from_user_transaction( + pub fn prepare_known_valid_raw_user_transaction( + &self, + raw_user_transaction: &RawNotarizedTransaction, + ) -> (RawLedgerTransaction, LedgerExecutable, LedgerTransactionHashes) { + let mut details = CaptureSupport::Expecting; + let (raw, executable) = self.prepare_raw_user_transaction(raw_user_transaction, &mut details) + .expect("The caller should have certainty the user transaction should be valid"); + (raw, executable, details.retrieve_captured().hashes) + } + + pub fn prepare_raw_user_transaction( &self, raw_user_transaction: &RawNotarizedTransaction, - ) -> Result<(RawLedgerTransaction, PreparedLedgerTransaction), TransactionValidationError> { + prepared_details: &mut CaptureSupport, + ) -> Result<(RawLedgerTransaction, LedgerExecutable), TransactionValidationError> { let user_transaction = raw_user_transaction.into_typed().map_err(PrepareError::DecodeError)?; let raw = LedgerTransaction::from(user_transaction) .to_raw() .map_err(PrepareError::EncodeError)?; let prepared = raw.prepare(self.transaction_validator.read().preparation_settings())?; - Ok((raw, prepared)) + prepared_details.capture_if_required(|| { + let hashes = prepared.create_hashes(); + let end_epoch_exclusive = prepared.as_user().unwrap().end_epoch_exclusive(); + PreparedUserTransactionDetails { + hashes, + end_epoch_exclusive, + } + }); + let executable = prepared.validate( + self.transaction_validator.read().deref(), + AcceptedLedgerTransactionKind::UserOnly, + ) + .map_err(|err| err.into_user_validation_error())? + .create_ledger_executable(); + Ok((raw, executable)) } } +pub struct PreparedUserTransactionDetails { + pub hashes: LedgerTransactionHashes, + pub end_epoch_exclusive: Epoch, +} + pub struct PreparedScenarioMetadata { pub committed_transaction_names: Vec<(StateVersion, String)>, pub end_state: EndState, } struct PendingTransactionResult { - pub intent_hash: TransactionIntentHash, + pub transaction_intent_hash: TransactionIntentHash, pub notarized_transaction_hash: NotarizedTransactionHash, pub invalid_at_epoch: Epoch, pub rejection_reason: Option, diff --git a/core-rust/state-manager/src/transaction/preview.rs b/core-rust/state-manager/src/transaction/preview.rs index 803b484023..5023cf01cf 100644 --- a/core-rust/state-manager/src/transaction/preview.rs +++ b/core-rust/state-manager/src/transaction/preview.rs @@ -58,9 +58,11 @@ impl TransactionPreviewer { .read() .validate_preview_intent_v1(intent) .map_err(PreviewError::TransactionValidationError)?; + let disable_auth = validated.flags.disable_auth; + let executable = validated.create_executable(); let transaction_logic = self .execution_configurator - .wrap_preview_transaction(validated); + .wrap_preview_transaction(&executable, disable_auth); let receipt = transaction_logic.execute_on(&database); let (state_changes, global_balance_summary) = match &receipt.result { @@ -111,8 +113,8 @@ impl TransactionPreviewer { PublicKey::Secp256k1(Secp256k1PrivateKey::from_u64(2).unwrap().public_key()) }); let (max_epoch_range, network_id) = { - let config = self.transaction_validator.read().config(); - (config.max_epoch_range, config) + let validator = self.transaction_validator.read(); + (validator.config().max_epoch_range, validator.network_id().unwrap()) }; let start_epoch_inclusive = start_epoch_inclusive.unwrap_or(at_epoch); let end_epoch_exclusive = end_epoch_exclusive.unwrap_or_else(|| { diff --git a/core-rust/state-manager/src/transaction/series_execution.rs b/core-rust/state-manager/src/transaction/series_execution.rs index b3077546be..ba5d808901 100644 --- a/core-rust/state-manager/src/transaction/series_execution.rs +++ b/core-rust/state-manager/src/transaction/series_execution.rs @@ -134,10 +134,11 @@ where /// the transaction's ledger hash). pub fn execute_and_update_state( &mut self, - transaction: &ValidatedLedgerTransaction, + executable: &LedgerExecutable, + hashes: &LedgerTransactionHashes, description: &str, ) -> Result { - let result = self.execute_no_state_update(transaction, description); + let result = self.execute_no_state_update(executable, hashes, description); if let Ok(commit) = &result { self.update_state(commit); } @@ -152,17 +153,18 @@ where /// the transaction's ledger hash). pub fn execute_no_state_update( &mut self, - transaction: &ValidatedLedgerTransaction, + executable: &LedgerExecutable, + hashes: &LedgerTransactionHashes, description: &str, ) -> Result { let described_ledger_transaction_hash = DescribedTransactionHash { - ledger_hash: transaction.ledger_transaction_hash(), + ledger_hash: hashes.ledger_transaction_hash, description, }; self.execute_wrapped_no_state_update( &described_ledger_transaction_hash, self.execution_configurator - .wrap_ledger_transaction(transaction, &described_ledger_transaction_hash), + .wrap_ledger_transaction(hashes, executable, &described_ledger_transaction_hash), ) } diff --git a/core-rust/state-manager/src/transaction/validation.rs b/core-rust/state-manager/src/transaction/validation.rs index c3219df069..907e63db01 100644 --- a/core-rust/state-manager/src/transaction/validation.rs +++ b/core-rust/state-manager/src/transaction/validation.rs @@ -60,14 +60,15 @@ impl CommittabilityValidator { /// Determine whether it would be rejected given the current state of the substate store. pub fn check_for_rejection( &self, - transaction: &ValidatedUserTransaction, + executable: &ExecutableTransaction, + user_hashes: &UserTransactionHashes, timestamp: SystemTime, ) -> TransactionAttempt { let database = self.database.snapshot(); let executed_at_state_version = database.max_state_version(); let existing = - database.get_txn_state_version_by_identifier(&transaction.transaction_intent_hash()); + database.get_txn_state_version_by_identifier(&user_hashes.transaction_intent_hash); if let Some(state_version) = existing { let committed_transaction_identifiers = database @@ -77,12 +78,11 @@ impl CommittabilityValidator { return TransactionAttempt { rejection: Some(MempoolRejectionReason::AlreadyCommitted( AlreadyCommittedError { - notarized_transaction_hash: transaction.notarized_transaction_hash(), + notarized_transaction_hash: user_hashes.notarized_transaction_hash, committed_state_version: state_version, - committed_notarized_transaction_hash: *committed_transaction_identifiers - .payload - .typed - .user() + committed_notarized_transaction_hash: committed_transaction_identifiers + .transaction_hashes + .as_user() .expect("non-user transaction located by intent hash") .notarized_transaction_hash, }, @@ -96,7 +96,7 @@ impl CommittabilityValidator { let receipt = self .execution_configurator - .wrap_pending_transaction(transaction) + .wrap_pending_transaction(&executable, user_hashes) .execute_on(database.deref()); let result = match receipt.result { @@ -107,7 +107,7 @@ impl CommittabilityValidator { ) { panic!( "intent {:?} not found by Node, but reported as committed by Engine", - transaction.transaction_intent_hash() + user_hashes.transaction_intent_hash ); } Err(MempoolRejectionReason::FromExecution(Box::new(reason))) @@ -183,7 +183,7 @@ impl CachedCommittabilityValidator { .track_transaction_result( metadata.intent_hash, metadata.notarized_transaction_hash, - Some(metadata.invalid_from_epoch), + Some(metadata.end_epoch_exclusive), attempt, ) } @@ -192,29 +192,20 @@ impl CachedCommittabilityValidator { struct TransactionMetadata { intent_hash: TransactionIntentHash, notarized_transaction_hash: NotarizedTransactionHash, - invalid_from_epoch: Epoch, + end_epoch_exclusive: Epoch, } impl TransactionMetadata { - pub fn read_from_validated(validated: &ValidatedUserTransaction) -> Self { + pub fn read_from_user_executable( + executable: &ExecutableTransaction, + user_hashes: &UserTransactionHashes, + ) -> Self { Self { - intent_hash: validated.transaction_intent_hash(), - notarized_transaction_hash: validated.notarized_transaction_hash(), - invalid_from_epoch: match validated { - #[allow(deprecated)] - ValidatedUserTransaction::V1(validated) => { - validated - .prepared - .signed_intent - .intent - .header - .inner - .end_epoch_exclusive - }, - ValidatedUserTransaction::V2(validated) => { - validated.overall_validity_range.epoch_range.end_epoch_exclusive - }, - } + intent_hash: user_hashes.transaction_intent_hash, + notarized_transaction_hash: user_hashes.notarized_transaction_hash, + end_epoch_exclusive: executable.overall_epoch_range() + .expect("User executable transactions should have an epoch range") + .end_epoch_exclusive } } @@ -222,7 +213,7 @@ impl TransactionMetadata { Self { intent_hash: prepared.transaction_intent_hash(), notarized_transaction_hash: prepared.notarized_transaction_hash(), - invalid_from_epoch: match prepared { + end_epoch_exclusive: match prepared { #[allow(deprecated)] PreparedUserTransaction::V1(prepared) => { prepared @@ -267,7 +258,10 @@ impl CheckMetadata { } pub enum StaticValidation { - Valid(Box), + Valid { + executable: ExecutableTransaction, + user_hashes: UserTransactionHashes, + }, Invalid, } @@ -307,11 +301,16 @@ impl CachedCommittabilityValidator { match read_committability_validator.validate(prepared) { Ok(validated) => { // Transaction was valid - let's also attempt to execute it + let user_hashes = validated.hashes(); + let executable = validated.create_executable(); let attempt = - read_committability_validator.check_for_rejection(&validated, current_time); + read_committability_validator.check_for_rejection(&executable, &user_hashes, current_time); ( self.write_attempt(metadata, attempt), - CheckMetadata::Fresh(StaticValidation::Valid(Box::new(validated))), + CheckMetadata::Fresh(StaticValidation::Valid { + executable, + user_hashes, + }), ) } Err(validation_error) => { @@ -339,14 +338,15 @@ impl CachedCommittabilityValidator { /// Returns the transaction's new pending transaction record. pub fn check_for_rejection_validated( &self, - validated: &ValidatedUserTransaction, + executable: &ExecutableTransaction, + user_hashes: &UserTransactionHashes, ) -> PendingTransactionRecord { - let metadata = TransactionMetadata::read_from_validated(validated); + let metadata = TransactionMetadata::read_from_user_executable(&executable, &user_hashes); let attempt = self .committability_validator .read() - .check_for_rejection(validated, SystemTime::now()); + .check_for_rejection(executable, user_hashes, SystemTime::now()); self.write_attempt(metadata, attempt) } diff --git a/core-rust/state-manager/src/types.rs b/core-rust/state-manager/src/types.rs index 9a30c7d579..ca35a655f0 100644 --- a/core-rust/state-manager/src/types.rs +++ b/core-rust/state-manager/src/types.rs @@ -365,21 +365,55 @@ pub struct CommittableTransaction { /// Not included for the Round Change transaction which is inserted and doesn't come from the proposal pub index: Option, pub raw: RawLedgerTransaction, - pub intent_hash: Option, + pub transaction_intent_hash: Option, pub notarized_transaction_hash: Option, pub ledger_transaction_hash: LedgerTransactionHash, } +impl CommittableTransaction { + pub fn new(index: usize, raw: RawLedgerTransaction, ledger_transaction_hash: LedgerTransactionHash, user_hashes: UserTransactionHashes) -> Self { + Self { + index: Some(index.try_into().expect("Proposal index should be < u32::MAX")), + raw, + transaction_intent_hash: Some(user_hashes.transaction_intent_hash), + notarized_transaction_hash: Some(user_hashes.notarized_transaction_hash), + ledger_transaction_hash, + } + } +} + #[derive(Debug, ScryptoSbor)] pub struct RejectedTransaction { pub index: u32, // Note - these are None if the transaction can't even be prepared to determine the hashes - pub intent_hash: Option, + pub transaction_intent_hash: Option, pub notarized_transaction_hash: Option, pub ledger_transaction_hash: Option, pub error: String, } +impl RejectedTransaction { + pub fn failed_before_prepare(index: usize, error: String) -> Self { + Self { + index: index.try_into().expect("Proposal index should be < u32::MAX"), + transaction_intent_hash: None, + notarized_transaction_hash: None, + ledger_transaction_hash: None, + error, + } + } + + pub fn new(index: usize, error: String, ledger_transaction_hash: LedgerTransactionHash, user_hashes: UserTransactionHashes) -> Self { + Self { + index: index.try_into().expect("Proposal index should be < u32::MAX"), + transaction_intent_hash: Some(user_hashes.transaction_intent_hash), + notarized_transaction_hash: Some(user_hashes.notarized_transaction_hash), + ledger_transaction_hash: Some(ledger_transaction_hash), + error, + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, ScryptoSbor)] pub struct ActiveValidatorInfo { pub address: ComponentAddress, diff --git a/core/src/main/java/com/radixdlt/ledger/LedgerProofBundle.java b/core/src/main/java/com/radixdlt/ledger/LedgerProofBundle.java index 03ee3664a6..909e4dc473 100644 --- a/core/src/main/java/com/radixdlt/ledger/LedgerProofBundle.java +++ b/core/src/main/java/com/radixdlt/ledger/LedgerProofBundle.java @@ -79,12 +79,12 @@ public record LedgerProofBundle( LedgerProof primaryProof, // Latest (with respect to `primaryProof`) epoch change proof. // Could be the `primaryProof` itself. - LedgerProof closestEpochProofOnOrBefore, + LedgerProof latestProofWhichInitiatedAnEpochChange, // Latest (with respect to primaryProof) proof that initiated a protocol update. // Could be the `primaryProof` itself. - Option closestProtocolUpdateInitProofOnOrBefore, + Option latestProofWhichInitiatedAProtocolUpdate, // Latest (with respect to `primaryProof`) proof of ProtocolUpdate `origin`. - Option closestProtocolUpdateExecutionProofOnOrBefore) { + Option latestProtocolUpdateExecutionProof) { public static LedgerProofBundle mockedOfHeader(com.radixdlt.consensus.LedgerHeader ledgerHeader) { final var proof = @@ -101,20 +101,20 @@ public static LedgerProofBundle mockedOfHeader(com.radixdlt.consensus.LedgerHead * proof created during the protocol update. */ public LedgerHeader epochInitialHeader() { - return closestProtocolUpdateExecutionProofOnOrBefore + return latestProtocolUpdateExecutionProof .map( protocolUpdateExecutionProof -> { - // If we have executed some protocol updates, check if the latest + // If we have executed some protocoxl updates, check if the latest // proof we have is actually newer than the real epoch proof. // If so, use it instead of an epoch proof. if (protocolUpdateExecutionProof.stateVersion() - >= closestEpochProofOnOrBefore.stateVersion()) { + >= latestProofWhichInitiatedAnEpochChange.stateVersion()) { return protocolUpdateExecutionProof.ledgerHeader(); } else { - return closestEpochProofOnOrBefore.ledgerHeader(); + return latestProofWhichInitiatedAnEpochChange.ledgerHeader(); } }) - .orElse(closestEpochProofOnOrBefore.ledgerHeader()); + .orElse(latestProofWhichInitiatedAnEpochChange.ledgerHeader()); } /** @@ -129,7 +129,7 @@ public Round resultantRound() { case LedgerProofOrigin.ProtocolUpdate protocolUpdate -> // This assumes that protocol updates always happen at epoch boundary // (which is true, for now) - closestProtocolUpdateInitProofOnOrBefore.unwrap().ledgerHeader(); + latestProofWhichInitiatedAProtocolUpdate.unwrap().ledgerHeader(); }; return maybeEpochChangeHeader.nextEpoch().isPresent() @@ -138,7 +138,7 @@ public Round resultantRound() { } public long resultantEpoch() { - return closestEpochProofOnOrBefore.ledgerHeader().nextEpoch().unwrap().epoch().toLong(); + return latestProofWhichInitiatedAnEpochChange.ledgerHeader().nextEpoch().unwrap().epoch().toLong(); } public long resultantStateVersion() { @@ -161,7 +161,7 @@ public LedgerProof trimProtocolUpdate() { // Since protocol updates can't themselves trigger another protocol update, // this is guaranteed to be a consensus (or, possibly in some testing corner case, genesis) // proof. - closestProtocolUpdateInitProofOnOrBefore().unwrap(); + latestProofWhichInitiatedAProtocolUpdate().unwrap(); }; } } diff --git a/core/src/main/java/com/radixdlt/logger/EventLoggerModule.java b/core/src/main/java/com/radixdlt/logger/EventLoggerModule.java index 21eab121f9..e2804c78e1 100644 --- a/core/src/main/java/com/radixdlt/logger/EventLoggerModule.java +++ b/core/src/main/java/com/radixdlt/logger/EventLoggerModule.java @@ -227,7 +227,7 @@ private static void logProtocolUpdate(LedgerProofBundle proof) { } else if (proof.primaryProof().origin() instanceof LedgerProofOrigin.ProtocolUpdate) { // Protocol update init proof must be present if latest proof is of ProtocolUpdate origin. final var protocolUpdateInitHeader = - proof.closestProtocolUpdateInitProofOnOrBefore().unwrap().ledgerHeader(); + proof.latestProofWhichInitiatedAProtocolUpdate().unwrap().ledgerHeader(); final var postProtocolUpdateHeader = proof.primaryProof().ledgerHeader(); final var initStateVersion = protocolUpdateInitHeader.stateVersion().toLong(); final var postStateVersion = postProtocolUpdateHeader.stateVersion().toLong(); diff --git a/core/src/main/java/com/radixdlt/monitoring/InMemorySystemInfo.java b/core/src/main/java/com/radixdlt/monitoring/InMemorySystemInfo.java index c1f66462b6..9cca63817b 100644 --- a/core/src/main/java/com/radixdlt/monitoring/InMemorySystemInfo.java +++ b/core/src/main/java/com/radixdlt/monitoring/InMemorySystemInfo.java @@ -86,7 +86,7 @@ public InMemorySystemInfo( ProtocolState initialProtocolState) { this.rustStateReader = rustStateReader; final var latestEpochChange = - latestProof.closestEpochProofOnOrBefore().ledgerHeader().nextEpoch().unwrap(); + latestProof.latestProofWhichInitiatedAnEpochChange().ledgerHeader().nextEpoch().unwrap(); this.state = new InMemorySystemInfoState( initialProtocolState, diff --git a/core/src/main/java/com/radixdlt/rev2/REv2StateComputer.java b/core/src/main/java/com/radixdlt/rev2/REv2StateComputer.java index 939d6a29f1..6518f6d426 100644 --- a/core/src/main/java/com/radixdlt/rev2/REv2StateComputer.java +++ b/core/src/main/java/com/radixdlt/rev2/REv2StateComputer.java @@ -99,6 +99,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import static com.radixdlt.lang.Option.none; + /** REv2 State Computer implementation */ public final class REv2StateComputer implements StateComputerLedger.StateComputer { private static final Logger log = LogManager.getLogger(); @@ -285,14 +287,13 @@ public StateComputerLedger.StateComputerPrepareResult prepare( @Override public LedgerProofBundle commit(LedgerExtension ledgerExtension, VertexStoreState vertexStore) { final var proof = ledgerExtension.proof(); - final var header = proof.ledgerHeader(); final Option vertexStoreBytes; if (vertexStore != null) { vertexStoreBytes = Option.some(serialization.toDson(vertexStore.toSerialized(), DsonOutput.Output.ALL)); } else { - vertexStoreBytes = Option.none(); + vertexStoreBytes = none(); } var commitRequest = @@ -308,43 +309,51 @@ public LedgerProofBundle commit(LedgerExtension ledgerExtension, VertexStoreStat }) .unwrap(); - final var maybeNextEpoch = header.nextEpoch(); - final var maybeNextProtocolVersion = header.nextProtocolVersion(); + // Prepare to update the latestProof bundle + var newLatestProof = proof; + var latestProofWhichInitiatedAnEpochChange = this.latestProof.latestProofWhichInitiatedAnEpochChange(); + var latestProofWhichInitiatedAProtocolUpdate = this.latestProof.latestProofWhichInitiatedAProtocolUpdate(); + var latestProtocolUpdateExecutionProof = this.latestProof.latestProtocolUpdateExecutionProof(); - if (maybeNextProtocolVersion.isPresent() && maybeNextEpoch.isEmpty()) { - throw new IllegalStateException("Protocol updates must happen at epoch boundary"); + if (newLatestProof.ledgerHeader().nextProtocolVersion().isPresent() && newLatestProof.ledgerHeader().nextEpoch().isEmpty()) { + throw new IllegalStateException("Initial protocol update triggers must happen at epoch boundary"); } - // Synchronously apply a protocol update while we still hold a StateComputerResult lock - final var maybePostProtocolUpdateProof = - maybeNextProtocolVersion.map( - nextProtocolVersion -> - this.rustProtocolUpdate.applyProtocolUpdate(nextProtocolVersion).postUpdateProof()); - - final var newLatestProof = maybePostProtocolUpdateProof.orElse(proof); + // A loop here allows us to enact protocol updates back-to-back + var lastHandledProof = newLatestProof; + Option maybeNextEpoch = none(); + do { + final var newHeader = newLatestProof.ledgerHeader(); + if (newHeader.nextEpoch().isPresent()) { + latestProofWhichInitiatedAnEpochChange = newLatestProof; + maybeNextEpoch = newHeader.nextEpoch(); + } + if (newHeader.nextProtocolVersion().isPresent()) { + final var nextProtocolVersion = newHeader.nextProtocolVersion().unwrap(); + latestProofWhichInitiatedAProtocolUpdate = Option.some(newLatestProof); + final var finalExecutionProof = this.rustProtocolUpdate.applyProtocolUpdate(nextProtocolVersion).postUpdateProof(); + newLatestProof = finalExecutionProof; + latestProtocolUpdateExecutionProof = Option.some(finalExecutionProof); + } + } while (newLatestProof != lastHandledProof); // This presence of the protocol update in the proof is validated in rust - to ensure that if - // any protocol update - // is present, our node agrees it should be committed. + // any protocol update is present, our node agrees it should be committed. // We then can trust that we should trigger the application of the protocol update here. // NOTE: In the future, we may be able to move this down into Rust. this.latestProof = new LedgerProofBundle( newLatestProof, - maybeNextEpoch.isPresent() ? proof : this.latestProof.closestEpochProofOnOrBefore(), - maybeNextProtocolVersion.isPresent() - ? Option.some(proof) - : this.latestProof.closestProtocolUpdateInitProofOnOrBefore(), - maybePostProtocolUpdateProof.isPresent() - ? Option.some(maybePostProtocolUpdateProof.unwrap()) - : this.latestProof.closestProtocolUpdateExecutionProofOnOrBefore()); + latestProofWhichInitiatedAnEpochChange, + latestProofWhichInitiatedAProtocolUpdate, + latestProtocolUpdateExecutionProof); final var maybeEpochChange = maybeNextEpoch.map( nextEpoch -> { final var initialState = VertexStoreState.createNewForNextEpoch( - REv2ToConsensus.ledgerHeader(latestProof.epochInitialHeader()), + REv2ToConsensus.ledgerHeader(this.latestProof.epochInitialHeader()), nextEpoch.epoch().toLong(), hasher); final var validatorSet = REv2ToConsensus.validatorSet(nextEpoch.validators()); @@ -352,7 +361,7 @@ public LedgerProofBundle commit(LedgerExtension ledgerExtension, VertexStoreStat ProposerElections.defaultRotation(nextEpoch.epoch().toLong(), validatorSet); final var bftConfiguration = new BFTConfiguration(proposerElection, validatorSet, initialState); - return new EpochChange(latestProof, bftConfiguration); + return new EpochChange(this.latestProof, bftConfiguration); }); maybeEpochChange.ifPresent( @@ -364,12 +373,12 @@ public LedgerProofBundle commit(LedgerExtension ledgerExtension, VertexStoreStat final var ledgerUpdate = new LedgerUpdate( commitSummary, - latestProof, + this.latestProof, maybeEpochChange, protocolState, ledgerExtension.transactions()); ledgerUpdateEventDispatcher.dispatch(ledgerUpdate); - return latestProof; + return this.latestProof; } } diff --git a/core/src/main/java/com/radixdlt/rev2/modules/REv2ConsensusRecoveryModule.java b/core/src/main/java/com/radixdlt/rev2/modules/REv2ConsensusRecoveryModule.java index 49813e4a42..f4504b566f 100644 --- a/core/src/main/java/com/radixdlt/rev2/modules/REv2ConsensusRecoveryModule.java +++ b/core/src/main/java/com/radixdlt/rev2/modules/REv2ConsensusRecoveryModule.java @@ -102,7 +102,7 @@ private BFTConfiguration initialConfig( private BFTValidatorSet initialValidatorSet(LedgerProofBundle latestProof) { return REv2ToConsensus.validatorSet( latestProof - .closestEpochProofOnOrBefore() + .latestProofWhichInitiatedAnEpochChange() .ledgerHeader() .nextEpoch() .orElseThrow() diff --git a/core/src/test-core/java/com/radixdlt/statecomputer/MockedStateComputer.java b/core/src/test-core/java/com/radixdlt/statecomputer/MockedStateComputer.java index 027750c7ae..f62d3f5fcd 100644 --- a/core/src/test-core/java/com/radixdlt/statecomputer/MockedStateComputer.java +++ b/core/src/test-core/java/com/radixdlt/statecomputer/MockedStateComputer.java @@ -131,10 +131,10 @@ public LedgerProofBundle commit( ledgerExtension.proof(), ledgerExtension.proof().ledgerHeader().nextEpoch().isPresent() ? ledgerExtension.proof() - : latestProof.closestEpochProofOnOrBefore(), + : latestProof.latestProofWhichInitiatedAnEpochChange(), ledgerExtension.proof().ledgerHeader().nextProtocolVersion().isPresent() ? Option.some(ledgerExtension.proof()) - : latestProof.closestProtocolUpdateInitProofOnOrBefore(), + : latestProof.latestProofWhichInitiatedAProtocolUpdate(), Option.empty()); final var maybeEpochChange = diff --git a/core/src/test/java/com/radixdlt/api/core/TransactionStreamTest.java b/core/src/test/java/com/radixdlt/api/core/TransactionStreamTest.java index ac37265045..556c8ec8e8 100644 --- a/core/src/test/java/com/radixdlt/api/core/TransactionStreamTest.java +++ b/core/src/test/java/com/radixdlt/api/core/TransactionStreamTest.java @@ -440,7 +440,7 @@ public void test_core_api_can_return_vm_boot_substate_in_protocol_update_receipt test.getInstance(0, REv2TransactionsAndProofReader.class) .getLatestProofBundle() .orElseThrow() - .closestProtocolUpdateInitProofOnOrBefore() + .latestProofWhichInitiatedAProtocolUpdate() .unwrap() .stateVersion(); diff --git a/core/src/test/java/com/radixdlt/rev2/protocol/AnemoneProtocolUpdateTest.java b/core/src/test/java/com/radixdlt/rev2/protocol/AnemoneProtocolUpdateTest.java index 1afe71b023..6cd33821ba 100644 --- a/core/src/test/java/com/radixdlt/rev2/protocol/AnemoneProtocolUpdateTest.java +++ b/core/src/test/java/com/radixdlt/rev2/protocol/AnemoneProtocolUpdateTest.java @@ -176,7 +176,7 @@ public void test_get_current_time_second_precision() { assertEquals( PROTOCOL_VERSION_NAME, postProtocolUpdateProof - .closestProtocolUpdateInitProofOnOrBefore() + .latestProofWhichInitiatedAProtocolUpdate() .unwrap() .ledgerHeader() .nextProtocolVersion()