Skip to content

Conversation

@ceyonur
Copy link
Collaborator

@ceyonur ceyonur commented May 23, 2025

Why this should be merged

Seperates atomic block verification, accept, reject from block by introducing a general block extender/extension interface

How this works

This pull request introduces significant changes to the EVM plugin to support block extensions, refactor block verification, and improve atomic transaction handling. The most notable updates include replacing the snowman.Block interface with a new extension.ExtendedBlock, introducing a new blockExtension for handling block extensions. These changes aim to enhance modularity, maintainability, and support for future upgrades.

  • Added a new blockExtension implementation in plugin/evm/atomic_block_extension.go to handle block extensions, including atomic transaction extraction, syntactic/semantic verification, and UTXO validation.

These changes pave the way for a more extensible and robust EVM implementation, aligning with upcoming protocol upgrades and improving the handling of atomic transactions.

How this was tested

Existing UTs/e2e tests

Need to be documented?

no

Need to update RELEASES.md?

No

@ceyonur ceyonur changed the base branch from master to atomic-sync-pkg May 23, 2025 15:37
@ceyonur ceyonur marked this pull request as ready for review May 27, 2025 20:58
@ceyonur ceyonur requested a review from a team as a code owner May 27, 2025 20:58
if atomicState, err := b.vm.atomicBackend.GetVerifiedAtomicState(b.ethBlock.Hash()); err == nil {
_ = atomicState.Reject() // ignore this error so we can return the original error instead.
}
if b.extension != nil && (err != nil || !writes) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a comment here about what might be cleaned up would be helpful.

Copy link
Collaborator

@ARR4N ARR4N Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same outcome can be achieved with a slight refactor, which IMO is preferable to a comment. Comments should be reserved for why, not how nor what. "How" and "what" can be communicated in code, and if they're not then it suggests that the code needs to be changed. "Why" can't be communicated in code, so it requires a comment.

if noWrite := err != nil || !writes; noWrite && b.extension != nil {
  b.extension.CleanupVerified()
}

return err
}

// semanticVerify verifies that a *Block is internally consistent.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this section could still go in another file. It's relatively tangential to the operations here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

semanticVerify does not do much other than calling the extension. I find it a bit weird to split a type's functions into different files unless it leads to a gigantic file (like VM)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weren't these just moved from block_verification.go into here and the methods renamed? Or were they changed beyond that? The move adds a significant amount of unnecessary review.

Base automatically changed from atomic-sync-pkg to master June 3, 2025 22:44
@ceyonur ceyonur force-pushed the atomic-block-extension branch from 2ddb618 to d477ea2 Compare June 4, 2025 09:01
@ceyonur ceyonur enabled auto-merge June 4, 2025 23:57
@ceyonur ceyonur added this pull request to the merge queue Jun 4, 2025
Merged via the queue into master with commit 3d7a2c8 Jun 5, 2025
9 checks passed
@ceyonur ceyonur deleted the atomic-block-extension branch June 5, 2025 00:10
@ARR4N
Copy link
Collaborator

ARR4N commented Jun 5, 2025

This was merged while I was still reviewing (sorry it's taken so long) as I'm trying to get my head around the ultimate goal of block extensions. Are there use cases beyond adding atomic transactions?

Abstractions add complexity (through indirection) and a requirement to maintain 2x the amount of code (implementation and interface definition), which I think are only justified when there is a need for more than one implementation.

EDIT: to clarify, I like the abstraction of the atomic functionality as standalone methods, and think it simplifies things for SAE integration. What I'm still concerned about is possibly premature abstraction and YAGNI.


// Block implements the snowman.Block interface
type Block struct {
// wrappedBlock implements the snowman.wrappedBlock interface
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

snowman.Block interface

// Block implements the snowman.Block interface
type Block struct {
// wrappedBlock implements the snowman.wrappedBlock interface
type wrappedBlock struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes this a wrapped block? If I understand correctly it's actually wrapping something (the extension), not being wrapped. What am I misunderstanding?

vm: vm,
atomicTxs: atomicTxs,
}, nil
if vm.extensionConfig.BlockExtender != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(subjective, readability) I would return early if it is nil, signalling to the reader what they need to remember and concentrate on:

extender := vm.extensionConfig.BlockExtender
if extender == nil {
  return b, nil
}

ext, err := extender.NewBlockExtension(b)
if err != nil {
  return nil, ...
}
b.extension = ext
return b, nil

Extensive nesting adds complexity that adds cognitive load. Effectively our brains have to keep a stack for which every level of indentation is a push. The shorter the stack, the easier it is for the reader.

if atomicState, err := b.vm.atomicBackend.GetVerifiedAtomicState(b.ethBlock.Hash()); err == nil {
_ = atomicState.Reject() // ignore this error so we can return the original error instead.
}
if b.extension != nil && (err != nil || !writes) {
Copy link
Collaborator

@ARR4N ARR4N Jun 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same outcome can be achieved with a slight refactor, which IMO is preferable to a comment. Comments should be reserved for why, not how nor what. "How" and "what" can be communicated in code, and if they're not then it suggests that the code needs to be changed. "Why" can't be communicated in code, so it requires a comment.

if noWrite := err != nil || !writes; noWrite && b.extension != nil {
  b.extension.CleanupVerified()
}

return err
}

// semanticVerify verifies that a *Block is internally consistent.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weren't these just moved from block_verification.go into here and the methods renamed? Or were they changed beyond that? The move adds a significant amount of unnecessary review.

blockExtension, ok := wrappedBlock.GetBlockExtension().(atomic.AtomicBlockContext)
assert.True(ok, "unknown block extension type")

assert.Equal(txID, blockExtension.AtomicTxs()[0].ID(), "block does not include expected transaction")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only check the first one?

extDataHashes = fujiExtDataHashes
}
vm.syntacticBlockValidator = NewBlockValidator(extDataHashes)
vm.extensionConfig.BlockExtender = newBlockExtender(extDataHashes, vm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coding the specific concrete implementation of an abstraction suggests the premature abstraction I was concerned about in my overall PR comment.


// Note: the status of block is set by ChainState
blk, err := vm.newBlock(block)
blk, err := wrapBlock(block, vm)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed from a method to a function that accepts the original method's receiver value anyway?

return nil, err
}

return blk.(*wrappedBlock), nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't safe in production code as it could panic.

It's also another instance of a hard-coded concrete implementation of the abstraction.

if lastAcceptedBlock == nil {
return nil
}
return lastAcceptedBlock.(*wrappedBlock)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants