diff --git a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs index d835322f2af015..f7206c4f424712 100644 --- a/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs +++ b/aptos-move/aptos-gas-schedule/src/gas_schedule/transaction.rs @@ -79,12 +79,19 @@ crate::gas_schedule::macros::define_gas_parameters!( [ storage_io_per_state_slot_read: InternalGasPerArg, { 0..=9 => "load_data.base", 10.. => "storage_io_per_state_slot_read"}, - 300_000, + // At the current mainnet scale, we should assume most levels of the (hexary) JMT nodes + // in cache, hence target charging 1-2 4k-sized pages for each read. Notice the cost + // of seeking for the leaf node is covered by the first page of the "value size fee" + // (storage_io_per_state_byte_read) defined below. + 800_000, ], [ storage_io_per_state_byte_read: InternalGasPerByte, { 0..=9 => "load_data.per_byte", 10.. => "storage_io_per_state_byte_read"}, - 300, + // Notice in the latest IoPricing, bytes are charged at 4k intervals (even the smallest + // read will be charged for 4KB) to reflect the assumption that every roughly 4k bytes + // might require a separate random IO upon the FS. + 100, ], [load_data_failure: InternalGas, "load_data.failure", 0], // Gas parameters for writing data to storage. @@ -110,7 +117,7 @@ crate::gas_schedule::macros::define_gas_parameters!( ], [memory_quota: AbstractValueSize, { 1.. => "memory_quota" }, 10_000_000], [ - free_write_bytes_quota: NumBytes, + legacy_free_write_bytes_quota: NumBytes, { 5.. => "free_write_bytes_quota" }, 1024, // 1KB free per state write ], @@ -211,7 +218,7 @@ impl TransactionGasParameters { pub fn storage_fee_for_bytes(&self, key: &StateKey, op_size: &WriteOpSize) -> Fee { if let Some(value_size) = op_size.write_len() { let size = NumBytes::new(key.size() as u64) + NumBytes::new(value_size); - if let Some(excess) = size.checked_sub(self.free_write_bytes_quota) { + if let Some(excess) = size.checked_sub(self.legacy_free_write_bytes_quota) { return excess * self.storage_fee_per_excess_state_byte; } } diff --git a/aptos-move/aptos-vm-types/src/storage/io_pricing.rs b/aptos-move/aptos-vm-types/src/storage/io_pricing.rs index a208e5d32b5f2a..ed05627df37013 100644 --- a/aptos-move/aptos-vm-types/src/storage/io_pricing.rs +++ b/aptos-move/aptos-vm-types/src/storage/io_pricing.rs @@ -120,7 +120,7 @@ impl IoPricingV2 { 0 => unreachable!("PricingV2 not applicable for feature version 0"), 1..=2 => 0.into(), 3..=4 => 1024.into(), - 5.. => gas_params.vm.txn.free_write_bytes_quota, + 5.. => gas_params.vm.txn.legacy_free_write_bytes_quota, } } @@ -167,24 +167,43 @@ impl IoPricingV2 { #[derive(Debug, Clone)] pub struct IoPricingV3 { pub feature_version: u64, - pub free_write_bytes_quota: NumBytes, + pub legacy_free_write_bytes_quota: NumBytes, } impl IoPricingV3 { + fn read_size(&self, loaded: NumBytes) -> NumBytes { + if self.feature_version >= 12 { + // round up bytes to whole pages + + const PAGE_SIZE: u64 = 4096; + let loaded_u64: u64 = loaded.into(); + let r = loaded_u64 % PAGE_SIZE; + let rounded_up = loaded_u64 + if r == 0 { 0 } else { PAGE_SIZE - r }; + NumBytes::new(rounded_up) + } else { + loaded + } + } + fn calculate_read_gas( &self, loaded: NumBytes, ) -> impl GasExpression { - STORAGE_IO_PER_STATE_SLOT_READ * NumArgs::from(1) + STORAGE_IO_PER_STATE_BYTE_READ * loaded + STORAGE_IO_PER_STATE_SLOT_READ * NumArgs::from(1) + + STORAGE_IO_PER_STATE_BYTE_READ * self.read_size(loaded) } fn write_op_size(&self, key: &StateKey, value_size: u64) -> NumBytes { let key_size = NumBytes::new(key.size() as u64); let value_size = NumBytes::new(value_size); + let size = key_size + value_size; - (key_size + value_size) - .checked_sub(self.free_write_bytes_quota) - .unwrap_or(NumBytes::zero()) + if self.feature_version >= 12 { + size.checked_sub(self.legacy_free_write_bytes_quota) + .unwrap_or(NumBytes::zero()) + } else { + size + } } fn io_gas_per_write( @@ -232,7 +251,7 @@ impl IoPricing { }, 10.. => V3(IoPricingV3 { feature_version, - free_write_bytes_quota: gas_params.vm.txn.free_write_bytes_quota, + legacy_free_write_bytes_quota: gas_params.vm.txn.legacy_free_write_bytes_quota, }), } } diff --git a/aptos-move/aptos-vm-types/src/storage/mod.rs b/aptos-move/aptos-vm-types/src/storage/mod.rs index 12a8dfb5a8f2fa..68c4ced4a1e1c9 100644 --- a/aptos-move/aptos-vm-types/src/storage/mod.rs +++ b/aptos-move/aptos-vm-types/src/storage/mod.rs @@ -38,7 +38,7 @@ impl StorageGasParameters { Self { io_pricing: IoPricing::V3(IoPricingV3 { feature_version: LATEST_GAS_FEATURE_VERSION, - free_write_bytes_quota, + legacy_free_write_bytes_quota: free_write_bytes_quota, }), change_set_configs: ChangeSetConfigs::unlimited_at_gas_feature_version( LATEST_GAS_FEATURE_VERSION, diff --git a/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs b/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs index 012538886f613b..56a5269bb05427 100644 --- a/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs +++ b/aptos-move/e2e-move-tests/src/tests/per_category_gas_limits.rs @@ -91,7 +91,7 @@ fn io_limit_reached_by_new_bytes() { // Allow 10 value bytes charged at most. gas_params.vm.txn.max_io_gas = 110_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); test_create_multiple_items(&mut h, &acc, |status| { @@ -112,7 +112,7 @@ fn storage_limit_reached_by_new_bytes() { // Allow 10 value bytes charged at most. gas_params.vm.txn.max_storage_fee = 11_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); test_create_multiple_items(&mut h, &acc, |status| { @@ -134,7 +134,7 @@ fn out_of_gas_while_charging_write_gas() { // Bump max gas allowed gas_params.vm.txn.maximum_number_of_gas_units = 1_000_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); // Allow 10 value bytes charged at most. Notice this is in external units. h.set_max_gas_per_txn(110_000); @@ -156,7 +156,7 @@ fn out_of_gas_while_charging_storage_fee() { // Bump max gas allowed gas_params.vm.txn.maximum_number_of_gas_units = 1_000_000_000.into(); // Make the key bytes free, only play around value sizes. - gas_params.vm.txn.free_write_bytes_quota = state_key_size(); + gas_params.vm.txn.legacy_free_write_bytes_quota = state_key_size(); }); // Allow 10 value bytes charged at most. Notice this is in external units, // which is 1/100x octas or 1Mx internal units. diff --git a/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs b/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs index 30716ef97fbfd2..034bc2d68204ee 100644 --- a/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs +++ b/aptos-move/e2e-testsuite/src/tests/failed_transaction_tests.rs @@ -40,7 +40,7 @@ fn failed_transaction_cleanup_test() { let gas_params = AptosGasParameters::zeros(); let storage_gas_params = - StorageGasParameters::unlimited(gas_params.vm.txn.free_write_bytes_quota); + StorageGasParameters::unlimited(gas_params.vm.txn.legacy_free_write_bytes_quota); let change_set_configs = storage_gas_params.change_set_configs.clone(); diff --git a/aptos-move/framework/table-natives/src/lib.rs b/aptos-move/framework/table-natives/src/lib.rs index 55717e75c0d638..331694c6b75621 100644 --- a/aptos-move/framework/table-natives/src/lib.rs +++ b/aptos-move/framework/table-natives/src/lib.rs @@ -287,7 +287,17 @@ fn charge_load_cost( match loaded { Some(Some(num_bytes)) => { - context.charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_PER_BYTE * num_bytes) + if context.gas_feature_version() >= 12 { + // round up bytes to whole pages + const PAGE_SIZE: u64 = 4096; + let loaded_u64: u64 = num_bytes.into(); + let r = loaded_u64 % PAGE_SIZE; + let rounded_up = loaded_u64 + if r == 0 { 0 } else { PAGE_SIZE - r }; + context + .charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_PER_BYTE * NumBytes::new(rounded_up)) + } else { + context.charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_PER_BYTE * num_bytes) + } }, Some(None) => context.charge(COMMON_LOAD_BASE_NEW + COMMON_LOAD_FAILURE), None => Ok(()),