Skip to content

Conversation

@noot
Copy link
Contributor

@noot noot commented Oct 31, 2023

Summary

turns out both prepare_proposal and process_proposal are called for a proposer node. since we moved transaction execution to prepare/process_proposal, when the proposer called both these functions, execution would fail during process_proposal since the state was already updated for post-execution.

Background

see summary.

Changes

  • track if we're the proposer during the block lifecycle in the App struct, and if we are, don't execute transactions during process_proposal

Testing

local testing

@noot noot added the docker-build used to trigger docker builds on PRs label Oct 31, 2023
@github-actions github-actions bot added the sequencer pertaining to the astria-sequencer crate label Oct 31, 2023
@noot noot changed the title fix(sequencer): only run prepare proposal if proposer fix(sequencer): run only prepare_proposal if proposer Oct 31, 2023
@github-actions github-actions bot added the conductor pertaining to the astria-conductor crate label Oct 31, 2023
@noot noot marked this pull request as ready for review October 31, 2023 21:53
@noot noot changed the title fix(sequencer): run only prepare_proposal if proposer fix(sequencer): run only prepare_proposal if proposer Oct 31, 2023
sequencer_block_hash = ?block.block_hash,
sequencer_block_height = block.header.height.value(),
execution_block_hash = hex::encode(&executed_block.hash),
tx_count,
Copy link
Contributor

Choose a reason for hiding this comment

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

This is getting messy. Can you instrument execute_block and move the tx count and the other 2 fields derived from the block into the span?


// set to true when `prepare_proposal` is called, indicating we are the proposer for this slot.
// set to false when `commit` is called, finalizing the block.
// when this is se to true, `process_proposal` is not executed, as we have already executed
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// when this is se to true, `process_proposal` is not executed, as we have already executed
// if true, `process_proposal` is not executed, as we have already executed

process_proposal: abci::request::ProcessProposal,
) -> anyhow::Result<()> {
if self.is_proposer {
return Ok(());
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a debug log?


// Get the latest version of the state, now that we've committed it.
self.state = Arc::new(StateDelta::new(storage.latest_snapshot()));
self.is_proposer = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Add a comment for why this is necessary to if the reader forgot what is_propser is.

Copy link
Contributor

@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.

Just a few comments.

&mut self,
process_proposal: abci::request::ProcessProposal,
) -> anyhow::Result<()> {
if self.is_proposer {
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 block out only specific tasks within here when running? IE only avoid the re-execution?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

basically everything is here is verification and re-execution, don't think we need to re-do any of it

// when this is se to true, `process_proposal` is not executed, as we have already executed
// the transactions for the block during `prepare_proposal`, and re-executing them would
// cause failure.
is_proposer: bool,
Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit concerned about this mechanism in adversarial or dysfunctional scenarios?

What if we can't get a commit to the block in time our proposer gets skipped/slashed but still thinks it's in "proposer" mode? Could we instead check in execute_block_data somehow if that block has already been executed and just skip it if it has?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah i agree, this wouldn't work in that case. i updated this to always set is_proposer = false when process_proposal is called, so process_proposal will skip only in the case where prepare_proposal is called directly before it. so if we propose a block, but it's not accepted, and then process_proposal is called for the next proposed block, it will execute as normal

@noot noot requested a review from joroshiba November 1, 2023 18:35
@noot noot merged commit 6bfbcda into main Nov 1, 2023
@noot noot deleted the noot/fix-proposing branch November 1, 2023 20:33
sambukowski pushed a commit that referenced this pull request Nov 6, 2023
turns out both `prepare_proposal` and `process_proposal` are called for
a proposer node. since we moved transaction execution to
prepare/process_proposal, when the proposer called both these functions,
execution would fail during `process_proposal` since the state was
already updated for post-execution.

see summary.

- track if we're the proposer during the block lifecycle in the `App`
struct, and if we are, don't execute transactions during
`process_proposal`

local testing
sambukowski pushed a commit that referenced this pull request Nov 9, 2023
turns out both `prepare_proposal` and `process_proposal` are called for
a proposer node. since we moved transaction execution to
prepare/process_proposal, when the proposer called both these functions,
execution would fail during `process_proposal` since the state was
already updated for post-execution.

see summary.

- track if we're the proposer during the block lifecycle in the `App`
struct, and if we are, don't execute transactions during
`process_proposal`

local testing
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conductor pertaining to the astria-conductor crate docker-build used to trigger docker builds on PRs sequencer pertaining to the astria-sequencer crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants