diff --git a/cmd/block/info/output.go b/cmd/block/info/output.go index 9b1b19e..40278b8 100644 --- a/cmd/block/info/output.go +++ b/cmd/block/info/output.go @@ -18,6 +18,7 @@ import ( "context" "encoding/hex" "fmt" + "math/big" "sort" "strings" "time" @@ -25,6 +26,7 @@ import ( eth2client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/pkg/errors" "github.com/prysmaticlabs/go-bitfield" @@ -267,6 +269,96 @@ func outputBlockSyncAggregate(ctx context.Context, eth2Client eth2client.Service return res.String(), nil } +func outputBellatrixBlockText(ctx context.Context, data *dataOut, signedBlock *bellatrix.SignedBeaconBlock) (string, error) { + if signedBlock == nil { + return "", errors.New("no block supplied") + } + + body := signedBlock.Message.Body + + res := strings.Builder{} + + // General info. + blockRoot, err := signedBlock.Message.HashTreeRoot() + if err != nil { + return "", errors.Wrap(err, "failed to obtain block root") + } + bodyRoot, err := signedBlock.Message.Body.HashTreeRoot() + if err != nil { + return "", errors.Wrap(err, "failed to generate body root") + } + + tmp, err := outputBlockGeneral(ctx, + data.verbose, + signedBlock.Message.Slot, + blockRoot, + bodyRoot, + signedBlock.Message.ParentRoot, + signedBlock.Message.StateRoot, + signedBlock.Message.Body.Graffiti, + data.genesisTime, + data.slotDuration, + data.slotsPerEpoch) + if err != nil { + return "", err + } + res.WriteString(tmp) + + // Eth1 data. + if data.verbose { + tmp, err := outputBlockETH1Data(ctx, body.ETH1Data) + if err != nil { + return "", err + } + res.WriteString(tmp) + } + + // Sync aggregate. + tmp, err = outputBlockSyncAggregate(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.SyncAggregate, phase0.Epoch(uint64(signedBlock.Message.Slot)/data.slotsPerEpoch)) + if err != nil { + return "", err + } + res.WriteString(tmp) + + // Attestations. + tmp, err = outputBlockAttestations(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.Attestations) + if err != nil { + return "", err + } + res.WriteString(tmp) + + // Attester slashings. + tmp, err = outputBlockAttesterSlashings(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.AttesterSlashings) + if err != nil { + return "", err + } + res.WriteString(tmp) + + res.WriteString(fmt.Sprintf("Proposer slashings: %d\n", len(body.ProposerSlashings))) + // Add verbose proposer slashings. + + tmp, err = outputBlockDeposits(ctx, data.verbose, signedBlock.Message.Body.Deposits) + if err != nil { + return "", err + } + res.WriteString(tmp) + + // Voluntary exits. + tmp, err = outputBlockVoluntaryExits(ctx, data.eth2Client, data.verbose, signedBlock.Message.Body.VoluntaryExits) + if err != nil { + return "", err + } + res.WriteString(tmp) + + tmp, err = outputBlockExecutionPayload(ctx, data.verbose, signedBlock.Message.Body.ExecutionPayload) + if err != nil { + return "", err + } + res.WriteString(tmp) + + return res.String(), nil +} + func outputAltairBlockText(ctx context.Context, data *dataOut, signedBlock *altair.SignedBeaconBlock) (string, error) { if signedBlock == nil { return "", errors.New("no block supplied") @@ -427,6 +519,56 @@ func outputPhase0BlockText(ctx context.Context, data *dataOut, signedBlock *phas return res.String(), nil } +func outputBlockExecutionPayload(ctx context.Context, + verbose bool, + payload *bellatrix.ExecutionPayload, +) ( + string, + error, +) { + if payload == nil { + return "", nil + } + + res := strings.Builder{} + res.WriteString("Execution payload:\n") + res.WriteString(" Execution block number: ") + res.WriteString(fmt.Sprintf("%d\n", payload.BlockNumber)) + if verbose { + baseFeePerGasBEBytes := make([]byte, len(payload.BaseFeePerGas)) + for i := 0; i < 32; i++ { + baseFeePerGasBEBytes[i] = payload.BaseFeePerGas[32-1-i] + } + baseFeePerGas := new(big.Int).SetBytes(baseFeePerGasBEBytes) + res.WriteString(" Base fee per gas: ") + res.WriteString(string2eth.WeiToString(baseFeePerGas, true)) + res.WriteString("\n Block hash: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.BlockHash)) + res.WriteString(" Parent hash: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.ParentHash)) + res.WriteString(" Fee recipient: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.FeeRecipient)) + res.WriteString(" Gas limit: ") + res.WriteString(fmt.Sprintf("%d\n", payload.GasLimit)) + res.WriteString(" Gas used: ") + res.WriteString(fmt.Sprintf("%d\n", payload.GasUsed)) + res.WriteString(" Timestamp: ") + res.WriteString(fmt.Sprintf("%s (%d)\n", time.Unix(int64(payload.Timestamp), 0).String(), payload.Timestamp)) + res.WriteString(" Prev RANDAO: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.PrevRandao)) + res.WriteString(" Receipts root: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.ReceiptsRoot)) + res.WriteString(" State root: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.StateRoot)) + res.WriteString(" Extra data: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.ExtraData)) + res.WriteString(" Logs bloom: ") + res.WriteString(fmt.Sprintf("%#x\n", payload.LogsBloom)) + } + + return res.String(), nil +} + // intersection returns a list of items common between the two sets. func intersection(set1 []uint64, set2 []uint64) []phase0.ValidatorIndex { sort.Slice(set1, func(i, j int) bool { return set1[i] < set1[j] }) diff --git a/cmd/block/info/process.go b/cmd/block/info/process.go index ce646ee..aa6cfce 100644 --- a/cmd/block/info/process.go +++ b/cmd/block/info/process.go @@ -23,6 +23,7 @@ import ( api "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/pkg/errors" ) @@ -70,6 +71,10 @@ func process(ctx context.Context, data *dataIn) (*dataOut, error) { if err := outputAltairBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Altair); err != nil { return nil, errors.Wrap(err, "failed to output block") } + case spec.DataVersionBellatrix: + if err := outputBellatrixBlock(ctx, data.jsonOutput, data.sszOutput, signedBlock.Bellatrix); err != nil { + return nil, errors.Wrap(err, "failed to output block") + } default: return nil, errors.New("unknown block version") } @@ -122,6 +127,13 @@ func headEventHandler(event *api.Event) { } return } + case spec.DataVersionBellatrix: + if err := outputBellatrixBlock(context.Background(), jsonOutput, sszOutput, signedBlock.Bellatrix); err != nil { + if !jsonOutput { + fmt.Printf("Failed to output block: %v\n", err) + } + return + } default: if !jsonOutput { fmt.Printf("Unknown block version: %v\n", signedBlock.Version) @@ -171,3 +183,27 @@ func outputAltairBlock(ctx context.Context, jsonOutput bool, sszOutput bool, sig } return nil } + +func outputBellatrixBlock(ctx context.Context, jsonOutput bool, sszOutput bool, signedBlock *bellatrix.SignedBeaconBlock) 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 := outputBellatrixBlockText(ctx, results, signedBlock) + if err != nil { + return errors.Wrap(err, "failed to generate text") + } + fmt.Printf("%s\n", data) + } + return nil +} diff --git a/cmd/synccommittee/inclusion/process.go b/cmd/synccommittee/inclusion/process.go index d864a91..fc292ed 100644 --- a/cmd/synccommittee/inclusion/process.go +++ b/cmd/synccommittee/inclusion/process.go @@ -80,6 +80,13 @@ func (c *command) process(ctx context.Context) error { } else { c.inclusions = append(c.inclusions, 2) } + case spec.DataVersionBellatrix: + aggregate = block.Bellatrix.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 63fbd92..2ededb2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/OneOfOne/xxhash v1.2.5 // indirect github.com/attestantio/dirk v1.1.0 - github.com/attestantio/go-eth2-client v0.10.0 + github.com/attestantio/go-eth2-client v0.11.0 github.com/aws/aws-sdk-go v1.42.44 // indirect github.com/ferranbt/fastssz v0.0.0-20220103083642-bc5fefefa28b github.com/gofrs/uuid v4.2.0+incompatible diff --git a/go.sum b/go.sum index 896ff99..2a44729 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/attestantio/dirk v1.1.0/go.mod h1:2jkOw/XHjvIDdhDcmj+Z3kuVPpxMcQ6zxzz github.com/attestantio/go-eth2-client v0.8.1/go.mod h1:kEK9iAAOBoADO5wEkd84FEOzjT1zXgVWveQsqn+uBGg= github.com/attestantio/go-eth2-client v0.10.0 h1:nmOmzErfz4I2gEkucHKOaFwkbwD4i6JbIX38Z8Dm4Tc= github.com/attestantio/go-eth2-client v0.10.0/go.mod h1:ijuXoXJCBFMexUYaBOl8PXfZKwYUFJy7cV03TMdw8Bo= +github.com/attestantio/go-eth2-client v0.11.0 h1:8/Jn5AAfd+4tOggLi+FvOv9/ORaObECv42ab7vK2FJc= +github.com/attestantio/go-eth2-client v0.11.0/go.mod h1:zXL/BxC0cBBhxj+tP7QG7t9Ufoa8GwQLdlbvZRd9+dM= github.com/aws/aws-sdk-go v1.33.17/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.40.41/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.41.19/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= @@ -354,6 +356,8 @@ github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.0.6/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.11 h1:i2lw1Pm7Yi/4O6XCSyJWqEHI2MDw2FzUK6o/D21xn2A= +github.com/klauspost/cpuid/v2 v2.0.11/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -841,6 +845,8 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27 h1:XDXtA5hveEEV8JB2l7nhMTp3t3cHp9ZpwcdjqyEWLlo= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/services/chaintime/standard/service.go b/services/chaintime/standard/service.go index fdd80f3..40d4a62 100644 --- a/services/chaintime/standard/service.go +++ b/services/chaintime/standard/service.go @@ -32,6 +32,7 @@ type Service struct { slotsPerEpoch uint64 epochsPerSyncCommitteePeriod uint64 altairForkEpoch phase0.Epoch + bellatrixForkEpoch phase0.Epoch } // module-wide log. @@ -92,12 +93,20 @@ func New(ctx context.Context, params ...Parameter) (*Service, error) { } log.Trace().Uint64("epoch", uint64(altairForkEpoch)).Msg("Obtained Altair fork epoch") + bellatrixForkEpoch, err := fetchBellatrixForkEpoch(ctx, parameters.forkScheduleProvider) + if err != nil { + // Set to far future epoch. + bellatrixForkEpoch = 0xffffffffffffffff + } + log.Trace().Uint64("epoch", uint64(bellatrixForkEpoch)).Msg("Obtained Bellatrix fork epoch") + s := &Service{ genesisTime: genesisTime, slotDuration: slotDuration, slotsPerEpoch: slotsPerEpoch, epochsPerSyncCommitteePeriod: epochsPerSyncCommitteePeriod, altairForkEpoch: altairForkEpoch, + bellatrixForkEpoch: bellatrixForkEpoch, } return s, nil @@ -213,3 +222,28 @@ func fetchAltairForkEpoch(ctx context.Context, provider eth2client.ForkScheduleP } return 0, errors.New("no altair fork obtained") } + +// BellatrixInitialEpoch provides the epoch at which the Bellatrix hard fork takes place. +func (s *Service) BellatrixInitialEpoch() phase0.Epoch { + return s.bellatrixForkEpoch +} + +func fetchBellatrixForkEpoch(ctx context.Context, provider eth2client.ForkScheduleProvider) (phase0.Epoch, error) { + forkSchedule, err := provider.ForkSchedule(ctx) + if err != nil { + return 0, err + } + count := 0 + for i := range forkSchedule { + count++ + if bytes.Equal(forkSchedule[i].CurrentVersion[:], forkSchedule[i].PreviousVersion[:]) { + // This is the genesis fork; ignore it. + continue + } + if count == 1 { + return forkSchedule[i].Epoch, nil + } + count++ + } + return 0, errors.New("no bellatrix fork obtained") +}