diff --git a/cmd/block/analyze/process.go b/cmd/block/analyze/process.go index b1b2204..4c72898 100644 --- a/cmd/block/analyze/process.go +++ b/cmd/block/analyze/process.go @@ -528,6 +528,13 @@ func (c *command) analyzeSyncCommittees(_ context.Context, block *spec.Versioned c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions) c.analysis.Value += c.analysis.SyncCommitee.Value return nil + case spec.DataVersionFulu: + c.analysis.SyncCommitee.Contributions = int(block.Fulu.Message.Body.SyncAggregate.SyncCommitteeBits.Count()) + c.analysis.SyncCommitee.PossibleContributions = int(block.Fulu.Message.Body.SyncAggregate.SyncCommitteeBits.Len()) + c.analysis.SyncCommitee.Score = float64(c.syncRewardWeight) / float64(c.weightDenominator) + c.analysis.SyncCommitee.Value = c.analysis.SyncCommitee.Score * float64(c.analysis.SyncCommitee.Contributions) + c.analysis.Value += c.analysis.SyncCommitee.Value + return nil default: return fmt.Errorf("unsupported block version %d", block.Version) } diff --git a/cmd/block/info/process.go b/cmd/block/info/process.go index b1a967a..14c4204 100644 --- a/cmd/block/info/process.go +++ b/cmd/block/info/process.go @@ -90,6 +90,8 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) { err = processDenebBlock(ctx, data, block) case spec.DataVersionElectra: err = processElectraBlock(ctx, data, block) + case spec.DataVersionFulu: + err = processFuluBlock(ctx, data, block) default: return nil, errors.New("unknown block version") } @@ -215,6 +217,35 @@ func processElectraBlock(ctx context.Context, return nil } +func processFuluBlock(ctx context.Context, + data *dataIn, + block *spec.VersionedSignedBeaconBlock, +) error { + var blobSidecars []*deneb.BlobSidecar + kzgCommitments, err := block.BlobKZGCommitments() + if err != nil { + return err + } + if len(kzgCommitments) > 0 { + blobSidecarsResponse, err := results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{ + Block: data.blockID, + }) + if err != nil { + var apiErr *api.Error + if errors.As(err, &apiErr) && apiErr.StatusCode != http.StatusNotFound { + return errors.Wrap(err, "failed to obtain blob sidecars") + } + } else { + blobSidecars = blobSidecarsResponse.Data + } + } + if err := outputFuluBlock(ctx, data.jsonOutput, data.sszOutput, block.Fulu, blobSidecars); err != nil { + return errors.Wrap(err, "failed to output block") + } + + return nil +} + func headEventHandler(ctx context.Context, headEvent *apiv1.HeadEvent) { blockID := fmt.Sprintf("%#x", headEvent.Block[:]) blockResponse, err := results.eth2Client.(eth2client.SignedBeaconBlockProvider).SignedBeaconBlock(ctx, &api.SignedBeaconBlockOpts{ @@ -263,6 +294,46 @@ func headEventHandler(ctx context.Context, headEvent *apiv1.HeadEvent) { blobSidecars = blobSidecarsResponse.Data } err = outputDenebBlock(context.Background(), jsonOutput, sszOutput, block.Deneb, blobSidecars) + case spec.DataVersionElectra: + var blobSidecars []*deneb.BlobSidecar + var kzgCommitments []deneb.KZGCommitment + kzgCommitments, err = block.BlobKZGCommitments() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain KZG commitments: %v\n", err) + return + } + if len(kzgCommitments) > 0 { + var blobSidecarsResponse *api.Response[[]*deneb.BlobSidecar] + blobSidecarsResponse, err = results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{ + Block: blockID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain blob sidecars: %v\n", err) + return + } + blobSidecars = blobSidecarsResponse.Data + } + err = outputElectraBlock(context.Background(), jsonOutput, sszOutput, block.Electra, blobSidecars) + case spec.DataVersionFulu: + var blobSidecars []*deneb.BlobSidecar + var kzgCommitments []deneb.KZGCommitment + kzgCommitments, err = block.BlobKZGCommitments() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain KZG commitments: %v\n", err) + return + } + if len(kzgCommitments) > 0 { + var blobSidecarsResponse *api.Response[[]*deneb.BlobSidecar] + blobSidecarsResponse, err = results.eth2Client.(eth2client.BlobSidecarsProvider).BlobSidecars(ctx, &api.BlobSidecarsOpts{ + Block: blockID, + }) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to obtain blob sidecars: %v\n", err) + return + } + blobSidecars = blobSidecarsResponse.Data + } + err = outputFuluBlock(context.Background(), jsonOutput, sszOutput, block.Fulu, blobSidecars) default: err = errors.New("unknown block version") } @@ -424,6 +495,35 @@ func outputElectraBlock(ctx context.Context, return nil } +func outputFuluBlock(ctx context.Context, + jsonOutput bool, + sszOutput bool, + signedBlock *electra.SignedBeaconBlock, + blobs []*deneb.BlobSidecar, +) error { + switch { + case jsonOutput: + data, err := json.Marshal(signedBlock) + if err != nil { + return errors.Wrap(err, "failed to generate JSON") + } + fmt.Printf("%s\n", string(data)) + case sszOutput: + data, err := signedBlock.MarshalSSZ() + if err != nil { + return errors.Wrap(err, "failed to generate SSZ") + } + fmt.Printf("%x\n", data) + default: + data, err := outputElectraBlockText(ctx, results, signedBlock, blobs) + if err != nil { + return errors.Wrap(err, "failed to generate text") + } + fmt.Print(data) + } + return nil +} + func timeToBlockID(ctx context.Context, eth2Client eth2client.Service, input string) (string, error) { var timestamp time.Time diff --git a/cmd/chain/eth1votes/process.go b/cmd/chain/eth1votes/process.go index 2daff8c..caeae5d 100644 --- a/cmd/chain/eth1votes/process.go +++ b/cmd/chain/eth1votes/process.go @@ -100,6 +100,9 @@ func (c *command) process(ctx context.Context) error { case spec.DataVersionElectra: c.incumbent = state.Electra.ETH1Data c.eth1DataVotes = state.Electra.ETH1DataVotes + case spec.DataVersionFulu: + c.incumbent = state.Fulu.ETH1Data + c.eth1DataVotes = state.Fulu.ETH1DataVotes default: return fmt.Errorf("unhandled beacon state version %v", state.Version) } diff --git a/cmd/epoch/summary/process.go b/cmd/epoch/summary/process.go index 4f4255a..ef0caa2 100644 --- a/cmd/epoch/summary/process.go +++ b/cmd/epoch/summary/process.go @@ -529,6 +529,8 @@ func (c *command) processBlobs(ctx context.Context) error { c.summary.Blobs += len(block.Deneb.Message.Body.BlobKZGCommitments) case spec.DataVersionElectra: c.summary.Blobs += len(block.Electra.Message.Body.BlobKZGCommitments) + case spec.DataVersionFulu: + c.summary.Blobs += len(block.Fulu.Message.Body.BlobKZGCommitments) default: return fmt.Errorf("unhandled block version %v", block.Version) } diff --git a/cmd/synccommittee/inclusion/process.go b/cmd/synccommittee/inclusion/process.go index b9c071f..122fd3a 100644 --- a/cmd/synccommittee/inclusion/process.go +++ b/cmd/synccommittee/inclusion/process.go @@ -127,6 +127,13 @@ func (c *command) process(ctx context.Context) error { } else { c.inclusions = append(c.inclusions, 2) } + case spec.DataVersionFulu: + aggregate = block.Fulu.Message.Body.SyncAggregate + if aggregate.SyncCommitteeBits.BitAt(c.committeeIndex) { + c.inclusions = append(c.inclusions, 1) + } else { + c.inclusions = append(c.inclusions, 2) + } default: return fmt.Errorf("unhandled block version %v", block.Version) } diff --git a/go.mod b/go.mod index 38d5cb6..f0cd2fc 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.23.0 toolchain go1.23.2 require ( - github.com/attestantio/go-eth2-client v0.26.0 + github.com/attestantio/go-eth2-client v0.26.1-0.20250721122214-dc2928832acc github.com/ferranbt/fastssz v0.1.4 github.com/gofrs/uuid v4.4.0+incompatible github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index a6552f7..25b7f00 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/attestantio/go-eth2-client v0.26.0 h1:oDWKvIUJfvr1EBi/w9L6mawYZHOCymjHkml7fZplT20= github.com/attestantio/go-eth2-client v0.26.0/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= +github.com/attestantio/go-eth2-client v0.26.1-0.20250721122214-dc2928832acc h1:rLrx/o7WeRqmE9iXvhyBMDrugOPZRhNBLQD/4XbG6hU= +github.com/attestantio/go-eth2-client v0.26.1-0.20250721122214-dc2928832acc/go.mod h1:fvULSL9WtNskkOB4i+Yyr6BKpNHXvmpGZj9969fCrfY= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=