diff --git a/Cargo.lock b/Cargo.lock index 3c5bd20b38fd8..c4b46a00b18ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1206,6 +1206,7 @@ dependencies = [ "aptos-crypto-derive", "bcs", "chrono", + "claim", "hex", "itertools", "mirai-annotations", diff --git a/api/src/transactions.rs b/api/src/transactions.rs index 026e2be9a37ba..3c4650ed5f297 100644 --- a/api/src/transactions.rs +++ b/api/src/transactions.rs @@ -349,6 +349,10 @@ impl Transactions { TransactionStatus::Keep(exec_status) => exec_status, _ => ExecutionStatus::MiscellaneousError(None), }; + + // TODO: Here we need to materialize deltas. + let (_, output) = output.into(); + let zero_hash = HashValue::zero(); let info = TransactionInfo::new( zero_hash, diff --git a/api/types/src/convert.rs b/api/types/src/convert.rs index 08ec62f4b9233..0108c26bc1a7d 100644 --- a/api/types/src/convert.rs +++ b/api/types/src/convert.rs @@ -274,8 +274,6 @@ impl<'a, R: MoveResolverExt + ?Sized> MoveConverter<'a, R> { data: self.try_into_resource(&typ, &val)?, }), }, - // Deltas never use access paths. - WriteOp::Delta(..) => unreachable!("unexpected conversion"), }; Ok(ret) } @@ -312,8 +310,6 @@ impl<'a, R: MoveResolverExt + ?Sized> MoveConverter<'a, R> { data, }) } - // Deltas are materialized into WriteOP::Value(..) in executor. - WriteOp::Delta(..) => unreachable!("unexpected conversion"), }; Ok(ret) } diff --git a/aptos-move/aptos-vm/src/adapter_common.rs b/aptos-move/aptos-vm/src/adapter_common.rs index d3d728cf3b97e..d87cdef337c95 100644 --- a/aptos-move/aptos-vm/src/adapter_common.rs +++ b/aptos-move/aptos-vm/src/adapter_common.rs @@ -5,7 +5,9 @@ use crate::{counters::*, data_cache::StateViewCache}; use anyhow::Result; use aptos_state_view::StateView; use aptos_types::{ - transaction::{SignatureCheckedTransaction, SignedTransaction, VMValidatorResult}, + transaction::{ + SignatureCheckedTransaction, SignedTransaction, TransactionOutputExt, VMValidatorResult, + }, vm_status::{StatusCode, VMStatus}, }; @@ -64,7 +66,7 @@ pub trait VMAdapter { txn: &PreprocessedTransaction, data_cache: &S, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput, Option), VMStatus>; + ) -> Result<(VMStatus, TransactionOutputExt, Option), VMStatus>; } /// Validate a signed transaction by performing the following: @@ -204,6 +206,8 @@ pub(crate) fn execute_block_impl( &data_cache.as_move_resolver(), &log_context, )?; + // TODO: apply deltas. + let (_, output) = output.into(); if !output.status().is_discarded() { data_cache.push_write_set(output.write_set()); } else { @@ -273,7 +277,7 @@ pub(crate) fn preprocess_transaction(txn: Transaction) -> Preproce } } -pub(crate) fn discard_error_vm_status(err: VMStatus) -> (VMStatus, TransactionOutput) { +pub(crate) fn discard_error_vm_status(err: VMStatus) -> (VMStatus, TransactionOutputExt) { let vm_status = err.clone(); let error_code = match err.keep_or_discard() { Ok(_) => { @@ -285,12 +289,12 @@ pub(crate) fn discard_error_vm_status(err: VMStatus) -> (VMStatus, TransactionOu (vm_status, discard_error_output(error_code)) } -pub(crate) fn discard_error_output(err: StatusCode) -> TransactionOutput { +pub(crate) fn discard_error_output(err: StatusCode) -> TransactionOutputExt { // Since this transaction will be discarded, no writeset will be included. - TransactionOutput::new( + TransactionOutputExt::from(TransactionOutput::new( WriteSet::default(), vec![], 0, TransactionStatus::Discard(err), - ) + )) } diff --git a/aptos-move/aptos-vm/src/aptos_vm.rs b/aptos-move/aptos-vm/src/aptos_vm.rs index ce9b55a523b3a..948f49f6d9ad8 100644 --- a/aptos-move/aptos-vm/src/aptos_vm.rs +++ b/aptos-move/aptos-vm/src/aptos_vm.rs @@ -32,8 +32,8 @@ use aptos_types::{ on_chain_config::{new_epoch_event_key, VMConfig, VMPublishingOption, Version}, transaction::{ ChangeSet, ExecutionStatus, ModuleBundle, SignatureCheckedTransaction, SignedTransaction, - Transaction, TransactionOutput, TransactionPayload, TransactionStatus, VMValidatorResult, - WriteSetPayload, + Transaction, TransactionOutput, TransactionOutputExt, TransactionPayload, + TransactionStatus, VMValidatorResult, WriteSetPayload, }, vm_status::{StatusCode, VMStatus}, write_set::{WriteSet, WriteSetMut}, @@ -154,7 +154,7 @@ impl AptosVM { txn_data: &TransactionMetadata, storage: &S, log_context: &AdapterLogSchema, - ) -> TransactionOutput { + ) -> TransactionOutputExt { self.failed_transaction_cleanup_and_keep_vm_status( error_code, gas_status, @@ -172,7 +172,7 @@ impl AptosVM { txn_data: &TransactionMetadata, storage: &S, log_context: &AdapterLogSchema, - ) -> (VMStatus, TransactionOutput) { + ) -> (VMStatus, TransactionOutputExt) { gas_status.set_metering(false); let mut session = self.0.new_session(storage, SessionId::txn_meta(txn_data)); match TransactionStatus::from(error_code.clone()) { @@ -212,7 +212,7 @@ impl AptosVM { gas_status: &mut GasStatus, txn_data: &TransactionMetadata, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { gas_status.set_metering(false); self.0 .run_success_epilogue(&mut session, gas_status, txn_data, log_context)?; @@ -236,7 +236,7 @@ impl AptosVM { txn_data: &TransactionMetadata, payload: &TransactionPayload, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { fail_point!("move_adapter::execute_script_or_script_function", |_| { Err(VMStatus::Error( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, @@ -395,7 +395,7 @@ impl AptosVM { txn_data: &TransactionMetadata, modules: &ModuleBundle, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { fail_point!("move_adapter::execute_module", |_| { Err(VMStatus::Error( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, @@ -495,7 +495,7 @@ impl AptosVM { storage: &S, txn: &SignatureCheckedTransaction, log_context: &AdapterLogSchema, - ) -> (VMStatus, TransactionOutput) { + ) -> (VMStatus, TransactionOutputExt) { macro_rules! unwrap_or_discard { ($res: expr) => { match $res { @@ -570,7 +570,7 @@ impl AptosVM { writeset_payload: &WriteSetPayload, txn_sender: Option, session_id: SessionId, - ) -> Result> { + ) -> Result> { let mut gas_status = GasStatus::new_unmetered(); Ok(match writeset_payload { @@ -656,7 +656,7 @@ impl AptosVM { storage: &S, writeset_payload: WriteSetPayload, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { // TODO: user specified genesis id to distinguish different genesis write sets let genesis_id = HashValue::zero(); let change_set = match self.execute_writeset( @@ -674,7 +674,12 @@ impl AptosVM { SYSTEM_TRANSACTIONS_EXECUTED.inc(); Ok(( VMStatus::Executed, - TransactionOutput::new(write_set, events, 0, VMStatus::Executed.into()), + TransactionOutputExt::from(TransactionOutput::new( + write_set, + events, + 0, + VMStatus::Executed.into(), + )), )) } @@ -683,7 +688,7 @@ impl AptosVM { storage: &S, block_metadata: BlockMetadata, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { fail_point!("move_adapter::process_block_prologue", |_| { Err(VMStatus::Error( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, @@ -761,7 +766,7 @@ impl AptosVM { storage: &S, txn: &SignatureCheckedTransaction, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { fail_point!("move_adapter::process_writeset_transaction", |_| { Err(VMStatus::Error( StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR, @@ -804,7 +809,7 @@ impl AptosVM { writeset_payload: &WriteSetPayload, txn_data: TransactionMetadata, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt), VMStatus> { let change_set = match self.execute_writeset( storage, writeset_payload, @@ -884,12 +889,12 @@ impl AptosVM { Ok(( VMStatus::Executed, - TransactionOutput::new( + TransactionOutputExt::from(TransactionOutput::new( write_set, events, 0, TransactionStatus::Keep(ExecutionStatus::Success), - ), + )), )) } @@ -911,7 +916,7 @@ impl AptosVM { pub fn simulate_signed_transaction( txn: &SignedTransaction, state_view: &impl StateView, - ) -> (VMStatus, TransactionOutput) { + ) -> (VMStatus, TransactionOutputExt) { let vm = AptosVM::new(state_view); let simulation_vm = AptosSimulationVM(vm); let log_context = AdapterLogSchema::new(state_view.id(), 0); @@ -1048,7 +1053,7 @@ impl VMAdapter for AptosVM { txn: &PreprocessedTransaction, data_cache: &S, log_context: &AdapterLogSchema, - ) -> Result<(VMStatus, TransactionOutput, Option), VMStatus> { + ) -> Result<(VMStatus, TransactionOutputExt, Option), VMStatus> { Ok(match txn { PreprocessedTransaction::BlockMetadata(block_metadata) => { let (vm_status, output) = @@ -1097,7 +1102,11 @@ impl VMAdapter for AptosVM { 0, TransactionStatus::Keep(ExecutionStatus::Success), ); - (VMStatus::Executed, output, Some("state_checkpoint".into())) + ( + VMStatus::Executed, + TransactionOutputExt::from(output), + Some("state_checkpoint".into()), + ) } }) } @@ -1136,7 +1145,7 @@ impl AptosSimulationVM { storage: &S, txn: &SignedTransaction, log_context: &AdapterLogSchema, - ) -> (VMStatus, TransactionOutput) { + ) -> (VMStatus, TransactionOutputExt) { // simulation transactions should not carry valid signatures, otherwise malicious fullnodes // may execute them without user's explicit permission. if txn.clone().check_signature().is_ok() { @@ -1185,13 +1194,14 @@ impl AptosSimulationVM { if txn_status.is_discarded() { discard_error_vm_status(err) } else { - self.0.failed_transaction_cleanup_and_keep_vm_status( + let (vm_status, output) = self.0.failed_transaction_cleanup_and_keep_vm_status( err, &mut gas_status, &txn_data, storage, log_context, - ) + ); + (vm_status, output) } } } diff --git a/aptos-move/aptos-vm/src/aptos_vm_impl.rs b/aptos-move/aptos-vm/src/aptos_vm_impl.rs index 335abf3d9bca4..93c5391dd16b7 100644 --- a/aptos-move/aptos-vm/src/aptos_vm_impl.rs +++ b/aptos-move/aptos-vm/src/aptos_vm_impl.rs @@ -18,7 +18,7 @@ use aptos_types::{ on_chain_config::{ ConfigStorage, OnChainConfig, VMConfig, VMPublishingOption, Version, APTOS_VERSION_3, }, - transaction::{ExecutionStatus, TransactionOutput, TransactionStatus}, + transaction::{ExecutionStatus, TransactionOutput, TransactionOutputExt, TransactionStatus}, vm_status::{StatusCode, VMStatus}, }; use fail::fail_point; @@ -569,18 +569,17 @@ pub(crate) fn get_transaction_output( gas_left: GasUnits, txn_data: &TransactionMetadata, status: ExecutionStatus, -) -> Result { +) -> Result { let gas_used: u64 = txn_data.max_gas_amount().sub(gas_left).get(); let session_out = session.finish().map_err(|e| e.into_vm_status())?; - let (write_set, events) = session_out.into_change_set(ap_cache)?.into_inner(); - - Ok(TransactionOutput::new( - write_set, - events, - gas_used, - TransactionStatus::Keep(status), - )) + let (delta_change_set, change_set) = session_out.into_change_set_ext(ap_cache)?.into_inner(); + let (write_set, events) = change_set.into_inner(); + + let txn_output = + TransactionOutput::new(write_set, events, gas_used, TransactionStatus::Keep(status)); + + Ok(TransactionOutputExt::new(delta_change_set, txn_output)) } #[test] diff --git a/aptos-move/aptos-vm/src/data_cache.rs b/aptos-move/aptos-vm/src/data_cache.rs index 9a8a45679f860..5bf86f807bf56 100644 --- a/aptos-move/aptos-vm/src/data_cache.rs +++ b/aptos-move/aptos-vm/src/data_cache.rs @@ -72,9 +72,6 @@ impl<'a, S: StateView> StateViewCache<'a, S> { self.data_map.remove(ap); self.data_map.insert(ap.clone(), None); } - WriteOp::Delta(..) => { - unimplemented!("sequential execution is not supported for deltas") - } } } } diff --git a/aptos-move/aptos-vm/src/move_vm_ext/session.rs b/aptos-move/aptos-vm/src/move_vm_ext/session.rs index ead428ca050c0..54339fdc83116 100644 --- a/aptos-move/aptos-vm/src/move_vm_ext/session.rs +++ b/aptos-move/aptos-vm/src/move_vm_ext/session.rs @@ -10,8 +10,9 @@ use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; use aptos_types::{ block_metadata::BlockMetadata, contract_event::ContractEvent, + delta_change_set::DeltaChangeSet, state_store::state_key::StateKey, - transaction::{ChangeSet, SignatureCheckedTransaction}, + transaction::{ChangeSet, ChangeSetExt, SignatureCheckedTransaction}, write_set::{WriteOp, WriteSetMut}, }; use move_deps::{ @@ -194,6 +195,16 @@ impl SessionOutput { Ok(ChangeSet::new(write_set, events)) } + pub fn into_change_set_ext( + self, + ap_cache: &mut C, + ) -> Result { + // TODO: extract `DeltaChangeSet` from Aggregator extension (when it lands) + // and initialize `ChangeSetExt` properly. + self.into_change_set(ap_cache) + .map(|change_set| ChangeSetExt::new(DeltaChangeSet::empty(), change_set)) + } + pub fn squash(&mut self, other: Self) -> Result<(), VMStatus> { self.change_set .squash(other.change_set) diff --git a/aptos-move/aptos-vm/src/parallel_executor/storage_wrapper.rs b/aptos-move/aptos-vm/src/parallel_executor/storage_wrapper.rs index f8faee478b623..45f57fc402abf 100644 --- a/aptos-move/aptos-vm/src/parallel_executor/storage_wrapper.rs +++ b/aptos-move/aptos-vm/src/parallel_executor/storage_wrapper.rs @@ -35,9 +35,6 @@ impl<'a, S: StateView> StateView for VersionedView<'a, S> { Some(v) => Ok(match v.as_ref() { WriteOp::Value(w) => Some(w.clone()), WriteOp::Deletion => None, - WriteOp::Delta(..) => { - unimplemented!("parallel execution is not supported for deltas") - } }), None => self.base_view.get_state_value(state_key), } diff --git a/aptos-move/aptos-vm/src/parallel_executor/vm_wrapper.rs b/aptos-move/aptos-vm/src/parallel_executor/vm_wrapper.rs index 84abb998ce582..83e1b5c75449e 100644 --- a/aptos-move/aptos-vm/src/parallel_executor/vm_wrapper.rs +++ b/aptos-move/aptos-vm/src/parallel_executor/vm_wrapper.rs @@ -67,6 +67,9 @@ impl<'a, S: 'a + StateView> ExecutorTask for AptosVMWrapper<'a, S> { .execute_single_transaction(txn, &versioned_view, &log_context) { Ok((vm_status, output, sender)) => { + // TODO: pass deltas to `AptosTransactionOutput` once we support parallel execution. + let (_, output) = output.into(); + if output.status().is_discarded() { match sender { Some(s) => trace!( diff --git a/aptos-move/e2e-tests/src/data_store.rs b/aptos-move/e2e-tests/src/data_store.rs index 50dce426ebe1a..f4d95fe1e48aa 100644 --- a/aptos-move/e2e-tests/src/data_store.rs +++ b/aptos-move/e2e-tests/src/data_store.rs @@ -50,7 +50,6 @@ impl FakeDataStore { WriteOp::Deletion => { self.remove(state_key); } - WriteOp::Delta(..) => unreachable!("deltas are only used in executor"), } } } diff --git a/aptos-move/genesis-viewer/src/main.rs b/aptos-move/genesis-viewer/src/main.rs index cfe3c0b980655..da91ae97eca9a 100644 --- a/aptos-move/genesis-viewer/src/main.rs +++ b/aptos-move/genesis-viewer/src/main.rs @@ -188,7 +188,6 @@ fn print_modules(ws: &WriteSet) { AccessPath::try_from(k.clone()).expect("State key can't be converted to access path"); match v { WriteOp::Deletion => panic!("found WriteOp::Deletion in WriteSet"), - WriteOp::Delta(..) => panic!("found WriteOp::Delta in WriteSet"), WriteOp::Value(blob) => { let tag = ap.path.get(0).expect("empty blob in WriteSet"); if *tag == 0 { @@ -214,7 +213,6 @@ fn print_resources(storage: &impl MoveResolverExt, ws: &WriteSet) { AccessPath::try_from(k.clone()).expect("State key can't be converted to access path"); match v { WriteOp::Deletion => panic!("found WriteOp::Deletion in WriteSet"), - WriteOp::Delta(..) => panic!("found WriteOp::Delta in WriteSet"), WriteOp::Value(blob) => { let tag = ap.path.get(0).expect("empty blob in WriteSet"); if *tag == 1 { @@ -245,7 +243,6 @@ fn print_account_states(storage: &impl MoveResolverExt, ws: &WriteSet) { AccessPath::try_from(k.clone()).expect("State key can't be converted to access path"); match v { WriteOp::Deletion => panic!("found WriteOp::Deletion in WriteSet"), - WriteOp::Delta(..) => panic!("found WriteOp::Delta in WriteSet"), WriteOp::Value(blob) => { let tag = ap.path.get(0).expect("empty blob in WriteSet"); if *tag == 1 { diff --git a/aptos-move/transaction-replay/src/lib.rs b/aptos-move/transaction-replay/src/lib.rs index 437d46a5adf2a..8b87681013cff 100644 --- a/aptos-move/transaction-replay/src/lib.rs +++ b/aptos-move/transaction-replay/src/lib.rs @@ -161,6 +161,10 @@ impl AptosDebugger { &AdapterLogSchema::new(state_view.id(), 0), ) .map_err(|err| format_err!("Unexpected VM Error: {:?}", err))?; + + // Since we execute write sets, deltas cannot be produced. + let (_, output) = output.into(); + if save_write_set { self.save_write_sets(&output)?; } @@ -177,12 +181,10 @@ impl AptosDebugger { access_path::Path::Resource(tag) => match op { WriteOp::Deletion => state_view.delete_resource(addr, tag)?, WriteOp::Value(bytes) => state_view.save_resource(addr, tag, bytes)?, - WriteOp::Delta(..) => unreachable!("deltas are only used in executor"), }, access_path::Path::Code(module_id) => match op { WriteOp::Deletion => state_view.delete_module(&module_id)?, WriteOp::Value(bytes) => state_view.save_module(&module_id, bytes)?, - WriteOp::Delta(..) => unreachable!("deltas are only used in executor"), }, } } diff --git a/documentation/specifications/move_adapter/README.md b/documentation/specifications/move_adapter/README.md index ecea93a548bf1..17daa933611bb 100644 --- a/documentation/specifications/move_adapter/README.md +++ b/documentation/specifications/move_adapter/README.md @@ -531,28 +531,13 @@ pub struct TransactionOutput { /// `WriteSet` contains all access paths that one transaction modifies. /// Each of them is a `WriteOp` where `Value(val)` means that serialized /// representation should be updated to `val`, and `Deletion` means that -/// we are going to delete this access path. A special `WriteOp` used by -/// aggregator - `Delta(op, limit)`, means that `op` should be applied to -/// deserialized representation and a postcondition `result <= limit` must be -/// ensured. +/// we are going to delete this access path. pub struct WriteSet { write_set: Vec<(AccessPath, WriteOp)>, } -/// `DeltaOperation` specifies the partial function which is used to apply -/// delta. -pub enum DeltaOperation { - Addition(u128), - Subtraction(u128), -} - -/// Value when delta application overflows, i.e. the postcondition of delta -/// application. -pub struct DeltaLimit(pub u128); - pub enum WriteOp { Deletion, - Delta(DeltaOperation, DeltaLimit), Value(Vec), } diff --git a/execution/executor-types/src/in_memory_state_calculator.rs b/execution/executor-types/src/in_memory_state_calculator.rs index ba33a9ce630ca..104f0238c8caa 100644 --- a/execution/executor-types/src/in_memory_state_calculator.rs +++ b/execution/executor-types/src/in_memory_state_calculator.rs @@ -259,7 +259,6 @@ fn process_state_key_write_op( let state_value = match write_op { WriteOp::Value(new_value) => StateValue::from(new_value), WriteOp::Deletion => StateValue::empty(), - WriteOp::Delta(..) => unreachable!("deltas are only used in executor"), }; match state_cache.entry(state_key.clone()) { hash_map::Entry::Occupied(mut entry) => { diff --git a/storage/indexer/src/lib.rs b/storage/indexer/src/lib.rs index 2c7f8483cc52b..69a843ce65a42 100644 --- a/storage/indexer/src/lib.rs +++ b/storage/indexer/src/lib.rs @@ -169,7 +169,6 @@ impl<'a> TableInfoParser<'a> { StateKey::Raw(_) => (), }, WriteOp::Deletion => (), - WriteOp::Delta(_, _) => (), } Ok(()) } diff --git a/types/Cargo.toml b/types/Cargo.toml index be9f9eb57496a..0e8bf5d2cd5c5 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -35,6 +35,7 @@ aptos-crypto-derive = { path = "../crates/aptos-crypto-derive" } move-deps = { path = "../aptos-move/move-deps", features = ["address32"] } [dev-dependencies] +claim = "0.5.0" proptest = "1.0.0" proptest-derive = "0.3.0" regex = "1.5.5" diff --git a/types/src/delta_change_set.rs b/types/src/delta_change_set.rs new file mode 100644 index 0000000000000..ca36d7b7cd2bc --- /dev/null +++ b/types/src/delta_change_set.rs @@ -0,0 +1,134 @@ +// Copyright (c) Aptos +// SPDX-License-Identifier: Apache-2.0 + +//! Parallel data aggregation uses a `Delta` op. Every delta is is a state key +//! (for accessing the storage) and an operation: a partial function with a +//! postcondition. + +use crate::state_store::state_key::StateKey; + +/// Specifies different delta partial function specifications. +#[derive(Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum DeltaOp { + /// Addition of `value` which overflows on `limit`. + Addition { value: u128, limit: u128 }, + /// Subtraction of `value` which cannot go below zero. + Subtraction { value: u128 }, +} + +impl DeltaOp { + /// Returns optional result of delta application to `base` (None if + /// postocndition not satisfied). + pub fn apply_to(&self, base: u128) -> Option { + match self { + DeltaOp::Addition { value, limit } => addition(base, *value, *limit), + DeltaOp::Subtraction { value } => subtraction(base, *value), + } + } +} + +/// Implements application of `Addition` to `base`. +fn addition(base: u128, value: u128, limit: u128) -> Option { + if value > (limit - base) { + None + } else { + Some(base + value) + } +} + +/// Implements application of `Subtraction` to `base`. +fn subtraction(base: u128, value: u128) -> Option { + if value > base { + None + } else { + Some(base - value) + } +} + +impl std::fmt::Debug for DeltaOp { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DeltaOp::Addition { value, limit } => { + write!(f, "+{} ensures result <= {}", value, limit) + } + DeltaOp::Subtraction { value } => { + write!(f, "-{} ensures 0 <= result", value) + } + } + } +} + +/// Serializes value after delta application. +pub fn serialize(value: &u128) -> Vec { + bcs::to_bytes(value).expect("unexpected serialization error") +} + +/// Deserializes value for delta application. +pub fn deserialize(value_bytes: &[u8]) -> u128 { + bcs::from_bytes(value_bytes).expect("unexpected deserialization error") +} + +/// `DeltaChangeSet` contains all access paths that one transaction wants to update with deltas. +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub struct DeltaChangeSet { + delta_change_set: Vec<(StateKey, DeltaOp)>, +} + +impl DeltaChangeSet { + pub fn empty() -> Self { + DeltaChangeSet { + delta_change_set: vec![], + } + } + + pub fn new(delta_change_set: Vec<(StateKey, DeltaOp)>) -> Self { + DeltaChangeSet { delta_change_set } + } + + pub fn push(&mut self, delta: (StateKey, DeltaOp)) { + self.delta_change_set.push(delta); + } + + pub fn pop(&mut self) { + self.delta_change_set.pop(); + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.delta_change_set.is_empty() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use claim::{assert_matches, assert_some_eq}; + + fn addition(value: u128, limit: u128) -> DeltaOp { + DeltaOp::Addition { value, limit } + } + + fn subtraction(value: u128) -> DeltaOp { + DeltaOp::Subtraction { value } + } + + #[test] + fn test_delta_addition() { + let add5 = addition(5, 100); + assert_some_eq!(add5.apply_to(0), 5); + assert_some_eq!(add5.apply_to(5), 10); + assert_some_eq!(add5.apply_to(95), 100); + + assert_matches!(add5.apply_to(96), None); + } + + #[test] + fn test_delta_subtraction() { + let sub5 = subtraction(5); + assert_matches!(sub5.apply_to(0), None); + assert_matches!(sub5.apply_to(1), None); + + assert_some_eq!(sub5.apply_to(5), 0); + assert_some_eq!(sub5.apply_to(100), 95); + } +} diff --git a/types/src/lib.rs b/types/src/lib.rs index 31b6fb22f9e92..1a9e2a3617ada 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -11,6 +11,7 @@ pub mod block_info; pub mod block_metadata; pub mod chain_id; pub mod contract_event; +pub mod delta_change_set; pub mod epoch_change; pub mod epoch_state; pub mod event; diff --git a/types/src/transaction/change_set.rs b/types/src/transaction/change_set.rs index 5fa602d92668d..1525e0cb3797d 100644 --- a/types/src/transaction/change_set.rs +++ b/types/src/transaction/change_set.rs @@ -1,7 +1,7 @@ // Copyright (c) Aptos // SPDX-License-Identifier: Apache-2.0 -use crate::{contract_event::ContractEvent, write_set::WriteSet}; +use crate::{contract_event::ContractEvent, delta_change_set::DeltaChangeSet, write_set::WriteSet}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] @@ -27,3 +27,22 @@ impl ChangeSet { &self.events } } + +/// Extension of `ChangeSet` that also holds deltas. +pub struct ChangeSetExt { + delta_change_set: DeltaChangeSet, + change_set: ChangeSet, +} + +impl ChangeSetExt { + pub fn new(delta_change_set: DeltaChangeSet, change_set: ChangeSet) -> Self { + ChangeSetExt { + delta_change_set, + change_set, + } + } + + pub fn into_inner(self) -> (DeltaChangeSet, ChangeSet) { + (self.delta_change_set, self.change_set) + } +} diff --git a/types/src/transaction/mod.rs b/types/src/transaction/mod.rs index 8ab4b027aa558..a3b600ee3f008 100644 --- a/types/src/transaction/mod.rs +++ b/types/src/transaction/mod.rs @@ -6,6 +6,7 @@ use crate::{ block_metadata::BlockMetadata, chain_id::ChainId, contract_event::ContractEvent, + delta_change_set::DeltaChangeSet, ledger_info::LedgerInfo, proof::{ accumulator::InMemoryAccumulator, TransactionInfoListWithProof, TransactionInfoWithProof, @@ -40,7 +41,7 @@ mod module; mod script; mod transaction_argument; -pub use change_set::ChangeSet; +pub use change_set::{ChangeSet, ChangeSetExt}; pub use module::{Module, ModuleBundle}; pub use script::{ ArgumentABI, Script, ScriptABI, ScriptFunction, ScriptFunctionABI, TransactionScriptABI, @@ -951,6 +952,55 @@ impl TransactionOutput { } } +/// Extension of `TransactionOutput` that also holds `DeltaChangeSet` +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct TransactionOutputExt { + delta_change_set: DeltaChangeSet, + output: TransactionOutput, +} + +impl TransactionOutputExt { + pub fn new(delta_change_set: DeltaChangeSet, output: TransactionOutput) -> Self { + TransactionOutputExt { + delta_change_set, + output, + } + } + + pub fn delta_change_set(&self) -> &DeltaChangeSet { + &self.delta_change_set + } + + pub fn write_set(&self) -> &WriteSet { + &self.output.write_set + } + + pub fn events(&self) -> &[ContractEvent] { + &self.output.events + } + + pub fn gas_used(&self) -> u64 { + self.output.gas_used + } + + pub fn status(&self) -> &TransactionStatus { + &self.output.status + } + + pub fn into(self) -> (DeltaChangeSet, TransactionOutput) { + (self.delta_change_set, self.output) + } +} + +impl From for TransactionOutputExt { + fn from(output: TransactionOutput) -> Self { + TransactionOutputExt { + delta_change_set: DeltaChangeSet::empty(), + output, + } + } +} + /// `TransactionInfo` is the object we store in the transaction accumulator. It consists of the /// transaction as well as the execution result of this transaction. #[derive(Clone, CryptoHasher, BCSCryptoHash, Debug, Eq, PartialEq, Serialize, Deserialize)] diff --git a/types/src/write_set.rs b/types/src/write_set.rs index 00c2d5df8b758..689216955056e 100644 --- a/types/src/write_set.rs +++ b/types/src/write_set.rs @@ -2,46 +2,17 @@ // SPDX-License-Identifier: Apache-2.0 //! For each transaction the VM executes, the VM will output a `WriteSet` that contains each access -//! path it updates. For each access path, the VM can either give its new value or delete it. For -//! aggregator, delta updates are used (note: this is a temporary solution and ideally we should -//! modify `ChangeSet` and `TransactionOutput` to keep deltas internal to executor). +//! path it updates. For each access path, the VM can either give its new value or delete it. use crate::state_store::state_key::StateKey; use anyhow::Result; use aptos_crypto_derive::{BCSCryptoHash, CryptoHasher}; use serde::{Deserialize, Serialize}; -/// Specifies partial function such as +X or -X to use with `WriteOp::Delta`. -#[derive(Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] -pub enum DeltaOperation { - Addition(u128), - Subtraction(u128), -} - -impl std::fmt::Debug for DeltaOperation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - DeltaOperation::Addition(value) => write!(f, "+{}", value), - DeltaOperation::Subtraction(value) => write!(f, "-{}", value), - } - } -} - -#[derive(Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)] -pub struct DeltaLimit(pub u128); - -impl std::fmt::Debug for DeltaLimit { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "result <= {}", self.0) - } -} - #[derive(Clone, Eq, Hash, PartialEq, Serialize, Deserialize)] pub enum WriteOp { Deletion, Value(#[serde(with = "serde_bytes")] Vec), - #[serde(skip)] - Delta(DeltaOperation, DeltaLimit), } impl WriteOp { @@ -49,7 +20,6 @@ impl WriteOp { pub fn is_deletion(&self) -> bool { match self { WriteOp::Deletion => true, - WriteOp::Delta(..) => false, WriteOp::Value(_) => false, } } @@ -66,9 +36,6 @@ impl std::fmt::Debug for WriteOp { .map(|byte| format!("{:02x}", byte)) .collect::() ), - WriteOp::Delta(op, limit) => { - write!(f, "Delta({:?} ensures {:?})", op, limit) - } WriteOp::Deletion => write!(f, "Deletion"), } }