Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fendermint/app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ tracing-subscriber = { workspace = true }
literally = { workspace = true }

fendermint_abci = { path = "../abci" }
fendermint_actors_api = { path = "../actors/api" }
fendermint_app_options = { path = "./options" }
fendermint_app_settings = { path = "./settings" }
fendermint_crypto = { path = "../crypto" }
Expand Down
2 changes: 1 addition & 1 deletion fendermint/app/options/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ pub struct SealGenesisArgs {
/// The solidity artifacts output path. If you are using ipc-monorepo, it should be the `out` folder
/// of `make build`
#[arg(long, short)]
pub artifacts_path: Option<PathBuf>,
pub artifacts_path: PathBuf,

/// The sealed genesis state output path, i.e. finalized genesis state CAR file dump path
#[arg(long, short)]
Expand Down
138 changes: 92 additions & 46 deletions fendermint/app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,22 @@
use std::future::Future;
use std::sync::Arc;

use crate::observe::{
BlockCommitted, BlockProposalEvaluated, BlockProposalReceived, BlockProposalSent, Message,
MpoolReceived,
};
use crate::validators::ValidatorCache;
use crate::AppExitCode;
use crate::BlockHeight;
use crate::{tmconv::*, VERSION};
use anyhow::{anyhow, Context, Result};
use async_stm::{atomically, atomically_or_err};
use async_trait::async_trait;
use cid::Cid;
use fendermint_abci::util::take_until_max_size;
use fendermint_abci::{AbciResult, Application};
use fendermint_actors_api::gas_market::Reading;
use fendermint_crypto::PublicKey;
use fendermint_storage::{
Codec, Encode, KVCollection, KVRead, KVReadable, KVStore, KVWritable, KVWrite,
};
Expand Down Expand Up @@ -41,18 +51,9 @@ use num_traits::Zero;
use serde::{Deserialize, Serialize};
use tendermint::abci::request::CheckTxKind;
use tendermint::abci::{request, response};
use tendermint_rpc::Client;
use tendermint::consensus::params::Params as TendermintConsensusParams;
use tracing::instrument;

use crate::observe::{
BlockCommitted, BlockProposalEvaluated, BlockProposalReceived, BlockProposalSent, Message,
MpoolReceived,
};
use crate::validators::ValidatorTracker;
use crate::AppExitCode;
use crate::BlockHeight;
use crate::{tmconv::*, VERSION};

#[derive(Serialize)]
#[repr(u8)]
pub enum AppStoreKey {
Expand Down Expand Up @@ -117,11 +118,10 @@ pub struct AppConfig<S: KVStore> {

/// Handle ABCI requests.
#[derive(Clone)]
pub struct App<DB, SS, S, I, C>
pub struct App<DB, SS, S, I>
where
SS: Blockstore + Clone + 'static,
S: KVStore,
C: Client,
{
/// Database backing all key-value operations.
db: Arc<DB>,
Expand Down Expand Up @@ -162,13 +162,11 @@ where
///
/// Zero means unlimited.
state_hist_size: u64,
/// Tracks the validator
validators: ValidatorTracker<C>,
/// The cometbft client
client: C,
/// Caches the validators.
validators_cache: Arc<tokio::sync::Mutex<Option<ValidatorCache>>>,
}

impl<DB, SS, S, I, C> App<DB, SS, S, I, C>
impl<DB, SS, S, I> App<DB, SS, S, I>
where
S: KVStore
+ Codec<AppState>
Expand All @@ -177,7 +175,6 @@ where
+ Codec<FvmStateParams>,
DB: KVWritable<S> + KVReadable<S> + Clone + 'static,
SS: Blockstore + Clone + 'static,
C: Client + Clone,
{
pub fn new(
config: AppConfig<S>,
Expand All @@ -186,7 +183,6 @@ where
interpreter: I,
chain_env: ChainEnv,
snapshots: Option<SnapshotClient>,
client: C,
) -> Result<Self> {
let app = Self {
db: Arc::new(db),
Expand All @@ -201,15 +197,14 @@ where
snapshots,
exec_state: Arc::new(tokio::sync::Mutex::new(None)),
check_state: Arc::new(tokio::sync::Mutex::new(None)),
validators: ValidatorTracker::new(client.clone()),
client,
validators_cache: Arc::new(tokio::sync::Mutex::new(None)),
};
app.init_committed_state()?;
Ok(app)
}
}

impl<DB, SS, S, I, C> App<DB, SS, S, I, C>
impl<DB, SS, S, I> App<DB, SS, S, I>
where
S: KVStore
+ Codec<AppState>
Expand All @@ -218,7 +213,6 @@ where
+ Codec<FvmStateParams>,
DB: KVWritable<S> + KVReadable<S> + 'static + Clone,
SS: Blockstore + 'static + Clone,
C: Client,
{
/// Get an owned clone of the state store.
fn state_store_clone(&self) -> SS {
Expand All @@ -244,6 +238,7 @@ where
chain_id: 0,
power_scale: 0,
app_version: 0,
consensus_params: None,
},
};
self.set_committed_state(state)?;
Expand Down Expand Up @@ -294,6 +289,31 @@ where
.context("commit failed")
}

/// Diff our current consensus params with new values, and return Some with the final params
/// if they differ (and therefore a consensus layer update is necessary).
fn maybe_update_app_state(
&self,
gas_market: &Reading,
) -> Result<Option<TendermintConsensusParams>> {
let mut state = self.committed_state()?;
let current = state
.state_params
.consensus_params
.ok_or_else(|| anyhow!("no current consensus params in state"))?;

if current.block.max_gas == gas_market.block_gas_limit as i64 {
return Ok(None); // No update necessary.
}

// Proceeding with update.
let mut updated = current;
updated.block.max_gas = gas_market.block_gas_limit as i64;
state.state_params.consensus_params = Some(updated.clone());
self.set_committed_state(state)?;

Ok(Some(updated))
}

/// Put the execution state during block execution. Has to be empty.
async fn put_exec_state(&self, state: FvmExecState<SS>) {
let mut guard = self.exec_state.lock().await;
Expand Down Expand Up @@ -397,6 +417,37 @@ where
_ => Err(anyhow!("invalid app state json")),
}
}

/// Replaces the current validators cache with a new one.
async fn replace_validators_cache(&self) -> Result<()> {
let mut state = self
.read_only_view(None)?
.ok_or_else(|| anyhow!("exec state should be present"))?;

let mut cache = self.validators_cache.lock().await;
*cache = Some(ValidatorCache::new_from_state(&mut state)?);
Ok(())
}

/// Retrieves a validator from the cache, initializing it if necessary.
async fn get_validator_from_cache(&self, id: &tendermint::account::Id) -> Result<PublicKey> {
let mut cache = self.validators_cache.lock().await;

// If cache is not initialized, update it from the state
if cache.is_none() {
let mut state = self
.read_only_view(None)?
.ok_or_else(|| anyhow!("exec state should be present"))?;

*cache = Some(ValidatorCache::new_from_state(&mut state)?);
}

// Retrieve the validator from the cache
cache
.as_ref()
.context("Validator cache is not available")?
.get_validator(id)
}
}

// NOTE: The `Application` interface doesn't allow failures at the moment. The protobuf
Expand All @@ -405,7 +456,7 @@ where
// the `tower-abci` library would throw an exception when it tried to convert a
// `Response::Exception` into a `ConsensusResponse` for example.
#[async_trait]
impl<DB, SS, S, I, C> Application for App<DB, SS, S, I, C>
impl<DB, SS, S, I> Application for App<DB, SS, S, I>
where
S: KVStore
+ Codec<AppState>
Expand Down Expand Up @@ -436,7 +487,6 @@ where
Query = BytesMessageQuery,
Output = BytesMessageQueryRes,
>,
C: Client + Sync + Clone,
{
/// Provide information about the ABCI application.
async fn info(&self, _request: request::Info) -> AbciResult<response::Info> {
Expand All @@ -463,7 +513,11 @@ where
// Make it easy to spot any discrepancies between nodes.
tracing::info!(genesis_hash = genesis_hash.to_string(), "genesis");

let (validators, state_params) = read_genesis_car(genesis_bytes, &self.state_store).await?;
let (validators, mut state_params) =
read_genesis_car(genesis_bytes, &self.state_store).await?;

state_params.consensus_params = Some(request.consensus_params);

let validators =
to_validator_updates(validators).context("failed to convert validators")?;

Expand All @@ -488,7 +542,7 @@ where
};

let response = response::InitChain {
consensus_params: None,
consensus_params: None, // not updating the proposed consensus params
validators,
app_hash: app_state.app_hash(),
};
Expand Down Expand Up @@ -732,8 +786,7 @@ where
state_params.timestamp = to_timestamp(request.header.time);

let validator = self
.validators
.get_validator(&request.header.proposer_address, block_height)
.get_validator_from_cache(&request.header.proposer_address)
.await?;

let state = FvmExecState::new(db, self.multi_engine.as_ref(), block_height, state_params)
Expand Down Expand Up @@ -796,7 +849,7 @@ where
// End the interpreter for this block.
let EndBlockOutput {
power_updates,
block_gas_limit: new_block_gas_limit,
gas_market,
events,
} = self
.modify_exec_state(|s| self.interpreter.end(s))
Expand All @@ -807,22 +860,15 @@ where
let validator_updates =
to_validator_updates(power_updates.0).context("failed to convert validator updates")?;

// If the block gas limit has changed, we need to update the consensus layer so it can
// pack subsequent blocks against the new limit.
let consensus_param_updates = {
let mut consensus_params = self
.client
.consensus_params(tendermint::block::Height::try_from(request.height)?)
.await?
.consensus_params;

if consensus_params.block.max_gas != new_block_gas_limit as i64 {
consensus_params.block.max_gas = new_block_gas_limit as i64;
Some(consensus_params)
} else {
None
}
};
// Replace the validator cache if the validator set has changed.
if !validator_updates.is_empty() {
self.replace_validators_cache().await?;
}

// Maybe update the app state with the new block gas limit.
let consensus_param_updates = self
.maybe_update_app_state(&gas_market)
.context("failed to update block gas limit")?;

let ret = response::EndBlock {
validator_updates,
Expand Down
7 changes: 2 additions & 5 deletions fendermint/app/src/cmd/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,13 @@ fn set_ipc_gateway(genesis_file: &PathBuf, args: &GenesisIpcGatewayArgs) -> anyh
async fn seal_genesis(genesis_file: &PathBuf, args: &SealGenesisArgs) -> anyhow::Result<()> {
let genesis_params = read_genesis(genesis_file)?;

let mut builder = GenesisBuilder::new(
let builder = GenesisBuilder::new(
args.builtin_actors_path.clone(),
args.custom_actors_path.clone(),
args.artifacts_path.clone(),
genesis_params,
);

if let Some(ref ipc_system_artifacts) = args.artifacts_path {
builder = builder.with_ipc_system_contracts(ipc_system_artifacts.clone());
}

builder.write_to(args.output_path.clone()).await
}

Expand Down
3 changes: 1 addition & 2 deletions fendermint/app/src/cmd/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ async fn run(settings: Settings) -> anyhow::Result<()> {
None
};

let app: App<_, _, AppStore, _, _> = App::new(
let app: App<_, _, AppStore, _> = App::new(
AppConfig {
app_namespace: ns.app,
state_hist_namespace: ns.state_hist,
Expand All @@ -314,7 +314,6 @@ async fn run(settings: Settings) -> anyhow::Result<()> {
parent_finality_votes: parent_finality_votes.clone(),
},
snapshots,
tendermint_client.clone(),
)?;

if let Some((agent_proxy, config)) = ipc_tuple {
Expand Down
Loading