Conversation
Codecov Report
... and 11 files with indirect coverage changes
Flags with carried forward coverage won't be shown. Click here to find out more.
|
mattsse
left a comment
There was a problem hiding this comment.
I like the direction,
we should think about the trait a bit more
| /// Returns an [action][`HookAction`], if any, according to the passed [event][`HookEvent`]. | ||
| fn on_event(&mut self, event: HookEvent) -> Result<Option<HookAction>, HookError>; |
There was a problem hiding this comment.
this I don't understand after reading the event docs.
why do we feed the outcome of poll back into an event callback, this looks unnecessary because the hook already knows this.
There was a problem hiding this comment.
we act on Started/Finished here:
reth/crates/consensus/beacon/src/engine/hooks/controller.rs
Lines 64 to 68 in 2a6fd5c
reth/crates/consensus/beacon/src/engine/hooks/controller.rs
Lines 123 to 127 in 2a6fd5c
also, there might be no action required (as will be for snapshots, for example), but the hook still need to signal as about its status.
We can combine it into one HookEvent::Finished(HookAction) though, and it will be returned on hook polling.
There was a problem hiding this comment.
ok, I just made the Hook::poll return both event and an optional action, so we could:
- Act on event and set/unset the
running_hook_with_db_write - Pass the action further down
| /// Returns [dependencies][`HookDependencies`] for running this hook. | ||
| fn dependencies(&self) -> HookDependencies; |
There was a problem hiding this comment.
this implies the dependencies are required for the duration of the hook which might not be the case.
I think we can solve this a bit differently, for example with multiple poll functions:
like poll_start -> Poll<Deps>,
and poll_hook()
There was a problem hiding this comment.
not following...
this implies the dependencies are required for the duration of the hook which might not be the case.
for DB write access, this is the case. Not sure what other dependencies we might have in the future though, so you might be right, but should we be ready for this now?
| hooks: Vec<Option<Box<dyn Hook>>>, | ||
| /// Next hook index to poll. | ||
| next_hook_idx: usize, | ||
| /// Currently running hook with DB write access, if any. | ||
| running_hook_with_db_write: Option<(usize, Box<dyn Hook>)>, |
86fad45 to
f86f4da
Compare
| match self.blockchain.restore_canonical_hashes() { | ||
| Ok(()) => {} | ||
| Err(error) => { | ||
| error!(target: "consensus::engine", ?error, "Error restoring blockchain tree state"); | ||
| return Err(error.into()) |
There was a problem hiding this comment.
| match self.blockchain.restore_canonical_hashes() { | |
| Ok(()) => {} | |
| Err(error) => { | |
| error!(target: "consensus::engine", ?error, "Error restoring blockchain tree state"); | |
| return Err(error.into()) | |
| if let Err(error) = self.blockchain.restore_canonical_hashes() { | |
| error!(target: "consensus::engine", ?error, "Error restoring blockchain tree state"); | |
| return Err(error.into()) |
| /// Dependencies that [hook][`Hook`] require for execution. | ||
| pub struct HookDependencies { | ||
| /// Hook needs DB write access. If `true`, then only one hook with DB write access can be run | ||
| /// at a time. | ||
| pub db_write: bool, | ||
| } |
There was a problem hiding this comment.
shall we keep it simple for now and get rid of this generalization? dependencies naming is a bit confusing and db write access feels important enough to be extracted to fn db_access_level() -> HookDbAccess on the Hook trait
enum HookDbAccess {
RO,
RW
}or smth similar
There was a problem hiding this comment.
agree, that would be enough for prune and snapshot hooks, and we can add more functionality on top later
There was a problem hiding this comment.
it would also be nice to somehow restrict the database provider that custom user-provided hook has access to according to this level
| if is_pending && !this.forkchoice_state_tracker.is_latest_invalid() { | ||
| if let Poll::Ready(result) = this.hooks.poll_next_hook( | ||
| cx, | ||
| HookArguments { tip_block_number: this.blockchain.canonical_tip().number }, | ||
| HookDependencies { db_write: this.sync.is_pipeline_active() }, | ||
| ) { | ||
| if let Err(err) = this.on_hook_action(result?) { | ||
| return Poll::Ready(Err(err)) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
So we are only polling one hook (maybe two if there was a previous write poll) per engine poll.
Is that intended? why not more? we don't want to potentially block the FCU handle?
There was a problem hiding this comment.
random thought: is it possible to run them in parallel here (as long as they're read-only)?
There was a problem hiding this comment.
we don't want to potentially block the FCU handle?
yes, that was the main reason, so we would return the control asap and process any new incoming messages, because they're higher priority than hooks
| pub enum HookAction { | ||
| /// Notify about a [SyncState] update. | ||
| UpdateSyncState(SyncState), | ||
| /// Read the last relevant canonical hashes from the database and update the block indices of | ||
| /// the blockchain tree. | ||
| RestoreCanonicalHashes, | ||
| } |
There was a problem hiding this comment.
Given these variants... is the goal to move the engine fcu handling to an hook as well?
There was a problem hiding this comment.
hmm yeah, that's a good idea, I think we can do that with both engine messages handling and pipeline. This would also breakdown the huge engine/mod.rs file and separate the engine polling, engine messages and pipeline control logics. @mattsse wdyt?
There was a problem hiding this comment.
I don't want to hide the actual FCU inside a dyn Hook,
the entire purpose of the engine is to handle fcu and payload, this should be baked in directly
e552f18 to
ba037ee
Compare
ba037ee to
3b69c3b
Compare
|
|
||
| /// Hook that will be run during the main loop of | ||
| /// [consensus engine][`crate::engine::BeaconConsensusEngine`]. | ||
| pub trait Hook: Send + Sync + 'static { |
There was a problem hiding this comment.
I wanna start bikeshedding the name,
how about EngineHook?
|
|
||
| /// Arguments passed to the [hook polling function][`Hook::poll`]. | ||
| #[derive(Copy, Clone, Debug)] | ||
| pub struct HookArguments { |
There was a problem hiding this comment.
I don't think Arguments is fitting here,
how about EngineContext?
This PR introduces the engine hooks (name is debatable, we can also call them triggers or actions). They are used to extend the functionality of consensus engine with custom logic provided by the caller (in
src/node/mod.rsfor now, and in CLI builder later).The first instance of such functionality is the pruner, that needs to be run on every iteration of the main engine loop. Next will be snapshot hook (#4588).
Only the way pruner is called from the engine has changed in this PR, and no changes in engine or pruner behavior are expected.