Skip to content

fix: Parse blocks sent by builder with fork version.#63

Merged
bharath-123 merged 9 commits intobharath/fulufrom
bharath/fulu-payload-parsing
Aug 6, 2025
Merged

fix: Parse blocks sent by builder with fork version.#63
bharath-123 merged 9 commits intobharath/fulufrom
bharath/fulu-payload-parsing

Conversation

@bharath-123
Copy link
Owner

@bharath-123 bharath-123 commented Jul 31, 2025

📝 Summary

Make parsing of blocks sent via SubmitNewBlocks to VersionedSubmitBlockRequest dependent on fork version.

⛱ Motivation and Context

When receiving blocks from the builder via SubmitNewBlocks API, we parse the block to its corresponding fork specific VersionedSubmitBlockRequest. With the Fusaka hard fork, the VersionedSubmitBlockRequest for Fulu and Electra are the same since the only change is the addition of cell_proofs which are just extended blob proofs. The current way of unmarshalling the payload unmarshalls an Electra VersionedSubmitBlockRequest to a Fulu one.

To avoid such incidents, we want to parse the incoming block specifc to the fork.

The builder doesn't send the Eth-Consensus-Version like the proposers do, so we need to infer the fork version to parse the VersionedSubmitBlockRequest. To do this, we parse the value of the slot from the raw payload bytes. Based on the slot, we parse the payload according to the corresponding fork version.

✅ I have run these commands

  • make lint
  • make test-race
  • go mod tidy
  • I have seen and agree to CONTRIBUTING.md

Comment on lines +2121 to +2132
func (api *RelayAPI) getForkFromSlot(slot uint64) spec.DataVersion {
if api.isFulu(slot) {
return spec.DataVersionFulu
} else if api.isElectra(slot) {
return spec.DataVersionElectra
} else if api.isDeneb(slot) {
return spec.DataVersionDeneb
} else if api.isCapella(slot) {
return spec.DataVersionCapella
}
return spec.DataVersionPhase0
}
Copy link

Choose a reason for hiding this comment

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

This would be a little cleaner as a switch:

func (api *RelayAPI) getForkFromSlot(slot uint64) spec.DataVersion {
	switch {
	case api.isFulu(slot):
		return spec.DataVersionFulu
	case api.isElectra(slot):
		return spec.DataVersionElectra
	case api.isDeneb(slot):
		return spec.DataVersionDeneb
	case api.isCapella(slot):
		return spec.DataVersionCapella
	default:
		return spec.DataVersionPhase0
	}
}

Comment on lines +2191 to +2210
if contentType == ApplicationOctetStream {
if contentType == "application/octet-stream" {
Copy link

Choose a reason for hiding this comment

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

Can we revert this change?

Comment on lines +2216 to 2234
builderEthConsensusVersion := req.Header.Get(HeaderEthConsensusVersion)
if builderEthConsensusVersion == "" {
// don't reject a builder submission if the Eth-Consensus-Version header is not present
builderEthConsensusVersion = api.getForkFromSlot(latestSlot).String()
log.Warnf("builder did not send the Eth-Consensus-Version header, using latestSlot %d to infer the fork version: %s", latestSlot, builderEthConsensusVersion)
}
Copy link

Choose a reason for hiding this comment

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

Instead of using the latest slot, I think we should try to use SubmitBlockRequest.message.slot (where message is BidTrace). This should solve this fork boundary issue; where a builder submits a Fulu block while latestSlot is still in Electra. This idea would require partial decoding, should be possible though.

Copy link

Choose a reason for hiding this comment

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

Image

Copy link
Owner Author

Choose a reason for hiding this comment

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

ya ideally latestSlot would never be in electra, since payload attributes ideally always reflects the latest slot. But partially decoding the payload could be much more accurate.

@bharath-123 bharath-123 force-pushed the bharath/fulu-payload-parsing branch from 6e87b09 to 9a51a31 Compare August 4, 2025 09:24
// SSZ decoding failed. try JSON as fallback (some builders used octet-stream for json before)
if err2 := json.Unmarshal(requestPayloadBytes, payload); err2 != nil {
log.WithError(fmt.Errorf("%w / %w", err, err2)).Warn("could not decode payload - SSZ or JSON")
builderEthConsensusVersion := req.Header.Get(HeaderEthConsensusVersion)
Copy link
Owner Author

Choose a reason for hiding this comment

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

I am contemplating whether we should even have this header since we already have the fallback logic?

Copy link

Choose a reason for hiding this comment

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

Yeah honestly might not even be necessary for submitNewBlock... Might prevent a bug though so I'd be in favor of keeping it. Eg a builder forgets to update their client and continues to build electra payloads in fulu.

@bharath-123
Copy link
Owner Author

Tests are failing because the new submitBlock json payloads which are added can't be found in the CI run for some reason? And the lint is failing due to some dynamic return types. will look into them.

case api.isCapella(slot):
return spec.DataVersionCapella
default:
return spec.DataVersionPhase0
Copy link

Choose a reason for hiding this comment

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

I think I missed this before. This should be bellatrix.

@bharath-123 bharath-123 force-pushed the bharath/fulu-payload-parsing branch from b2fb3a0 to d06635e Compare August 4, 2025 16:25
}

func (r *VersionedSubmitBlockRequest) UnmarshalWithVersion(input []byte, contentType, ethConsensusVersion string) error {
if contentType == "application/octet-stream" {
Copy link

@faheelsattar faheelsattar Aug 5, 2025

Choose a reason for hiding this comment

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

i think it will be better to replace it with ApplicationOctetStream variable? We can move it from service to constants inside the common package so it resolves in other places as well

Copy link
Owner Author

Choose a reason for hiding this comment

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

ya thats a good idea

}

func getSlotFromBuilderSSZPayload(input []byte) (uint64, error) {
if len(input) < 4 {

Choose a reason for hiding this comment

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

shouldn't this be if len(input) < 8 since it might cause a panic when accessing input[4:8]

Copy link
Owner Author

Choose a reason for hiding this comment

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

ya you are right. the check should be there.


messageOffset := binary.LittleEndian.Uint32(input[4:8])

slot := binary.LittleEndian.Uint64(input[messageOffset : messageOffset+8])

Choose a reason for hiding this comment

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

probably also need to add a check here before accessing
if messageOffset + 8 > len(input) throw error

return
slot, err := getSlotFromBuilderJSONPayload(requestPayloadBytes)
if err != nil {
log.WithError(err).Warn("could not get slot from builder ssz payload")
Copy link

Choose a reason for hiding this comment

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

These error messages should be swapped right?

Copy link
Owner Author

Choose a reason for hiding this comment

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

good catch! will do it

Copy link

@jtraglia jtraglia left a comment

Choose a reason for hiding this comment

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

LGTM but you need to fix the CI checks.

@bharath-123 bharath-123 force-pushed the bharath/fulu-payload-parsing branch from 064e0ec to a2ad020 Compare August 6, 2025 15:55
@bharath-123 bharath-123 merged commit a657181 into bharath/fulu Aug 6, 2025
1 of 2 checks passed
@bharath-123 bharath-123 deleted the bharath/fulu-payload-parsing branch August 6, 2025 16:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants