1use std::{
3 collections::{BTreeMap, BTreeSet},
4 convert::{TryFrom, TryInto},
5 fmt::Display,
6};
7
8use anyhow::anyhow;
9use oasis_runtime_sdk_macros::{handler, sdk_derive};
10use thiserror::Error;
11
12use crate::{
13 callformat,
14 context::Context,
15 core::consensus::beacon::EpochTime,
16 dispatcher,
17 error::Error as SDKError,
18 keymanager, migration,
19 module::{
20 self, CallResult, InvariantHandler as _, MethodHandler as _, Module as _,
21 ModuleInfoHandler as _,
22 },
23 sender::SenderMeta,
24 state::{CurrentState, Mode, Options, TransactionWithMeta},
25 storage::{self},
26 types::{
27 token::{self, Denomination},
28 transaction::{
29 self, AddressSpec, AuthProof, Call, CallFormat, CallerAddress, SignerInfo, Transaction,
30 UnverifiedTransaction,
31 },
32 },
33 Runtime,
34};
35
36use self::types::RuntimeInfoResponse;
37
38#[cfg(test)]
39mod test;
40pub mod types;
41
42pub const MODULE_NAME: &str = "core";
44
45#[derive(Error, Debug, oasis_runtime_sdk_macros::Error)]
47pub enum Error {
48 #[error("malformed transaction: {0}")]
49 #[sdk_error(code = 1)]
50 MalformedTransaction(#[source] anyhow::Error),
51
52 #[error("invalid transaction: {0}")]
53 #[sdk_error(code = 2)]
54 InvalidTransaction(#[from] transaction::Error),
55
56 #[error("invalid method: {0}")]
57 #[sdk_error(code = 3)]
58 InvalidMethod(String),
59
60 #[error("invalid nonce")]
61 #[sdk_error(code = 4)]
62 InvalidNonce,
63
64 #[error("insufficient balance to pay fees")]
65 #[sdk_error(code = 5)]
66 InsufficientFeeBalance,
67
68 #[error("out of message slots")]
69 #[sdk_error(code = 6)]
70 OutOfMessageSlots,
71
72 #[error("message handler not invoked")]
73 #[sdk_error(code = 8)]
74 MessageHandlerNotInvoked,
75
76 #[error("missing message handler")]
77 #[sdk_error(code = 9)]
78 MessageHandlerMissing(u32),
79
80 #[error("invalid argument: {0}")]
81 #[sdk_error(code = 10)]
82 InvalidArgument(#[source] anyhow::Error),
83
84 #[error("gas overflow")]
85 #[sdk_error(code = 11)]
86 GasOverflow,
87
88 #[error("out of gas (limit: {0} wanted: {1})")]
89 #[sdk_error(code = 12)]
90 OutOfGas(u64, u64),
91
92 #[error("too many authentication slots")]
93 #[sdk_error(code = 15)]
94 TooManyAuth,
95
96 #[error("multisig too many signers")]
97 #[sdk_error(code = 16)]
98 MultisigTooManySigners,
99
100 #[error("invariant violation: {0}")]
101 #[sdk_error(code = 17)]
102 InvariantViolation(String),
103
104 #[error("invalid call format: {0}")]
105 #[sdk_error(code = 18)]
106 InvalidCallFormat(#[source] anyhow::Error),
107
108 #[error("{0}")]
109 #[sdk_error(transparent, abort)]
110 Abort(#[source] dispatcher::Error),
111
112 #[error("no module could authenticate the transaction")]
113 #[sdk_error(code = 19)]
114 NotAuthenticated,
115
116 #[error("gas price too low")]
117 #[sdk_error(code = 20)]
118 GasPriceTooLow,
119
120 #[error("forbidden in secure build")]
121 #[sdk_error(code = 21)]
122 ForbiddenInSecureBuild,
123
124 #[error("forbidden by node policy")]
125 #[sdk_error(code = 22)]
126 Forbidden,
127
128 #[error("transaction is too large")]
129 #[sdk_error(code = 23)]
130 OversizedTransaction,
131
132 #[error("transaction is expired or not yet valid")]
133 #[sdk_error(code = 24)]
134 ExpiredTransaction,
135
136 #[error("read-only transaction attempted modifications")]
137 #[sdk_error(code = 25)]
138 ReadOnlyTransaction,
139
140 #[error("future nonce")]
141 #[sdk_error(code = 26)]
142 FutureNonce,
143
144 #[error("call depth exceeded (depth: {0} max: {1})")]
145 #[sdk_error(code = 27)]
146 CallDepthExceeded(u16, u16),
147
148 #[error("{0}")]
149 #[sdk_error(transparent)]
150 TxSimulationFailed(#[from] TxSimulationFailure),
151}
152
153impl Error {
154 pub fn out_of_gas<Cfg: Config>(limit: u64, wanted: u64) -> Self {
157 if Cfg::EMIT_GAS_USED_EVENTS {
158 Self::OutOfGas(limit, wanted)
159 } else {
160 Self::OutOfGas(0, 0)
162 }
163 }
164}
165
166#[derive(Error, Debug)]
168pub struct TxSimulationFailure {
169 message: String,
170 module_name: String,
171 code: u32,
172}
173
174impl TxSimulationFailure {
175 pub fn is_error_core_out_of_gas(&self) -> bool {
177 self.module_name == MODULE_NAME && self.code == 12
178 }
179}
180
181impl Display for TxSimulationFailure {
182 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183 write!(f, "{}", self.message)
184 }
185}
186
187impl SDKError for TxSimulationFailure {
188 fn module_name(&self) -> &str {
189 &self.module_name
190 }
191
192 fn code(&self) -> u32 {
193 self.code
194 }
195}
196
197impl TryFrom<CallResult> for TxSimulationFailure {
198 type Error = anyhow::Error;
199
200 fn try_from(value: CallResult) -> Result<Self, Self::Error> {
201 match value {
202 CallResult::Failed {
203 module,
204 code,
205 message,
206 } => Ok(TxSimulationFailure {
207 code,
208 module_name: module,
209 message,
210 }),
211 _ => Err(anyhow!("CallResult not Failed")),
212 }
213 }
214}
215
216#[derive(Debug, PartialEq, Eq, cbor::Encode, oasis_runtime_sdk_macros::Event)]
218#[cbor(untagged)]
219pub enum Event {
220 #[sdk_event(code = 1)]
221 GasUsed { amount: u64 },
222}
223
224#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
226pub struct GasCosts {
227 pub tx_byte: u64,
228 pub storage_byte: u64,
229
230 pub auth_signature: u64,
231 pub auth_multisig_signer: u64,
232
233 pub callformat_x25519_deoxysii: u64,
234}
235
236#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
238pub struct DynamicMinGasPrice {
239 pub enabled: bool,
244
245 pub target_block_gas_usage_percentage: u8,
251 pub min_price_max_change_denominator: u8,
257}
258
259#[derive(Error, Debug)]
261pub enum ParameterValidationError {
262 #[error("invalid dynamic target block gas usage percentage (10-100)")]
263 InvalidTargetBlockGasUsagePercentage,
264 #[error("invalid dynamic min price max change denominator (1-50)")]
265 InvalidMinPriceMaxChangeDenominator,
266}
267#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
269pub struct Parameters {
270 pub max_batch_gas: u64,
271 pub max_tx_size: u32,
272 pub max_tx_signers: u32,
273 pub max_multisig_signers: u32,
274 pub gas_costs: GasCosts,
275 pub min_gas_price: BTreeMap<token::Denomination, u128>,
276 pub dynamic_min_gas_price: DynamicMinGasPrice,
277}
278
279impl module::Parameters for Parameters {
280 type Error = ParameterValidationError;
281
282 fn validate_basic(&self) -> Result<(), Self::Error> {
283 let dmgp = &self.dynamic_min_gas_price;
285 if dmgp.enabled {
286 if dmgp.target_block_gas_usage_percentage < 10
287 || dmgp.target_block_gas_usage_percentage > 100
288 {
289 return Err(ParameterValidationError::InvalidTargetBlockGasUsagePercentage);
290 }
291 if dmgp.min_price_max_change_denominator < 1
292 || dmgp.min_price_max_change_denominator > 50
293 {
294 return Err(ParameterValidationError::InvalidMinPriceMaxChangeDenominator);
295 }
296 }
297 Ok(())
298 }
299}
300
301pub trait API {
303 type Config: Config;
305
306 fn use_batch_gas(gas: u64) -> Result<(), Error>;
310
311 fn use_tx_gas(gas: u64) -> Result<(), Error>;
315
316 fn remaining_batch_gas() -> u64;
318
319 fn used_batch_gas() -> u64;
321
322 fn remaining_tx_gas() -> u64;
324
325 fn used_tx_gas() -> u64;
327
328 fn max_batch_gas() -> u64;
330
331 fn min_gas_price(denom: &token::Denomination) -> Option<u128>;
333
334 fn set_priority(priority: u64);
336
337 fn take_priority() -> u64;
339
340 fn set_sender_meta(meta: SenderMeta);
342
343 fn take_sender_meta() -> SenderMeta;
345
346 fn estimate_gas_search_max_iters<C: Context>(ctx: &C) -> u64;
349
350 fn has_epoch_changed() -> bool;
352}
353
354#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
356pub struct Genesis {
357 pub parameters: Parameters,
358}
359
360#[derive(Clone, Debug, Default, cbor::Encode, cbor::Decode)]
362pub struct LocalConfig {
363 #[cbor(optional)]
365 pub min_gas_price: BTreeMap<token::Denomination, u128>,
366
367 #[cbor(optional)]
372 pub max_estimated_gas: u64,
373
374 #[cbor(optional)]
380 pub estimate_gas_search_max_iters: u64,
381}
382
383pub mod state {
385 pub const METADATA: &[u8] = &[0x01];
387 pub const MESSAGE_HANDLERS: &[u8] = &[0x02];
389 pub const LAST_EPOCH: &[u8] = &[0x03];
391 pub const DYNAMIC_MIN_GAS_PRICE: &[u8] = &[0x04];
393}
394
395#[allow(clippy::declare_interior_mutable_const)]
397pub trait Config: 'static {
398 const DEFAULT_LOCAL_MIN_GAS_PRICE: once_cell::unsync::Lazy<
401 BTreeMap<token::Denomination, u128>,
402 > = once_cell::unsync::Lazy::new(BTreeMap::new);
403
404 const DEFAULT_LOCAL_ESTIMATE_GAS_SEARCH_MAX_ITERS: u64 = 0;
407
408 const ESTIMATE_GAS_EXTRA_FAIL: once_cell::unsync::Lazy<BTreeMap<&'static str, u64>> =
410 once_cell::unsync::Lazy::new(BTreeMap::new);
411
412 const MIN_GAS_PRICE_EXEMPT_METHODS: once_cell::unsync::Lazy<BTreeSet<&'static str>> =
414 once_cell::unsync::Lazy::new(BTreeSet::new);
415
416 const EMIT_GAS_USED_EVENTS: bool = true;
420
421 const ALLOW_INTERACTIVE_READ_ONLY_TRANSACTIONS: bool = false;
425
426 const GAS_COST_CALL_CALLDATA_PUBLIC_KEY: u64 = 20;
428 const GAS_COST_CALL_CURRENT_EPOCH: u64 = 10;
430}
431
432pub struct Module<Cfg: Config> {
433 _cfg: std::marker::PhantomData<Cfg>,
434}
435
436const CONTEXT_KEY_GAS_USED: &str = "core.GasUsed";
437const CONTEXT_KEY_PRIORITY: &str = "core.Priority";
438const CONTEXT_KEY_SENDER_META: &str = "core.SenderMeta";
439const CONTEXT_KEY_EPOCH_CHANGED: &str = "core.EpochChanged";
440
441impl<Cfg: Config> API for Module<Cfg> {
442 type Config = Cfg;
443
444 fn use_batch_gas(gas: u64) -> Result<(), Error> {
445 if CurrentState::with_env(|env| env.is_check_only()) {
447 return Ok(());
448 }
449 let batch_gas_limit = Self::params().max_batch_gas;
450 let batch_gas_used = Self::used_batch_gas();
451 let batch_new_gas_used = batch_gas_used
455 .checked_add(gas)
456 .ok_or(Error::Abort(dispatcher::Error::BatchOutOfGas))?;
457 if batch_new_gas_used > batch_gas_limit {
458 return Err(Error::Abort(dispatcher::Error::BatchOutOfGas));
459 }
460
461 CurrentState::with(|state| {
462 state
463 .block_value::<u64>(CONTEXT_KEY_GAS_USED)
464 .set(batch_new_gas_used);
465 });
466
467 Ok(())
468 }
469
470 fn use_tx_gas(gas: u64) -> Result<(), Error> {
471 let (gas_limit, gas_used) = CurrentState::with(|state| {
472 (
473 state.env().tx_auth_info().fee.gas,
474 *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default(),
475 )
476 });
477 let new_gas_used = {
478 let sum = gas_used.checked_add(gas).ok_or(Error::GasOverflow)?;
479 if sum > gas_limit {
480 return Err(Error::out_of_gas::<Cfg>(gas_limit, sum));
481 }
482 sum
483 };
484
485 Self::use_batch_gas(gas)?;
486
487 CurrentState::with(|state| {
488 *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default() = new_gas_used;
489 });
490
491 Ok(())
492 }
493
494 fn remaining_batch_gas() -> u64 {
495 let batch_gas_limit = Self::params().max_batch_gas;
496 batch_gas_limit.saturating_sub(Self::used_batch_gas())
497 }
498
499 fn used_batch_gas() -> u64 {
500 CurrentState::with(|state| {
501 state
502 .block_value::<u64>(CONTEXT_KEY_GAS_USED)
503 .get()
504 .cloned()
505 .unwrap_or_default()
506 })
507 }
508
509 fn remaining_tx_gas() -> u64 {
510 let (gas_limit, gas_used) = CurrentState::with(|state| {
511 (
512 state.env().tx_auth_info().fee.gas,
513 *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default(),
514 )
515 });
516 let remaining_tx = gas_limit.saturating_sub(gas_used);
517 let remaining_batch = Self::remaining_batch_gas();
519 std::cmp::min(remaining_tx, remaining_batch)
520 }
521
522 fn used_tx_gas() -> u64 {
523 CurrentState::with(|state| *state.local_value::<u64>(CONTEXT_KEY_GAS_USED).or_default())
524 }
525
526 fn max_batch_gas() -> u64 {
527 Self::params().max_batch_gas
528 }
529
530 fn min_gas_price(denom: &token::Denomination) -> Option<u128> {
531 Self::min_gas_prices().get(denom).copied()
532 }
533
534 fn set_priority(priority: u64) {
535 CurrentState::with(|state| {
536 state.block_value::<u64>(CONTEXT_KEY_PRIORITY).set(priority);
537 })
538 }
539
540 fn take_priority() -> u64 {
541 CurrentState::with(|state| {
542 state
543 .block_value::<u64>(CONTEXT_KEY_PRIORITY)
544 .take()
545 .unwrap_or_default()
546 })
547 }
548
549 fn set_sender_meta(meta: SenderMeta) {
550 CurrentState::with(|state| {
551 state
552 .block_value::<SenderMeta>(CONTEXT_KEY_SENDER_META)
553 .set(meta);
554 });
555 }
556
557 fn take_sender_meta() -> SenderMeta {
558 CurrentState::with(|state| {
559 state
560 .block_value::<SenderMeta>(CONTEXT_KEY_SENDER_META)
561 .take()
562 .unwrap_or_default()
563 })
564 }
565
566 fn estimate_gas_search_max_iters<C: Context>(ctx: &C) -> u64 {
567 ctx.local_config(MODULE_NAME)
568 .as_ref()
569 .map(|cfg: &LocalConfig| cfg.estimate_gas_search_max_iters)
570 .unwrap_or(Cfg::DEFAULT_LOCAL_ESTIMATE_GAS_SEARCH_MAX_ITERS)
571 }
572
573 fn has_epoch_changed() -> bool {
574 CurrentState::with(|state| {
575 *state
576 .block_value(CONTEXT_KEY_EPOCH_CHANGED)
577 .get()
578 .unwrap_or(&false)
579 })
580 }
581}
582
583#[sdk_derive(Module)]
584impl<Cfg: Config> Module<Cfg> {
585 const NAME: &'static str = MODULE_NAME;
586 type Error = Error;
587 type Event = Event;
588 type Parameters = Parameters;
589 type Genesis = Genesis;
590
591 #[migration(init)]
592 pub fn init(genesis: Genesis) {
593 Self::set_params(genesis.parameters);
595 }
596
597 #[handler(query = "core.EstimateGas", allow_private_km)]
602 pub fn query_estimate_gas<C: Context>(
603 ctx: &C,
604 mut args: types::EstimateGasQuery,
605 ) -> Result<u64, Error> {
606 let mut extra_gas = 0u64;
607 if ctx.is_confidential() {
610 args.caller = Some(
611 args.caller
612 .unwrap_or_else(|| {
613 args.tx
614 .auth_info
615 .signer_info
616 .first()
617 .map(|si| si.address_spec.caller_address())
618 .unwrap_or(CallerAddress::Address(Default::default()))
619 })
620 .zeroized(),
621 );
622 args.propagate_failures = false; }
624 args.tx.auth_info.fee.gas = {
626 let local_max_estimated_gas = Self::get_local_max_estimated_gas(ctx);
627 if local_max_estimated_gas == 0 {
628 Self::params().max_batch_gas
629 } else {
630 local_max_estimated_gas
631 }
632 };
633 args.tx.auth_info.fee.amount =
634 token::BaseUnits::new(u64::MAX.into(), token::Denomination::NATIVE);
635 args.tx.auth_info.fee.consensus_messages = ctx.max_messages();
636 let auth_proofs: Result<_, Error> = args
639 .tx
640 .auth_info
641 .signer_info
642 .iter()
643 .map(|si| match si.address_spec {
644 transaction::AddressSpec::Signature(_) => {
646 Ok(transaction::AuthProof::Signature(vec![0; 64].into()))
647 }
648 transaction::AddressSpec::Multisig(ref cfg) => {
650 Ok(transaction::AuthProof::Multisig(
651 cfg.signers
652 .iter()
653 .map(|_| Some(vec![0; 64].into()))
654 .collect(),
655 ))
656 }
657 transaction::AddressSpec::Internal(_) => Err(Error::MalformedTransaction(anyhow!(
659 "internal address spec used"
660 ))),
661 })
662 .collect();
663 let tx_envelope =
664 transaction::UnverifiedTransaction(cbor::to_vec(args.tx.clone()), auth_proofs?);
665 let tx_size: u32 = cbor::to_vec(tx_envelope)
666 .len()
667 .try_into()
668 .map_err(|_| Error::InvalidArgument(anyhow!("transaction too large")))?;
669 let propagate_failures = args.propagate_failures;
670 let bs_max_iters = Self::estimate_gas_search_max_iters(ctx);
671
672 if let Some(caller) = args.caller.clone() {
674 extra_gas = extra_gas.saturating_add(
677 Self::compute_signature_verification_cost(
678 &Self::params(),
679 &args.tx.auth_info.signer_info,
680 )
681 .unwrap_or_default(),
682 );
683
684 args.tx.auth_info.signer_info = vec![transaction::SignerInfo {
685 address_spec: transaction::AddressSpec::Internal(caller),
686 nonce: args
687 .tx
688 .auth_info
689 .signer_info
690 .first()
691 .map(|si| si.nonce)
692 .unwrap_or_default(),
693 }];
694 }
695
696 #[allow(clippy::borrow_interior_mutable_const)]
698 let extra_gas_fail = *Cfg::ESTIMATE_GAS_EXTRA_FAIL
699 .get(args.tx.call.method.as_str())
700 .unwrap_or(&0);
701
702 let simulate = |tx: &transaction::Transaction, gas: u64, report_failure: bool| {
704 let mut tx = tx.clone();
705 tx.auth_info.fee.gas = gas;
706 let call = tx.call.clone(); CurrentState::with_transaction_opts(
709 Options::new()
710 .with_mode(Mode::Simulate)
711 .with_tx(TransactionWithMeta {
712 data: tx,
713 size: tx_size,
714 index: 0,
715 hash: Default::default(),
716 }),
717 || {
718 let (result, _) = dispatcher::Dispatcher::<C::Runtime>::dispatch_tx_call(
719 ctx,
720 call,
721 &Default::default(),
722 );
723 if !result.is_success() && report_failure {
724 let err: TxSimulationFailure = result.try_into().unwrap(); return Err(Error::TxSimulationFailed(err));
727 }
728 let gas_used = Self::used_batch_gas();
731 if result.is_success() {
732 Ok(gas_used)
733 } else {
734 Ok(gas_used.saturating_add(extra_gas_fail).clamp(0, gas))
735 }
736 },
737 )
738 };
739
740 let (cap, mut lo, mut hi) = (
742 args.tx.auth_info.fee.gas,
743 10_u128,
744 args.tx.auth_info.fee.gas as u128,
745 ); let (mut iters, mut fast_path_tried) = (0, false);
749 let (mut has_succeeded, mut tried_with_max_gas) = (false, false);
754 while (lo + 1 < hi) && iters < bs_max_iters {
755 iters += 1;
756
757 let mid = (hi + lo) / 2;
758 match simulate(&args.tx, mid as u64, true) {
759 Ok(r) => {
760 hi = mid;
762
763 has_succeeded = true;
766
767 if !fast_path_tried && (lo + 1 < hi) {
770 fast_path_tried = true;
771
772 match simulate(&args.tx, r, true) {
775 Ok(_) => hi = r as u128,
776 _ => continue,
777 }
778 match simulate(&args.tx, r - 1, true) {
780 Err(_) => {
781 break;
783 }
784 _ => continue,
785 }
786 }
787 }
788 Err(_) if has_succeeded => {
789 lo = mid
793 }
794 Err(Error::TxSimulationFailed(failure)) if failure.is_error_core_out_of_gas() => {
795 lo = mid
797 }
798 r @ Err(_) => {
799 let mut res = r;
800 if !tried_with_max_gas {
801 tried_with_max_gas = true;
802 res = simulate(&args.tx, cap, true)
808 }
809 match res {
810 Ok(_) => {
811 has_succeeded = true;
812 lo = mid
814 }
815 err if propagate_failures => {
816 return err;
818 }
819 _ => {
820 break;
825 }
826 }
827 }
828 }
829 }
830
831 let result = if hi == Into::<u128>::into(cap) {
833 simulate(&args.tx, cap, propagate_failures)
835 } else {
836 Ok(hi as u64)
837 };
838
839 result.map(|est| est.saturating_add(extra_gas).clamp(0, cap))
841 }
842
843 #[handler(query = "core.CheckInvariants", expensive)]
845 fn query_check_invariants<C: Context>(ctx: &C, _args: ()) -> Result<(), Error> {
846 <C::Runtime as Runtime>::Modules::check_invariants(ctx)
847 }
848
849 fn calldata_public_key_common<C: Context>(
850 ctx: &C,
851 ) -> Result<types::CallDataPublicKeyQueryResponse, Error> {
852 let key_manager = ctx
853 .key_manager()
854 .ok_or_else(|| Error::InvalidArgument(anyhow!("key manager not available")))?;
855 let epoch = ctx.epoch();
856 let public_key = key_manager
857 .get_public_ephemeral_key(callformat::get_key_pair_id(epoch), epoch)
858 .map_err(|err| match err {
859 keymanager::KeyManagerError::InvalidEpoch(..) => {
860 Error::InvalidCallFormat(anyhow!("invalid epoch"))
861 }
862 _ => Error::Abort(err.into()),
863 })?;
864
865 Ok(types::CallDataPublicKeyQueryResponse { public_key, epoch })
866 }
867
868 #[handler(query = "core.CallDataPublicKey")]
870 fn query_calldata_public_key<C: Context>(
871 ctx: &C,
872 _args: (),
873 ) -> Result<types::CallDataPublicKeyQueryResponse, Error> {
874 Self::calldata_public_key_common(ctx)
875 }
876
877 #[handler(call = "core.CallDataPublicKey", internal)]
879 fn internal_calldata_public_key<C: Context>(
880 ctx: &C,
881 _args: (),
882 ) -> Result<types::CallDataPublicKeyQueryResponse, Error> {
883 <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_CALLDATA_PUBLIC_KEY)?;
884 Self::calldata_public_key_common(ctx)
885 }
886
887 #[handler(call = "core.CurrentEpoch", internal)]
889 fn internal_current_epoch<C: Context>(ctx: &C, _args: ()) -> Result<u64, Error> {
890 <C::Runtime as Runtime>::Core::use_tx_gas(Cfg::GAS_COST_CALL_CURRENT_EPOCH)?;
891 Ok(ctx.epoch())
892 }
893
894 #[handler(query = "core.MinGasPrice")]
896 fn query_min_gas_price<C: Context>(
897 ctx: &C,
898 _args: (),
899 ) -> Result<BTreeMap<token::Denomination, u128>, Error> {
900 let mut mgp = Self::min_gas_prices();
901
902 for (denom, price) in mgp.iter_mut() {
904 let local_mgp = Self::get_local_min_gas_price(ctx, denom);
905 if local_mgp > *price {
906 *price = local_mgp;
907 }
908 }
909
910 Ok(mgp)
911 }
912
913 #[handler(query = "core.RuntimeInfo")]
915 fn query_runtime_info<C: Context>(ctx: &C, _args: ()) -> Result<RuntimeInfoResponse, Error> {
916 Ok(RuntimeInfoResponse {
917 runtime_version: <C::Runtime as Runtime>::VERSION,
918 state_version: <C::Runtime as Runtime>::STATE_VERSION,
919 modules: <C::Runtime as Runtime>::Modules::module_info(ctx),
920 })
921 }
922
923 #[handler(query = "core.ExecuteReadOnlyTx", expensive, allow_private_km)]
929 fn query_execute_read_only_tx<C: Context>(
930 ctx: &C,
931 args: types::ExecuteReadOnlyTxQuery,
932 ) -> Result<types::ExecuteReadOnlyTxResponse, Error> {
933 if !Cfg::ALLOW_INTERACTIVE_READ_ONLY_TRANSACTIONS {
934 return Err(Error::Forbidden);
935 }
936
937 CurrentState::with_transaction_opts(Options::new().with_mode(Mode::Simulate), || {
938 let tx_size = args
942 .tx
943 .len()
944 .try_into()
945 .map_err(|_| Error::OversizedTransaction)?;
946 let tx = dispatcher::Dispatcher::<C::Runtime>::decode_tx(ctx, &args.tx)?;
947
948 if !tx.call.read_only {
950 return Err(Error::InvalidArgument(anyhow::anyhow!(
951 "only read-only transactions are allowed"
952 )));
953 }
954 if tx.auth_info.not_before.is_none() || tx.auth_info.not_after.is_none() {
956 return Err(Error::InvalidArgument(anyhow::anyhow!(
957 "only read-only transactions with expiry are allowed"
958 )));
959 }
960
961 let (result, _) = dispatcher::Dispatcher::<C::Runtime>::execute_tx_opts(
963 ctx,
964 tx,
965 &dispatcher::DispatchOptions {
966 tx_size,
967 method_authorizer: Some(&|method| {
968 <C::Runtime as Runtime>::Modules::is_allowed_interactive_call(method)
971 && <C::Runtime as Runtime>::is_allowed_interactive_call(method)
972 }),
973 ..Default::default()
974 },
975 )
976 .map_err(|err| Error::InvalidArgument(err.into()))?;
977
978 Ok(types::ExecuteReadOnlyTxResponse { result })
979 })
980 }
981}
982
983impl<Cfg: Config> Module<Cfg> {
984 fn min_gas_prices() -> BTreeMap<Denomination, u128> {
985 let params = Self::params();
986 if params.dynamic_min_gas_price.enabled {
987 CurrentState::with_store(|store| {
988 let store =
989 storage::TypedStore::new(storage::PrefixStore::new(store, &MODULE_NAME));
990 store
991 .get(state::DYNAMIC_MIN_GAS_PRICE)
992 .unwrap_or(params.min_gas_price)
994 })
995 } else {
996 params.min_gas_price
997 }
998 }
999
1000 fn get_local_min_gas_price<C: Context>(ctx: &C, denom: &token::Denomination) -> u128 {
1001 #[allow(clippy::borrow_interior_mutable_const)]
1002 ctx.local_config(MODULE_NAME)
1003 .as_ref()
1004 .map(|cfg: &LocalConfig| cfg.min_gas_price.get(denom).copied())
1005 .unwrap_or_else(|| Cfg::DEFAULT_LOCAL_MIN_GAS_PRICE.get(denom).copied())
1006 .unwrap_or_default()
1007 }
1008
1009 fn get_local_max_estimated_gas<C: Context>(ctx: &C) -> u64 {
1010 ctx.local_config(MODULE_NAME)
1011 .as_ref()
1012 .map(|cfg: &LocalConfig| cfg.max_estimated_gas)
1013 .unwrap_or_default()
1014 }
1015
1016 fn enforce_min_gas_price<C: Context>(ctx: &C, call: &Call) -> Result<(), Error> {
1017 #[allow(clippy::borrow_interior_mutable_const)]
1019 if Cfg::MIN_GAS_PRICE_EXEMPT_METHODS.contains(call.method.as_str()) {
1020 return Ok(());
1021 }
1022
1023 let fee = CurrentState::with_env(|env| env.tx_auth_info().fee.clone());
1024 let denom = fee.amount.denomination();
1025
1026 match Self::min_gas_price(denom) {
1027 None => return Err(Error::GasPriceTooLow),
1029
1030 Some(min_gas_price) => {
1032 if CurrentState::with_env(|env| env.is_check_only()) {
1033 let local_mgp = Self::get_local_min_gas_price(ctx, denom);
1034
1035 if fee.gas_price() < local_mgp {
1037 return Err(Error::GasPriceTooLow);
1038 }
1039 }
1040
1041 if fee.gas_price() < min_gas_price {
1042 return Err(Error::GasPriceTooLow);
1043 }
1044 }
1045 }
1046
1047 Ok(())
1048 }
1049
1050 fn compute_signature_verification_cost(
1051 params: &Parameters,
1052 signer_info: &[SignerInfo],
1053 ) -> Option<u64> {
1054 let mut num_signature: u64 = 0;
1055 let mut num_multisig_signer: u64 = 0;
1056 for si in signer_info {
1057 match &si.address_spec {
1058 AddressSpec::Signature(_) => {
1059 num_signature = num_signature.checked_add(1)?;
1060 }
1061 AddressSpec::Multisig(config) => {
1062 num_multisig_signer =
1063 num_multisig_signer.checked_add(config.signers.len() as u64)?;
1064 }
1065 AddressSpec::Internal(_) => {}
1066 }
1067 }
1068
1069 let signature_cost = num_signature.checked_mul(params.gas_costs.auth_signature)?;
1070 let multisig_signer_cost =
1071 num_multisig_signer.checked_mul(params.gas_costs.auth_multisig_signer)?;
1072 let sum = signature_cost.checked_add(multisig_signer_cost)?;
1073
1074 Some(sum)
1075 }
1076}
1077
1078impl<Cfg: Config> module::TransactionHandler for Module<Cfg> {
1079 fn approve_raw_tx<C: Context>(_ctx: &C, tx: &[u8]) -> Result<(), Error> {
1080 let params = Self::params();
1081 if tx.len() > TryInto::<usize>::try_into(params.max_tx_size).unwrap() {
1082 return Err(Error::OversizedTransaction);
1083 }
1084 Ok(())
1085 }
1086
1087 fn approve_unverified_tx<C: Context>(
1088 _ctx: &C,
1089 utx: &UnverifiedTransaction,
1090 ) -> Result<(), Error> {
1091 let params = Self::params();
1092 if utx.1.len() > params.max_tx_signers as usize {
1093 return Err(Error::TooManyAuth);
1094 }
1095 for auth_proof in &utx.1 {
1096 if let AuthProof::Multisig(config) = auth_proof {
1097 if config.len() > params.max_multisig_signers as usize {
1098 return Err(Error::MultisigTooManySigners);
1099 }
1100 }
1101 }
1102 Ok(())
1103 }
1104
1105 fn authenticate_tx<C: Context>(
1106 ctx: &C,
1107 tx: &Transaction,
1108 ) -> Result<module::AuthDecision, Error> {
1109 let round = ctx.runtime_header().round;
1111 if let Some(not_before) = tx.auth_info.not_before {
1112 if round < not_before {
1113 return Err(Error::ExpiredTransaction);
1115 }
1116 }
1117 if let Some(not_after) = tx.auth_info.not_after {
1118 if round > not_after {
1119 return Err(Error::ExpiredTransaction);
1121 }
1122 }
1123
1124 Ok(module::AuthDecision::Continue)
1125 }
1126
1127 fn before_handle_call<C: Context>(ctx: &C, call: &Call) -> Result<(), Error> {
1128 let params = Self::params();
1130 let fee = CurrentState::with_env(|env| env.tx_auth_info().fee.clone());
1131 if fee.gas > params.max_batch_gas {
1132 return Err(Error::GasOverflow);
1133 }
1134 if fee.consensus_messages > ctx.max_messages() {
1135 return Err(Error::OutOfMessageSlots);
1136 }
1137
1138 if CurrentState::with_env(|env| env.is_internal()) {
1140 return Ok(());
1141 }
1142
1143 Self::enforce_min_gas_price(ctx, call)?;
1145
1146 let tx_size = CurrentState::with_env(|env| env.tx_size());
1148 Self::use_tx_gas(
1149 params
1150 .gas_costs
1151 .tx_byte
1152 .checked_mul(tx_size.into())
1153 .ok_or(Error::GasOverflow)?,
1154 )?;
1155
1156 let total = CurrentState::with_env(|env| {
1158 Self::compute_signature_verification_cost(¶ms, &env.tx_auth_info().signer_info)
1159 })
1160 .ok_or(Error::GasOverflow)?;
1161 Self::use_tx_gas(total)?;
1162
1163 match call.format {
1165 CallFormat::Plain => {} CallFormat::EncryptedX25519DeoxysII => {
1167 Self::use_tx_gas(params.gas_costs.callformat_x25519_deoxysii)?
1168 }
1169 }
1170
1171 Ok(())
1172 }
1173
1174 fn after_handle_call<C: Context>(
1175 ctx: &C,
1176 result: module::CallResult,
1177 ) -> Result<module::CallResult, Error> {
1178 if CurrentState::with_env(|env| env.is_internal()) {
1180 return Ok(result);
1181 }
1182
1183 let params = Self::params();
1184
1185 let storage_gas = if params.gas_costs.storage_byte > 0 {
1187 let storage_update_bytes =
1188 CurrentState::with(|state| state.pending_store_update_byte_size());
1189 params
1190 .gas_costs
1191 .storage_byte
1192 .saturating_mul(storage_update_bytes as u64)
1193 } else {
1194 0
1195 };
1196
1197 let message_gas = {
1199 let emitted_message_count =
1200 CurrentState::with(|state| state.emitted_messages_local_count());
1201 let message_gas_cost = params
1204 .max_batch_gas
1205 .checked_div(ctx.max_messages().into())
1206 .unwrap_or(u64::MAX); message_gas_cost.saturating_mul(emitted_message_count as u64)
1208 };
1209
1210 let used_gas = Self::used_tx_gas();
1212 let max_gas = std::cmp::max(used_gas, std::cmp::max(storage_gas, message_gas));
1213
1214 let maybe_out_of_gas = Self::use_tx_gas(max_gas - used_gas); if Cfg::EMIT_GAS_USED_EVENTS {
1221 let used_gas = Self::used_tx_gas();
1222 CurrentState::with(|state| {
1223 state.emit_unconditional_event(Event::GasUsed { amount: used_gas });
1224 });
1225 }
1226
1227 maybe_out_of_gas?;
1229
1230 Ok(result)
1231 }
1232}
1233
1234fn min_gas_price_update(
1242 gas_used: u128,
1243 target_gas_used: u128,
1244 min_price_max_change_denominator: u128,
1245 current_price: u128,
1246) -> u128 {
1247 if target_gas_used == 0 || min_price_max_change_denominator == 0 {
1249 return current_price;
1250 }
1251
1252 let delta = (gas_used.max(target_gas_used) - gas_used.min(target_gas_used)).saturating_mul(100)
1254 / target_gas_used;
1255
1256 let price_change =
1258 (current_price.saturating_mul(delta) / 100) / min_price_max_change_denominator;
1259
1260 if gas_used > target_gas_used {
1262 current_price.saturating_add(price_change)
1263 } else {
1264 current_price.saturating_sub(price_change)
1265 }
1266}
1267
1268impl<Cfg: Config> module::BlockHandler for Module<Cfg> {
1269 fn begin_block<C: Context>(ctx: &C) {
1270 CurrentState::with(|state| {
1271 let epoch = ctx.epoch();
1272
1273 let mut store = storage::PrefixStore::new(state.store(), &MODULE_NAME);
1275 let mut tstore = storage::TypedStore::new(&mut store);
1276 let previous_epoch: EpochTime = tstore.get(state::LAST_EPOCH).unwrap_or_default();
1277 if epoch != previous_epoch {
1278 tstore.insert(state::LAST_EPOCH, epoch);
1279 }
1280
1281 state
1283 .block_value(CONTEXT_KEY_EPOCH_CHANGED)
1284 .set(epoch != previous_epoch);
1285 });
1286 }
1287
1288 fn end_block<C: Context>(_ctx: &C) {
1289 let params = Self::params();
1290 if !params.dynamic_min_gas_price.enabled {
1291 return;
1292 }
1293
1294 let gas_used = Self::used_batch_gas() as u128;
1299 let max_batch_gas = Self::max_batch_gas() as u128;
1300 let target_gas_used = max_batch_gas.saturating_mul(
1301 params
1302 .dynamic_min_gas_price
1303 .target_block_gas_usage_percentage as u128,
1304 ) / 100;
1305
1306 let mut mgp = Self::min_gas_prices();
1308 mgp.iter_mut().for_each(|(d, price)| {
1309 let mut new_min_price = min_gas_price_update(
1310 gas_used,
1311 target_gas_used,
1312 params
1313 .dynamic_min_gas_price
1314 .min_price_max_change_denominator as u128,
1315 *price,
1316 );
1317
1318 if let Some(min_price) = params.min_gas_price.get(d) {
1320 if new_min_price < *min_price {
1321 new_min_price = *min_price;
1322 }
1323 }
1324 *price = new_min_price;
1325 });
1326
1327 CurrentState::with_store(|store| {
1329 let mut store = storage::PrefixStore::new(store, &MODULE_NAME);
1330 let mut tstore = storage::TypedStore::new(&mut store);
1331 tstore.insert(state::DYNAMIC_MIN_GAS_PRICE, mgp);
1332 });
1333 }
1334}
1335
1336impl<Cfg: Config> module::InvariantHandler for Module<Cfg> {}