-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: customizable gas markets (with EIP-1559 default), base fee osci…
…llation, premium distribution (#1173) Co-authored-by: cryptoAtwill <willes.lau@protocol.ai>
Showing
33 changed files
with
1,533 additions
and
153 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
[package] | ||
name = "fendermint_actors_api" | ||
description = "API and interface types for pluggable actors." | ||
license.workspace = true | ||
edition.workspace = true | ||
authors.workspace = true | ||
version = "0.1.0" | ||
|
||
[lib] | ||
## lib is necessary for integration tests | ||
## cdylib is necessary for Wasm build | ||
crate-type = ["cdylib", "lib"] | ||
|
||
[dependencies] | ||
anyhow = { workspace = true } | ||
cid = { workspace = true } | ||
fil_actors_runtime = { workspace = true } | ||
fvm_ipld_blockstore = { workspace = true } | ||
fvm_ipld_encoding = { workspace = true } | ||
fvm_shared = { workspace = true } | ||
log = { workspace = true } | ||
multihash = { workspace = true } | ||
num-derive = { workspace = true } | ||
num-traits = { workspace = true } | ||
serde = { workspace = true } | ||
hex-literal = { workspace = true } | ||
frc42_dispatch = { workspace = true } | ||
|
||
[dev-dependencies] | ||
fil_actors_evm_shared = { workspace = true } | ||
fil_actors_runtime = { workspace = true, features = ["test_utils"] } | ||
|
||
[features] | ||
fil-actor = ["fil_actors_runtime/fil-actor"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
use fil_actors_runtime::runtime::Runtime; | ||
use fil_actors_runtime::ActorError; | ||
use fvm_ipld_encoding::tuple::*; | ||
use fvm_shared::econ::TokenAmount; | ||
use num_derive::FromPrimitive; | ||
|
||
pub type Gas = u64; | ||
|
||
/// A reading of the current gas market state for use by consensus. | ||
#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] | ||
pub struct Reading { | ||
/// The current gas limit for the block. | ||
pub block_gas_limit: Gas, | ||
/// The current base fee for the block. | ||
pub base_fee: TokenAmount, | ||
} | ||
|
||
/// The current utilization for the client to report to the gas market. | ||
#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] | ||
pub struct Utilization { | ||
/// The gas used by the current block, at the end of the block. To be invoked as an implicit | ||
/// message, so that gas metering for this message is disabled. | ||
pub block_gas_used: Gas, | ||
} | ||
|
||
#[derive(FromPrimitive)] | ||
#[repr(u64)] | ||
pub enum Method { | ||
CurrentReading = frc42_dispatch::method_hash!("CurrentReading"), | ||
UpdateUtilization = frc42_dispatch::method_hash!("UpdateUtilization"), | ||
} | ||
|
||
/// The trait to be implemented by a gas market actor, provided here for convenience, | ||
/// using the standard Runtime libraries. Ready to be implemented as-is by an actor. | ||
pub trait GasMarket { | ||
/// Returns the current gas market reading. | ||
fn current_reading(rt: &impl Runtime) -> Result<Reading, ActorError>; | ||
|
||
/// Updates the current utilization in the gas market, returning the reading after the update. | ||
fn update_utilization( | ||
rt: &impl Runtime, | ||
utilization: Utilization, | ||
) -> Result<Reading, ActorError>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
pub mod gas_market; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
[package] | ||
name = "fendermint_actor_gas_market_eip1559" | ||
description = "EIP-1559 gas market actor for IPC. Singleton actor to be deployed at ID 98." | ||
license.workspace = true | ||
edition.workspace = true | ||
authors.workspace = true | ||
version = "0.1.0" | ||
|
||
[lib] | ||
## lib is necessary for integration tests | ||
## cdylib is necessary for Wasm build | ||
crate-type = ["cdylib", "lib"] | ||
|
||
[dependencies] | ||
anyhow = { workspace = true } | ||
cid = { workspace = true } | ||
fendermint_actors_api = { workspace = true } | ||
fil_actors_runtime = { workspace = true } | ||
fvm_ipld_blockstore = { workspace = true } | ||
fvm_ipld_encoding = { workspace = true } | ||
fvm_shared = { workspace = true } | ||
log = { workspace = true } | ||
multihash = { workspace = true } | ||
num-derive = { workspace = true } | ||
num-traits = { workspace = true } | ||
serde = { workspace = true } | ||
hex-literal = { workspace = true } | ||
frc42_dispatch = { workspace = true } | ||
|
||
[dev-dependencies] | ||
fil_actors_evm_shared = { workspace = true } | ||
fil_actors_runtime = { workspace = true, features = ["test_utils"] } | ||
|
||
[features] | ||
fil-actor = ["fil_actors_runtime/fil-actor"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,344 @@ | ||
// Copyright 2021-2023 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
use fendermint_actors_api::gas_market::Gas; | ||
use fil_actors_runtime::actor_error; | ||
use fil_actors_runtime::runtime::{ActorCode, Runtime}; | ||
use fil_actors_runtime::SYSTEM_ACTOR_ADDR; | ||
use fil_actors_runtime::{actor_dispatch, ActorError}; | ||
use fvm_ipld_encoding::tuple::*; | ||
use fvm_shared::econ::TokenAmount; | ||
use fvm_shared::METHOD_CONSTRUCTOR; | ||
use num_derive::FromPrimitive; | ||
use std::cmp::Ordering; | ||
|
||
#[cfg(feature = "fil-actor")] | ||
fil_actors_runtime::wasm_trampoline!(Actor); | ||
|
||
pub const ACTOR_NAME: &str = "gas_market_eip1559"; | ||
|
||
pub type SetConstants = Constants; | ||
|
||
#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] | ||
pub struct State { | ||
pub base_fee: TokenAmount, | ||
pub constants: Constants, | ||
} | ||
|
||
/// Constant params used by EIP-1559. | ||
#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] | ||
pub struct Constants { | ||
pub block_gas_limit: Gas, | ||
/// The minimal base fee floor when gas utilization is low. | ||
pub minimal_base_fee: TokenAmount, | ||
/// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559). | ||
pub elasticity_multiplier: u64, | ||
/// Base fee max change denominator as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559). | ||
pub base_fee_max_change_denominator: u64, | ||
} | ||
|
||
#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] | ||
pub struct ConstructorParams { | ||
initial_base_fee: TokenAmount, | ||
constants: Constants, | ||
} | ||
|
||
pub struct Actor {} | ||
|
||
#[derive(FromPrimitive)] | ||
#[repr(u64)] | ||
pub enum Method { | ||
Constructor = METHOD_CONSTRUCTOR, | ||
GetConstants = frc42_dispatch::method_hash!("GetConstants"), | ||
SetConstants = frc42_dispatch::method_hash!("SetConstants"), | ||
|
||
// Standard methods. | ||
CurrentReading = fendermint_actors_api::gas_market::Method::CurrentReading as u64, | ||
UpdateUtilization = fendermint_actors_api::gas_market::Method::UpdateUtilization as u64, | ||
} | ||
|
||
impl Actor { | ||
/// Creates the actor | ||
pub fn constructor(rt: &impl Runtime, params: ConstructorParams) -> Result<(), ActorError> { | ||
rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; | ||
|
||
let st = State { | ||
base_fee: params.initial_base_fee, | ||
constants: params.constants, | ||
}; | ||
|
||
rt.create(&st) | ||
} | ||
|
||
fn set_constants(rt: &impl Runtime, constants: SetConstants) -> Result<(), ActorError> { | ||
rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; | ||
|
||
rt.transaction(|st: &mut State, _rt| { | ||
st.constants = constants; | ||
Ok(()) | ||
})?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn get_constants(rt: &impl Runtime) -> Result<Constants, ActorError> { | ||
rt.validate_immediate_caller_accept_any()?; | ||
rt.state::<State>().map(|s| s.constants) | ||
} | ||
} | ||
|
||
impl fendermint_actors_api::gas_market::GasMarket for Actor { | ||
fn current_reading( | ||
rt: &impl Runtime, | ||
) -> Result<fendermint_actors_api::gas_market::Reading, ActorError> { | ||
rt.validate_immediate_caller_accept_any()?; | ||
|
||
let st = rt.state::<State>()?; | ||
Ok(fendermint_actors_api::gas_market::Reading { | ||
block_gas_limit: st.constants.block_gas_limit, | ||
base_fee: st.base_fee, | ||
}) | ||
} | ||
|
||
fn update_utilization( | ||
rt: &impl Runtime, | ||
utilization: fendermint_actors_api::gas_market::Utilization, | ||
) -> Result<fendermint_actors_api::gas_market::Reading, ActorError> { | ||
rt.validate_immediate_caller_is(std::iter::once(&SYSTEM_ACTOR_ADDR))?; | ||
|
||
rt.transaction(|st: &mut State, _rt| { | ||
st.base_fee = st.next_base_fee(utilization.block_gas_used); | ||
Ok(fendermint_actors_api::gas_market::Reading { | ||
block_gas_limit: st.constants.block_gas_limit, | ||
base_fee: st.base_fee.clone(), | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
impl Default for Constants { | ||
fn default() -> Self { | ||
Self { | ||
// Matching the Filecoin block gas limit. Note that IPC consensus != Filecoin Expected Consensus, | ||
// TODO | ||
block_gas_limit: 10_000_000_000, | ||
// Matching Filecoin's minimal base fee. | ||
minimal_base_fee: TokenAmount::from_atto(100), | ||
// Elasticity multiplier as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) | ||
elasticity_multiplier: 2, | ||
// Base fee max change denominator as defined in [EIP-1559](https://eips.ethereum.org/EIPS/eip-1559) | ||
base_fee_max_change_denominator: 8, | ||
} | ||
} | ||
} | ||
|
||
impl State { | ||
fn next_base_fee(&self, gas_used: Gas) -> TokenAmount { | ||
let base_fee = self.base_fee.clone(); | ||
let gas_target = self.constants.block_gas_limit / self.constants.elasticity_multiplier; | ||
|
||
match gas_used.cmp(&gas_target) { | ||
Ordering::Equal => base_fee, | ||
Ordering::Less => { | ||
let base_fee_delta = base_fee.atto() * (gas_target - gas_used) | ||
/ gas_target | ||
/ self.constants.base_fee_max_change_denominator; | ||
let base_fee_delta = TokenAmount::from_atto(base_fee_delta); | ||
if base_fee_delta >= base_fee { | ||
self.constants.minimal_base_fee.clone() | ||
} else { | ||
base_fee - base_fee_delta | ||
} | ||
} | ||
Ordering::Greater => { | ||
let gas_used_delta = gas_used - gas_target; | ||
let delta = base_fee.atto() * gas_used_delta | ||
/ gas_target | ||
/ self.constants.base_fee_max_change_denominator; | ||
base_fee + TokenAmount::from_atto(delta).max(TokenAmount::from_atto(1)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
// This import is necessary so that the actor_dispatch macro can find the methods on the GasMarket | ||
// trait, implemented by Self. | ||
use fendermint_actors_api::gas_market::GasMarket; | ||
|
||
impl ActorCode for Actor { | ||
type Methods = Method; | ||
|
||
fn name() -> &'static str { | ||
ACTOR_NAME | ||
} | ||
|
||
actor_dispatch! { | ||
Constructor => constructor, | ||
SetConstants => set_constants, | ||
GetConstants => get_constants, | ||
|
||
CurrentReading => current_reading, | ||
UpdateUtilization => update_utilization, | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::{Actor, Constants, ConstructorParams, Method, State}; | ||
use fendermint_actors_api::gas_market::{Reading, Utilization}; | ||
use fil_actors_runtime::test_utils::{expect_empty, MockRuntime, SYSTEM_ACTOR_CODE_ID}; | ||
use fil_actors_runtime::SYSTEM_ACTOR_ADDR; | ||
use fvm_ipld_encoding::ipld_block::IpldBlock; | ||
use fvm_shared::address::Address; | ||
use fvm_shared::econ::TokenAmount; | ||
use fvm_shared::error::ExitCode; | ||
|
||
pub fn construct_and_verify() -> MockRuntime { | ||
let rt = MockRuntime { | ||
receiver: Address::new_id(10), | ||
..Default::default() | ||
}; | ||
|
||
rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); | ||
rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); | ||
|
||
let result = rt | ||
.call::<Actor>( | ||
Method::Constructor as u64, | ||
IpldBlock::serialize_cbor(&ConstructorParams { | ||
initial_base_fee: TokenAmount::from_atto(100), | ||
constants: Constants::default(), | ||
}) | ||
.unwrap(), | ||
) | ||
.unwrap(); | ||
expect_empty(result); | ||
rt.verify(); | ||
rt.reset(); | ||
|
||
rt | ||
} | ||
|
||
#[test] | ||
fn test_set_ok() { | ||
let rt = construct_and_verify(); | ||
|
||
rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); | ||
rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); | ||
|
||
let r = rt.call::<Actor>( | ||
Method::SetConstants as u64, | ||
IpldBlock::serialize_cbor(&Constants { | ||
minimal_base_fee: Default::default(), | ||
elasticity_multiplier: 0, | ||
base_fee_max_change_denominator: 0, | ||
block_gas_limit: 20, | ||
}) | ||
.unwrap(), | ||
); | ||
assert!(r.is_ok()); | ||
|
||
let s = rt.get_state::<State>(); | ||
assert_eq!(s.constants.block_gas_limit, 20); | ||
} | ||
|
||
#[test] | ||
fn test_update_utilization_full_usage() { | ||
let rt = construct_and_verify(); | ||
|
||
rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); | ||
rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); | ||
|
||
let r = rt.call::<Actor>( | ||
Method::UpdateUtilization as u64, | ||
IpldBlock::serialize_cbor(&Utilization { | ||
// full block usage | ||
block_gas_used: 10_000_000_000, | ||
}) | ||
.unwrap(), | ||
); | ||
assert!(r.is_ok()); | ||
|
||
rt.expect_validate_caller_any(); | ||
let r = rt | ||
.call::<Actor>(Method::CurrentReading as u64, None) | ||
.unwrap() | ||
.unwrap(); | ||
let reading = r.deserialize::<Reading>().unwrap(); | ||
assert_eq!(reading.base_fee, TokenAmount::from_atto(112)); | ||
} | ||
|
||
#[test] | ||
fn test_update_utilization_equal_usage() { | ||
let rt = construct_and_verify(); | ||
|
||
rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); | ||
rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); | ||
|
||
let r = rt.call::<Actor>( | ||
Method::UpdateUtilization as u64, | ||
IpldBlock::serialize_cbor(&Utilization { | ||
// full block usage | ||
block_gas_used: 5_000_000_000, | ||
}) | ||
.unwrap(), | ||
); | ||
assert!(r.is_ok()); | ||
|
||
rt.expect_validate_caller_any(); | ||
let r = rt | ||
.call::<Actor>(Method::CurrentReading as u64, None) | ||
.unwrap() | ||
.unwrap(); | ||
let reading = r.deserialize::<Reading>().unwrap(); | ||
assert_eq!(reading.base_fee, TokenAmount::from_atto(100)); | ||
} | ||
|
||
#[test] | ||
fn test_update_utilization_under_usage() { | ||
let rt = construct_and_verify(); | ||
|
||
rt.set_caller(*SYSTEM_ACTOR_CODE_ID, SYSTEM_ACTOR_ADDR); | ||
rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); | ||
|
||
let r = rt.call::<Actor>( | ||
Method::UpdateUtilization as u64, | ||
IpldBlock::serialize_cbor(&Utilization { | ||
// full block usage | ||
block_gas_used: 100_000_000, | ||
}) | ||
.unwrap(), | ||
); | ||
assert!(r.is_ok()); | ||
|
||
rt.expect_validate_caller_any(); | ||
let r = rt | ||
.call::<Actor>(Method::CurrentReading as u64, None) | ||
.unwrap() | ||
.unwrap(); | ||
let reading = r.deserialize::<Reading>().unwrap(); | ||
assert_eq!(reading.base_fee, TokenAmount::from_atto(88)); | ||
} | ||
|
||
#[test] | ||
fn test_not_allowed() { | ||
let rt = construct_and_verify(); | ||
rt.set_caller(*SYSTEM_ACTOR_CODE_ID, Address::new_id(1000)); | ||
rt.expect_validate_caller_addr(vec![SYSTEM_ACTOR_ADDR]); | ||
|
||
let code = rt | ||
.call::<Actor>( | ||
Method::SetConstants as u64, | ||
IpldBlock::serialize_cbor(&Constants { | ||
minimal_base_fee: TokenAmount::from_atto(10000), | ||
elasticity_multiplier: 0, | ||
base_fee_max_change_denominator: 0, | ||
block_gas_limit: 20, | ||
}) | ||
.unwrap(), | ||
) | ||
.unwrap_err() | ||
.exit_code(); | ||
assert_eq!(code, ExitCode::USR_FORBIDDEN) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
//! Tracks the validator id from tendermint to their corresponding public key. | ||
use anyhow::anyhow; | ||
use fendermint_crypto::PublicKey; | ||
use fvm_shared::clock::ChainEpoch; | ||
use std::collections::HashMap; | ||
use std::sync::{Arc, RwLock}; | ||
use tendermint::block::Height; | ||
use tendermint_rpc::{Client, Paging}; | ||
|
||
#[derive(Clone)] | ||
pub(crate) struct ValidatorTracker<C> { | ||
client: C, | ||
public_keys: Arc<RwLock<HashMap<tendermint::account::Id, PublicKey>>>, | ||
} | ||
|
||
impl<C: Client> ValidatorTracker<C> { | ||
pub fn new(client: C) -> Self { | ||
Self { | ||
client, | ||
public_keys: Arc::new(RwLock::new(HashMap::new())), | ||
} | ||
} | ||
} | ||
|
||
impl<C: Client + Sync> ValidatorTracker<C> { | ||
/// Get the public key of the validator by id. Note that the id is expected to be a validator. | ||
pub async fn get_validator( | ||
&self, | ||
id: &tendermint::account::Id, | ||
height: ChainEpoch, | ||
) -> anyhow::Result<PublicKey> { | ||
if let Some(key) = self.get_from_cache(id) { | ||
return Ok(key); | ||
} | ||
|
||
// this means validators have changed, re-pull all validators | ||
let height = Height::try_from(height)?; | ||
let response = self.client.validators(height, Paging::All).await?; | ||
|
||
let mut new_validators = HashMap::new(); | ||
let mut pubkey = None; | ||
for validator in response.validators { | ||
let p = validator.pub_key.secp256k1().unwrap(); | ||
let compressed = p.to_encoded_point(true); | ||
let b = compressed.as_bytes(); | ||
let key = PublicKey::parse_slice(b, None)?; | ||
|
||
if *id == validator.address { | ||
pubkey = Some(key); | ||
} | ||
|
||
new_validators.insert(validator.address, key); | ||
} | ||
|
||
*self.public_keys.write().unwrap() = new_validators; | ||
|
||
// cannot find the validator, this should not have happened usually | ||
pubkey.ok_or_else(|| anyhow!("{} not validator", id)) | ||
} | ||
|
||
fn get_from_cache(&self, id: &tendermint::account::Id) -> Option<PublicKey> { | ||
let keys = self.public_keys.read().unwrap(); | ||
keys.get(id).copied() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,327 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
mod staking; | ||
|
||
use async_trait::async_trait; | ||
use fendermint_actor_gas_market_eip1559::Constants; | ||
use fendermint_contract_test::Tester; | ||
use fendermint_crypto::{PublicKey, SecretKey}; | ||
use fendermint_vm_actor_interface::eam::EthAddress; | ||
use fendermint_vm_actor_interface::gas_market::GAS_MARKET_ACTOR_ADDR; | ||
use fendermint_vm_actor_interface::system::SYSTEM_ACTOR_ADDR; | ||
use fendermint_vm_core::Timestamp; | ||
use fendermint_vm_genesis::{Account, Actor, ActorMeta, Genesis, PermissionMode, SignerAddr}; | ||
use fendermint_vm_interpreter::fvm::store::memory::MemoryBlockstore; | ||
use fendermint_vm_interpreter::fvm::upgrades::{Upgrade, UpgradeScheduler}; | ||
use fendermint_vm_interpreter::fvm::FvmMessageInterpreter; | ||
use fvm::executor::{ApplyKind, Executor}; | ||
use fvm_ipld_encoding::RawBytes; | ||
use fvm_shared::address::Address; | ||
use fvm_shared::bigint::Zero; | ||
use fvm_shared::econ::TokenAmount; | ||
use fvm_shared::message::Message; | ||
use fvm_shared::version::NetworkVersion; | ||
use lazy_static::lazy_static; | ||
use rand::rngs::StdRng; | ||
use rand::SeedableRng; | ||
use tendermint_rpc::Client; | ||
|
||
lazy_static! { | ||
static ref ADDR: Address = | ||
Address::new_secp256k1(&rand_secret_key().public_key().serialize()).unwrap(); | ||
static ref ADDR2: Address = | ||
Address::new_secp256k1(&rand_secret_key().public_key().serialize()).unwrap(); | ||
} | ||
const CHAIN_NAME: &str = "mychain"; | ||
type I = FvmMessageInterpreter<MemoryBlockstore, NeverCallClient>; | ||
|
||
// returns a seeded secret key which is guaranteed to be the same every time | ||
fn rand_secret_key() -> SecretKey { | ||
SecretKey::random(&mut StdRng::seed_from_u64(123)) | ||
} | ||
|
||
/// Creates a default tester with validator public key | ||
async fn default_tester() -> (Tester<I>, PublicKey) { | ||
tester_with_upgrader(UpgradeScheduler::new()).await | ||
} | ||
|
||
/// Creates a default tester with validator public key | ||
async fn tester_with_upgrader( | ||
upgrade_scheduler: UpgradeScheduler<MemoryBlockstore>, | ||
) -> (Tester<I>, PublicKey) { | ||
let validator = rand_secret_key().public_key(); | ||
|
||
let interpreter: FvmMessageInterpreter<MemoryBlockstore, _> = | ||
FvmMessageInterpreter::new(NeverCallClient, None, 1.05, 1.05, false, upgrade_scheduler); | ||
|
||
let genesis = Genesis { | ||
chain_name: CHAIN_NAME.to_string(), | ||
timestamp: Timestamp(0), | ||
network_version: NetworkVersion::V21, | ||
base_fee: TokenAmount::zero(), | ||
power_scale: 0, | ||
validators: Vec::new(), | ||
accounts: vec![ | ||
Actor { | ||
meta: ActorMeta::Account(Account { | ||
owner: SignerAddr(*ADDR), | ||
}), | ||
balance: TokenAmount::from_whole(100), | ||
}, | ||
Actor { | ||
meta: ActorMeta::Account(Account { | ||
owner: SignerAddr(*ADDR2), | ||
}), | ||
balance: TokenAmount::from_whole(10), | ||
}, | ||
], | ||
eam_permission_mode: PermissionMode::Unrestricted, | ||
ipc: None, | ||
}; | ||
(Tester::new(interpreter, genesis).await.unwrap(), validator) | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_gas_market_base_fee_oscillation() { | ||
let (mut tester, _) = default_tester().await; | ||
|
||
let num_msgs = 10; | ||
let block_gas_limit = 6178630; | ||
let base_gas_limit = block_gas_limit / num_msgs; | ||
|
||
let messages = (0..num_msgs) | ||
.map(|i| Message { | ||
version: 0, | ||
from: *ADDR, | ||
to: Address::new_id(10), | ||
sequence: i, | ||
value: TokenAmount::from_atto(1), | ||
method_num: 0, | ||
params: Default::default(), | ||
gas_limit: base_gas_limit, | ||
gas_fee_cap: Default::default(), | ||
gas_premium: TokenAmount::from_atto(1), | ||
}) | ||
.collect::<Vec<Message>>(); | ||
|
||
let producer = rand_secret_key().public_key(); | ||
|
||
// block 1: set the gas constants | ||
let height = 1; | ||
tester.begin_block(height, producer).await.unwrap(); | ||
tester | ||
.execute_msgs(vec![custom_gas_limit(block_gas_limit)]) | ||
.await | ||
.unwrap(); | ||
tester.end_block(height).await.unwrap(); | ||
tester.commit().await.unwrap(); | ||
|
||
// | ||
let height = 2; | ||
tester.begin_block(height, producer).await.unwrap(); | ||
let before_reading = tester | ||
.modify_exec_state(|mut state| async { | ||
let reading = state.read_gas_market()?; | ||
Ok((state, reading)) | ||
}) | ||
.await | ||
.unwrap(); | ||
tester.execute_msgs(messages).await.unwrap(); | ||
tester.end_block(height).await.unwrap(); | ||
tester.commit().await.unwrap(); | ||
|
||
let height = 3; | ||
tester.begin_block(height, producer).await.unwrap(); | ||
let post_full_block_reading = tester | ||
.modify_exec_state(|mut state| async { | ||
let reading = state.read_gas_market()?; | ||
Ok((state, reading)) | ||
}) | ||
.await | ||
.unwrap(); | ||
tester.end_block(height).await.unwrap(); | ||
tester.commit().await.unwrap(); | ||
assert!( | ||
before_reading.base_fee < post_full_block_reading.base_fee, | ||
"base fee should have increased" | ||
); | ||
|
||
let height = 4; | ||
tester.begin_block(height, producer).await.unwrap(); | ||
let post_empty_block_reading = tester | ||
.modify_exec_state(|mut state| async { | ||
let reading = state.read_gas_market()?; | ||
Ok((state, reading)) | ||
}) | ||
.await | ||
.unwrap(); | ||
tester.end_block(height).await.unwrap(); | ||
tester.commit().await.unwrap(); | ||
assert!( | ||
post_empty_block_reading.base_fee < post_full_block_reading.base_fee, | ||
"base fee should have decreased" | ||
); | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_gas_market_premium_distribution() { | ||
let (mut tester, validator) = default_tester().await; | ||
let evm_address = Address::from(EthAddress::new_secp256k1(&validator.serialize()).unwrap()); | ||
|
||
let num_msgs = 10; | ||
let total_gas_limit = 62306300; | ||
let premium = 1; | ||
let base_gas_limit = total_gas_limit / num_msgs; | ||
|
||
let messages = (0..num_msgs) | ||
.map(|i| Message { | ||
version: 0, | ||
from: *ADDR, | ||
to: *ADDR2, | ||
sequence: i, | ||
value: TokenAmount::from_atto(1), | ||
method_num: 0, | ||
params: Default::default(), | ||
gas_limit: base_gas_limit, | ||
gas_fee_cap: TokenAmount::from_atto(base_gas_limit), | ||
gas_premium: TokenAmount::from_atto(premium), | ||
}) | ||
.collect::<Vec<Message>>(); | ||
|
||
let proposer = rand_secret_key().public_key(); | ||
|
||
// iterate over all the upgrades | ||
let height = 1; | ||
tester.begin_block(height, proposer).await.unwrap(); | ||
let initial_balance = tester | ||
.modify_exec_state(|state| async { | ||
let tree = state.state_tree(); | ||
let balance = tree | ||
.get_actor_by_address(&evm_address)? | ||
.map(|v| v.balance) | ||
.unwrap_or(TokenAmount::zero()); | ||
Ok((state, balance)) | ||
}) | ||
.await | ||
.unwrap(); | ||
assert_eq!(initial_balance, TokenAmount::zero()); | ||
|
||
tester.execute_msgs(messages).await.unwrap(); | ||
tester.end_block(height).await.unwrap(); | ||
let final_balance = tester | ||
.modify_exec_state(|state| async { | ||
let tree = state.state_tree(); | ||
let balance = tree | ||
.get_actor_by_address(&evm_address)? | ||
.map(|v| v.balance) | ||
.unwrap_or(TokenAmount::zero()); | ||
Ok((state, balance)) | ||
}) | ||
.await | ||
.unwrap(); | ||
tester.commit().await.unwrap(); | ||
|
||
assert!( | ||
final_balance > initial_balance, | ||
"validator balance should have increased" | ||
) | ||
} | ||
|
||
#[tokio::test] | ||
async fn test_gas_market_upgrade() { | ||
let mut upgrader = UpgradeScheduler::new(); | ||
|
||
// Initial block gas limit is determined by the default constants. | ||
let initial_block_gas_limit = Constants::default().block_gas_limit; | ||
let updated_block_gas_limit = 200; | ||
|
||
// Attach an upgrade at epoch 2 that changes the gas limit to 200. | ||
upgrader | ||
.add( | ||
Upgrade::new(CHAIN_NAME, 2, Some(1), move |state| { | ||
println!( | ||
"[Upgrade at height {}] Update gas market params", | ||
state.block_height() | ||
); | ||
state.execute_with_executor(|executor| { | ||
// cannot capture updated_block_gas_limit due to Upgrade::new wanting a fn pointer. | ||
let msg = custom_gas_limit(200); | ||
executor.execute_message(msg, ApplyKind::Implicit, 0)?; | ||
Ok(()) | ||
}) | ||
}) | ||
.unwrap(), | ||
) | ||
.unwrap(); | ||
|
||
// Create a new tester with the upgrader attached. | ||
let (mut tester, _) = tester_with_upgrader(upgrader).await; | ||
|
||
let producer = rand_secret_key().public_key(); | ||
|
||
// At height 1, simply read the block gas limit and ensure it's the default. | ||
let height = 1; | ||
tester.begin_block(height, producer).await.unwrap(); | ||
let reading = tester | ||
.modify_exec_state(|mut state| async { | ||
let reading = state.read_gas_market()?; | ||
Ok((state, reading)) | ||
}) | ||
.await | ||
.unwrap(); | ||
assert_eq!( | ||
reading.block_gas_limit, initial_block_gas_limit, | ||
"block gas limit should be the default as per constants" | ||
); | ||
tester.end_block(height).await.unwrap(); | ||
tester.commit().await.unwrap(); | ||
|
||
// The upgrade above should have updated the gas limit to 200. | ||
let height = 2; | ||
tester.begin_block(height, producer).await.unwrap(); | ||
let reading = tester | ||
.modify_exec_state(|mut state| async { | ||
let reading = state.read_gas_market()?; | ||
Ok((state, reading)) | ||
}) | ||
.await | ||
.unwrap(); | ||
assert_eq!( | ||
reading.block_gas_limit, updated_block_gas_limit, | ||
"gas limit post-upgrade should be {updated_block_gas_limit}" | ||
); | ||
} | ||
|
||
fn custom_gas_limit(block_gas_limit: u64) -> Message { | ||
let gas_constants = fendermint_actor_gas_market_eip1559::SetConstants { | ||
block_gas_limit, | ||
..Default::default() | ||
}; | ||
|
||
Message { | ||
version: 0, | ||
from: SYSTEM_ACTOR_ADDR, | ||
to: GAS_MARKET_ACTOR_ADDR, | ||
sequence: 0, | ||
value: Default::default(), | ||
method_num: fendermint_actor_gas_market_eip1559::Method::SetConstants as u64, | ||
params: RawBytes::serialize(&gas_constants).unwrap(), | ||
gas_limit: 10000000, | ||
gas_fee_cap: Default::default(), | ||
gas_premium: Default::default(), | ||
} | ||
} | ||
|
||
#[derive(Clone)] | ||
struct NeverCallClient; | ||
|
||
#[async_trait] | ||
impl Client for NeverCallClient { | ||
async fn perform<R>(&self, _request: R) -> Result<R::Output, tendermint_rpc::Error> | ||
where | ||
R: tendermint_rpc::SimpleRequest, | ||
{ | ||
todo!() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
define_id!(GAS_MARKET { id: 98 }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
use crate::fvm::FvmMessage; | ||
use anyhow::{bail, Context}; | ||
|
||
use fendermint_actors_api::gas_market::{Gas, Reading, Utilization}; | ||
use fendermint_vm_actor_interface::gas_market::GAS_MARKET_ACTOR_ADDR; | ||
use fendermint_vm_actor_interface::{reward, system}; | ||
use fvm::executor::{ApplyKind, ApplyRet, Executor}; | ||
use fvm_shared::address::Address; | ||
use fvm_shared::econ::TokenAmount; | ||
use fvm_shared::METHOD_SEND; | ||
use num_traits::Zero; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct BlockGasTracker { | ||
/// The current base fee. | ||
base_fee: TokenAmount, | ||
/// The current block gas limit. | ||
block_gas_limit: Gas, | ||
/// The cumulative gas premiums claimable by the block producer. | ||
cumul_gas_premium: TokenAmount, | ||
/// The accumulated gas usage throughout the block. | ||
cumul_gas_used: Gas, | ||
} | ||
|
||
impl BlockGasTracker { | ||
pub fn create<E: Executor>(executor: &mut E) -> anyhow::Result<BlockGasTracker> { | ||
let mut ret = Self { | ||
base_fee: Zero::zero(), | ||
block_gas_limit: Zero::zero(), | ||
cumul_gas_premium: Zero::zero(), | ||
cumul_gas_used: Zero::zero(), | ||
}; | ||
|
||
let reading = Self::read_gas_market(executor)?; | ||
|
||
ret.base_fee = reading.base_fee; | ||
ret.block_gas_limit = reading.block_gas_limit; | ||
|
||
Ok(ret) | ||
} | ||
|
||
pub fn available(&self) -> Gas { | ||
self.block_gas_limit.saturating_sub(self.cumul_gas_used) | ||
} | ||
|
||
pub fn ensure_sufficient_gas(&self, msg: &FvmMessage) -> anyhow::Result<()> { | ||
let available_gas = self.available(); | ||
if msg.gas_limit > available_gas { | ||
bail!("message gas limit exceed available block gas limit; consensus engine may be misbehaving; txn gas limit: {}, block gas available: {}", | ||
msg.gas_limit, | ||
available_gas | ||
); | ||
} | ||
Ok(()) | ||
} | ||
|
||
pub fn record_utilization(&mut self, ret: &ApplyRet) { | ||
self.cumul_gas_premium += ret.miner_tip.clone(); | ||
self.cumul_gas_used = self.cumul_gas_used.saturating_add(ret.msg_receipt.gas_used); | ||
|
||
// sanity check, should not happen; only trace if it does so we can debug later. | ||
if self.cumul_gas_used >= self.block_gas_limit { | ||
tracing::warn!("out of block gas; cumulative gas used exceeds block gas limit!"); | ||
} | ||
} | ||
|
||
pub fn finalize<E: Executor>( | ||
&self, | ||
executor: &mut E, | ||
premium_recipient: Option<Address>, | ||
) -> anyhow::Result<Reading> { | ||
if let Some(premium_recipient) = premium_recipient { | ||
self.distribute_premiums(executor, premium_recipient)? | ||
} | ||
self.commit_utilization(executor) | ||
} | ||
|
||
pub fn read_gas_market<E: Executor>(executor: &mut E) -> anyhow::Result<Reading> { | ||
let msg = FvmMessage { | ||
from: system::SYSTEM_ACTOR_ADDR, | ||
to: GAS_MARKET_ACTOR_ADDR, | ||
sequence: 0, // irrelevant for implicit executions. | ||
gas_limit: i64::MAX as u64, | ||
method_num: fendermint_actors_api::gas_market::Method::CurrentReading as u64, | ||
params: fvm_ipld_encoding::RawBytes::default(), | ||
value: Default::default(), | ||
version: Default::default(), | ||
gas_fee_cap: Default::default(), | ||
gas_premium: Default::default(), | ||
}; | ||
|
||
let apply_ret = Self::apply_implicit_message(executor, msg)?; | ||
|
||
if let Some(err) = apply_ret.failure_info { | ||
bail!("failed to acquire gas market reading: {}", err); | ||
} | ||
|
||
fvm_ipld_encoding::from_slice::<Reading>(&apply_ret.msg_receipt.return_data) | ||
.context("failed to parse gas market reading") | ||
} | ||
|
||
fn commit_utilization<E: Executor>(&self, executor: &mut E) -> anyhow::Result<Reading> { | ||
let params = fvm_ipld_encoding::RawBytes::serialize(Utilization { | ||
block_gas_used: self.cumul_gas_used, | ||
})?; | ||
|
||
let msg = FvmMessage { | ||
from: system::SYSTEM_ACTOR_ADDR, | ||
to: GAS_MARKET_ACTOR_ADDR, | ||
sequence: 0, // irrelevant for implicit executions. | ||
gas_limit: i64::MAX as u64, | ||
method_num: fendermint_actors_api::gas_market::Method::UpdateUtilization as u64, | ||
params, | ||
value: Default::default(), | ||
version: Default::default(), | ||
gas_fee_cap: Default::default(), | ||
gas_premium: Default::default(), | ||
}; | ||
|
||
let apply_ret = Self::apply_implicit_message(executor, msg)?; | ||
fvm_ipld_encoding::from_slice::<Reading>(&apply_ret.msg_receipt.return_data) | ||
.context("failed to parse gas utilization result") | ||
} | ||
|
||
fn distribute_premiums<E: Executor>( | ||
&self, | ||
executor: &mut E, | ||
premium_recipient: Address, | ||
) -> anyhow::Result<()> { | ||
if self.cumul_gas_premium.is_zero() { | ||
return Ok(()); | ||
} | ||
|
||
let msg = FvmMessage { | ||
from: reward::REWARD_ACTOR_ADDR, | ||
to: premium_recipient, | ||
sequence: 0, // irrelevant for implicit executions. | ||
gas_limit: i64::MAX as u64, | ||
method_num: METHOD_SEND, | ||
params: fvm_ipld_encoding::RawBytes::default(), | ||
value: self.cumul_gas_premium.clone(), | ||
version: Default::default(), | ||
gas_fee_cap: Default::default(), | ||
gas_premium: Default::default(), | ||
}; | ||
Self::apply_implicit_message(executor, msg)?; | ||
|
||
Ok(()) | ||
} | ||
|
||
fn apply_implicit_message<E: Executor>( | ||
executor: &mut E, | ||
msg: FvmMessage, | ||
) -> anyhow::Result<ApplyRet> { | ||
let apply_ret = executor.execute_message(msg, ApplyKind::Implicit, 0)?; | ||
if let Some(err) = apply_ret.failure_info { | ||
bail!("failed to apply message: {}", err) | ||
} | ||
Ok(apply_ret) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright 2022-2024 Protocol Labs | ||
// SPDX-License-Identifier: Apache-2.0, MIT | ||
|
||
//! Gas related message selection | ||
use crate::fvm::state::FvmExecState; | ||
use fendermint_vm_message::signed::SignedMessage; | ||
use fvm_ipld_blockstore::Blockstore; | ||
|
||
/// Implement this trait to perform message selection | ||
pub trait MessageSelector { | ||
fn select_messages<DB: Blockstore + Clone + 'static>( | ||
&self, | ||
state: &FvmExecState<DB>, | ||
msgs: Vec<SignedMessage>, | ||
) -> Vec<SignedMessage>; | ||
} | ||
|
||
pub(crate) struct GasLimitSelector; | ||
|
||
impl MessageSelector for GasLimitSelector { | ||
fn select_messages<DB: Blockstore + Clone + 'static>( | ||
&self, | ||
state: &FvmExecState<DB>, | ||
mut msgs: Vec<SignedMessage>, | ||
) -> Vec<SignedMessage> { | ||
let total_gas_limit = state.block_gas_tracker().available(); | ||
|
||
// Sort by gas limit descending | ||
msgs.sort_by(|a, b| b.message.gas_limit.cmp(&a.message.gas_limit)); | ||
|
||
let mut total_gas_limit_consumed = 0; | ||
msgs.into_iter() | ||
.take_while(|msg| { | ||
let gas_limit = msg.message.gas_limit; | ||
let accepted = total_gas_limit_consumed + gas_limit <= total_gas_limit; | ||
if accepted { | ||
total_gas_limit_consumed += gas_limit; | ||
} | ||
accepted | ||
}) | ||
.collect() | ||
} | ||
} |