-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
feat: add new BlockchainProvider type #9656
Conversation
f2b99de
to
c51f37a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about this a bit, as you highlighted we're now running into cyclic dep issues.
we could first move the types to provider like this but I wonder if we could improve the engine/tree layout instead (or later)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should move these types to a new crate instead.
the provider crate is already a bit messy, so I wonder how to structure this.
I think the ideal solution here is that the engine/tree does not require the new provider type directly and instead operates on provider traits and has the Inmemorystate as a separate field, because otherwise we need to go through the provider to update state and exposing these functions publicly on the provider type sounds a bit dangerous
we should also consider splitting the content of engine/tree into multiple smaller crates for
- persistence + database
- orchestrator + handlers
- state for the inmemory types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes total sense, will prepare changes for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cool, I think we can have the orchestrator and related types in one crate and then the actual engineapi impl in another, and the db+persistence in an additional crate
so that
engineapiimpl imports orchestrator + persistence
c51f37a
to
5847557
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I have removed some redundant tree trait impls that will be handled differently now in the communication of the beaconenggine and the tree, and changed some of them to use the in memory representation
I've also left some questions mainly regarding the pending state provider @mattsse ptal
trace!(target: "providers::blockchain", "Getting provider for pending state"); | ||
|
||
if let Some(block) = self.canonical_in_memory_state.pending_block_num_hash() { | ||
if let Ok(pending) = self.tree.pending_state_provider(block.hash) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what to do with this pending_state_provider
, should we implement it in the in memory state?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd need to check if there's a pending block in the canonical memory state, if not this should fallback to latest
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is similar to the previous question, this method returns a ProviderResult<StateProviderBox>
how do we obtain a StateProvider
including in memory information? I think this affects all the methods in the StateProviderFactory
impl
} | ||
|
||
fn pending_state_by_hash(&self, block_hash: B256) -> ProviderResult<Option<StateProviderBox>> { | ||
if let Some(state) = self.tree.find_pending_state_provider(block_hash) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same, also related to pending state provider
DB: Send + Sync, | ||
{ | ||
fn subscribe_to_canonical_state(&self) -> CanonStateNotifications { | ||
self.tree.subscribe_to_canonical_state() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we impl this trait CanonStateSubscriptions
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, we need this in rpc and other components, what we need to do is, move the broadcast channel into the canonicalinmemory type, as a replacement for
reth/crates/blockchain-tree/src/blockchain_tree.rs
Lines 73 to 74 in 8c690ee
/// Broadcast channel for canon state changes notifications. | |
canon_state_notification_sender: CanonStateNotificationSender, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
crates/chain-state/src/in_memory.rs
Outdated
&self.0.execution_outcome().receipts | ||
} | ||
|
||
/// Returns a vector of `Receipt` of executed block that determines the state. | ||
pub fn flattened_receipts(&self) -> Vec<Receipt> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required by a method in BlockReader
is it ok to flatten like this the Vec<Vec<Option<Receipt>>>
that Receipts
contains?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately the Receipts
type refers to multiple blocks' worth of receipts because the ExecutionOutcome
is for both single and multiple blocks. In this case we can expect there to be only one element in the outer Vec
. One thing we could do is debug_assert
that the list is length one, and .first().unwrap()
until we find a better way to handle this, does that make sense?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes total sense, done here ef07c86 ptal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
makes sense to me, I like the comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome, finding the corresponding values will be a bit more tricky because now that we store canonical blocks in two locations we also need to check both of them when we try to find blocks.
similar situation as with the existing blockchainprovider but now the separation is not pending vs canonical but memory vs disk, for now we can simply check both locations, the inmemory lookup should be super cheap because this is just an rwlock.
functionality wise this pr looks complete and we can iterate on this and the inmemorycanonical state in more smaller scoped prs
I believe we also need to keep track of the block range we currently have in memory and think about race conditions a bit.
since we flush to disko before we update the memory state, we have a guarantee that the state is present somewhere, so we should likely always check inmemory first and then Hit disk if we couldn't find it
BlockSource::Pending => self | ||
.canonical_in_memory_state | ||
.state_by_hash(hash) | ||
.map(|block_state| block_state.block().block().clone().unseal()), | ||
BlockSource::Canonical => self.database.block_by_hash(hash)?, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is now a bit blurry, because the canonical in memory state is also canonical, so we need to handle this like any.
and pending should just check the pending block
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very good point, done here 6a00ec8, now we handle BlockSource::Canonical
and BlockSource::Any
with the same code, I've changed it a bit to check the in memory representation before hitting the db, let me know wdyt. And for BlockSource::Pending
we just return the pending block if present.
self.ensure_canonical_block(block_number)?; | ||
self.database.history_by_block_number(block_number) | ||
} | ||
|
||
fn history_by_block_hash(&self, block_hash: BlockHash) -> ProviderResult<StateProviderBox> { | ||
trace!(target: "providers::blockchain", ?block_hash, "Getting history by block hash"); | ||
self.database.history_by_block_hash(block_hash) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these would also need to check the in memory provider as well because from caller pov all blocks in there are also historical blocks, part of the canonical chain
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm but these methods are returning a StateProviderBox
, which is Box<dyn StateProvider>
and in memory we are only storing a set of blocks, how can we impl StateProvider
for the in memory rep. of a few blocks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're still lacking this feature: #9614
to unblock this pr we can ignore the memory state for now and track in an issue instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, I've left todo's about this
trace!(target: "providers::blockchain", "Getting provider for pending state"); | ||
|
||
if let Some(block) = self.canonical_in_memory_state.pending_block_num_hash() { | ||
if let Ok(pending) = self.tree.pending_state_provider(block.hash) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd need to check if there's a pending block in the canonical memory state, if not this should fallback to latest
DB: Send + Sync, | ||
{ | ||
fn subscribe_to_canonical_state(&self) -> CanonStateNotifications { | ||
self.tree.subscribe_to_canonical_state() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, we need this in rpc and other components, what we need to do is, move the broadcast channel into the canonicalinmemory type, as a replacement for
reth/crates/blockchain-tree/src/blockchain_tree.rs
Lines 73 to 74 in 8c690ee
/// Broadcast channel for canon state changes notifications. | |
canon_state_notification_sender: CanonStateNotificationSender, |
7669fdd
to
ef07c86
Compare
6a00ec8
to
3afd7f3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets send it
Closes #9607