Only return viable payload-status variants from get_head - #5509
Only return viable payload-status variants from get_head#55090xsamalt wants to merge 18 commits into
get_head#5509Conversation
|
What do you think about renaming |
|
Thanks for the suggestion. I like the direction, since the function is now FFG-testing payload-status variants rather than blocks, so One thing to flag: pysetup merges same-named functions across forks but has no rename detection, so renaming in Happy to make this change if you think it's worth it, wanted to confirm the approach and the |
|
I think we can alter the spec by introducing |
|
Good call, that also sidesteps the deprecation issue since Gloas redefines these functions rather than inheriting phase0's, so a phase0-origin rename should merge cleanly. I'll rework the PR: rename |
|
Pushed the rename. |
|
I was thinking about a little bit different design:
What are your thoughts on this? |
|
Implemented as described: |
| while True: | ||
| children = get_node_children(store, blocks, head) | ||
| children = [ | ||
| child for child in get_node_children(store, head) if child in filtered_node_tree |
There was a problem hiding this comment.
I think this leads to possibility that get_head returns PENDING variant, which has an effect downstream to the consumers of this function. We should make sure this always return EMPTY or FULL
There was a problem hiding this comment.
Good point, traced it: get_head can only return PENDING if the justified root's subtree has zero viable EMPTY/FULL nodes (any PENDING node in the set has a viable EMPTY/FULL child, so only base can be returned). That can't happen for a well-formed store, but it isn't asserted anywhere. want me to add an assert to Gloas get_head?
There was a problem hiding this comment.
Every viable PENDING node has at least a viable EMPTY child because the viability in both cases is determined by a node.root. Also, store.justified_checkpoint.root block must have at least a single viable descendant with post_state.justified_checkpoint == store.justified_checkpoint in the store.
I don’t think it worth adding any additional asserts.
There was a problem hiding this comment.
To clarify, if get_filtered_node_tree is empty, under the current status quo, get_head will return EMPTY(justified) because it calls children = get_node_children(store, blocks, head) unconditionally.
The new change here
children = [
child for child in get_node_children(store, head) if child in filtered_node_tree
]
means children will be empty because filtered_node_tree is empty.
Then get_head will return ForkChoiceNode(root=store.justified_checkpoint.root) which has default payload status to PENDING.
This will have downstream effect, eg. should_build_on_full will always fail because it asserts head to not be PENDING.
I think the fix is not having assertion in get_head, but rather a soft override. Something like
if head.payload_status == PAYLOAD_STATUS_PENDING:
# Entire viable tree is empty fall back to the EMPTY variant of the justified root
head = ForkChoiceNode(root=head.root, payload_status=PAYLOAD_STATUS_EMPTY)
return head
to line 496
There was a problem hiding this comment.
Wdyt about the following mod to get_head prior to the tree traversal:
# [New in Gloas:EIP7732]
if not any(filtered_node_tree):
# Must never return a pending node
return ForkChoiceNode(
root=store.justified_checkpoint.root,
payload_status=PAYLOAD_STATUS_EMPTY
)There was a problem hiding this comment.
Yes I think this is good too
…block-tree-payload-variants
Builtin hash is no longer shadowed in the compiled spec namespace after ethereum#5555, so the dataclass-generated hash can be used.
filter_block_treeget_head returning unviable Gloas payload-status variants
get_head returning unviable Gloas payload-status variantsget_head
|
Also, please take a moment to mark resolved comment as "resolved". |
| @with_gloas_and_later | ||
| @with_presets([MINIMAL], reason="too slow") | ||
| @spec_state_test | ||
| def test_get_head_prunes_childless_unviable_full_variant(spec, state): |
There was a problem hiding this comment.
Can you add a test that's the mirror of this case?
ie. childless EMPTY variant of a block that fails FFG test and K builds on FULL(B)?
There was a problem hiding this comment.
Also add one more:
both FULL(B) and EMPTY(B) pass FFG test, both childless, see if get_filtered_node_tree contains both
|
It would be great if @potuz could take a look at this PR as he was opposing to making this change into the spec |
In Gloas,
get_node_childrenexpands aPENDINGnode into itsEMPTYandFULLpayload-status variants without consulting the filtered block tree or any FFG test (introduced in #5249). As a result, a childless variant of a block that fails the FFG test is never pruned: it can become a leaf of the LMD-GHOST walk and be returned byget_head, even though it is not viable (#5496).This PR makes
filter_block_treeoperate on payload-status variants, keyed by(root, payload_status), so that every variant is FFG-tested independently. The FFG test itself is unchanged and still computed per block root.get_node_childrennow only returns children present in the filtered block tree, so the head walk can never stop at a non-viable variant, andget_filtered_block_treeseeds the recursion from a pending node atstore.justified_checkpoint.root.Fixes #5496