-
Notifications
You must be signed in to change notification settings - Fork 214
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
Refactor block/blobs types #4491
Refactor block/blobs types #4491
Conversation
Use type system to enforce invariant that a pre-4844 block cannot have a sidecar.
0131a87
to
48288c7
Compare
If you update this to current
via #4495 |
done |
Co-authored-by: tersec <tersec@users.noreply.github.com>
let signedBlockAndBlobs = | ||
when blck is eip4844.SignedBeaconBlock: | ||
# TODO: Fetch blobs from EE | ||
eip4844.SignedBeaconBlockAndBlobsSidecar( |
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.
Similar post-EIP4844 issue as in the other PR -- this will fall back to just blck
, without the sidecar, unless someone notices specifically by auditing all the usages of eip4844.SignedBeaconBlock
or otherwise.
The exact fallout of this depends on what uses which types.
proc routeSignedBeaconBlock*(
router: ref MessageRouter, blckAndBlobs: ForkySignedBeaconBlockMaybeBlobs):
Future[RouteBlockResult] {.async.} =
Currently this PR has
ForkySignedBeaconBlockMaybeBlobs* =
phase0.SignedBeaconBlock |
altair.SignedBeaconBlock |
bellatrix.SignedBeaconBlock |
capella.SignedBeaconBlock |
eip4844.SignedBeaconBlockAndBlobsSidecar
which means that it will result in a compile-time error, which is good -- it'll alert people that this is maybe not the intended use of that function.
However, the ForkySignedBeaconBlockMaybeBlobs
type looks potentially usable as a more general-purpose function.
Maybe making it clearer, via its name or some in-line comments/documentation, that it's meant for gossip-routable and similar blocks, and it would be incorrect to add plain eip4844.SignedBeaconBlock
(or, probably, future, forks' SignedBeaconBlock
s) to it.
Then, one can safely use this when foo is eip4844.SignedBeaconBlock
construction safely.
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.
Maybe making it clearer, via its name or some in-line comments/documentation
Will add a comment. Re name, I've had a hard time coming up with a more descriptive (not to mention shorter) name. Will noodle on it some more.
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.
However, the ForkySignedBeaconBlockMaybeBlobs type looks potentially usable as a more general-purpose function.
I don't think I follow - it's a type, but could be a function - maybe you could sketch the signature of the function you have in mind?
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.
Oops, you're right, mis-typed. Not "function", but "type".
Yeah, a more general-purpose type. That is, per above, as already discussed, because it doesn't specify that it's specifically crafted around the requirements of block gossip, it's potentially tempting to reuse or extend that type for other purposes in a way which creates potential bugs for its original purpose.
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.
Similar post-EIP4844 issue as in the other PR -- this will fall back to just blck, without the sidecar, unless someone notices specifically by auditing all the usages of eip4844.SignedBeaconBlock or otherwise.
I tried addressing this with the init*
constructor approach you outlined below but... the fundamental issue is still that we do have to explicitly check here (either literally here, at L806, or in some nearby function call) if the incoming block is eip4844.SignedBeaconBlock
, in order to know that we need to fetch the corresponding blobs. To call the init*
templates below, we'd need to have already determined if this is a eip4844 block.
So I guess the solution here (at least for the specific issue of avoiding silent future failure) would be static: doAssert high(BeaconStateFork) == BeaconStateFork.EIP4844
as you indicated in the other PR.
Unless I'm missing something?
template init*(blck: phase0.SignedBeaconBlock): ForkySignedBeaconBlockMaybeBlobs =
blck
template init*(blck: altair.SignedBeaconBlock): ForkySignedBeaconBlockMaybeBlobs =
blck
template init*(blck: bellatrix.SignedBeaconBlock): ForkySignedBeaconBlockMaybeBlobs =
blck
template init*(blck: capella.SignedBeaconBlock): ForkySignedBeaconBlockMaybeBlobs =
blck
template init*(blck: eip4844.SignedBeaconBlock, blobs: eip4844.BlobsSidecar): ForkySignedBeaconBlockMaybeBlobs =
eip4844.SignedBeaconBlockAndBlobsSidecar(
beacon_block: blck,
blobs_sidecar: blobs
)
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.
Sure, then I agree that static: doAssert high(BeaconStateFork) == BeaconStateFork.EIP4844
is the best available approach.
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.
Actually - the high(BeaconStateFork) == BeaconStateFork.EIP4844
pattern is for enums, not generic types.
And returning to the root issue:
Similar post-EIP4844 issue as in the other PR -- this will fall back to just blck, without the sidecar
I think this isn't an issue here? If a new SignedBeaconBlock
type is added, the ForkySignedBeaconBlockMaybeBlobs
generic isn't extended to included it, that should be result compilation error at any place (for example, a function call) where that new SignedBeaconBlock
type is used in lieu of a ForkySignedBeaconBlockMaybeBlobs
. Conversely, if I remove one of the types from ForkySignedBeaconBlockMaybeBlobs
, I get a "type mismatch" compilation error.
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.
The high(BeaconStateFork) == BeaconStateFork.EIP4844
pattern doesn't really depend on what it's guarding -- it's a generic compile-time mechanism to flag that a particular section of code depends in some way not otherwise found in a compile-time or maybe runtime way on the particular forks known. It can be used just as readily for code based on generic types as enums, though typically when there are generic types, there are better approaches. For most specific cases, indeed, there are better methods, but it's a fallback approach.
And, yes, what you note, that if "a new SignedBeaconBlock type is added, the ForkySignedBeaconBlockMaybeBlobs generic isn't extended to included it" is true by default, if people remain aware that it's supposed to match the gossip types, not some other potentially interesting use of a type called
ForkySignedBeaconBlockMaybeBlobs`
To be a bit more explicit, the sequence of events I'm concerned about, arrayed over months/years, is:
-
this PR gets merged, without better in-line documentation (comments, naming of type, some
static: doAssert not default(eip4844.SignedBeaconBlock) is ForkySignedBeaconBlockMaybeBlobs
thing, etc) -
someone else, unaware of the very specific purpose of the shape of
ForkySignedBeaconBlockMaybeBlobs
, either (unlikely) for EIP4844, or (more likely) more-or-less mechanically adding on to this type later, doesn't know that they shouldn't add the bareSignedBeaconBlock
for EIP4844 or later, and only the with-blobs version. This will not trigger any immediate error or compilation warning -- it'll just be one more possible type that type class can match.
There's still no full bug here, but it's primed and one step away:
- then, later yet, someone changes the routing or other functions which take a
ForkySignedBeaconBlockMaybeBlobs
, and now they also (accidentally, from step 2) take a bareSignedBeaconBlock
type from an EIP48844-or-later fork, and, e.g., try to broadcast it. That's a bug. Currently, the type system will catch this category of bugs, but after this PR, it might not.
(2) and (3) are more likely to happen specifically in the next couple of hardforks after EIP4844 -- enough of them, and the pattern of only the with-blobs version would become obvious, but after the first one, it might look like just an oversight or omission.
It's not a current bug, and the code is sort of fine as it is in a narrow way, but it's a potential future maintenance hazard by weakening the type checking guarantees if someone without full understanding of a the reason/context for the code to via steps (2)/(3), maybe different people even for each, induce a bug.
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.
high(BeaconStateFork) == BeaconStateFork.EIP4844 [..] can be used just as readily for code based on generic types as enums
What's the equivalent of "high" for generic types? it must be staring at me in the eyes, but I don't see one.
(comments, naming of type, some static: doAssert not default(eip4844.SignedBeaconBlock) is ForkySignedBeaconBlockMaybeBlobs thing, etc)
Added a comment and assert in c9232d0.
Re name, the best I'm coming up with is GossipSignedBeaconBlockMaybeBlobs. That's a tad more descriptive and emphasises the gossip-oriented motivation for this type, but on the other hand there's a strong discipline here (as commented at the top of forks.nim) that generic types for dealing with forks are Forky*
, so I'm not sure why this particular case should deviate from that. Happy to change if you think it's worthwhile.
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.
Re name, the best I'm coming up with is GossipSignedBeaconBlockMaybeBlobs. That's a tad more descriptive and emphasises the gossip-oriented motivation for this type, but on the other hand there's a strong discipline here (as commented at the top of forks.nim) that generic types for dealing with forks are Forky*, so I'm not sure why this particular case should deviate from that. Happy to change if you think it's worthwhile.
Agree. With the other changes, the existing name is good.
At a high-level my takeaway given all this discussion is that this thing isn't (yet?) structured properly. I guess the question is, is it an improvement over status quo (#4454 (comment)) ? Doesn't seem like an obvious yes. |
beacon_chain/spec/forks.nim
Outdated
ForkySignedBeaconBlockMaybeBlobs* = | ||
phase0.SignedBeaconBlock | | ||
altair.SignedBeaconBlock | | ||
bellatrix.SignedBeaconBlock | | ||
capella.SignedBeaconBlock | | ||
eip4844.SignedBeaconBlockAndBlobsSidecar | ||
# This type should only contain types that are gossiped. | ||
static: doAssert not default(eip4844.SignedBeaconBlock) is ForkySignedBeaconBlockMaybeBlobs |
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 can't be put in a type
section -- everything here is implicitly type Foo
, e.g., ForkySignedBeaconBlockMaybeBlobs* = ...
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.
c9232d0 addresses this
With your latest commit (fixed by moving it outside the |
Use type system to enforce invariant that a pre-4844 block cannot have a sidecar.
This is a first step along the lines of what we discussed in #4454 (comment). This could go be extended further by changing more/all places that take a
Opt[blob]
to drop theOpt
and take aForkySignedBeaconBlockMaybeBlobs
instead (better/shorter name suggestions welcome).@tersec what do you think?