Skip to content

Commit

Permalink
Merge pull request #1612 from oasisprotocol/kostko/feature/extra-stor…
Browse files Browse the repository at this point in the history
…age-costs

runtime-sdk: Support storage update gas costs
  • Loading branch information
kostko authored Jan 25, 2024
2 parents 385af35 + 0797ae6 commit 136ccd5
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 8 deletions.
1 change: 0 additions & 1 deletion runtime-sdk/modules/contracts/src/abi/oasis/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use oasis_runtime_sdk::{
error::Error as _,
modules,
modules::core,
state::CurrentState,
testing::mock,
types::{address::Address, transaction::CallFormat},
};
Expand Down
7 changes: 1 addition & 6 deletions runtime-sdk/src/dispatcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1003,12 +1003,7 @@ mod test {
max_tx_size: 32 * 1024,
max_tx_signers: 1,
max_multisig_signers: 8,
gas_costs: core::GasCosts {
tx_byte: 0,
auth_signature: 0,
auth_multisig_signer: 0,
callformat_x25519_deoxysii: 0,
},
gas_costs: Default::default(),
min_gas_price: BTreeMap::from([(token::Denomination::NATIVE, 0)]),
dynamic_min_gas_price: Default::default(),
},
Expand Down
24 changes: 23 additions & 1 deletion runtime-sdk/src/modules/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ pub enum Event {
#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
pub struct GasCosts {
pub tx_byte: u64,
pub storage_byte: u64,

pub auth_signature: u64,
pub auth_multisig_signer: u64,
Expand Down Expand Up @@ -1108,8 +1109,29 @@ impl<Cfg: Config> module::TransactionHandler for Module<Cfg> {
_ctx: &C,
result: module::CallResult,
) -> Result<module::CallResult, Error> {
// Skip handling for internally generated calls.
if CurrentState::with_env(|env| env.is_internal()) {
return Ok(result);
}

// Charge storage update gas cost if this would be greater than the gas use.
let params = Self::params();
if params.gas_costs.storage_byte > 0 {
let storage_update_bytes =
CurrentState::with(|state| state.pending_store_update_byte_size());
let storage_gas = params
.gas_costs
.storage_byte
.saturating_mul(storage_update_bytes as u64);
let used_gas = Self::used_tx_gas();

if storage_gas > used_gas {
Self::use_tx_gas(storage_gas - used_gas)?;
}
}

// Emit gas used event (if this is not an internally generated call).
if Cfg::EMIT_GAS_USED_EVENTS && !CurrentState::with_env(|env| env.is_internal()) {
if Cfg::EMIT_GAS_USED_EVENTS {
let used_gas = Self::used_tx_gas();
CurrentState::with(|state| {
state.emit_unconditional_event(Event::GasUsed { amount: used_gas });
Expand Down
119 changes: 119 additions & 0 deletions runtime-sdk/src/modules/core/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ impl GasWasterModule {
const METHOD_WASTE_GAS_CALLER: &'static str = "test.WasteGasCaller";
const METHOD_SPECIFIC_GAS_REQUIRED: &'static str = "test.SpecificGasRequired";
const METHOD_SPECIFIC_GAS_REQUIRED_HUGE: &'static str = "test.SpecificGasRequiredHuge";
const METHOD_STORAGE_UPDATE: &'static str = "test.StorageUpdate";
const METHOD_STORAGE_REMOVE: &'static str = "test.StorageRemove";
}

#[sdk_derive(Module)]
Expand Down Expand Up @@ -296,6 +298,26 @@ impl GasWasterModule {
Ok(())
}
}

#[handler(call = Self::METHOD_STORAGE_UPDATE)]
fn storage_update<C: Context>(
_ctx: &C,
args: (Vec<u8>, Vec<u8>, u64),
) -> Result<(), <GasWasterModule as module::Module>::Error> {
<C::Runtime as Runtime>::Core::use_tx_gas(args.2)?;
CurrentState::with_store(|store| store.insert(&args.0, &args.1));
Ok(())
}

#[handler(call = Self::METHOD_STORAGE_REMOVE)]
fn storage_remove<C: Context>(
_ctx: &C,
args: Vec<u8>,
) -> Result<(), <GasWasterModule as module::Module>::Error> {
<C::Runtime as Runtime>::Core::use_tx_gas(2)?;
CurrentState::with_store(|store| store.remove(&args));
Ok(())
}
}

impl module::BlockHandler for GasWasterModule {}
Expand Down Expand Up @@ -339,6 +361,7 @@ impl Runtime for GasWasterRuntime {
max_multisig_signers: 8,
gas_costs: super::GasCosts {
tx_byte: 0,
storage_byte: 0,
auth_signature: Self::AUTH_SIGNATURE_GAS,
auth_multisig_signer: Self::AUTH_MULTISIG_GAS,
callformat_x25519_deoxysii: 0,
Expand Down Expand Up @@ -878,6 +901,7 @@ fn test_min_gas_price() {
max_multisig_signers: 8,
gas_costs: super::GasCosts {
tx_byte: 0,
storage_byte: 0,
auth_signature: GasWasterRuntime::AUTH_SIGNATURE_GAS,
auth_multisig_signer: GasWasterRuntime::AUTH_MULTISIG_GAS,
callformat_x25519_deoxysii: 0,
Expand Down Expand Up @@ -1099,6 +1123,8 @@ fn test_module_info() {
MethodHandlerInfo { kind: types::MethodHandlerKind::Call, name: "test.WasteGasCaller".to_string() },
MethodHandlerInfo { kind: types::MethodHandlerKind::Call, name: "test.SpecificGasRequired".to_string() },
MethodHandlerInfo { kind: types::MethodHandlerKind::Call, name: "test.SpecificGasRequiredHuge".to_string() },
MethodHandlerInfo { kind: types::MethodHandlerKind::Call, name: "test.StorageUpdate".to_string() },
MethodHandlerInfo { kind: types::MethodHandlerKind::Call, name: "test.StorageRemove".to_string() },
],
},
}
Expand Down Expand Up @@ -1156,6 +1182,7 @@ fn test_dynamic_min_gas_price() {
max_multisig_signers: 8,
gas_costs: super::GasCosts {
tx_byte: 0,
storage_byte: 0,
auth_signature: GasWasterRuntime::AUTH_SIGNATURE_GAS,
auth_multisig_signer: GasWasterRuntime::AUTH_MULTISIG_GAS,
callformat_x25519_deoxysii: 0,
Expand Down Expand Up @@ -1257,3 +1284,95 @@ fn test_dynamic_min_gas_price() {
);
assert_eq!(Core::min_gas_price(&ctx, &denom), Some(100));
}

#[test]
fn test_storage_gas() {
let mut mock = mock::Mock::default();
let ctx = mock.create_ctx_for_runtime::<GasWasterRuntime>(false);

GasWasterRuntime::migrate(&ctx);

let storage_byte_cost = 1;
Core::set_params(Parameters {
max_batch_gas: 10_000,
gas_costs: super::GasCosts {
tx_byte: 0,
storage_byte: storage_byte_cost,
..Default::default()
},
..Core::params()
});

let mut signer = mock::Signer::new(0, keys::alice::sigspec());

let key = b"foo".to_vec();
let value = b"bar".to_vec();

// Insert (non-storage gas smaller than storage gas).
let expected_gas_use = (key.len() + value.len()) as u64 * storage_byte_cost;
let dispatch_result = signer.call_opts(
&ctx,
GasWasterModule::METHOD_STORAGE_UPDATE,
(key.clone(), value.clone(), 2), // Use 2 extra gas, make sure it is not charged.
mock::CallOptions {
fee: transaction::Fee {
gas: 10_000,
..Default::default()
},
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");

let tags = &dispatch_result.tags;
assert_eq!(tags.len(), 1, "one event should have been emitted");
assert_eq!(tags[0].key, b"core\x00\x00\x00\x01"); // core.GasUsed (code = 1) event

#[derive(Debug, Default, cbor::Decode)]
struct GasUsedEvent {
amount: u64,
}

let events: Vec<GasUsedEvent> = cbor::from_slice(&tags[0].value).unwrap();
assert_eq!(events.len(), 1); // Just one gas used event.
assert_eq!(events[0].amount, expected_gas_use);

// Insert (non-storage gas larger than storage gas)
let expected_gas_use = 42; // No storage gas should be charged.
let dispatch_result = signer.call_opts(
&ctx,
GasWasterModule::METHOD_STORAGE_UPDATE,
(key.clone(), value.clone(), 42), // Use 42 extra gas, it should be charged.
mock::CallOptions {
fee: transaction::Fee {
gas: 10_000,
..Default::default()
},
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");

let tags = &dispatch_result.tags;
let events: Vec<GasUsedEvent> = cbor::from_slice(&tags[0].value).unwrap();
assert_eq!(events.len(), 1); // Just one gas used event.
assert_eq!(events[0].amount, expected_gas_use);

// Remove.
let expected_gas_use = key.len() as u64 * storage_byte_cost;
let dispatch_result = signer.call_opts(
&ctx,
GasWasterModule::METHOD_STORAGE_REMOVE,
key,
mock::CallOptions {
fee: transaction::Fee {
gas: 10_000,
..Default::default()
},
},
);
assert!(dispatch_result.result.is_success(), "call should succeed");

let tags = &dispatch_result.tags;
let events: Vec<GasUsedEvent> = cbor::from_slice(&tags[0].value).unwrap();
assert_eq!(events.len(), 1); // Just one gas used event.
assert_eq!(events[0].amount, expected_gas_use);
}
8 changes: 8 additions & 0 deletions runtime-sdk/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,14 @@ impl State {
.unwrap_or_default()
}

/// Size (in bytes) of any pending updates in the associated store.
pub fn pending_store_update_byte_size(&self) -> usize {
self.store
.as_ref()
.map(|store| store.pending_update_byte_size())
.unwrap_or_default()
}

/// Random number generator.
pub fn rng(&mut self) -> &mut RootRng {
self.rng.as_mut().unwrap()
Expand Down
4 changes: 4 additions & 0 deletions runtime-sdk/src/storage/mkvs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,8 @@ impl<M: mkvs::MKVS> NestedStore for MKVSStore<M> {
fn has_pending_updates(&self) -> bool {
true
}

fn pending_update_byte_size(&self) -> usize {
0
}
}
3 changes: 3 additions & 0 deletions runtime-sdk/src/storage/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ pub trait NestedStore: Store {

/// Whether there are any store updates pending to be committed.
fn has_pending_updates(&self) -> bool;

/// Size (in bytes) of any pending updates.
fn pending_update_byte_size(&self) -> usize;
}

impl<S: Store + ?Sized> Store for &mut S {
Expand Down
19 changes: 19 additions & 0 deletions runtime-sdk/src/storage/overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ impl<S: Store> NestedStore for OverlayStore<S> {
fn has_pending_updates(&self) -> bool {
!self.dirty.is_empty()
}

fn pending_update_byte_size(&self) -> usize {
let updated_size = self
.overlay
.iter()
.map(|(key, value)| key.len() + value.len())
.reduce(|acc, bytes| acc + bytes)
.unwrap_or_default();

let removed_size = self
.dirty
.iter()
.filter(|key| !self.overlay.contains_key(key.as_slice()))
.map(|key| key.len())
.reduce(|acc, bytes| acc + bytes)
.unwrap_or_default();

updated_size.saturating_add(removed_size)
}
}

impl<S: Store> Store for OverlayStore<S> {
Expand Down
1 change: 1 addition & 0 deletions tests/runtimes/simple-keyvalue/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl sdk::Runtime for Runtime {
max_multisig_signers: 8,
gas_costs: modules::core::GasCosts {
tx_byte: 1,
storage_byte: 1,
auth_signature: 10,
auth_multisig_signer: 10,
callformat_x25519_deoxysii: 50,
Expand Down

0 comments on commit 136ccd5

Please sign in to comment.