Skip to content
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

chore(block-builder): a few minor cleanups #2412

Merged
merged 16 commits into from
Jan 29, 2025
Merged
39 changes: 16 additions & 23 deletions beacon/validator/block_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import (
payloadtime "github.com/berachain/beacon-kit/beacon/payload-time"
ctypes "github.com/berachain/beacon-kit/consensus-types/types"
"github.com/berachain/beacon-kit/consensus/types"
datypes "github.com/berachain/beacon-kit/da/types"
"github.com/berachain/beacon-kit/errors"
"github.com/berachain/beacon-kit/payload/builder"
"github.com/berachain/beacon-kit/primitives/bytes"
"github.com/berachain/beacon-kit/primitives/common"
"github.com/berachain/beacon-kit/primitives/crypto"
Expand All @@ -41,21 +41,19 @@ import (

// BuildBlockAndSidecars builds a new beacon block.
//
//nolint:funlen

func (s *Service) BuildBlockAndSidecars(
ctx context.Context,
slotData *types.SlotData,
) ([]byte, []byte, error) {
var (
signedBlk *ctypes.SignedBeaconBlock
blk *ctypes.BeaconBlock
sidecars datypes.BlobSidecars
forkData *ctypes.ForkData
)

startTime := time.Now()
defer s.metrics.measureRequestBlockForProposalTime(startTime)

if !s.localPayloadBuilder.Enabled() {
Copy link
Collaborator Author

@abi87 abi87 Jan 26, 2025

Choose a reason for hiding this comment

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

a minor optimization: if block building is disabled (as it should be, e.g. in full nodes) we could return early here.
This is currently only done in retrieveExecutionPayload so we partially build the block, to reject it all

Copy link
Contributor

Choose a reason for hiding this comment

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

Q: do we know if PrepareProposal is ever called for a non-validator node?

// node is not supposed to build blocks
return nil, nil, builder.ErrPayloadBuilderDisabled
}

// The goal here is to acquire a payload whose parent is the previously
// finalized block, such that, if this payload is accepted, it will be
// the next finalized block in the chain. A byproduct of this design
Expand Down Expand Up @@ -85,7 +83,7 @@ func (s *Service) BuildBlockAndSidecars(
}

// Create a new empty block from the current state.
blk, err = s.getEmptyBeaconBlockForSlot(st, blkSlot)
blk, err := s.getEmptyBeaconBlockForSlot(st, blkSlot)
if err != nil {
return nil, nil, err
}
Expand All @@ -95,9 +93,6 @@ func (s *Service) BuildBlockAndSidecars(
if err != nil {
return nil, nil, err
}
if envelope == nil {
Copy link
Collaborator Author

@abi87 abi87 Jan 25, 2025

Choose a reason for hiding this comment

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

this check is not necessary since all code path leading here do intercept the condition envelope == nil and return an error.
I added a unit test around GetPayload to show this

return nil, nil, ErrNilPayload
}

// We have to assemble the block body prior to producing the sidecars
// since we need to generate the inclusion proofs.
Expand All @@ -117,13 +112,13 @@ func (s *Service) BuildBlockAndSidecars(
}

// Craft the signature and signed beacon block.
signedBlk, err = ctypes.NewSignedBeaconBlock(blk, forkData, s.chainSpec, s.signer)
signedBlk, err := ctypes.NewSignedBeaconBlock(blk, forkData, s.chainSpec, s.signer)
if err != nil {
return nil, nil, err
}

// Produce blob sidecars with new StateRoot
sidecars, err = s.blobFactory.BuildSidecars(signedBlk, envelope.GetBlobsBundle())
sidecars, err := s.blobFactory.BuildSidecars(signedBlk, envelope.GetBlobsBundle())
if err != nil {
return nil, nil, err
}
Expand Down Expand Up @@ -221,12 +216,11 @@ func (s *Service) retrieveExecutionPayload(
// TODO: Add external block builders to this flow.
//
// Get the payload for the block.
envelope, err := s.localPayloadBuilder.
RetrievePayload(
ctx,
blk.GetSlot(),
blk.GetParentBlockRoot(),
)
envelope, err := s.localPayloadBuilder.RetrievePayload(
ctx,
blk.GetSlot(),
blk.GetParentBlockRoot(),
)
if err == nil {
return envelope, nil
}
Expand All @@ -247,8 +241,7 @@ func (s *Service) retrieveExecutionPayload(

// The latest execution payload header will be from the previous block
// during the block building phase.
var lph *ctypes.ExecutionPayloadHeader
lph, err = st.GetLatestExecutionPayloadHeader()
lph, err := st.GetLatestExecutionPayloadHeader()
if err != nil {
return nil, err
}
Expand Down
23 changes: 9 additions & 14 deletions beacon/validator/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@ type Service struct {
// Building blocks are done by submitting forkchoice updates through.
// The local Builder.
localPayloadBuilder PayloadBuilder
// remotePayloadBuilders represents a list of remote block builders, these
// builders are connected to other execution clients via the EngineAPI.
remotePayloadBuilders []PayloadBuilder
Comment on lines -53 to -55
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not sure what is this for, but not really used. Dropped

// metrics is a metrics collector.
metrics *validatorMetrics
}
Expand All @@ -67,20 +64,18 @@ func NewService(
signer crypto.BLSSigner,
blobFactory BlobFactory,
localPayloadBuilder PayloadBuilder,
remotePayloadBuilders []PayloadBuilder,
ts TelemetrySink,
) *Service {
return &Service{
cfg: cfg,
logger: logger,
sb: sb,
chainSpec: chainSpec,
signer: signer,
stateProcessor: stateProcessor,
blobFactory: blobFactory,
localPayloadBuilder: localPayloadBuilder,
remotePayloadBuilders: remotePayloadBuilders,
metrics: newValidatorMetrics(ts),
cfg: cfg,
logger: logger,
sb: sb,
chainSpec: chainSpec,
signer: signer,
stateProcessor: stateProcessor,
blobFactory: blobFactory,
localPayloadBuilder: localPayloadBuilder,
metrics: newValidatorMetrics(ts),
}
}

Expand Down
3 changes: 3 additions & 0 deletions beacon/validator/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ type ForkData[T any] interface {
// PayloadBuilder represents a service that is responsible for
// building eth1 blocks.
type PayloadBuilder interface {
// Enabled may be enabled (e.g. for validators)
// or disabled (e.g. full nodes)
Enabled() bool
// RetrievePayload retrieves the payload for the given slot.
RetrievePayload(
ctx context.Context,
Expand Down
2 changes: 2 additions & 0 deletions config/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ implementation = "{{.BeaconKit.KZG.Implementation}}"
[beacon-kit.payload-builder]
# Enabled determines if the local payload builder is enabled.
# It should be enabled for validators, but it can be disabled
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

hopefully this clarifies how this variable should be set

# for full nodes.
enabled = {{ .BeaconKit.PayloadBuilder.Enabled }}
# Post bellatrix, this address will receive the transaction fees produced by any blocks
Expand Down
11 changes: 4 additions & 7 deletions execution/client/ethclient/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,17 +142,14 @@ func (s *Client) GetPayloadV3(
) (ctypes.BuiltExecutionPayloadEnv, error) {
var t *ctypes.ExecutionPayload
result := &ctypes.ExecutionPayloadEnvelope[*engineprimitives.BlobsBundleV1[
eip4844.KZGCommitment, eip4844.KZGProof, eip4844.Blob,
eip4844.KZGCommitment,
eip4844.KZGProof,
eip4844.Blob,
]]{
ExecutionPayload: t.Empty(version.Deneb),
Copy link
Collaborator Author

@abi87 abi87 Jan 26, 2025

Choose a reason for hiding this comment

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

This is one of key point of this cleanup: ExecutionPayload is never nil, cause Empty is the default empty ExecutionPayloadEnvelope struct.
I added a UT to verify it

}

if err := s.Call(
ctx, result, GetPayloadMethodV3, payloadID,
); err != nil {
return nil, err
}
return result, nil
return result, s.Call(ctx, result, GetPayloadMethodV3, payloadID)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

nit for readability, don't feel strongly about this.
The only relevant point here is that ExecutionPayload is not nil

Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer how it was actually, think it was slightly more readable

}

/* -------------------------------------------------------------------------- */
Expand Down
3 changes: 0 additions & 3 deletions node-core/components/validator_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,6 @@ func ProvideValidatorService[
in.Signer,
in.SidecarFactory,
in.LocalBuilder,
[]validator.PayloadBuilder{
in.LocalBuilder,
},
in.TelemetrySink,
), nil
}
54 changes: 24 additions & 30 deletions payload/builder/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,27 @@ func (pb *PayloadBuilder) RequestPayloadAsync(
}

if payloadID, found := pb.pc.Get(slot, parentBlockRoot); found {
pb.logger.Warn(
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

not sure why this is warning, I don't think this is an error condition really?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

it should be for validators since if optimistic build is enabled, still we should probably revert to info

pb.logger.Info(
"aborting payload build; payload already exists in cache",
"for_slot",
slot.Base10(),
"parent_block_root",
parentBlockRoot,
"for_slot", slot.Base10(),
"parent_block_root", parentBlockRoot,
)
return &payloadID, nil
}

// Assemble the payload attributes.
attrs, err := pb.attributesFactory.
BuildPayloadAttributes(st, slot, timestamp, parentBlockRoot)
attrs, err := pb.attributesFactory.BuildPayloadAttributes(
st,
slot,
timestamp,
parentBlockRoot,
)
if err != nil {
return nil, err
}

// Submit the forkchoice update to the execution client.
var payloadID *engineprimitives.PayloadID
payloadID, _, err = pb.ee.NotifyForkchoiceUpdate(
payloadID, _, err := pb.ee.NotifyForkchoiceUpdate(
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought this would shadow the err here

ctx, &ctypes.ForkchoiceUpdateRequest{
State: &engineprimitives.ForkchoiceStateV1{
HeadBlockHash: headEth1BlockHash,
Expand Down Expand Up @@ -166,29 +167,9 @@ func (pb *PayloadBuilder) RetrievePayload(
return nil, err
}

overrideBuilder := envelope.ShouldOverrideBuilder()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

below is just stuff for logging. I consolidated it slightly

args := []any{
"for_slot", slot.Base10(),
"override_builder", overrideBuilder,
}

payload := envelope.GetExecutionPayload()
if !payload.IsNil() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this check does not make sense. Note that we use payload below, without checking if is nil

args = append(args,
"payload_block_hash", payload.GetBlockHash(),
"parent_hash", payload.GetParentHash(),
)
}

blobsBundle := envelope.GetBlobsBundle()
if blobsBundle != nil {
args = append(args, "num_blobs", len(blobsBundle.GetBlobs()))
}

pb.logger.Info("Payload retrieved from local builder", args...)

// If the payload was built by a different builder, something is
// wrong the EL<>CL setup.
payload := envelope.GetExecutionPayload()
if payload.GetFeeRecipient() != pb.cfg.SuggestedFeeRecipient {
pb.logger.Warn(
"Payload fee recipient does not match suggested fee recipient - "+
Expand All @@ -197,6 +178,19 @@ func (pb *PayloadBuilder) RetrievePayload(
"suggested_fee_recipient", pb.cfg.SuggestedFeeRecipient,
)
}

// log some data
args := []any{
"for_slot", slot.Base10(),
"override_builder", envelope.ShouldOverrideBuilder(),
"payload_block_hash", payload.GetBlockHash(),
"parent_hash", payload.GetParentHash(),
}
if blobsBundle := envelope.GetBlobsBundle(); blobsBundle != nil {
args = append(args, "num_blobs", len(blobsBundle.GetBlobs()))
}
pb.logger.Info("Payload retrieved from local builder", args...)

return envelope, err
}

Expand Down
Loading