Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(sequencer): generate SequencerBlock after transaction execution in proposal phase #1562

Merged
merged 11 commits into from
Oct 7, 2024

Conversation

noot
Copy link
Collaborator

@noot noot commented Sep 25, 2024

Summary

refactor the sequencer app to generate and store the resulting SequencerBlock after transaction execution even in the proposal phase.

Background

previously, we were only generating the SequencerBlock in finalize_block, however with the upcoming builder APIs (#1519) we require the (proposed) SequencerBlock to be available after execution in the proposal phase.

Changes

  • create a post_execute_transactions method and move the after-execution logic that generates the SequencerBlock from finalize_block to there.
  • call this method after transaction execution in process_proposal.
  • if txs were executed in prepare_proposal, post_execute_transactions is still called in process_proposal, as the block hash is not available in prepare_proposal.

Testing

existing unit tests pass, app logic was not changed, just refactored

Related Issues

related to #1322

@noot noot requested a review from a team as a code owner September 25, 2024 19:01
@noot noot requested a review from Fraser999 September 25, 2024 19:01
@github-actions github-actions bot added the sequencer pertaining to the astria-sequencer crate label Sep 25, 2024
Comment on lines 169 to 171
// cache of results of executing of transactions in `process_proposal`.
// cleared at the end of the block.
finalize_block: Option<abci::response::FinalizeBlock>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we utilize the ephemeral storage within cnidarium vs adding to state on the app? I know @SuperFluffy has been trying to decrease our global state bloat.

Copy link
Collaborator Author

@noot noot Sep 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is functionally the same - it's an object stored in memory and cleared at the start of each block. the ephemeral store is also only in memory and is cleared at the end of each block. is the suggestion to have less fields within the App struct?

Copy link
Member

@SuperFluffy SuperFluffy Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that we should employ the ephemeral store more rather than adding fields to app.

There is a tendency to add more and more fields to App that need to be actively set/unset, which is not clean (and I believe with which we had issues in the past).

For a reader it's also better to have a single source of truth of effects that happen during block building, rather than finding some in the ephemeral store and some in the "global" app state.

I should note that this applies to all the other fields as well.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved the fields to the ephemeral object store here: 591fd94

this is nice because the fields are automatically cleared when update_state_for_new_round is called, however having to get a StateDelta for writing to it is bit more cumbersome in parts (putting the execution_results in prepare_proposal)

@@ -458,6 +482,16 @@ impl App {
);

self.executed_proposal_hash = process_proposal.hash;
self.post_execute_transactions(
Copy link
Member

@SuperFluffy SuperFluffy Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not pass the entire process_proposal to this method?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is also called in finalize_block potentially, so it might not have process_proposal available

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is so complex, I don't know how to evaluate this change to it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah :/ i didn't write the test, just modified it/added some asserts for sanity when updating it

pub(crate) async fn finalize_block(
/// this must be called after a block's transactions are executed.
#[instrument(name = "App::post_execute_transactions", skip_all)]
async fn post_execute_transactions(
&mut self,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard to follow all the side effects in this function.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is basically everything that was in finalize_block after txs were executed (minus mempool cleaning, as that needs to be done only when the block is actually finalized)

// use a dummy app hash here - the actual app hash will be filled out in finalize_block.
let dummy_app_hash = AppHash::default();

let finalize_block = abci::response::FinalizeBlock {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's surprising that this method changes the internal state of self.finalize_block, but the finalize_block takes a finalize_block argument and then mutates the finalize_block field that some other part of the stack changed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i could change this to be some intermediate type that doesn't have the app hash yet, which is turned into finalize_block response only in finalize_block. would that be easier to follow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it's fine. We should probably sit down and refactor of all of this. It looks like we have a bit of code duplication going on.

Copy link
Member

@SuperFluffy SuperFluffy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a hard time following the changes, which might be an artifact of how app execution is structured.

I am also surprised that making sequencer blocks available during an earlier phase leads to changes in mempool tests.

@noot
Copy link
Collaborator Author

noot commented Sep 27, 2024

I am also surprised that making sequencer blocks available during an earlier phase leads to changes in mempool tests.

the changes are because process_proposal must now also be called if prepare_proposal is called (this is the normal ABCI flow btw). that's really the only change to the tests

Copy link
Contributor

@Fraser999 Fraser999 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's one mention of self.execution_results left behind in the doc comments for execute_transactions_prepare_proposal which would be good to have updated.

I also think we should look to abstract the usage of the ephemeral store behind our own API provided by the extension traits in app/state_ext.rs (similar to how cached block deposits are handled in the bridge extension traits). We'd also then keep the keys for these in app::state_ext along with the others. But I can handle changing to that as part of the ongoing storage updates.

Neither of these are blocking though, so approving as is.

@@ -629,15 +662,16 @@ impl App {
self.metrics
.set_transactions_in_mempool_total(self.mempool.len().await);

self.execution_results = Some(execution_results);
let mut state_tx = Arc::try_begin_transaction(&mut self.state)
Copy link
Member

@SuperFluffy SuperFluffy Oct 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks complex. Can you add comment above explaining why it's necessary? Seems like a hack around cnidarium not providing us with something we need.

I.e. // HACK: <reason> or // XXX: <reason>

@noot noot added this pull request to the merge queue Oct 7, 2024
Merged via the queue into main with commit f4dba95 Oct 7, 2024
44 checks passed
@noot noot deleted the noot/sequencer-builder-api branch October 7, 2024 08:25
steezeburger added a commit that referenced this pull request Oct 7, 2024
* main:
  refactor(sequencer): generate `SequencerBlock` after transaction execution in proposal phase (#1562)
  fix(sequencer)!: rewrite check_tx to be more efficient and fix regression (#1515)
  fix(charts): hermes monitoring and prometheus rules  (#1564)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
sequencer pertaining to the astria-sequencer crate
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants