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

app: 🥞 dynamic application Components #3740

Closed
cratelyn opened this issue Feb 5, 2024 · 5 comments
Closed

app: 🥞 dynamic application Components #3740

cratelyn opened this issue Feb 5, 2024 · 5 comments
Labels
A-node Area: System design and implementation for node software _P-low Priority: low

Comments

@cratelyn
Copy link
Contributor

cratelyn commented Feb 5, 2024

penumbra_app::App is a "bundle of Components". it should provide a way to dynamically define its constituent components. this, in tandem with an in-memory consensus engine feeding ABCI messages into the App, would allow us to easily test distinct parts of our state machine.

🏢 App today

as-is, the component stack is hard-coded, in blocks like this:

ShieldedPool::init_chain(&mut state_tx, None).await;
Distributions::init_chain(&mut state_tx, None).await;
Staking::init_chain(&mut state_tx, None).await;
Ibc::init_chain(&mut state_tx, None).await;
Dex::init_chain(&mut state_tx, None).await;
Governance::init_chain(&mut state_tx, None).await;
CommunityPool::init_chain(&mut state_tx, None).await;
Fee::init_chain(&mut state_tx, None).await;
Funding::init_chain(&mut state_tx, None).await;

// Run each of the begin block handlers for each component, in sequence:
let mut arc_state_tx = Arc::new(state_tx);
Sct::begin_block(&mut arc_state_tx, begin_block).await;
ShieldedPool::begin_block(&mut arc_state_tx, begin_block).await;
Distributions::begin_block(&mut arc_state_tx, begin_block).await;
Ibc::begin_block::<PenumbraHost, StateDelta<Arc<StateDelta<cnidarium::Snapshot>>>>(
&mut arc_state_tx,
begin_block,
)
.await;
CommunityPool::begin_block(&mut arc_state_tx, begin_block).await;
Governance::begin_block(&mut arc_state_tx, begin_block).await;
Staking::begin_block(&mut arc_state_tx, begin_block).await;
Fee::begin_block(&mut arc_state_tx, begin_block).await;
Funding::begin_block(&mut arc_state_tx, begin_block).await;

let mut arc_state_tx = Arc::new(state_tx);
ShieldedPool::end_block(&mut arc_state_tx, end_block).await;
Distributions::end_block(&mut arc_state_tx, end_block).await;
Ibc::end_block(&mut arc_state_tx, end_block).await;
Dex::end_block(&mut arc_state_tx, end_block).await;
CommunityPool::end_block(&mut arc_state_tx, end_block).await;
Governance::end_block(&mut arc_state_tx, end_block).await;
Staking::end_block(&mut arc_state_tx, end_block).await;
Fee::end_block(&mut arc_state_tx, end_block).await;
Funding::end_block(&mut arc_state_tx, end_block).await;

(...and so on...)

invoking these programmatically would help prevent bugs as we continue to maintain and extend App. for example, it's easy to miss that the Sct component is only invoked upon init_chain and begin_block. components should be consistently invoked by the App. (#3739)

🧪 testing benefits

in #3588, we are building an in-memory mock consensus engine. as background, regarding our goals with testing, consider these excerpts from #1664:

[O]ur state machine is written as a series of traits that compose additional behavior onto StateRead/StateWrite to ensure that all execution can be done transactionally[.]

We want to be able to write unit tests that check parts of the state machine in isolation. For instance, we'd like to be able to check individual consensus rules (does this consensus rule reject this input?), or to be able to check pre/post conditions for specific parts of the state machine (do we maintain the desired invariants after processing this input? do we write the data we expect?).

as is, the unit tests in penumbra-app work with a different, specific subset of components, by hard-coding the calls to these hooks, like so:

// Execute EndBlock for the Dex, to actually execute the swaps...
Dex::end_block(&mut state, &end_block).await;
ShieldedPool::end_block(&mut state, &end_block).await;

in a sense, this is re-implementing the internals of App, which makes tests both more tedious to implement and more brittle to change over time.

an interface to select components programmatically would allow test authors to connect a mock consensus engine to distinct state machine components, and generally write unit tests more effectively.

@cratelyn cratelyn self-assigned this Feb 5, 2024
@cratelyn cratelyn added this to Penumbra Feb 5, 2024
@github-project-automation github-project-automation bot moved this to 🗄️ Backlog in Penumbra Feb 5, 2024
@github-project-automation github-project-automation bot moved this to Future in Testnets Feb 5, 2024
@hdevalence
Copy link
Member

So far this has been a non-goal. Components have to execute in order, and this design assumption cannot be easily changed. Making components dynamic would have to have some way of dynamically specifiying constraints on execution ordering, adding additional complexity.

However, it's a little unclear what the upside would be. Why is it desirable to be able to punch out arbitrary subsets of the Penumbra application? Why don't we just test the whole thing, first?

@cratelyn cratelyn added the A-node Area: System design and implementation for node software label Feb 5, 2024
@cratelyn
Copy link
Contributor Author

cratelyn commented Feb 5, 2024

However, it's a little unclear what the upside would be. Why is it desirable to be able to punch out arbitrary subsets of the Penumbra application? Why don't we just test the whole thing, first?

i am a little surprised to hear this! this seemed like a reasonable approach after our conversation in #3695, where you said:

Do we want to write tests of pd? I'm not sure that should be our goal. Our existing app-level integration tests are part of the app crate. What functionality should be in pd as a library rather than in the app crate?

we had a conversation after that where you pointed me towards #1664, which states that "we want to be able to write unit tests that check parts of the state machine in isolation."

our app tests today do this by working directly with components like Dex, CompactBlockManager, and ShieldedPool. but it seemed to me like a better way to do this in the long run would be to create an App containing these components, instead of rewriting parts of the App internals out-of-band in test functions.

@cratelyn
Copy link
Contributor Author

cratelyn commented Feb 5, 2024

(following up to the above) ☝️ perhaps an unstated assumption here on my part is that this would not be about changing the application components in the "real" App. those should stay untouched, and the "stack" as-is should be provided as an ergonomic default.

@cratelyn
Copy link
Contributor Author

cratelyn commented Feb 6, 2024

@hdevalence talked today and after some direct discussion, i agree this can be put aside for the time being. i'm going to mark this as a low priority and save it for later on in the broader in-memory consensus effort.

@cratelyn cratelyn added the _P-low Priority: low label Feb 6, 2024
@cratelyn cratelyn removed their assignment Feb 6, 2024
@cratelyn
Copy link
Contributor Author

cratelyn commented Feb 8, 2024

after further thought, going to close this as unplanned work.

@cratelyn cratelyn closed this as not planned Won't fix, can't repro, duplicate, stale Feb 8, 2024
@github-project-automation github-project-automation bot moved this from 🗄️ Backlog to Done in Penumbra Feb 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-node Area: System design and implementation for node software _P-low Priority: low
Projects
Archived in project
Status: Future
Development

No branches or pull requests

2 participants