Conversation
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Duplicate state computation may cause app_hash mismatch
finalize_blocknow stores the exact finalized post-state and app hash forcommit, andcommitvalidates executor output against that finalized state before persisting it.
- ✅ Fixed: Missing failed transaction logging in finalize_block
finalize_blocknow logsdeliver_tx failedwith code/log/info whenever a transaction result has a non-zero code.
Or push these changes by commenting:
@cursor push c598246194
Preview (c598246194)
diff --git a/fendermint/app/src/app.rs b/fendermint/app/src/app.rs
--- a/fendermint/app/src/app.rs
+++ b/fendermint/app/src/app.rs
@@ -92,6 +92,11 @@
state_params: FvmStateParams,
}
+struct FinalizedBlockState {
+ state: SubnetAppState,
+ app_hash: tendermint::hash::AppHash,
+}
+
impl SubnetAppState {
pub fn app_hash(&self) -> tendermint::hash::AppHash {
derive_subnet_app_hash(self)
@@ -165,6 +170,8 @@
messages_interpreter: Arc<MI>,
light_client_commitments: Arc<tokio::sync::Mutex<Option<LightClientCommitments>>>,
+ /// Post-state computed in `finalize_block`, consumed by `commit`.
+ finalized_block_state: Arc<tokio::sync::Mutex<Option<FinalizedBlockState>>>,
/// Interface to the snapshotter, if enabled.
snapshots: Option<SnapshotClient>,
@@ -208,6 +215,7 @@
state_hist_size: config.state_hist_size,
messages_interpreter: Arc::new(interpreter),
light_client_commitments: Arc::new(tokio::sync::Mutex::new(None)),
+ finalized_block_state: Arc::new(tokio::sync::Mutex::new(None)),
snapshots,
exec_state: Arc::new(tokio::sync::Mutex::new(None)),
check_state: Arc::new(tokio::sync::Mutex::new(None)),
@@ -420,6 +428,19 @@
guard.take().expect("exec state empty")
}
+ /// Put the post-state produced during finalization. Has to be empty.
+ async fn put_finalized_block_state(&self, state: FinalizedBlockState) {
+ let mut guard = self.finalized_block_state.lock().await;
+ assert!(guard.is_none(), "finalized block state not empty");
+ *guard = Some(state);
+ }
+
+ /// Take the post-state produced during finalization. Has to be non-empty.
+ async fn take_finalized_block_state(&self) -> FinalizedBlockState {
+ let mut guard = self.finalized_block_state.lock().await;
+ guard.take().expect("finalized block state empty")
+ }
+
/// Update the execution state using the provided closure.
///
/// Note: Deals with panics in the user provided closure as well.
@@ -880,7 +901,9 @@
let mut state_params = state.app_state.state_params.clone();
state_params.timestamp = to_timestamp(request.time);
- let validator = self.get_validator_from_cache(&request.proposer_address).await?;
+ let validator = self
+ .get_validator_from_cache(&request.proposer_address)
+ .await?;
let mut exec_state =
FvmExecState::new(db, self.multi_engine.as_ref(), block_height, state_params)
@@ -898,7 +921,10 @@
let mut tx_results = Vec::with_capacity(request.txs.len());
for tx in request.txs {
let msg = tx.to_vec();
- let result = self.messages_interpreter.apply_message(&mut exec_state, msg).await;
+ let result = self
+ .messages_interpreter
+ .apply_message(&mut exec_state, msg)
+ .await;
let deliver_tx = match result {
Ok(ApplyMessageResponse {
applied_message,
@@ -913,6 +939,15 @@
Err(ApplyMessageError::Other(e)) => Err(e).context("failed to apply message")?,
};
+ if deliver_tx.code != 0.into() {
+ tracing::info!(
+ code = ?deliver_tx.code,
+ log = %deliver_tx.log,
+ info = %deliver_tx.info,
+ "deliver_tx failed"
+ );
+ }
+
tx_results.push(tendermint::abci::types::ExecTxResult {
code: deliver_tx.code,
data: deliver_tx.data,
@@ -969,6 +1004,11 @@
projected_state.state_commitments = light_client_commitments;
let app_hash = projected_state.app_hash();
+ self.put_finalized_block_state(FinalizedBlockState {
+ state: projected_state,
+ app_hash: app_hash.clone(),
+ })
+ .await;
self.put_exec_state(exec_state).await;
Ok(response::FinalizeBlock {
@@ -983,11 +1023,12 @@
/// Commit the current state at the current height.
async fn commit(&self) -> AbciResult<response::Commit> {
let exec_state = self.take_exec_state().await;
+ let finalized = self.take_finalized_block_state().await;
+ let exec_block_height: BlockHeight = exec_state.block_height().try_into()?;
+ let exec_timestamp = exec_state.timestamp();
// Commit the execution state to the datastore.
- let mut state = self.committed_state()?;
- state.app_state.block_height = exec_state.block_height().try_into()?;
- state.app_state.state_params.timestamp = exec_state.timestamp();
+ let state = finalized.state;
let (
state_root,
@@ -1000,17 +1041,21 @@
_,
) = exec_state.commit().context("failed to commit FVM")?;
- state.app_state.state_params.state_root = state_root;
- state.app_state.state_params.app_version = app_version;
- state.app_state.state_params.base_fee = base_fee;
- state.app_state.state_params.circ_supply = circ_supply;
- state.app_state.state_params.power_scale = power_scale;
+ if state.app_state.block_height != exec_block_height
+ || state.app_state.state_params.timestamp != exec_timestamp
+ || state.app_state.state_params.state_root != state_root
+ || state.app_state.state_params.app_version != app_version
+ || state.app_state.state_params.base_fee != base_fee
+ || state.app_state.state_params.circ_supply != circ_supply
+ || state.app_state.state_params.power_scale != power_scale
+ {
+ return Err(anyhow!("finalized state does not match committed execution state").into());
+ }
let mut c = self.light_client_commitments.lock().await;
- // because of the take, no need to *c = None
- state.state_commitments = c.take();
+ let _ = c.take();
- let app_hash = state.app_hash();
+ let app_hash = finalized.app_hash;
let block_height = state.app_state.block_height;
// Tell CometBFT how much of the block history it can forget.This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
sergefdrv
reviewed
Mar 25, 2026
cryptoAtwill
approved these changes
Mar 25, 2026
Contributor
cryptoAtwill
left a comment
There was a problem hiding this comment.
Looks like only updating ABCI calls, if e2e tests are working, should be good.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Note
High Risk
High risk because it upgrades the Tendermint/CometBFT stack and migrates core ABCI handling from
BeginBlock/DeliverTx/EndBlocktoFinalizeBlock, impacting block execution, app-hash calculation, and RPC transaction result parsing.Overview
Upgrades the CometBFT/Tendermint dependency stack to
0.38.x(plustower-abci0.16andprost0.13), with associated lockfile churn and TLS/websocket crate updates.Migrates fendermint’s ABCI integration to the
v0.38APIs and ABCI 2.0 flow: replaces per-txdeliver_txwith block-levelfinalize_block, adds vote-extension hooks, updates server wiring (v038split/server andlisten_tcp), and makesFlushhandling non-panicking.Updates downstream consumers to the new response shapes (
tx_result/ExecTxResult), adjusts event attribute indexing helpers, extends genesis consensus params withabci, bumps test/dev CometBFT images tov0.38.x, and improves theactors-custom-carbuild script to locateCargo.lockreliably across externalCARGO_TARGET_DIRsetups.Written by Cursor Bugbot for commit e94966a. This will update automatically on new commits. Configure here.