diff --git a/api/v1/electra/blindedbeaconblock.go b/api/v1/electra/blindedbeaconblock.go new file mode 100644 index 00000000..7cf5e324 --- /dev/null +++ b/api/v1/electra/blindedbeaconblock.go @@ -0,0 +1,40 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// BlindedBeaconBlock represents a blinded beacon block. +type BlindedBeaconBlock struct { + Slot phase0.Slot + ProposerIndex phase0.ValidatorIndex + ParentRoot phase0.Root `ssz-size:"32"` + StateRoot phase0.Root `ssz-size:"32"` + Body *BlindedBeaconBlockBody +} + +// String returns a string version of the structure. +func (b *BlindedBeaconBlock) String() string { + data, err := yaml.Marshal(b) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/api/v1/electra/blindedbeaconblock_json.go b/api/v1/electra/blindedbeaconblock_json.go new file mode 100644 index 00000000..45851938 --- /dev/null +++ b/api/v1/electra/blindedbeaconblock_json.go @@ -0,0 +1,102 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// blindedBeaconBlockJSON is the spec representation of the struct. +type blindedBeaconBlockJSON struct { + Slot string `json:"slot"` + ProposerIndex string `json:"proposer_index"` + ParentRoot string `json:"parent_root"` + StateRoot string `json:"state_root"` + Body *BlindedBeaconBlockBody `json:"body"` +} + +// MarshalJSON implements json.Marshaler. +func (b *BlindedBeaconBlock) MarshalJSON() ([]byte, error) { + return json.Marshal(&blindedBeaconBlockJSON{ + Slot: fmt.Sprintf("%d", b.Slot), + ProposerIndex: fmt.Sprintf("%d", b.ProposerIndex), + ParentRoot: fmt.Sprintf("%#x", b.ParentRoot), + StateRoot: fmt.Sprintf("%#x", b.StateRoot), + Body: b.Body, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *BlindedBeaconBlock) UnmarshalJSON(input []byte) error { + var data blindedBeaconBlockJSON + if err := json.Unmarshal(input, &data); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return b.unpack(&data) +} + +func (b *BlindedBeaconBlock) unpack(data *blindedBeaconBlockJSON) error { + if data.Slot == "" { + return errors.New("slot missing") + } + slot, err := strconv.ParseUint(data.Slot, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for slot") + } + b.Slot = phase0.Slot(slot) + if data.ProposerIndex == "" { + return errors.New("proposer index missing") + } + proposerIndex, err := strconv.ParseUint(data.ProposerIndex, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for proposer index") + } + b.ProposerIndex = phase0.ValidatorIndex(proposerIndex) + if data.ParentRoot == "" { + return errors.New("parent root missing") + } + parentRoot, err := hex.DecodeString(strings.TrimPrefix(data.ParentRoot, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for parent root") + } + if len(parentRoot) != phase0.RootLength { + return errors.New("incorrect length for parent root") + } + copy(b.ParentRoot[:], parentRoot) + if data.StateRoot == "" { + return errors.New("state root missing") + } + stateRoot, err := hex.DecodeString(strings.TrimPrefix(data.StateRoot, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for state root") + } + if len(stateRoot) != phase0.RootLength { + return errors.New("incorrect length for state root") + } + copy(b.StateRoot[:], stateRoot) + if data.Body == nil { + return errors.New("body missing") + } + b.Body = data.Body + + return nil +} diff --git a/api/v1/electra/blindedbeaconblock_ssz.go b/api/v1/electra/blindedbeaconblock_ssz.go new file mode 100644 index 00000000..e28f63a6 --- /dev/null +++ b/api/v1/electra/blindedbeaconblock_ssz.go @@ -0,0 +1,139 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 449c9e7b5e2698820a83b7fa31c569b206b83e7d5fc608ac39e5c95530999882 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the BlindedBeaconBlock object +func (b *BlindedBeaconBlock) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BlindedBeaconBlock object to a target array +func (b *BlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(84) + + // Field (0) 'Slot' + dst = ssz.MarshalUint64(dst, uint64(b.Slot)) + + // Field (1) 'ProposerIndex' + dst = ssz.MarshalUint64(dst, uint64(b.ProposerIndex)) + + // Field (2) 'ParentRoot' + dst = append(dst, b.ParentRoot[:]...) + + // Field (3) 'StateRoot' + dst = append(dst, b.StateRoot[:]...) + + // Offset (4) 'Body' + dst = ssz.WriteOffset(dst, offset) + if b.Body == nil { + b.Body = new(BlindedBeaconBlockBody) + } + offset += b.Body.SizeSSZ() + + // Field (4) 'Body' + if dst, err = b.Body.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BlindedBeaconBlock object +func (b *BlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 84 { + return ssz.ErrSize + } + + tail := buf + var o4 uint64 + + // Field (0) 'Slot' + b.Slot = phase0.Slot(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'ProposerIndex' + b.ProposerIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[8:16])) + + // Field (2) 'ParentRoot' + copy(b.ParentRoot[:], buf[16:48]) + + // Field (3) 'StateRoot' + copy(b.StateRoot[:], buf[48:80]) + + // Offset (4) 'Body' + if o4 = ssz.ReadOffset(buf[80:84]); o4 > size { + return ssz.ErrOffset + } + + if o4 < 84 { + return ssz.ErrInvalidVariableOffset + } + + // Field (4) 'Body' + { + buf = tail[o4:] + if b.Body == nil { + b.Body = new(BlindedBeaconBlockBody) + } + if err = b.Body.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BlindedBeaconBlock object +func (b *BlindedBeaconBlock) SizeSSZ() (size int) { + size = 84 + + // Field (4) 'Body' + if b.Body == nil { + b.Body = new(BlindedBeaconBlockBody) + } + size += b.Body.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the BlindedBeaconBlock object +func (b *BlindedBeaconBlock) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BlindedBeaconBlock object with a hasher +func (b *BlindedBeaconBlock) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Slot' + hh.PutUint64(uint64(b.Slot)) + + // Field (1) 'ProposerIndex' + hh.PutUint64(uint64(b.ProposerIndex)) + + // Field (2) 'ParentRoot' + hh.PutBytes(b.ParentRoot[:]) + + // Field (3) 'StateRoot' + hh.PutBytes(b.StateRoot[:]) + + // Field (4) 'Body' + if err = b.Body.HashTreeRootWith(hh); err != nil { + return + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the BlindedBeaconBlock object +func (b *BlindedBeaconBlock) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} diff --git a/api/v1/electra/blindedbeaconblock_yaml.go b/api/v1/electra/blindedbeaconblock_yaml.go new file mode 100644 index 00000000..27a6f01f --- /dev/null +++ b/api/v1/electra/blindedbeaconblock_yaml.go @@ -0,0 +1,57 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "fmt" + + "github.com/goccy/go-yaml" +) + +// blindedBeaconBlockYAML is the spec representation of the struct. +type blindedBeaconBlockYAML struct { + Slot uint64 `yaml:"slot"` + ProposerIndex uint64 `yaml:"proposer_index"` + ParentRoot string `yaml:"parent_root"` + StateRoot string `yaml:"state_root"` + Body *BlindedBeaconBlockBody `yaml:"body"` +} + +// MarshalYAML implements yaml.Marshaler. +func (b *BlindedBeaconBlock) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&blindedBeaconBlockYAML{ + Slot: uint64(b.Slot), + ProposerIndex: uint64(b.ProposerIndex), + ParentRoot: fmt.Sprintf("%#x", b.ParentRoot), + StateRoot: fmt.Sprintf("%#x", b.StateRoot), + Body: b.Body, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (b *BlindedBeaconBlock) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var data blindedBeaconBlockJSON + if err := yaml.Unmarshal(input, &data); err != nil { + return err + } + + return b.unpack(&data) +} diff --git a/api/v1/electra/blindedbeaconblockbody.go b/api/v1/electra/blindedbeaconblockbody.go new file mode 100644 index 00000000..febc8d08 --- /dev/null +++ b/api/v1/electra/blindedbeaconblockbody.go @@ -0,0 +1,53 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// BlindedBeaconBlockBody represents the body of a blinded beacon block. +type BlindedBeaconBlockBody struct { + RANDAOReveal phase0.BLSSignature `ssz-size:"96"` + ETH1Data *phase0.ETH1Data + Graffiti [32]byte `ssz-size:"32"` + ProposerSlashings []*phase0.ProposerSlashing `ssz-max:"16"` + AttesterSlashings []*phase0.AttesterSlashing `ssz-max:"1"` + Attestations []*electra.Attestation `ssz-max:"8"` + Deposits []*phase0.Deposit `ssz-max:"16"` + VoluntaryExits []*phase0.SignedVoluntaryExit `ssz-max:"16"` + SyncAggregate *altair.SyncAggregate + ExecutionPayloadHeader *electra.ExecutionPayloadHeader + BLSToExecutionChanges []*capella.SignedBLSToExecutionChange `ssz-max:"16"` + BlobKZGCommitments []deneb.KZGCommitment `ssz-max:"4096" ssz-size:"?,48"` + Consolidations []*electra.SignedConsolidation `ssz-max:"16"` +} + +// String returns a string version of the structure. +func (b *BlindedBeaconBlockBody) String() string { + data, err := yaml.Marshal(b) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/api/v1/electra/blindedbeaconblockbody_json.go b/api/v1/electra/blindedbeaconblockbody_json.go new file mode 100644 index 00000000..aab420df --- /dev/null +++ b/api/v1/electra/blindedbeaconblockbody_json.go @@ -0,0 +1,203 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// blindedBeaconBlockBodyJSON is the spec representation of the struct. +type blindedBeaconBlockBodyJSON struct { + RANDAOReveal string `json:"randao_reveal"` + ETH1Data *phase0.ETH1Data `json:"eth1_data"` + Graffiti string `json:"graffiti"` + ProposerSlashings []*phase0.ProposerSlashing `json:"proposer_slashings"` + AttesterSlashings []*phase0.AttesterSlashing `json:"attester_slashings"` + Attestations []*electra.Attestation `json:"attestations"` + Deposits []*phase0.Deposit `json:"deposits"` + VoluntaryExits []*phase0.SignedVoluntaryExit `json:"voluntary_exits"` + SyncAggregate *altair.SyncAggregate `json:"sync_aggregate"` + ExecutionPayloadHeader *electra.ExecutionPayloadHeader `json:"execution_payload_header"` + BLSToExecutionChanges []*capella.SignedBLSToExecutionChange `json:"bls_to_execution_changes"` + BlobKZGCommitments []string `json:"blob_kzg_commitments"` + Consolidations []*electra.SignedConsolidation `json:"consolidations"` +} + +// MarshalJSON implements json.Marshaler. +func (b *BlindedBeaconBlockBody) MarshalJSON() ([]byte, error) { + blobKZGCommitments := make([]string, len(b.BlobKZGCommitments)) + for i := range b.BlobKZGCommitments { + blobKZGCommitments[i] = b.BlobKZGCommitments[i].String() + } + + return json.Marshal(&blindedBeaconBlockBodyJSON{ + RANDAOReveal: fmt.Sprintf("%#x", b.RANDAOReveal), + ETH1Data: b.ETH1Data, + Graffiti: fmt.Sprintf("%#x", b.Graffiti), + ProposerSlashings: b.ProposerSlashings, + AttesterSlashings: b.AttesterSlashings, + Attestations: b.Attestations, + Deposits: b.Deposits, + VoluntaryExits: b.VoluntaryExits, + SyncAggregate: b.SyncAggregate, + ExecutionPayloadHeader: b.ExecutionPayloadHeader, + BLSToExecutionChanges: b.BLSToExecutionChanges, + BlobKZGCommitments: blobKZGCommitments, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *BlindedBeaconBlockBody) UnmarshalJSON(input []byte) error { + var data blindedBeaconBlockBodyJSON + if err := json.Unmarshal(input, &data); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return b.unpack(&data) +} + +//nolint:gocyclo +func (b *BlindedBeaconBlockBody) unpack(data *blindedBeaconBlockBodyJSON) error { + if data.RANDAOReveal == "" { + return errors.New("RANDAO reveal missing") + } + randaoReveal, err := hex.DecodeString(strings.TrimPrefix(data.RANDAOReveal, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for RANDAO reveal") + } + if len(randaoReveal) != phase0.SignatureLength { + return errors.New("incorrect length for RANDAO reveal") + } + copy(b.RANDAOReveal[:], randaoReveal) + if data.ETH1Data == nil { + return errors.New("ETH1 data missing") + } + b.ETH1Data = data.ETH1Data + if data.Graffiti == "" { + return errors.New("graffiti missing") + } + graffiti, err := hex.DecodeString(strings.TrimPrefix(data.Graffiti, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for graffiti") + } + if len(graffiti) != phase0.GraffitiLength { + return errors.New("incorrect length for graffiti") + } + copy(b.Graffiti[:], graffiti) + if data.ProposerSlashings == nil { + return errors.New("proposer slashings missing") + } + for i := range data.ProposerSlashings { + if data.ProposerSlashings[i] == nil { + return fmt.Errorf("proposer slashings entry %d missing", i) + } + } + b.ProposerSlashings = data.ProposerSlashings + if data.AttesterSlashings == nil { + return errors.New("attester slashings missing") + } + for i := range data.AttesterSlashings { + if data.AttesterSlashings[i] == nil { + return fmt.Errorf("attester slashings entry %d missing", i) + } + } + b.AttesterSlashings = data.AttesterSlashings + if data.Attestations == nil { + return errors.New("attestations missing") + } + for i := range data.Attestations { + if data.Attestations[i] == nil { + return fmt.Errorf("attestations entry %d missing", i) + } + } + b.Attestations = data.Attestations + if data.Deposits == nil { + return errors.New("deposits missing") + } + for i := range data.Deposits { + if data.Deposits[i] == nil { + return fmt.Errorf("deposits entry %d missing", i) + } + } + b.Deposits = data.Deposits + if data.VoluntaryExits == nil { + return errors.New("voluntary exits missing") + } + for i := range data.VoluntaryExits { + if data.VoluntaryExits[i] == nil { + return fmt.Errorf("voluntary exits entry %d missing", i) + } + } + b.VoluntaryExits = data.VoluntaryExits + if data.SyncAggregate == nil { + return errors.New("sync aggregate missing") + } + b.SyncAggregate = data.SyncAggregate + if data.ExecutionPayloadHeader == nil { + return errors.New("execution payload header missing") + } + b.ExecutionPayloadHeader = data.ExecutionPayloadHeader + if data.BLSToExecutionChanges == nil { + b.BLSToExecutionChanges = make([]*capella.SignedBLSToExecutionChange, 0) + } else { + for i := range data.BLSToExecutionChanges { + if data.BLSToExecutionChanges[i] == nil { + return fmt.Errorf("bls to execution changes entry %d missing", i) + } + } + b.BLSToExecutionChanges = data.BLSToExecutionChanges + } + if data.BlobKZGCommitments == nil { + return errors.New("blob KZG commitments missing") + } + for i := range data.BlobKZGCommitments { + if data.BlobKZGCommitments[i] == "" { + return fmt.Errorf("blob KZG commitments entry %d missing", i) + } + } + b.BlobKZGCommitments = make([]deneb.KZGCommitment, len(data.BlobKZGCommitments)) + for i := range data.BlobKZGCommitments { + data, err := hex.DecodeString(strings.TrimPrefix(data.BlobKZGCommitments[i], "0x")) + if err != nil { + return errors.Wrap(err, "failed to parse blob KZG commitment") + } + if len(data) != deneb.KZGCommitmentLength { + return errors.New("incorrect length for blob KZG commitment") + } + copy(b.BlobKZGCommitments[i][:], data) + } + if data.Consolidations == nil { + b.Consolidations = make([]*electra.SignedConsolidation, 0) + } else { + for i := range data.Consolidations { + if data.Consolidations[i] == nil { + return fmt.Errorf("consolidations entry %d missing", i) + } + } + b.Consolidations = data.Consolidations + } + + return nil +} diff --git a/api/v1/electra/blindedbeaconblockbody_ssz.go b/api/v1/electra/blindedbeaconblockbody_ssz.go new file mode 100644 index 00000000..ac229e77 --- /dev/null +++ b/api/v1/electra/blindedbeaconblockbody_ssz.go @@ -0,0 +1,654 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 449c9e7b5e2698820a83b7fa31c569b206b83e7d5fc608ac39e5c95530999882 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the BlindedBeaconBlockBody object +func (b *BlindedBeaconBlockBody) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BlindedBeaconBlockBody object to a target array +func (b *BlindedBeaconBlockBody) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(396) + + // Field (0) 'RANDAOReveal' + dst = append(dst, b.RANDAOReveal[:]...) + + // Field (1) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if dst, err = b.ETH1Data.MarshalSSZTo(dst); err != nil { + return + } + + // Field (2) 'Graffiti' + dst = append(dst, b.Graffiti[:]...) + + // Offset (3) 'ProposerSlashings' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.ProposerSlashings) * 416 + + // Offset (4) 'AttesterSlashings' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + offset += 4 + offset += b.AttesterSlashings[ii].SizeSSZ() + } + + // Offset (5) 'Attestations' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(b.Attestations); ii++ { + offset += 4 + offset += b.Attestations[ii].SizeSSZ() + } + + // Offset (6) 'Deposits' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Deposits) * 1240 + + // Offset (7) 'VoluntaryExits' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.VoluntaryExits) * 112 + + // Field (8) 'SyncAggregate' + if b.SyncAggregate == nil { + b.SyncAggregate = new(altair.SyncAggregate) + } + if dst, err = b.SyncAggregate.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (9) 'ExecutionPayloadHeader' + dst = ssz.WriteOffset(dst, offset) + if b.ExecutionPayloadHeader == nil { + b.ExecutionPayloadHeader = new(electra.ExecutionPayloadHeader) + } + offset += b.ExecutionPayloadHeader.SizeSSZ() + + // Offset (10) 'BLSToExecutionChanges' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.BLSToExecutionChanges) * 172 + + // Offset (11) 'BlobKZGCommitments' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.BlobKZGCommitments) * 48 + + // Offset (12) 'Consolidations' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Consolidations) * 120 + + // Field (3) 'ProposerSlashings' + if size := len(b.ProposerSlashings); size > 16 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.ProposerSlashings", size, 16) + return + } + for ii := 0; ii < len(b.ProposerSlashings); ii++ { + if dst, err = b.ProposerSlashings[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (4) 'AttesterSlashings' + if size := len(b.AttesterSlashings); size > 1 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.AttesterSlashings", size, 1) + return + } + { + offset = 4 * len(b.AttesterSlashings) + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += b.AttesterSlashings[ii].SizeSSZ() + } + } + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + if dst, err = b.AttesterSlashings[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (5) 'Attestations' + if size := len(b.Attestations); size > 8 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.Attestations", size, 8) + return + } + { + offset = 4 * len(b.Attestations) + for ii := 0; ii < len(b.Attestations); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += b.Attestations[ii].SizeSSZ() + } + } + for ii := 0; ii < len(b.Attestations); ii++ { + if dst, err = b.Attestations[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (6) 'Deposits' + if size := len(b.Deposits); size > 16 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.Deposits", size, 16) + return + } + for ii := 0; ii < len(b.Deposits); ii++ { + if dst, err = b.Deposits[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (7) 'VoluntaryExits' + if size := len(b.VoluntaryExits); size > 16 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.VoluntaryExits", size, 16) + return + } + for ii := 0; ii < len(b.VoluntaryExits); ii++ { + if dst, err = b.VoluntaryExits[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (9) 'ExecutionPayloadHeader' + if dst, err = b.ExecutionPayloadHeader.MarshalSSZTo(dst); err != nil { + return + } + + // Field (10) 'BLSToExecutionChanges' + if size := len(b.BLSToExecutionChanges); size > 16 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.BLSToExecutionChanges", size, 16) + return + } + for ii := 0; ii < len(b.BLSToExecutionChanges); ii++ { + if dst, err = b.BLSToExecutionChanges[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (11) 'BlobKZGCommitments' + if size := len(b.BlobKZGCommitments); size > 4096 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.BlobKZGCommitments", size, 4096) + return + } + for ii := 0; ii < len(b.BlobKZGCommitments); ii++ { + dst = append(dst, b.BlobKZGCommitments[ii][:]...) + } + + // Field (12) 'Consolidations' + if size := len(b.Consolidations); size > 16 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.Consolidations", size, 16) + return + } + for ii := 0; ii < len(b.Consolidations); ii++ { + if dst, err = b.Consolidations[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BlindedBeaconBlockBody object +func (b *BlindedBeaconBlockBody) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 396 { + return ssz.ErrSize + } + + tail := buf + var o3, o4, o5, o6, o7, o9, o10, o11, o12 uint64 + + // Field (0) 'RANDAOReveal' + copy(b.RANDAOReveal[:], buf[0:96]) + + // Field (1) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if err = b.ETH1Data.UnmarshalSSZ(buf[96:168]); err != nil { + return err + } + + // Field (2) 'Graffiti' + copy(b.Graffiti[:], buf[168:200]) + + // Offset (3) 'ProposerSlashings' + if o3 = ssz.ReadOffset(buf[200:204]); o3 > size { + return ssz.ErrOffset + } + + if o3 < 396 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (4) 'AttesterSlashings' + if o4 = ssz.ReadOffset(buf[204:208]); o4 > size || o3 > o4 { + return ssz.ErrOffset + } + + // Offset (5) 'Attestations' + if o5 = ssz.ReadOffset(buf[208:212]); o5 > size || o4 > o5 { + return ssz.ErrOffset + } + + // Offset (6) 'Deposits' + if o6 = ssz.ReadOffset(buf[212:216]); o6 > size || o5 > o6 { + return ssz.ErrOffset + } + + // Offset (7) 'VoluntaryExits' + if o7 = ssz.ReadOffset(buf[216:220]); o7 > size || o6 > o7 { + return ssz.ErrOffset + } + + // Field (8) 'SyncAggregate' + if b.SyncAggregate == nil { + b.SyncAggregate = new(altair.SyncAggregate) + } + if err = b.SyncAggregate.UnmarshalSSZ(buf[220:380]); err != nil { + return err + } + + // Offset (9) 'ExecutionPayloadHeader' + if o9 = ssz.ReadOffset(buf[380:384]); o9 > size || o7 > o9 { + return ssz.ErrOffset + } + + // Offset (10) 'BLSToExecutionChanges' + if o10 = ssz.ReadOffset(buf[384:388]); o10 > size || o9 > o10 { + return ssz.ErrOffset + } + + // Offset (11) 'BlobKZGCommitments' + if o11 = ssz.ReadOffset(buf[388:392]); o11 > size || o10 > o11 { + return ssz.ErrOffset + } + + // Offset (12) 'Consolidations' + if o12 = ssz.ReadOffset(buf[392:396]); o12 > size || o11 > o12 { + return ssz.ErrOffset + } + + // Field (3) 'ProposerSlashings' + { + buf = tail[o3:o4] + num, err := ssz.DivideInt2(len(buf), 416, 16) + if err != nil { + return err + } + b.ProposerSlashings = make([]*phase0.ProposerSlashing, num) + for ii := 0; ii < num; ii++ { + if b.ProposerSlashings[ii] == nil { + b.ProposerSlashings[ii] = new(phase0.ProposerSlashing) + } + if err = b.ProposerSlashings[ii].UnmarshalSSZ(buf[ii*416 : (ii+1)*416]); err != nil { + return err + } + } + } + + // Field (4) 'AttesterSlashings' + { + buf = tail[o4:o5] + num, err := ssz.DecodeDynamicLength(buf, 1) + if err != nil { + return err + } + b.AttesterSlashings = make([]*phase0.AttesterSlashing, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if b.AttesterSlashings[indx] == nil { + b.AttesterSlashings[indx] = new(phase0.AttesterSlashing) + } + if err = b.AttesterSlashings[indx].UnmarshalSSZ(buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + + // Field (5) 'Attestations' + { + buf = tail[o5:o6] + num, err := ssz.DecodeDynamicLength(buf, 8) + if err != nil { + return err + } + b.Attestations = make([]*electra.Attestation, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if b.Attestations[indx] == nil { + b.Attestations[indx] = new(electra.Attestation) + } + if err = b.Attestations[indx].UnmarshalSSZ(buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + + // Field (6) 'Deposits' + { + buf = tail[o6:o7] + num, err := ssz.DivideInt2(len(buf), 1240, 16) + if err != nil { + return err + } + b.Deposits = make([]*phase0.Deposit, num) + for ii := 0; ii < num; ii++ { + if b.Deposits[ii] == nil { + b.Deposits[ii] = new(phase0.Deposit) + } + if err = b.Deposits[ii].UnmarshalSSZ(buf[ii*1240 : (ii+1)*1240]); err != nil { + return err + } + } + } + + // Field (7) 'VoluntaryExits' + { + buf = tail[o7:o9] + num, err := ssz.DivideInt2(len(buf), 112, 16) + if err != nil { + return err + } + b.VoluntaryExits = make([]*phase0.SignedVoluntaryExit, num) + for ii := 0; ii < num; ii++ { + if b.VoluntaryExits[ii] == nil { + b.VoluntaryExits[ii] = new(phase0.SignedVoluntaryExit) + } + if err = b.VoluntaryExits[ii].UnmarshalSSZ(buf[ii*112 : (ii+1)*112]); err != nil { + return err + } + } + } + + // Field (9) 'ExecutionPayloadHeader' + { + buf = tail[o9:o10] + if b.ExecutionPayloadHeader == nil { + b.ExecutionPayloadHeader = new(electra.ExecutionPayloadHeader) + } + if err = b.ExecutionPayloadHeader.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (10) 'BLSToExecutionChanges' + { + buf = tail[o10:o11] + num, err := ssz.DivideInt2(len(buf), 172, 16) + if err != nil { + return err + } + b.BLSToExecutionChanges = make([]*capella.SignedBLSToExecutionChange, num) + for ii := 0; ii < num; ii++ { + if b.BLSToExecutionChanges[ii] == nil { + b.BLSToExecutionChanges[ii] = new(capella.SignedBLSToExecutionChange) + } + if err = b.BLSToExecutionChanges[ii].UnmarshalSSZ(buf[ii*172 : (ii+1)*172]); err != nil { + return err + } + } + } + + // Field (11) 'BlobKZGCommitments' + { + buf = tail[o11:o12] + num, err := ssz.DivideInt2(len(buf), 48, 4096) + if err != nil { + return err + } + b.BlobKZGCommitments = make([]deneb.KZGCommitment, num) + for ii := 0; ii < num; ii++ { + copy(b.BlobKZGCommitments[ii][:], buf[ii*48:(ii+1)*48]) + } + } + + // Field (12) 'Consolidations' + { + buf = tail[o12:] + num, err := ssz.DivideInt2(len(buf), 120, 16) + if err != nil { + return err + } + b.Consolidations = make([]*electra.SignedConsolidation, num) + for ii := 0; ii < num; ii++ { + if b.Consolidations[ii] == nil { + b.Consolidations[ii] = new(electra.SignedConsolidation) + } + if err = b.Consolidations[ii].UnmarshalSSZ(buf[ii*120 : (ii+1)*120]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BlindedBeaconBlockBody object +func (b *BlindedBeaconBlockBody) SizeSSZ() (size int) { + size = 396 + + // Field (3) 'ProposerSlashings' + size += len(b.ProposerSlashings) * 416 + + // Field (4) 'AttesterSlashings' + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + size += 4 + size += b.AttesterSlashings[ii].SizeSSZ() + } + + // Field (5) 'Attestations' + for ii := 0; ii < len(b.Attestations); ii++ { + size += 4 + size += b.Attestations[ii].SizeSSZ() + } + + // Field (6) 'Deposits' + size += len(b.Deposits) * 1240 + + // Field (7) 'VoluntaryExits' + size += len(b.VoluntaryExits) * 112 + + // Field (9) 'ExecutionPayloadHeader' + if b.ExecutionPayloadHeader == nil { + b.ExecutionPayloadHeader = new(electra.ExecutionPayloadHeader) + } + size += b.ExecutionPayloadHeader.SizeSSZ() + + // Field (10) 'BLSToExecutionChanges' + size += len(b.BLSToExecutionChanges) * 172 + + // Field (11) 'BlobKZGCommitments' + size += len(b.BlobKZGCommitments) * 48 + + // Field (12) 'Consolidations' + size += len(b.Consolidations) * 120 + + return +} + +// HashTreeRoot ssz hashes the BlindedBeaconBlockBody object +func (b *BlindedBeaconBlockBody) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BlindedBeaconBlockBody object with a hasher +func (b *BlindedBeaconBlockBody) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'RANDAOReveal' + hh.PutBytes(b.RANDAOReveal[:]) + + // Field (1) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if err = b.ETH1Data.HashTreeRootWith(hh); err != nil { + return + } + + // Field (2) 'Graffiti' + hh.PutBytes(b.Graffiti[:]) + + // Field (3) 'ProposerSlashings' + { + subIndx := hh.Index() + num := uint64(len(b.ProposerSlashings)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.ProposerSlashings { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (4) 'AttesterSlashings' + { + subIndx := hh.Index() + num := uint64(len(b.AttesterSlashings)) + if num > 1 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.AttesterSlashings { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 1) + } + + // Field (5) 'Attestations' + { + subIndx := hh.Index() + num := uint64(len(b.Attestations)) + if num > 8 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Attestations { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 8) + } + + // Field (6) 'Deposits' + { + subIndx := hh.Index() + num := uint64(len(b.Deposits)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Deposits { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (7) 'VoluntaryExits' + { + subIndx := hh.Index() + num := uint64(len(b.VoluntaryExits)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.VoluntaryExits { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (8) 'SyncAggregate' + if b.SyncAggregate == nil { + b.SyncAggregate = new(altair.SyncAggregate) + } + if err = b.SyncAggregate.HashTreeRootWith(hh); err != nil { + return + } + + // Field (9) 'ExecutionPayloadHeader' + if err = b.ExecutionPayloadHeader.HashTreeRootWith(hh); err != nil { + return + } + + // Field (10) 'BLSToExecutionChanges' + { + subIndx := hh.Index() + num := uint64(len(b.BLSToExecutionChanges)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.BLSToExecutionChanges { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (11) 'BlobKZGCommitments' + { + if size := len(b.BlobKZGCommitments); size > 4096 { + err = ssz.ErrListTooBigFn("BlindedBeaconBlockBody.BlobKZGCommitments", size, 4096) + return + } + subIndx := hh.Index() + for _, i := range b.BlobKZGCommitments { + hh.PutBytes(i[:]) + } + numItems := uint64(len(b.BlobKZGCommitments)) + hh.MerkleizeWithMixin(subIndx, numItems, 4096) + } + + // Field (12) 'Consolidations' + { + subIndx := hh.Index() + num := uint64(len(b.Consolidations)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Consolidations { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the BlindedBeaconBlockBody object +func (b *BlindedBeaconBlockBody) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} diff --git a/api/v1/electra/blindedbeaconblockbody_yaml.go b/api/v1/electra/blindedbeaconblockbody_yaml.go new file mode 100644 index 00000000..2228b803 --- /dev/null +++ b/api/v1/electra/blindedbeaconblockbody_yaml.go @@ -0,0 +1,83 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "fmt" + + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// blindedBeaconBlockBodyYAML is the spec representation of the struct. +type blindedBeaconBlockBodyYAML struct { + RANDAOReveal string `yaml:"randao_reveal"` + ETH1Data *phase0.ETH1Data `yaml:"eth1_data"` + Graffiti string `yaml:"graffiti"` + ProposerSlashings []*phase0.ProposerSlashing `yaml:"proposer_slashings"` + AttesterSlashings []*phase0.AttesterSlashing `yaml:"attester_slashings"` + Attestations []*electra.Attestation `yaml:"attestations"` + Deposits []*phase0.Deposit `yaml:"deposits"` + VoluntaryExits []*phase0.SignedVoluntaryExit `yaml:"voluntary_exits"` + SyncAggregate *altair.SyncAggregate `yaml:"sync_aggregate"` + ExecutionPayloadHeader *electra.ExecutionPayloadHeader `yaml:"execution_payload_header"` + BLSToExecutionChanges []*capella.SignedBLSToExecutionChange `yaml:"bls_to_execution_changes"` + BlobKZGCommitments []string `yaml:"blob_kzg_commitments"` + Consolidations []*electra.SignedConsolidation `json:"consolidations"` +} + +// MarshalYAML implements yaml.Marshaler. +func (b *BlindedBeaconBlockBody) MarshalYAML() ([]byte, error) { + blobKZGCommitments := make([]string, len(b.BlobKZGCommitments)) + for i := range b.BlobKZGCommitments { + blobKZGCommitments[i] = b.BlobKZGCommitments[i].String() + } + + yamlBytes, err := yaml.MarshalWithOptions(&blindedBeaconBlockBodyYAML{ + RANDAOReveal: fmt.Sprintf("%#x", b.RANDAOReveal), + ETH1Data: b.ETH1Data, + Graffiti: fmt.Sprintf("%#x", b.Graffiti), + ProposerSlashings: b.ProposerSlashings, + AttesterSlashings: b.AttesterSlashings, + Attestations: b.Attestations, + Deposits: b.Deposits, + VoluntaryExits: b.VoluntaryExits, + SyncAggregate: b.SyncAggregate, + ExecutionPayloadHeader: b.ExecutionPayloadHeader, + BLSToExecutionChanges: b.BLSToExecutionChanges, + BlobKZGCommitments: blobKZGCommitments, + Consolidations: b.Consolidations, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (b *BlindedBeaconBlockBody) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var data blindedBeaconBlockBodyJSON + if err := yaml.Unmarshal(input, &data); err != nil { + return err + } + + return b.unpack(&data) +} diff --git a/api/v1/electra/blockcontents.go b/api/v1/electra/blockcontents.go new file mode 100644 index 00000000..26f69c9b --- /dev/null +++ b/api/v1/electra/blockcontents.go @@ -0,0 +1,40 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/goccy/go-yaml" +) + +// BlockContents represents the contents of a block, both block and blob. +type BlockContents struct { + Block *electra.BeaconBlock + KZGProofs []deneb.KZGProof `ssz-max:"4096" ssz-size:"?,48"` + Blobs []deneb.Blob `ssz-max:"4096" ssz-size:"?,131072"` +} + +// String returns a string version of the structure. +func (b *BlockContents) String() string { + data, err := yaml.Marshal(b) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/api/v1/electra/blockcontents_json.go b/api/v1/electra/blockcontents_json.go new file mode 100644 index 00000000..22b937c0 --- /dev/null +++ b/api/v1/electra/blockcontents_json.go @@ -0,0 +1,62 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/pkg/errors" +) + +// blockContentsJSON is the spec representation of the struct. +type blockContentsJSON struct { + Block *electra.BeaconBlock `json:"block"` + KZGProofs []deneb.KZGProof `json:"kzg_proofs"` + Blobs []deneb.Blob `json:"blobs"` +} + +// MarshalJSON implements json.Marshaler. +func (b *BlockContents) MarshalJSON() ([]byte, error) { + return json.Marshal(&blockContentsJSON{ + Block: b.Block, + KZGProofs: b.KZGProofs, + Blobs: b.Blobs, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *BlockContents) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&blockContentsJSON{}, input) + if err != nil { + return err + } + + if err := json.Unmarshal(raw["block"], &b.Block); err != nil { + return errors.Wrap(err, "block") + } + + if err := json.Unmarshal(raw["kzg_proofs"], &b.KZGProofs); err != nil { + return errors.Wrap(err, "kzg_proofs") + } + + if err := json.Unmarshal(raw["blobs"], &b.Blobs); err != nil { + return errors.Wrap(err, "blobs") + } + + return nil +} diff --git a/api/v1/electra/blockcontents_ssz.go b/api/v1/electra/blockcontents_ssz.go new file mode 100644 index 00000000..3f06bb73 --- /dev/null +++ b/api/v1/electra/blockcontents_ssz.go @@ -0,0 +1,200 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 449c9e7b5e2698820a83b7fa31c569b206b83e7d5fc608ac39e5c95530999882 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the BlockContents object +func (b *BlockContents) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BlockContents object to a target array +func (b *BlockContents) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(12) + + // Offset (0) 'Block' + dst = ssz.WriteOffset(dst, offset) + if b.Block == nil { + b.Block = new(electra.BeaconBlock) + } + offset += b.Block.SizeSSZ() + + // Offset (1) 'KZGProofs' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.KZGProofs) * 48 + + // Offset (2) 'Blobs' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Blobs) * 131072 + + // Field (0) 'Block' + if dst, err = b.Block.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'KZGProofs' + if size := len(b.KZGProofs); size > 4096 { + err = ssz.ErrListTooBigFn("BlockContents.KZGProofs", size, 4096) + return + } + for ii := 0; ii < len(b.KZGProofs); ii++ { + dst = append(dst, b.KZGProofs[ii][:]...) + } + + // Field (2) 'Blobs' + if size := len(b.Blobs); size > 4096 { + err = ssz.ErrListTooBigFn("BlockContents.Blobs", size, 4096) + return + } + for ii := 0; ii < len(b.Blobs); ii++ { + dst = append(dst, b.Blobs[ii][:]...) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BlockContents object +func (b *BlockContents) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 12 { + return ssz.ErrSize + } + + tail := buf + var o0, o1, o2 uint64 + + // Offset (0) 'Block' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 12 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (1) 'KZGProofs' + if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 { + return ssz.ErrOffset + } + + // Offset (2) 'Blobs' + if o2 = ssz.ReadOffset(buf[8:12]); o2 > size || o1 > o2 { + return ssz.ErrOffset + } + + // Field (0) 'Block' + { + buf = tail[o0:o1] + if b.Block == nil { + b.Block = new(electra.BeaconBlock) + } + if err = b.Block.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (1) 'KZGProofs' + { + buf = tail[o1:o2] + num, err := ssz.DivideInt2(len(buf), 48, 4096) + if err != nil { + return err + } + b.KZGProofs = make([]deneb.KZGProof, num) + for ii := 0; ii < num; ii++ { + copy(b.KZGProofs[ii][:], buf[ii*48:(ii+1)*48]) + } + } + + // Field (2) 'Blobs' + { + buf = tail[o2:] + num, err := ssz.DivideInt2(len(buf), 131072, 4096) + if err != nil { + return err + } + b.Blobs = make([]deneb.Blob, num) + for ii := 0; ii < num; ii++ { + copy(b.Blobs[ii][:], buf[ii*131072:(ii+1)*131072]) + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BlockContents object +func (b *BlockContents) SizeSSZ() (size int) { + size = 12 + + // Field (0) 'Block' + if b.Block == nil { + b.Block = new(electra.BeaconBlock) + } + size += b.Block.SizeSSZ() + + // Field (1) 'KZGProofs' + size += len(b.KZGProofs) * 48 + + // Field (2) 'Blobs' + size += len(b.Blobs) * 131072 + + return +} + +// HashTreeRoot ssz hashes the BlockContents object +func (b *BlockContents) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BlockContents object with a hasher +func (b *BlockContents) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Block' + if err = b.Block.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'KZGProofs' + { + if size := len(b.KZGProofs); size > 4096 { + err = ssz.ErrListTooBigFn("BlockContents.KZGProofs", size, 4096) + return + } + subIndx := hh.Index() + for _, i := range b.KZGProofs { + hh.PutBytes(i[:]) + } + numItems := uint64(len(b.KZGProofs)) + hh.MerkleizeWithMixin(subIndx, numItems, 4096) + } + + // Field (2) 'Blobs' + { + if size := len(b.Blobs); size > 4096 { + err = ssz.ErrListTooBigFn("BlockContents.Blobs", size, 4096) + return + } + subIndx := hh.Index() + for _, i := range b.Blobs { + hh.PutBytes(i[:]) + } + numItems := uint64(len(b.Blobs)) + hh.MerkleizeWithMixin(subIndx, numItems, 4096) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the BlockContents object +func (b *BlockContents) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} diff --git a/api/v1/electra/blockcontents_yaml.go b/api/v1/electra/blockcontents_yaml.go new file mode 100644 index 00000000..ed263115 --- /dev/null +++ b/api/v1/electra/blockcontents_yaml.go @@ -0,0 +1,62 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// blockContentsYAML is the spec representation of the struct. +type blockContentsYAML struct { + Block *electra.BeaconBlock `yaml:"block"` + KZGProofs []deneb.KZGProof `yaml:"kzg_proofs"` + Blobs []deneb.Blob `yaml:"blobs"` +} + +// MarshalYAML implements yaml.Marshaler. +func (b *BlockContents) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&blockContentsYAML{ + Block: b.Block, + KZGProofs: b.KZGProofs, + Blobs: b.Blobs, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (b *BlockContents) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var unmarshaled blockContentsJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return b.UnmarshalJSON(marshaled) +} diff --git a/api/v1/electra/generate.go b/api/v1/electra/generate.go new file mode 100644 index 00000000..6274fdf8 --- /dev/null +++ b/api/v1/electra/generate.go @@ -0,0 +1,19 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +// Need to `go install github.com/ferranbt/fastssz/sszgen@latest` for this to work. +//go:generate rm -f blindedbeaconblock_ssz.go blindedbeaconblockbody_ssz.go blockcontents_ssz.go signedblindedbeaconblock_ssz.go signedblockcontents_ssz.go +//go:generate sszgen --include ../../../spec/phase0,../../../spec/altair,../../../spec/bellatrix,../../../spec/capella,../../../spec/deneb,../../../spec/electra -path . --suffix ssz -objs BlindedBeaconBlock,BlindedBeaconBlockBody,BlockContents,SignedBlindedBeaconBlock,SignedBlockContents +//go:generate goimports -w blindedbeaconblock_ssz.go blindedbeaconblockbody_ssz.go blockcontents_ssz.go signedblindedbeaconblock_ssz.go signedblockcontents_ssz.go diff --git a/api/v1/electra/signedblindedbeaconblock.go b/api/v1/electra/signedblindedbeaconblock.go new file mode 100644 index 00000000..5eda60eb --- /dev/null +++ b/api/v1/electra/signedblindedbeaconblock.go @@ -0,0 +1,37 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// SignedBlindedBeaconBlock is a signed beacon block. +type SignedBlindedBeaconBlock struct { + Message *BlindedBeaconBlock + Signature phase0.BLSSignature `ssz-size:"96"` +} + +// String returns a string version of the structure. +func (s *SignedBlindedBeaconBlock) String() string { + data, err := yaml.Marshal(s) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/api/v1/electra/signedblindedbeaconblock_json.go b/api/v1/electra/signedblindedbeaconblock_json.go new file mode 100644 index 00000000..824eddd9 --- /dev/null +++ b/api/v1/electra/signedblindedbeaconblock_json.go @@ -0,0 +1,68 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// signedBlindedBeaconBlockJSON is the spec representation of the struct. +type signedBlindedBeaconBlockJSON struct { + Message *BlindedBeaconBlock `json:"message"` + Signature string `json:"signature"` +} + +// MarshalJSON implements json.Marshaler. +func (s *SignedBlindedBeaconBlock) MarshalJSON() ([]byte, error) { + return json.Marshal(&signedBlindedBeaconBlockJSON{ + Message: s.Message, + Signature: fmt.Sprintf("%#x", s.Signature), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *SignedBlindedBeaconBlock) UnmarshalJSON(input []byte) error { + var data signedBlindedBeaconBlockJSON + if err := json.Unmarshal(input, &data); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return s.unpack(&data) +} + +func (s *SignedBlindedBeaconBlock) unpack(data *signedBlindedBeaconBlockJSON) error { + if data.Message == nil { + return errors.New("message missing") + } + s.Message = data.Message + if data.Signature == "" { + return errors.New("signature missing") + } + signature, err := hex.DecodeString(strings.TrimPrefix(data.Signature, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for signature") + } + if len(signature) != phase0.SignatureLength { + return fmt.Errorf("incorrect length %d for signature", len(signature)) + } + copy(s.Signature[:], signature) + + return nil +} diff --git a/api/v1/electra/signedblindedbeaconblock_ssz.go b/api/v1/electra/signedblindedbeaconblock_ssz.go new file mode 100644 index 00000000..94fbd52a --- /dev/null +++ b/api/v1/electra/signedblindedbeaconblock_ssz.go @@ -0,0 +1,111 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 449c9e7b5e2698820a83b7fa31c569b206b83e7d5fc608ac39e5c95530999882 +// Version: 0.1.3 +package electra + +import ( + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the SignedBlindedBeaconBlock object +func (s *SignedBlindedBeaconBlock) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SignedBlindedBeaconBlock object to a target array +func (s *SignedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(100) + + // Offset (0) 'Message' + dst = ssz.WriteOffset(dst, offset) + if s.Message == nil { + s.Message = new(BlindedBeaconBlock) + } + offset += s.Message.SizeSSZ() + + // Field (1) 'Signature' + dst = append(dst, s.Signature[:]...) + + // Field (0) 'Message' + if dst, err = s.Message.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the SignedBlindedBeaconBlock object +func (s *SignedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 100 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'Message' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 100 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Signature' + copy(s.Signature[:], buf[4:100]) + + // Field (0) 'Message' + { + buf = tail[o0:] + if s.Message == nil { + s.Message = new(BlindedBeaconBlock) + } + if err = s.Message.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SignedBlindedBeaconBlock object +func (s *SignedBlindedBeaconBlock) SizeSSZ() (size int) { + size = 100 + + // Field (0) 'Message' + if s.Message == nil { + s.Message = new(BlindedBeaconBlock) + } + size += s.Message.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the SignedBlindedBeaconBlock object +func (s *SignedBlindedBeaconBlock) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SignedBlindedBeaconBlock object with a hasher +func (s *SignedBlindedBeaconBlock) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Message' + if err = s.Message.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Signature' + hh.PutBytes(s.Signature[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SignedBlindedBeaconBlock object +func (s *SignedBlindedBeaconBlock) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/api/v1/electra/signedblindedbeaconblock_yaml.go b/api/v1/electra/signedblindedbeaconblock_yaml.go new file mode 100644 index 00000000..dc875415 --- /dev/null +++ b/api/v1/electra/signedblindedbeaconblock_yaml.go @@ -0,0 +1,51 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "fmt" + + "github.com/goccy/go-yaml" +) + +// signedBlindedBeaconBlockYAML is the spec representation of the struct. +type signedBlindedBeaconBlockYAML struct { + Message *BlindedBeaconBlock `yaml:"message"` + Signature string `yaml:"signature"` +} + +// MarshalYAML implements yaml.Marshaler. +func (s *SignedBlindedBeaconBlock) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&signedBlindedBeaconBlockYAML{ + Message: s.Message, + Signature: fmt.Sprintf("%#x", s.Signature), + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (s *SignedBlindedBeaconBlock) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var data signedBlindedBeaconBlockJSON + if err := yaml.Unmarshal(input, &data); err != nil { + return err + } + + return s.unpack(&data) +} diff --git a/api/v1/electra/signedblockcontents.go b/api/v1/electra/signedblockcontents.go new file mode 100644 index 00000000..f3873645 --- /dev/null +++ b/api/v1/electra/signedblockcontents.go @@ -0,0 +1,40 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/goccy/go-yaml" +) + +// SignedBlockContents represents the contents of a block, both block and blob. +type SignedBlockContents struct { + SignedBlock *electra.SignedBeaconBlock + KZGProofs []deneb.KZGProof `ssz-max:"4096" ssz-size:"?,48"` + Blobs []deneb.Blob `ssz-max:"4096" ssz-size:"?,131072"` +} + +// String returns a string version of the structure. +func (s *SignedBlockContents) String() string { + data, err := yaml.Marshal(s) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/api/v1/electra/signedblockcontents_json.go b/api/v1/electra/signedblockcontents_json.go new file mode 100644 index 00000000..6ecbaa21 --- /dev/null +++ b/api/v1/electra/signedblockcontents_json.go @@ -0,0 +1,62 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/pkg/errors" +) + +// signedBlockContentsJSON is the spec representation of the struct. +type signedBlockContentsJSON struct { + SignedBlock *electra.SignedBeaconBlock `json:"signed_block"` + KZGProofs []deneb.KZGProof `json:"kzg_proofs"` + Blobs []deneb.Blob `json:"blobs"` +} + +// MarshalJSON implements json.Marshaler. +func (s *SignedBlockContents) MarshalJSON() ([]byte, error) { + return json.Marshal(&signedBlockContentsJSON{ + SignedBlock: s.SignedBlock, + KZGProofs: s.KZGProofs, + Blobs: s.Blobs, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *SignedBlockContents) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&signedBlockContentsJSON{}, input) + if err != nil { + return err + } + + if err := json.Unmarshal(raw["signed_block"], &s.SignedBlock); err != nil { + return errors.Wrap(err, "signed_block") + } + + if err := json.Unmarshal(raw["kzg_proofs"], &s.KZGProofs); err != nil { + return errors.Wrap(err, "kzg_proofs") + } + + if err := json.Unmarshal(raw["blobs"], &s.Blobs); err != nil { + return errors.Wrap(err, "blobs") + } + + return nil +} diff --git a/api/v1/electra/signedblockcontents_ssz.go b/api/v1/electra/signedblockcontents_ssz.go new file mode 100644 index 00000000..8c3af9a4 --- /dev/null +++ b/api/v1/electra/signedblockcontents_ssz.go @@ -0,0 +1,200 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 449c9e7b5e2698820a83b7fa31c569b206b83e7d5fc608ac39e5c95530999882 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the SignedBlockContents object +func (s *SignedBlockContents) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SignedBlockContents object to a target array +func (s *SignedBlockContents) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(12) + + // Offset (0) 'SignedBlock' + dst = ssz.WriteOffset(dst, offset) + if s.SignedBlock == nil { + s.SignedBlock = new(electra.SignedBeaconBlock) + } + offset += s.SignedBlock.SizeSSZ() + + // Offset (1) 'KZGProofs' + dst = ssz.WriteOffset(dst, offset) + offset += len(s.KZGProofs) * 48 + + // Offset (2) 'Blobs' + dst = ssz.WriteOffset(dst, offset) + offset += len(s.Blobs) * 131072 + + // Field (0) 'SignedBlock' + if dst, err = s.SignedBlock.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'KZGProofs' + if size := len(s.KZGProofs); size > 4096 { + err = ssz.ErrListTooBigFn("SignedBlockContents.KZGProofs", size, 4096) + return + } + for ii := 0; ii < len(s.KZGProofs); ii++ { + dst = append(dst, s.KZGProofs[ii][:]...) + } + + // Field (2) 'Blobs' + if size := len(s.Blobs); size > 4096 { + err = ssz.ErrListTooBigFn("SignedBlockContents.Blobs", size, 4096) + return + } + for ii := 0; ii < len(s.Blobs); ii++ { + dst = append(dst, s.Blobs[ii][:]...) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the SignedBlockContents object +func (s *SignedBlockContents) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 12 { + return ssz.ErrSize + } + + tail := buf + var o0, o1, o2 uint64 + + // Offset (0) 'SignedBlock' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 12 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (1) 'KZGProofs' + if o1 = ssz.ReadOffset(buf[4:8]); o1 > size || o0 > o1 { + return ssz.ErrOffset + } + + // Offset (2) 'Blobs' + if o2 = ssz.ReadOffset(buf[8:12]); o2 > size || o1 > o2 { + return ssz.ErrOffset + } + + // Field (0) 'SignedBlock' + { + buf = tail[o0:o1] + if s.SignedBlock == nil { + s.SignedBlock = new(electra.SignedBeaconBlock) + } + if err = s.SignedBlock.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (1) 'KZGProofs' + { + buf = tail[o1:o2] + num, err := ssz.DivideInt2(len(buf), 48, 4096) + if err != nil { + return err + } + s.KZGProofs = make([]deneb.KZGProof, num) + for ii := 0; ii < num; ii++ { + copy(s.KZGProofs[ii][:], buf[ii*48:(ii+1)*48]) + } + } + + // Field (2) 'Blobs' + { + buf = tail[o2:] + num, err := ssz.DivideInt2(len(buf), 131072, 4096) + if err != nil { + return err + } + s.Blobs = make([]deneb.Blob, num) + for ii := 0; ii < num; ii++ { + copy(s.Blobs[ii][:], buf[ii*131072:(ii+1)*131072]) + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SignedBlockContents object +func (s *SignedBlockContents) SizeSSZ() (size int) { + size = 12 + + // Field (0) 'SignedBlock' + if s.SignedBlock == nil { + s.SignedBlock = new(electra.SignedBeaconBlock) + } + size += s.SignedBlock.SizeSSZ() + + // Field (1) 'KZGProofs' + size += len(s.KZGProofs) * 48 + + // Field (2) 'Blobs' + size += len(s.Blobs) * 131072 + + return +} + +// HashTreeRoot ssz hashes the SignedBlockContents object +func (s *SignedBlockContents) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SignedBlockContents object with a hasher +func (s *SignedBlockContents) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'SignedBlock' + if err = s.SignedBlock.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'KZGProofs' + { + if size := len(s.KZGProofs); size > 4096 { + err = ssz.ErrListTooBigFn("SignedBlockContents.KZGProofs", size, 4096) + return + } + subIndx := hh.Index() + for _, i := range s.KZGProofs { + hh.PutBytes(i[:]) + } + numItems := uint64(len(s.KZGProofs)) + hh.MerkleizeWithMixin(subIndx, numItems, 4096) + } + + // Field (2) 'Blobs' + { + if size := len(s.Blobs); size > 4096 { + err = ssz.ErrListTooBigFn("SignedBlockContents.Blobs", size, 4096) + return + } + subIndx := hh.Index() + for _, i := range s.Blobs { + hh.PutBytes(i[:]) + } + numItems := uint64(len(s.Blobs)) + hh.MerkleizeWithMixin(subIndx, numItems, 4096) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SignedBlockContents object +func (s *SignedBlockContents) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/api/v1/electra/signedblockcontents_yaml.go b/api/v1/electra/signedblockcontents_yaml.go new file mode 100644 index 00000000..495af9e6 --- /dev/null +++ b/api/v1/electra/signedblockcontents_yaml.go @@ -0,0 +1,62 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/electra" + + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// signedBlockContentsYAML is the spec representation of the struct. +type signedBlockContentsYAML struct { + SignedBlock *electra.SignedBeaconBlock `yaml:"signed_block"` + KZGProofs []deneb.KZGProof `yaml:"kzg_proofs"` + Blobs []deneb.Blob `yaml:"blobs"` +} + +// MarshalYAML implements yaml.Marshaler. +func (s *SignedBlockContents) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&signedBlockContentsYAML{ + SignedBlock: s.SignedBlock, + KZGProofs: s.KZGProofs, + Blobs: s.Blobs, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (s *SignedBlockContents) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var unmarshaled signedBlockContentsJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return s.UnmarshalJSON(marshaled) +} diff --git a/api/v1/payloadattributesevent.go b/api/v1/payloadattributesevent.go index ef8175b6..8b7055ad 100644 --- a/api/v1/payloadattributesevent.go +++ b/api/v1/payloadattributesevent.go @@ -7,6 +7,8 @@ import ( "strconv" "strings" + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" @@ -40,6 +42,8 @@ type PayloadAttributesData struct { V2 *PayloadAttributesV2 // V3 is the v3 payload attributes. V3 *PayloadAttributesV3 + // V4 is the v4 payload attributes. + V4 *PayloadAttributesV4 } // PayloadAttributesV1 represents the payload attributes. @@ -78,6 +82,24 @@ type PayloadAttributesV3 struct { ParentBeaconBlockRoot phase0.Root } +// PayloadAttributesV4 represents the payload attributes v4. +type PayloadAttributesV4 struct { + // Timestamp is the timestamp of the payload. + Timestamp uint64 + // PrevRandao is the previous randao. + PrevRandao [32]byte + // SuggestedFeeRecipient is the suggested fee recipient. + SuggestedFeeRecipient bellatrix.ExecutionAddress + // Withdrawals is the list of withdrawals. + Withdrawals []*capella.Withdrawal + // ParentBeaconBlockRoot is the parent beacon block root. + ParentBeaconBlockRoot phase0.Root + // DepositReceipts is the list of deposit receipts. + DepositReceipts []*electra.DepositReceipt + // WithdrawalRequests is the list of execution layer withdrawal requests. + WithdrawalRequests []*electra.ExecutionLayerWithdrawalRequest +} + // payloadAttributesEventJSON is the spec representation of the event. type payloadAttributesEventJSON struct { Version spec.DataVersion `json:"version"` @@ -118,6 +140,17 @@ type payloadAttributesV3JSON struct { ParentBeaconBlockRoot string `json:"parent_beacon_block_root"` } +// payloadAttributesV4JSON is the spec representation of the payload attributes v4. +type payloadAttributesV4JSON struct { + Timestamp string `json:"timestamp"` + PrevRandao string `json:"prev_randao"` + SuggestedFeeRecipient string `json:"suggested_fee_recipient"` + Withdrawals []*capella.Withdrawal `json:"withdrawals"` + ParentBeaconBlockRoot string `json:"parent_beacon_block_root"` + DepositReceipts []*electra.DepositReceipt `json:"deposit_receipts"` + WithdrawalRequests []*electra.ExecutionLayerWithdrawalRequest `json:"withdrawal_requests"` +} + // UnmarshalJSON implements json.Unmarshaler. func (p *PayloadAttributesV1) UnmarshalJSON(input []byte) error { var payloadAttributes payloadAttributesV1JSON @@ -294,6 +327,96 @@ func (p *PayloadAttributesV3) unpack(data *payloadAttributesV3JSON) error { return nil } +// UnmarshalJSON implements json.Unmarshaler. +func (p *PayloadAttributesV4) UnmarshalJSON(input []byte) error { + var payloadAttributes payloadAttributesV4JSON + if err := json.Unmarshal(input, &payloadAttributes); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return p.unpack(&payloadAttributes) +} + +func (p *PayloadAttributesV4) unpack(data *payloadAttributesV4JSON) error { + var err error + + if data.Timestamp == "" { + return errors.New("payload attributes timestamp missing") + } + p.Timestamp, err = strconv.ParseUint(data.Timestamp, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for payload attributes timestamp") + } + + if data.PrevRandao == "" { + return errors.New("payload attributes prev randao missing") + } + prevRandao, err := hex.DecodeString(strings.TrimPrefix(data.PrevRandao, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for payload attributes prev randao") + } + if len(prevRandao) != 32 { + return errors.New("incorrect length for payload attributes prev randao") + } + copy(p.PrevRandao[:], prevRandao) + + if data.SuggestedFeeRecipient == "" { + return errors.New("payload attributes suggested fee recipient missing") + } + feeRecipient, err := hex.DecodeString(strings.TrimPrefix(data.SuggestedFeeRecipient, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for payload attributes suggested fee recipient") + } + if len(feeRecipient) != bellatrix.FeeRecipientLength { + return errors.New("incorrect length for payload attributes suggested fee recipient") + } + copy(p.SuggestedFeeRecipient[:], feeRecipient) + + if data.Withdrawals == nil { + return errors.New("payload attributes withdrawals missing") + } + for i := range data.Withdrawals { + if data.Withdrawals[i] == nil { + return fmt.Errorf("withdrawals entry %d missing", i) + } + } + p.Withdrawals = data.Withdrawals + + if data.ParentBeaconBlockRoot == "" { + return errors.New("payload attributes parent beacon block root missing") + } + parentBeaconBlockRoot, err := hex.DecodeString(strings.TrimPrefix(data.ParentBeaconBlockRoot, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for payload attributes parent beacon block root") + } + if len(parentBeaconBlockRoot) != phase0.RootLength { + return errors.New("incorrect length for payload attributes parent beacon block root") + } + copy(p.ParentBeaconBlockRoot[:], parentBeaconBlockRoot) + + if data.DepositReceipts == nil { + return errors.New("payload attributes deposit receipts missing") + } + for i := range data.DepositReceipts { + if data.DepositReceipts[i] == nil { + return fmt.Errorf("deposit receipts entry %d missing", i) + } + } + p.DepositReceipts = data.DepositReceipts + + if data.WithdrawalRequests == nil { + return errors.New("payload attributes withdraw requests missing") + } + for i := range data.WithdrawalRequests { + if data.WithdrawalRequests[i] == nil { + return fmt.Errorf("withdraw requests entry %d missing", i) + } + } + p.WithdrawalRequests = data.WithdrawalRequests + + return nil +} + // MarshalJSON implements json.Marshaler. func (e *PayloadAttributesEvent) MarshalJSON() ([]byte, error) { var payloadAttributes []byte @@ -339,6 +462,22 @@ func (e *PayloadAttributesEvent) MarshalJSON() ([]byte, error) { if err != nil { return nil, errors.Wrap(err, "failed to marshal payload attributes v3") } + case spec.DataVersionElectra: + if e.Data.V4 == nil { + return nil, errors.New("no payload attributes v4 data") + } + payloadAttributes, err = json.Marshal(&payloadAttributesV4JSON{ + Timestamp: strconv.FormatUint(e.Data.V4.Timestamp, 10), + PrevRandao: fmt.Sprintf("%#x", e.Data.V4.PrevRandao), + SuggestedFeeRecipient: e.Data.V4.SuggestedFeeRecipient.String(), + Withdrawals: e.Data.V4.Withdrawals, + ParentBeaconBlockRoot: fmt.Sprintf("%#x", e.Data.V4.ParentBeaconBlockRoot), + DepositReceipts: e.Data.V4.DepositReceipts, + WithdrawalRequests: e.Data.V4.WithdrawalRequests, + }) + if err != nil { + return nil, errors.Wrap(err, "failed to marshal payload attributes v4") + } default: return nil, fmt.Errorf("unsupported payload attributes version: %s", e.Version) } @@ -453,6 +592,13 @@ func (e *PayloadAttributesEvent) unpack(data *payloadAttributesEventJSON) error return err } e.Data.V3 = &payloadAttributes + case spec.DataVersionElectra: + var payloadAttributes PayloadAttributesV4 + err = json.Unmarshal(data.Data.PayloadAttributes, &payloadAttributes) + if err != nil { + return err + } + e.Data.V4 = &payloadAttributes default: return errors.New("unsupported data version") } diff --git a/api/v1/validatorstate_test.go b/api/v1/validatorstate_test.go index 22153139..0855e9c6 100644 --- a/api/v1/validatorstate_test.go +++ b/api/v1/validatorstate_test.go @@ -436,7 +436,7 @@ func TestMarshalJSON(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { require.NotPanics(t, func() { - res, err := json.Marshal(&test.state) //test.state.MarshalJSON() + res, err := json.Marshal(&test.state) // test.state.MarshalJSON() test.errFunc(t, err) require.Equal(t, test.expected, res) }) diff --git a/api/versionedblindedbeaconblock.go b/api/versionedblindedbeaconblock.go index cb5384f8..8fee1e7b 100644 --- a/api/versionedblindedbeaconblock.go +++ b/api/versionedblindedbeaconblock.go @@ -17,6 +17,7 @@ import ( apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/phase0" @@ -28,6 +29,7 @@ type VersionedBlindedBeaconBlock struct { Bellatrix *apiv1bellatrix.BlindedBeaconBlock Capella *apiv1capella.BlindedBeaconBlock Deneb *apiv1deneb.BlindedBeaconBlock + Electra *apiv1electra.BlindedBeaconBlock } // IsEmpty returns true if there is no block. @@ -56,6 +58,12 @@ func (v *VersionedBlindedBeaconBlock) Slot() (phase0.Slot, error) { } return v.Deneb.Slot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return 0, ErrDataMissing + } + + return v.Electra.Slot, nil default: return 0, ErrUnsupportedVersion } @@ -82,6 +90,12 @@ func (v *VersionedBlindedBeaconBlock) ProposerIndex() (phase0.ValidatorIndex, er } return v.Deneb.ProposerIndex, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return 0, ErrDataMissing + } + + return v.Electra.ProposerIndex, nil default: return 0, ErrUnsupportedVersion } @@ -111,6 +125,13 @@ func (v *VersionedBlindedBeaconBlock) RandaoReveal() (phase0.BLSSignature, error } return v.Deneb.Body.RANDAOReveal, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil { + return phase0.BLSSignature{}, ErrDataMissing + } + + return v.Electra.Body.RANDAOReveal, nil default: return phase0.BLSSignature{}, ErrUnsupportedVersion } @@ -140,35 +161,77 @@ func (v *VersionedBlindedBeaconBlock) Graffiti() ([32]byte, error) { } return v.Deneb.Body.Graffiti, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil { + return [32]byte{}, ErrDataMissing + } + + return v.Electra.Body.Graffiti, nil default: return [32]byte{}, ErrUnsupportedVersion } } // Attestations returns the attestations of the blinded beacon block. -func (v *VersionedBlindedBeaconBlock) Attestations() ([]*phase0.Attestation, error) { +func (v *VersionedBlindedBeaconBlock) Attestations() ([]spec.VersionedAttestation, error) { switch v.Version { case spec.DataVersionBellatrix: - if v.Bellatrix == nil || - v.Bellatrix.Body == nil { + if v.Bellatrix == nil || v.Bellatrix.Body == nil { return nil, ErrDataMissing } - return v.Bellatrix.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Bellatrix.Body.Attestations)) + for i, attestation := range v.Bellatrix.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionCapella: - if v.Capella == nil || - v.Capella.Body == nil { + if v.Capella == nil || v.Capella.Body == nil { return nil, ErrDataMissing } - return v.Capella.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Capella.Body.Attestations)) + for i, attestation := range v.Capella.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionDeneb: - if v.Deneb == nil || - v.Deneb.Body == nil { + if v.Deneb == nil || v.Deneb.Body == nil { + return nil, ErrDataMissing + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Deneb.Body.Attestations)) + for i, attestation := range v.Deneb.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case spec.DataVersionElectra: + if v.Electra == nil || v.Electra.Body == nil { return nil, ErrDataMissing } - return v.Deneb.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Electra.Body.Attestations)) + for i, attestation := range v.Electra.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, ErrUnsupportedVersion } @@ -195,6 +258,12 @@ func (v *VersionedBlindedBeaconBlock) Root() (phase0.Root, error) { } return v.Deneb.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -221,6 +290,12 @@ func (v *VersionedBlindedBeaconBlock) BodyRoot() (phase0.Root, error) { } return v.Deneb.Body.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Body.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -247,6 +322,12 @@ func (v *VersionedBlindedBeaconBlock) ParentRoot() (phase0.Root, error) { } return v.Deneb.ParentRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.ParentRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -273,6 +354,12 @@ func (v *VersionedBlindedBeaconBlock) StateRoot() (phase0.Root, error) { } return v.Deneb.StateRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.StateRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -305,6 +392,14 @@ func (v *VersionedBlindedBeaconBlock) TransactionsRoot() (phase0.Root, error) { } return v.Deneb.Body.ExecutionPayloadHeader.TransactionsRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil || + v.Electra.Body.ExecutionPayloadHeader == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Body.ExecutionPayloadHeader.TransactionsRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -337,6 +432,14 @@ func (v *VersionedBlindedBeaconBlock) FeeRecipient() (bellatrix.ExecutionAddress } return v.Deneb.Body.ExecutionPayloadHeader.FeeRecipient, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil || + v.Electra.Body.ExecutionPayloadHeader == nil { + return bellatrix.ExecutionAddress{}, ErrDataMissing + } + + return v.Electra.Body.ExecutionPayloadHeader.FeeRecipient, nil default: return bellatrix.ExecutionAddress{}, ErrUnsupportedVersion } @@ -369,6 +472,14 @@ func (v *VersionedBlindedBeaconBlock) Timestamp() (uint64, error) { } return v.Deneb.Body.ExecutionPayloadHeader.Timestamp, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil || + v.Electra.Body.ExecutionPayloadHeader == nil { + return 0, ErrDataMissing + } + + return v.Electra.Body.ExecutionPayloadHeader.Timestamp, nil default: return 0, ErrUnsupportedVersion } @@ -395,6 +506,12 @@ func (v *VersionedBlindedBeaconBlock) String() string { } return v.Deneb.String() + case spec.DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } diff --git a/api/versionedblindedbeaconblock_ssz.go b/api/versionedblindedbeaconblock_ssz.go index 845b8b3e..b1e139f5 100644 --- a/api/versionedblindedbeaconblock_ssz.go +++ b/api/versionedblindedbeaconblock_ssz.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: 08636ad5a6add4599e6b010f2318fc2d68a65ba8f1b4397b0424fa97b0cbc352 +// Hash: b276d9aad6cbae7805608c3adb43fa696d04a0acf5d2b67b74e90f4fdcbd4cc0 // Version: 0.1.3 package api @@ -7,6 +7,7 @@ import ( apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec" ssz "github.com/ferranbt/fastssz" ) @@ -19,7 +20,7 @@ func (v *VersionedBlindedBeaconBlock) MarshalSSZ() ([]byte, error) { // MarshalSSZTo ssz marshals the VersionedBlindedBeaconBlock object to a target array func (v *VersionedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err error) { dst = buf - offset := int(20) + offset := int(24) // Field (0) 'Version' dst = ssz.MarshalUint64(dst, uint64(v.Version)) @@ -45,6 +46,13 @@ func (v *VersionedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err } offset += v.Deneb.SizeSSZ() + // Offset (4) 'Electra' + dst = ssz.WriteOffset(dst, offset) + if v.Electra == nil { + v.Electra = new(apiv1electra.BlindedBeaconBlock) + } + offset += v.Electra.SizeSSZ() + // Field (1) 'Bellatrix' if dst, err = v.Bellatrix.MarshalSSZTo(dst); err != nil { return @@ -60,6 +68,11 @@ func (v *VersionedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err return } + // Field (4) 'Electra' + if dst, err = v.Electra.MarshalSSZTo(dst); err != nil { + return + } + return } @@ -67,12 +80,12 @@ func (v *VersionedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err func (v *VersionedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { var err error size := uint64(len(buf)) - if size < 20 { + if size < 24 { return ssz.ErrSize } tail := buf - var o1, o2, o3 uint64 + var o1, o2, o3, o4 uint64 // Field (0) 'Version' v.Version = spec.DataVersion(ssz.UnmarshallUint64(buf[0:8])) @@ -82,7 +95,7 @@ func (v *VersionedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { return ssz.ErrOffset } - if o1 < 20 { + if o1 < 24 { return ssz.ErrInvalidVariableOffset } @@ -96,6 +109,11 @@ func (v *VersionedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { return ssz.ErrOffset } + // Offset (4) 'Electra' + if o4 = ssz.ReadOffset(buf[20:24]); o4 > size || o3 > o4 { + return ssz.ErrOffset + } + // Field (1) 'Bellatrix' { buf = tail[o1:o2] @@ -120,7 +138,7 @@ func (v *VersionedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { // Field (3) 'Deneb' { - buf = tail[o3:] + buf = tail[o3:o4] if v.Deneb == nil { v.Deneb = new(apiv1deneb.BlindedBeaconBlock) } @@ -128,12 +146,23 @@ func (v *VersionedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { return err } } + + // Field (4) 'Electra' + { + buf = tail[o4:] + if v.Electra == nil { + v.Electra = new(apiv1electra.BlindedBeaconBlock) + } + if err = v.Electra.UnmarshalSSZ(buf); err != nil { + return err + } + } return err } // SizeSSZ returns the ssz encoded size in bytes for the VersionedBlindedBeaconBlock object func (v *VersionedBlindedBeaconBlock) SizeSSZ() (size int) { - size = 20 + size = 24 // Field (1) 'Bellatrix' if v.Bellatrix == nil { @@ -153,6 +182,12 @@ func (v *VersionedBlindedBeaconBlock) SizeSSZ() (size int) { } size += v.Deneb.SizeSSZ() + // Field (4) 'Electra' + if v.Electra == nil { + v.Electra = new(apiv1electra.BlindedBeaconBlock) + } + size += v.Electra.SizeSSZ() + return } @@ -183,6 +218,11 @@ func (v *VersionedBlindedBeaconBlock) HashTreeRootWith(hh ssz.HashWalker) (err e return } + // Field (4) 'Electra' + if err = v.Electra.HashTreeRootWith(hh); err != nil { + return + } + hh.Merkleize(indx) return } diff --git a/api/versionedblindedproposal.go b/api/versionedblindedproposal.go index f1325403..f3618854 100644 --- a/api/versionedblindedproposal.go +++ b/api/versionedblindedproposal.go @@ -17,6 +17,7 @@ import ( apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/phase0" @@ -28,6 +29,7 @@ type VersionedBlindedProposal struct { Bellatrix *apiv1bellatrix.BlindedBeaconBlock Capella *apiv1capella.BlindedBeaconBlock Deneb *apiv1deneb.BlindedBeaconBlock + Electra *apiv1electra.BlindedBeaconBlock } // IsEmpty returns true if there is no proposal. @@ -56,6 +58,12 @@ func (v *VersionedBlindedProposal) Slot() (phase0.Slot, error) { } return v.Deneb.Slot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return 0, ErrDataMissing + } + + return v.Electra.Slot, nil default: return 0, ErrUnsupportedVersion } @@ -82,6 +90,12 @@ func (v *VersionedBlindedProposal) ProposerIndex() (phase0.ValidatorIndex, error } return v.Deneb.ProposerIndex, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return 0, ErrDataMissing + } + + return v.Electra.ProposerIndex, nil default: return 0, ErrUnsupportedVersion } @@ -111,6 +125,13 @@ func (v *VersionedBlindedProposal) RandaoReveal() (phase0.BLSSignature, error) { } return v.Deneb.Body.RANDAOReveal, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil { + return phase0.BLSSignature{}, ErrDataMissing + } + + return v.Electra.Body.RANDAOReveal, nil default: return phase0.BLSSignature{}, ErrUnsupportedVersion } @@ -140,35 +161,77 @@ func (v *VersionedBlindedProposal) Graffiti() ([32]byte, error) { } return v.Deneb.Body.Graffiti, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil { + return [32]byte{}, ErrDataMissing + } + + return v.Electra.Body.Graffiti, nil default: return [32]byte{}, ErrUnsupportedVersion } } // Attestations returns the attestations of the blinded proposal. -func (v *VersionedBlindedProposal) Attestations() ([]*phase0.Attestation, error) { +func (v *VersionedBlindedProposal) Attestations() ([]spec.VersionedAttestation, error) { switch v.Version { case spec.DataVersionBellatrix: - if v.Bellatrix == nil || - v.Bellatrix.Body == nil { + if v.Bellatrix == nil || v.Bellatrix.Body == nil { return nil, ErrDataMissing } - return v.Bellatrix.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Bellatrix.Body.Attestations)) + for i, attestation := range v.Bellatrix.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionCapella: - if v.Capella == nil || - v.Capella.Body == nil { + if v.Capella == nil || v.Capella.Body == nil { return nil, ErrDataMissing } - return v.Capella.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Capella.Body.Attestations)) + for i, attestation := range v.Capella.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionDeneb: - if v.Deneb == nil || - v.Deneb.Body == nil { + if v.Deneb == nil || v.Deneb.Body == nil { + return nil, ErrDataMissing + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Deneb.Body.Attestations)) + for i, attestation := range v.Deneb.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case spec.DataVersionElectra: + if v.Electra == nil || v.Electra.Body == nil { return nil, ErrDataMissing } - return v.Deneb.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Electra.Body.Attestations)) + for i, attestation := range v.Electra.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, ErrUnsupportedVersion } @@ -195,6 +258,12 @@ func (v *VersionedBlindedProposal) Root() (phase0.Root, error) { } return v.Deneb.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -224,6 +293,13 @@ func (v *VersionedBlindedProposal) BodyRoot() (phase0.Root, error) { } return v.Deneb.Body.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Body.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -250,6 +326,12 @@ func (v *VersionedBlindedProposal) ParentRoot() (phase0.Root, error) { } return v.Deneb.ParentRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.ParentRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -276,6 +358,12 @@ func (v *VersionedBlindedProposal) StateRoot() (phase0.Root, error) { } return v.Deneb.StateRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.StateRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -308,6 +396,14 @@ func (v *VersionedBlindedProposal) TransactionsRoot() (phase0.Root, error) { } return v.Deneb.Body.ExecutionPayloadHeader.TransactionsRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil || + v.Electra.Body.ExecutionPayloadHeader == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Body.ExecutionPayloadHeader.TransactionsRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -340,6 +436,14 @@ func (v *VersionedBlindedProposal) FeeRecipient() (bellatrix.ExecutionAddress, e } return v.Deneb.Body.ExecutionPayloadHeader.FeeRecipient, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil || + v.Electra.Body.ExecutionPayloadHeader == nil { + return bellatrix.ExecutionAddress{}, ErrDataMissing + } + + return v.Electra.Body.ExecutionPayloadHeader.FeeRecipient, nil default: return bellatrix.ExecutionAddress{}, ErrUnsupportedVersion } @@ -372,6 +476,14 @@ func (v *VersionedBlindedProposal) Timestamp() (uint64, error) { } return v.Deneb.Body.ExecutionPayloadHeader.Timestamp, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Body == nil || + v.Electra.Body.ExecutionPayloadHeader == nil { + return 0, ErrDataMissing + } + + return v.Electra.Body.ExecutionPayloadHeader.Timestamp, nil default: return 0, ErrUnsupportedVersion } @@ -398,6 +510,12 @@ func (v *VersionedBlindedProposal) String() string { } return v.Deneb.String() + case spec.DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } diff --git a/api/versionedblockrequest.go b/api/versionedblockrequest.go index 3b8fc0c9..40bbaa56 100644 --- a/api/versionedblockrequest.go +++ b/api/versionedblockrequest.go @@ -19,6 +19,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -28,6 +29,7 @@ type VersionedBlockRequest struct { Bellatrix *bellatrix.SignedBeaconBlock Capella *capella.SignedBeaconBlock Deneb *deneb.SignedBeaconBlock + Electra *electra.SignedBeaconBlock } // Slot returns the slot of the signed beacon block. @@ -54,6 +56,13 @@ func (v *VersionedBlockRequest) Slot() (phase0.Slot, error) { } return v.Deneb.Message.Slot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.Slot, nil default: return 0, ErrUnsupportedVersion } @@ -89,13 +98,22 @@ func (v *VersionedBlockRequest) ExecutionBlockHash() (phase0.Hash32, error) { } return v.Deneb.Message.Body.ExecutionPayload.BlockHash, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil || + v.Electra.Message.Body.ExecutionPayload == nil { + return phase0.Hash32{}, ErrDataMissing + } + + return v.Electra.Message.Body.ExecutionPayload.BlockHash, nil default: return phase0.Hash32{}, ErrUnsupportedVersion } } // Attestations returns the attestations of the beacon block. -func (v *VersionedBlockRequest) Attestations() ([]*phase0.Attestation, error) { +func (v *VersionedBlockRequest) Attestations() ([]spec.VersionedAttestation, error) { switch v.Version { case spec.DataVersionBellatrix: if v.Bellatrix == nil || @@ -104,7 +122,15 @@ func (v *VersionedBlockRequest) Attestations() ([]*phase0.Attestation, error) { return nil, ErrDataMissing } - return v.Bellatrix.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Bellatrix.Message.Body.Attestations)) + for i, attestation := range v.Bellatrix.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionCapella: if v.Capella == nil || v.Capella.Message == nil || @@ -112,7 +138,15 @@ func (v *VersionedBlockRequest) Attestations() ([]*phase0.Attestation, error) { return nil, ErrDataMissing } - return v.Capella.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Capella.Message.Body.Attestations)) + for i, attestation := range v.Capella.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionDeneb: if v.Deneb == nil || v.Deneb.Message == nil || @@ -120,7 +154,31 @@ func (v *VersionedBlockRequest) Attestations() ([]*phase0.Attestation, error) { return nil, ErrDataMissing } - return v.Deneb.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Deneb.Message.Body.Attestations)) + for i, attestation := range v.Deneb.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Electra.Message.Body.Attestations)) + for i, attestation := range v.Electra.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, ErrUnsupportedVersion } @@ -150,6 +208,13 @@ func (v *VersionedBlockRequest) Root() (phase0.Root, error) { } return v.Deneb.Message.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -182,6 +247,14 @@ func (v *VersionedBlockRequest) BodyRoot() (phase0.Root, error) { } return v.Deneb.Message.Body.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.Body.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -211,6 +284,13 @@ func (v *VersionedBlockRequest) ParentRoot() (phase0.Root, error) { } return v.Deneb.Message.ParentRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.ParentRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -240,6 +320,13 @@ func (v *VersionedBlockRequest) StateRoot() (phase0.Root, error) { } return v.Deneb.Message.StateRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.StateRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -272,6 +359,14 @@ func (v *VersionedBlockRequest) AttesterSlashings() ([]*phase0.AttesterSlashing, } return v.Deneb.Message.Body.AttesterSlashings, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.AttesterSlashings, nil default: return nil, ErrUnsupportedVersion } @@ -304,6 +399,14 @@ func (v *VersionedBlockRequest) ProposerSlashings() ([]*phase0.ProposerSlashing, } return v.Deneb.Message.Body.ProposerSlashings, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.ProposerSlashings, nil default: return nil, ErrUnsupportedVersion } @@ -336,6 +439,14 @@ func (v *VersionedBlockRequest) SyncAggregate() (*altair.SyncAggregate, error) { } return v.Deneb.Message.Body.SyncAggregate, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.SyncAggregate, nil default: return nil, ErrUnsupportedVersion } @@ -362,6 +473,12 @@ func (v *VersionedBlockRequest) String() string { } return v.Deneb.String() + case spec.DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unsupported version" } diff --git a/api/versionedproposal.go b/api/versionedproposal.go index 5d565106..60fe1a74 100644 --- a/api/versionedproposal.go +++ b/api/versionedproposal.go @@ -16,6 +16,8 @@ package api import ( "math/big" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" + apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" @@ -41,6 +43,8 @@ type VersionedProposal struct { CapellaBlinded *apiv1capella.BlindedBeaconBlock Deneb *apiv1deneb.BlockContents DenebBlinded *apiv1deneb.BlindedBeaconBlock + Electra *apiv1electra.BlockContents + ElectraBlinded *apiv1electra.BlindedBeaconBlock } // IsEmpty returns true if there is no proposal. @@ -52,7 +56,9 @@ func (v *VersionedProposal) IsEmpty() bool { v.Capella == nil && v.CapellaBlinded == nil && v.Deneb == nil && - v.DenebBlinded == nil + v.DenebBlinded == nil && + v.Electra == nil && + v.ElectraBlinded == nil } // BodyRoot returns the body root of the proposal. @@ -84,6 +90,12 @@ func (v *VersionedProposal) BodyRoot() (phase0.Root, error) { } return v.Deneb.Block.Body.HashTreeRoot() + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Body.HashTreeRoot() + } + + return v.Electra.Block.Body.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -118,6 +130,12 @@ func (v *VersionedProposal) ParentRoot() (phase0.Root, error) { } return v.Deneb.Block.ParentRoot, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.ParentRoot, nil + } + + return v.Electra.Block.ParentRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -152,6 +170,12 @@ func (v *VersionedProposal) ProposerIndex() (phase0.ValidatorIndex, error) { } return v.Deneb.Block.ProposerIndex, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.ProposerIndex, nil + } + + return v.Electra.Block.ProposerIndex, nil default: return 0, ErrUnsupportedVersion } @@ -186,6 +210,12 @@ func (v *VersionedProposal) Root() (phase0.Root, error) { } return v.Deneb.Block.HashTreeRoot() + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.HashTreeRoot() + } + + return v.Electra.Block.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -220,6 +250,12 @@ func (v *VersionedProposal) Slot() (phase0.Slot, error) { } return v.Deneb.Block.Slot, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Slot, nil + } + + return v.Electra.Block.Slot, nil default: return 0, ErrUnsupportedVersion } @@ -254,40 +290,132 @@ func (v *VersionedProposal) StateRoot() (phase0.Root, error) { } return v.Deneb.Block.StateRoot, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.StateRoot, nil + } + + return v.Electra.Block.StateRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } } // Attestations returns the attestations of the proposal. -func (v *VersionedProposal) Attestations() ([]*phase0.Attestation, error) { +func (v *VersionedProposal) Attestations() ([]spec.VersionedAttestation, error) { if !v.bodyPresent() { return nil, ErrDataMissing } switch v.Version { case spec.DataVersionPhase0: - return v.Phase0.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Phase0.Body.Attestations)) + for i, attestation := range v.Phase0.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionPhase0, + Phase0: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionAltair: - return v.Altair.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Altair.Body.Attestations)) + for i, attestation := range v.Altair.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionAltair, + Altair: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionBellatrix: if v.Blinded { - return v.BellatrixBlinded.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.BellatrixBlinded.Body.Attestations)) + for i, attestation := range v.BellatrixBlinded.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil } - return v.Bellatrix.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Bellatrix.Body.Attestations)) + for i, attestation := range v.Bellatrix.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionCapella: if v.Blinded { - return v.CapellaBlinded.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.CapellaBlinded.Body.Attestations)) + for i, attestation := range v.CapellaBlinded.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Capella.Body.Attestations)) + for i, attestation := range v.Capella.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } } - return v.Capella.Body.Attestations, nil + return versionedAttestations, nil case spec.DataVersionDeneb: if v.Blinded { - return v.DenebBlinded.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.DenebBlinded.Body.Attestations)) + for i, attestation := range v.DenebBlinded.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Deneb.Block.Body.Attestations)) + for i, attestation := range v.Deneb.Block.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case spec.DataVersionElectra: + if v.Blinded { + versionedAttestations := make([]spec.VersionedAttestation, len(v.ElectraBlinded.Body.Attestations)) + for i, attestation := range v.ElectraBlinded.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Electra.Block.Body.Attestations)) + for i, attestation := range v.Electra.Block.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } } - return v.Deneb.Block.Body.Attestations, nil + return versionedAttestations, nil default: return nil, ErrUnsupportedVersion } @@ -322,6 +450,12 @@ func (v *VersionedProposal) Graffiti() ([32]byte, error) { } return v.Deneb.Block.Body.Graffiti, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Body.Graffiti, nil + } + + return v.Electra.Block.Body.Graffiti, nil default: return [32]byte{}, ErrUnsupportedVersion } @@ -356,6 +490,12 @@ func (v *VersionedProposal) RandaoReveal() (phase0.BLSSignature, error) { } return v.Deneb.Block.Body.RANDAOReveal, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Body.RANDAOReveal, nil + } + + return v.Electra.Block.Body.RANDAOReveal, nil default: return phase0.BLSSignature{}, ErrUnsupportedVersion } @@ -386,6 +526,12 @@ func (v *VersionedProposal) Transactions() ([]bellatrix.Transaction, error) { } return v.Deneb.Block.Body.ExecutionPayload.Transactions, nil + case spec.DataVersionElectra: + if v.Blinded { + return nil, ErrDataMissing + } + + return v.Electra.Block.Body.ExecutionPayload.Transactions, nil default: return nil, ErrUnsupportedVersion } @@ -416,6 +562,12 @@ func (v *VersionedProposal) FeeRecipient() (bellatrix.ExecutionAddress, error) { } return v.Deneb.Block.Body.ExecutionPayload.FeeRecipient, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Body.ExecutionPayloadHeader.FeeRecipient, nil + } + + return v.Electra.Block.Body.ExecutionPayload.FeeRecipient, nil default: return bellatrix.ExecutionAddress{}, ErrUnsupportedVersion } @@ -446,6 +598,12 @@ func (v *VersionedProposal) Timestamp() (uint64, error) { } return v.Deneb.Block.Body.ExecutionPayload.Timestamp, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Body.ExecutionPayloadHeader.Timestamp, nil + } + + return v.Electra.Block.Body.ExecutionPayload.Timestamp, nil default: return 0, ErrUnsupportedVersion } @@ -464,6 +622,12 @@ func (v *VersionedProposal) Blobs() ([]deneb.Blob, error) { } return v.Deneb.Blobs, nil + case spec.DataVersionElectra: + if v.Blinded { + return nil, ErrDataMissing + } + + return v.Electra.Blobs, nil default: return nil, ErrUnsupportedVersion } @@ -482,6 +646,12 @@ func (v *VersionedProposal) KZGProofs() ([]deneb.KZGProof, error) { } return v.Deneb.KZGProofs, nil + case spec.DataVersionElectra: + if v.Blinded { + return nil, ErrDataMissing + } + + return v.Electra.KZGProofs, nil default: return nil, ErrUnsupportedVersion } @@ -533,6 +703,12 @@ func (v *VersionedProposal) String() string { } return v.Deneb.String() + case spec.DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } @@ -562,6 +738,12 @@ func (v *VersionedProposal) proposalPresent() bool { } return v.Deneb.Block != nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded != nil + } + + return v.Electra.Block != nil } return false @@ -591,6 +773,12 @@ func (v *VersionedProposal) bodyPresent() bool { } return v.Deneb != nil && v.Deneb.Block != nil && v.Deneb.Block.Body != nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded != nil && v.ElectraBlinded.Body != nil + } + + return v.Electra != nil && v.Electra.Block != nil && v.Electra.Block.Body != nil } return false @@ -620,6 +808,12 @@ func (v *VersionedProposal) payloadPresent() bool { } return v.Deneb != nil && v.Deneb.Block != nil && v.Deneb.Block.Body != nil && v.Deneb.Block.Body.ExecutionPayload != nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded != nil && v.ElectraBlinded.Body != nil && v.ElectraBlinded.Body.ExecutionPayloadHeader != nil + } + + return v.Electra != nil && v.Electra.Block != nil && v.Electra.Block.Body != nil && v.Electra.Block.Body.ExecutionPayload != nil } return false diff --git a/api/versionedsignedblindedbeaconblock.go b/api/versionedsignedblindedbeaconblock.go index ea15cb7d..75713ca2 100644 --- a/api/versionedsignedblindedbeaconblock.go +++ b/api/versionedsignedblindedbeaconblock.go @@ -17,6 +17,7 @@ import ( apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -27,6 +28,7 @@ type VersionedSignedBlindedBeaconBlock struct { Bellatrix *apiv1bellatrix.SignedBlindedBeaconBlock Capella *apiv1capella.SignedBlindedBeaconBlock Deneb *apiv1deneb.SignedBlindedBeaconBlock + Electra *apiv1electra.SignedBlindedBeaconBlock } // Slot returns the slot of the signed beacon block. @@ -53,13 +55,20 @@ func (v *VersionedSignedBlindedBeaconBlock) Slot() (phase0.Slot, error) { } return v.Deneb.Message.Slot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.Slot, nil default: return 0, ErrUnsupportedVersion } } -// Attestations returns the attestations of the beacon block. -func (v *VersionedSignedBlindedBeaconBlock) Attestations() ([]*phase0.Attestation, error) { +// Attestations returns the attestations of the signed blinded beacon block. +func (v *VersionedSignedBlindedBeaconBlock) Attestations() ([]spec.VersionedAttestation, error) { switch v.Version { case spec.DataVersionBellatrix: if v.Bellatrix == nil || @@ -68,7 +77,15 @@ func (v *VersionedSignedBlindedBeaconBlock) Attestations() ([]*phase0.Attestatio return nil, ErrDataMissing } - return v.Bellatrix.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Bellatrix.Message.Body.Attestations)) + for i, attestation := range v.Bellatrix.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionCapella: if v.Capella == nil || v.Capella.Message == nil || @@ -76,7 +93,15 @@ func (v *VersionedSignedBlindedBeaconBlock) Attestations() ([]*phase0.Attestatio return nil, ErrDataMissing } - return v.Capella.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Capella.Message.Body.Attestations)) + for i, attestation := range v.Capella.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionDeneb: if v.Deneb == nil || v.Deneb.Message == nil || @@ -84,7 +109,31 @@ func (v *VersionedSignedBlindedBeaconBlock) Attestations() ([]*phase0.Attestatio return nil, ErrDataMissing } - return v.Deneb.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Deneb.Message.Body.Attestations)) + for i, attestation := range v.Deneb.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Electra.Message.Body.Attestations)) + for i, attestation := range v.Electra.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, ErrUnsupportedVersion } @@ -111,6 +160,12 @@ func (v *VersionedSignedBlindedBeaconBlock) Root() (phase0.Root, error) { } return v.Deneb.Message.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -137,6 +192,12 @@ func (v *VersionedSignedBlindedBeaconBlock) BodyRoot() (phase0.Root, error) { } return v.Deneb.Message.Body.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.Body.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -163,6 +224,12 @@ func (v *VersionedSignedBlindedBeaconBlock) ParentRoot() (phase0.Root, error) { } return v.Deneb.Message.ParentRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.ParentRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -189,6 +256,12 @@ func (v *VersionedSignedBlindedBeaconBlock) StateRoot() (phase0.Root, error) { } return v.Deneb.Message.StateRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.StateRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -215,6 +288,12 @@ func (v *VersionedSignedBlindedBeaconBlock) AttesterSlashings() ([]*phase0.Attes } return v.Deneb.Message.Body.AttesterSlashings, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.AttesterSlashings, nil default: return nil, ErrUnsupportedVersion } @@ -241,6 +320,12 @@ func (v *VersionedSignedBlindedBeaconBlock) ProposerSlashings() ([]*phase0.Propo } return v.Deneb.Message.Body.ProposerSlashings, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.ProposerSlashings, nil default: return nil, ErrUnsupportedVersion } @@ -270,6 +355,13 @@ func (v *VersionedSignedBlindedBeaconBlock) ProposerIndex() (phase0.ValidatorInd } return v.Deneb.Message.ProposerIndex, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.ProposerIndex, nil default: return 0, ErrUnsupportedVersion } @@ -305,6 +397,15 @@ func (v *VersionedSignedBlindedBeaconBlock) ExecutionBlockHash() (phase0.Hash32, } return v.Deneb.Message.Body.ExecutionPayloadHeader.BlockHash, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil || + v.Electra.Message.Body.ExecutionPayloadHeader == nil { + return phase0.Hash32{}, ErrDataMissing + } + + return v.Electra.Message.Body.ExecutionPayloadHeader.BlockHash, nil default: return phase0.Hash32{}, ErrUnsupportedVersion } @@ -340,6 +441,15 @@ func (v *VersionedSignedBlindedBeaconBlock) ExecutionBlockNumber() (uint64, erro } return v.Deneb.Message.Body.ExecutionPayloadHeader.BlockNumber, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil || + v.Electra.Message.Body.ExecutionPayloadHeader == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.Body.ExecutionPayloadHeader.BlockNumber, nil default: return 0, ErrUnsupportedVersion } @@ -366,6 +476,12 @@ func (v *VersionedSignedBlindedBeaconBlock) Signature() (phase0.BLSSignature, er } return v.Deneb.Signature, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.BLSSignature{}, ErrDataMissing + } + + return v.Electra.Signature, nil default: return phase0.BLSSignature{}, ErrUnsupportedVersion } diff --git a/api/versionedsignedblindedbeaconblock_ssz.go b/api/versionedsignedblindedbeaconblock_ssz.go index d55c1f38..93836e39 100644 --- a/api/versionedsignedblindedbeaconblock_ssz.go +++ b/api/versionedsignedblindedbeaconblock_ssz.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: 08636ad5a6add4599e6b010f2318fc2d68a65ba8f1b4397b0424fa97b0cbc352 +// Hash: b276d9aad6cbae7805608c3adb43fa696d04a0acf5d2b67b74e90f4fdcbd4cc0 // Version: 0.1.3 package api @@ -7,6 +7,7 @@ import ( apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec" ssz "github.com/ferranbt/fastssz" ) @@ -19,7 +20,7 @@ func (v *VersionedSignedBlindedBeaconBlock) MarshalSSZ() ([]byte, error) { // MarshalSSZTo ssz marshals the VersionedSignedBlindedBeaconBlock object to a target array func (v *VersionedSignedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err error) { dst = buf - offset := int(20) + offset := int(24) // Field (0) 'Version' dst = ssz.MarshalUint64(dst, uint64(v.Version)) @@ -45,6 +46,13 @@ func (v *VersionedSignedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte } offset += v.Deneb.SizeSSZ() + // Offset (4) 'Electra' + dst = ssz.WriteOffset(dst, offset) + if v.Electra == nil { + v.Electra = new(apiv1electra.SignedBlindedBeaconBlock) + } + offset += v.Electra.SizeSSZ() + // Field (1) 'Bellatrix' if dst, err = v.Bellatrix.MarshalSSZTo(dst); err != nil { return @@ -60,6 +68,11 @@ func (v *VersionedSignedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte return } + // Field (4) 'Electra' + if dst, err = v.Electra.MarshalSSZTo(dst); err != nil { + return + } + return } @@ -67,12 +80,12 @@ func (v *VersionedSignedBlindedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte func (v *VersionedSignedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { var err error size := uint64(len(buf)) - if size < 20 { + if size < 24 { return ssz.ErrSize } tail := buf - var o1, o2, o3 uint64 + var o1, o2, o3, o4 uint64 // Field (0) 'Version' v.Version = spec.DataVersion(ssz.UnmarshallUint64(buf[0:8])) @@ -82,7 +95,7 @@ func (v *VersionedSignedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { return ssz.ErrOffset } - if o1 < 20 { + if o1 < 24 { return ssz.ErrInvalidVariableOffset } @@ -96,6 +109,11 @@ func (v *VersionedSignedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { return ssz.ErrOffset } + // Offset (4) 'Electra' + if o4 = ssz.ReadOffset(buf[20:24]); o4 > size || o3 > o4 { + return ssz.ErrOffset + } + // Field (1) 'Bellatrix' { buf = tail[o1:o2] @@ -120,7 +138,7 @@ func (v *VersionedSignedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { // Field (3) 'Deneb' { - buf = tail[o3:] + buf = tail[o3:o4] if v.Deneb == nil { v.Deneb = new(apiv1deneb.SignedBlindedBeaconBlock) } @@ -128,12 +146,23 @@ func (v *VersionedSignedBlindedBeaconBlock) UnmarshalSSZ(buf []byte) error { return err } } + + // Field (4) 'Electra' + { + buf = tail[o4:] + if v.Electra == nil { + v.Electra = new(apiv1electra.SignedBlindedBeaconBlock) + } + if err = v.Electra.UnmarshalSSZ(buf); err != nil { + return err + } + } return err } // SizeSSZ returns the ssz encoded size in bytes for the VersionedSignedBlindedBeaconBlock object func (v *VersionedSignedBlindedBeaconBlock) SizeSSZ() (size int) { - size = 20 + size = 24 // Field (1) 'Bellatrix' if v.Bellatrix == nil { @@ -153,6 +182,12 @@ func (v *VersionedSignedBlindedBeaconBlock) SizeSSZ() (size int) { } size += v.Deneb.SizeSSZ() + // Field (4) 'Electra' + if v.Electra == nil { + v.Electra = new(apiv1electra.SignedBlindedBeaconBlock) + } + size += v.Electra.SizeSSZ() + return } @@ -183,6 +218,11 @@ func (v *VersionedSignedBlindedBeaconBlock) HashTreeRootWith(hh ssz.HashWalker) return } + // Field (4) 'Electra' + if err = v.Electra.HashTreeRootWith(hh); err != nil { + return + } + hh.Merkleize(indx) return } diff --git a/api/versionedsignedblindedproposal.go b/api/versionedsignedblindedproposal.go index 32cfe2f1..c1353d17 100644 --- a/api/versionedsignedblindedproposal.go +++ b/api/versionedsignedblindedproposal.go @@ -17,6 +17,7 @@ import ( apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -27,6 +28,7 @@ type VersionedSignedBlindedProposal struct { Bellatrix *apiv1bellatrix.SignedBlindedBeaconBlock Capella *apiv1capella.SignedBlindedBeaconBlock Deneb *apiv1deneb.SignedBlindedBeaconBlock + Electra *apiv1electra.SignedBlindedBeaconBlock } // Slot returns the slot of the signed blinded proposal. @@ -53,13 +55,20 @@ func (v *VersionedSignedBlindedProposal) Slot() (phase0.Slot, error) { } return v.Deneb.Message.Slot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.Slot, nil default: return 0, ErrUnsupportedVersion } } -// Attestations returns the attestations of the blinded proposal. -func (v *VersionedSignedBlindedProposal) Attestations() ([]*phase0.Attestation, error) { +// Attestations returns the attestations of the signed blinded proposal. +func (v *VersionedSignedBlindedProposal) Attestations() ([]spec.VersionedAttestation, error) { switch v.Version { case spec.DataVersionBellatrix: if v.Bellatrix == nil || @@ -68,7 +77,15 @@ func (v *VersionedSignedBlindedProposal) Attestations() ([]*phase0.Attestation, return nil, ErrDataMissing } - return v.Bellatrix.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Bellatrix.Message.Body.Attestations)) + for i, attestation := range v.Bellatrix.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionCapella: if v.Capella == nil || v.Capella.Message == nil || @@ -76,7 +93,15 @@ func (v *VersionedSignedBlindedProposal) Attestations() ([]*phase0.Attestation, return nil, ErrDataMissing } - return v.Capella.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Capella.Message.Body.Attestations)) + for i, attestation := range v.Capella.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case spec.DataVersionDeneb: if v.Deneb == nil || v.Deneb.Message == nil || @@ -84,7 +109,31 @@ func (v *VersionedSignedBlindedProposal) Attestations() ([]*phase0.Attestation, return nil, ErrDataMissing } - return v.Deneb.Message.Body.Attestations, nil + versionedAttestations := make([]spec.VersionedAttestation, len(v.Deneb.Message.Body.Attestations)) + for i, attestation := range v.Deneb.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + versionedAttestations := make([]spec.VersionedAttestation, len(v.Electra.Message.Body.Attestations)) + for i, attestation := range v.Electra.Message.Body.Attestations { + versionedAttestations[i] = spec.VersionedAttestation{ + Version: spec.DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, ErrUnsupportedVersion } @@ -111,6 +160,12 @@ func (v *VersionedSignedBlindedProposal) Root() (phase0.Root, error) { } return v.Deneb.Message.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -139,6 +194,14 @@ func (v *VersionedSignedBlindedProposal) BodyRoot() (phase0.Root, error) { } return v.Deneb.Message.Body.HashTreeRoot() + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.Body.HashTreeRoot() default: return phase0.Root{}, ErrUnsupportedVersion } @@ -166,6 +229,13 @@ func (v *VersionedSignedBlindedProposal) ParentRoot() (phase0.Root, error) { } return v.Deneb.Message.ParentRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.ParentRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -193,6 +263,13 @@ func (v *VersionedSignedBlindedProposal) StateRoot() (phase0.Root, error) { } return v.Deneb.Message.StateRoot, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return phase0.Root{}, ErrDataMissing + } + + return v.Electra.Message.StateRoot, nil default: return phase0.Root{}, ErrUnsupportedVersion } @@ -221,6 +298,14 @@ func (v *VersionedSignedBlindedProposal) AttesterSlashings() ([]*phase0.Attester } return v.Deneb.Message.Body.AttesterSlashings, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.AttesterSlashings, nil default: return nil, ErrUnsupportedVersion } @@ -249,6 +334,14 @@ func (v *VersionedSignedBlindedProposal) ProposerSlashings() ([]*phase0.Proposer } return v.Deneb.Message.Body.ProposerSlashings, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil { + return nil, ErrDataMissing + } + + return v.Electra.Message.Body.ProposerSlashings, nil default: return nil, ErrUnsupportedVersion } @@ -278,6 +371,13 @@ func (v *VersionedSignedBlindedProposal) ProposerIndex() (phase0.ValidatorIndex, } return v.Deneb.Message.ProposerIndex, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.ProposerIndex, nil default: return 0, ErrUnsupportedVersion } @@ -313,6 +413,15 @@ func (v *VersionedSignedBlindedProposal) ExecutionBlockHash() (phase0.Hash32, er } return v.Deneb.Message.Body.ExecutionPayloadHeader.BlockHash, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil || + v.Electra.Message.Body.ExecutionPayloadHeader == nil { + return phase0.Hash32{}, ErrDataMissing + } + + return v.Electra.Message.Body.ExecutionPayloadHeader.BlockHash, nil default: return phase0.Hash32{}, ErrUnsupportedVersion } @@ -348,6 +457,15 @@ func (v *VersionedSignedBlindedProposal) ExecutionBlockNumber() (uint64, error) } return v.Deneb.Message.Body.ExecutionPayloadHeader.BlockNumber, nil + case spec.DataVersionElectra: + if v.Electra == nil || + v.Electra.Message == nil || + v.Electra.Message.Body == nil || + v.Electra.Message.Body.ExecutionPayloadHeader == nil { + return 0, ErrDataMissing + } + + return v.Electra.Message.Body.ExecutionPayloadHeader.BlockNumber, nil default: return 0, ErrUnsupportedVersion } @@ -374,6 +492,12 @@ func (v *VersionedSignedBlindedProposal) Signature() (phase0.BLSSignature, error } return v.Deneb.Signature, nil + case spec.DataVersionElectra: + if v.Electra == nil { + return phase0.BLSSignature{}, ErrDataMissing + } + + return v.Electra.Signature, nil default: return phase0.BLSSignature{}, ErrUnsupportedVersion } diff --git a/api/versionedsignedproposal.go b/api/versionedsignedproposal.go index 0993661e..6ce45440 100644 --- a/api/versionedsignedproposal.go +++ b/api/versionedsignedproposal.go @@ -17,6 +17,8 @@ import ( "errors" "math/big" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" + apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" apiv1deneb "github.com/attestantio/go-eth2-client/api/v1/deneb" @@ -41,6 +43,8 @@ type VersionedSignedProposal struct { CapellaBlinded *apiv1capella.SignedBlindedBeaconBlock Deneb *apiv1deneb.SignedBlockContents DenebBlinded *apiv1deneb.SignedBlindedBeaconBlock + Electra *apiv1electra.SignedBlockContents + ElectraBlinded *apiv1electra.SignedBlindedBeaconBlock } // AssertPresent throws an error if the expected proposal @@ -76,6 +80,13 @@ func (v *VersionedSignedProposal) AssertPresent() error { if v.DenebBlinded == nil && v.Blinded { return errors.New("blinded deneb proposal not present") } + case spec.DataVersionElectra: + if v.Electra == nil && !v.Blinded { + return errors.New("electra proposal not present") + } + if v.ElectraBlinded == nil && v.Blinded { + return errors.New("blinded electra proposal not present") + } default: return errors.New("unsupported version") } @@ -112,6 +123,12 @@ func (v *VersionedSignedProposal) Slot() (phase0.Slot, error) { } return v.Deneb.SignedBlock.Message.Slot, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Message.Slot, nil + } + + return v.Electra.SignedBlock.Message.Slot, nil default: return 0, ErrUnsupportedVersion } @@ -146,6 +163,12 @@ func (v *VersionedSignedProposal) ProposerIndex() (phase0.ValidatorIndex, error) } return v.Deneb.SignedBlock.Message.ProposerIndex, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Message.ProposerIndex, nil + } + + return v.Electra.SignedBlock.Message.ProposerIndex, nil default: return 0, ErrUnsupportedVersion } @@ -176,6 +199,12 @@ func (v *VersionedSignedProposal) ExecutionBlockHash() (phase0.Hash32, error) { } return v.Deneb.SignedBlock.Message.Body.ExecutionPayload.BlockHash, nil + case spec.DataVersionElectra: + if v.Blinded { + return v.ElectraBlinded.Message.Body.ExecutionPayloadHeader.BlockHash, nil + } + + return v.Electra.SignedBlock.Message.Body.ExecutionPayload.BlockHash, nil default: return phase0.Hash32{}, ErrUnsupportedVersion } @@ -238,6 +267,20 @@ func (v *VersionedSignedProposal) String() string { } return v.Deneb.String() + case spec.DataVersionElectra: + if v.Blinded { + if v.ElectraBlinded == nil { + return "" + } + + return v.ElectraBlinded.String() + } + + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unsupported version" } @@ -284,6 +327,19 @@ func (v *VersionedSignedProposal) assertMessagePresent() error { return ErrDataMissing } } + case spec.DataVersionElectra: + if v.Blinded { + if v.ElectraBlinded == nil || + v.ElectraBlinded.Message == nil { + return ErrDataMissing + } + } else { + if v.Electra == nil || + v.Electra.SignedBlock == nil || + v.Electra.SignedBlock.Message == nil { + return ErrDataMissing + } + } default: return ErrUnsupportedVersion } @@ -346,6 +402,23 @@ func (v *VersionedSignedProposal) assertExecutionPayloadPresent() error { return ErrDataMissing } } + case spec.DataVersionElectra: + if v.Blinded { + if v.ElectraBlinded == nil || + v.ElectraBlinded.Message == nil || + v.ElectraBlinded.Message.Body == nil || + v.ElectraBlinded.Message.Body.ExecutionPayloadHeader == nil { + return ErrDataMissing + } + } else { + if v.Electra == nil || + v.Electra.SignedBlock == nil || + v.Electra.SignedBlock.Message == nil || + v.Electra.SignedBlock.Message.Body == nil || + v.Electra.SignedBlock.Message.Body.ExecutionPayload == nil { + return ErrDataMissing + } + } default: return ErrUnsupportedVersion } diff --git a/api/versionedsignedvalidatorregistration_ssz.go b/api/versionedsignedvalidatorregistration_ssz.go index fc05109d..e4c0219d 100644 --- a/api/versionedsignedvalidatorregistration_ssz.go +++ b/api/versionedsignedvalidatorregistration_ssz.go @@ -1,5 +1,5 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: 08636ad5a6add4599e6b010f2318fc2d68a65ba8f1b4397b0424fa97b0cbc352 +// Hash: b276d9aad6cbae7805608c3adb43fa696d04a0acf5d2b67b74e90f4fdcbd4cc0 // Version: 0.1.3 package api diff --git a/http/beaconstate.go b/http/beaconstate.go index 6e61597b..1dc7fb1c 100644 --- a/http/beaconstate.go +++ b/http/beaconstate.go @@ -19,6 +19,8 @@ import ( "errors" "fmt" + "github.com/attestantio/go-eth2-client/spec/electra" + client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec" @@ -96,6 +98,11 @@ func (*Service) beaconStateFromSSZ(res *httpResponse) (*api.Response[*spec.Versi if err := response.Data.Deneb.UnmarshalSSZ(res.body); err != nil { return nil, errors.Join(errors.New("failed to decode deneb beacon state"), err) } + case spec.DataVersionElectra: + response.Data.Electra = &electra.BeaconState{} + if err := response.Data.Electra.UnmarshalSSZ(res.body); err != nil { + return nil, errors.Join(errors.New("failed to decode electra beacon state"), err) + } default: return nil, fmt.Errorf("unhandled state version %s", res.consensusVersion) } @@ -122,6 +129,8 @@ func (*Service) beaconStateFromJSON(res *httpResponse) (*api.Response[*spec.Vers response.Data.Capella, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &capella.BeaconState{}) case spec.DataVersionDeneb: response.Data.Deneb, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &deneb.BeaconState{}) + case spec.DataVersionElectra: + response.Data.Electra, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &electra.BeaconState{}) default: err = fmt.Errorf("unsupported version %s", res.consensusVersion) } diff --git a/http/blindedproposal.go b/http/blindedproposal.go index 3f4b1443..ea1c6d94 100644 --- a/http/blindedproposal.go +++ b/http/blindedproposal.go @@ -19,6 +19,8 @@ import ( "errors" "fmt" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" + client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" @@ -126,6 +128,11 @@ func (*Service) blindedProposalFromSSZ(res *httpResponse) (*api.Response[*api.Ve if err := response.Data.Deneb.UnmarshalSSZ(res.body); err != nil { return nil, errors.Join(errors.New("failed to decode deneb blinded beacon block proposal"), err) } + case spec.DataVersionElectra: + response.Data.Electra = &apiv1electra.BlindedBeaconBlock{} + if err := response.Data.Electra.UnmarshalSSZ(res.body); err != nil { + return nil, errors.Join(errors.New("failed to decode electra blinded beacon block proposal"), err) + } default: return nil, fmt.Errorf("unhandled block proposal version %s", res.consensusVersion) } @@ -148,6 +155,8 @@ func (*Service) blindedProposalFromJSON(res *httpResponse) (*api.Response[*api.V response.Data.Capella, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &apiv1capella.BlindedBeaconBlock{}) case spec.DataVersionDeneb: response.Data.Deneb, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &apiv1deneb.BlindedBeaconBlock{}) + case spec.DataVersionElectra: + response.Data.Electra, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &apiv1electra.BlindedBeaconBlock{}) default: return nil, fmt.Errorf("unsupported version %s", res.consensusVersion) } diff --git a/http/proposal.go b/http/proposal.go index 4c0f0871..2baf737b 100644 --- a/http/proposal.go +++ b/http/proposal.go @@ -21,6 +21,8 @@ import ( "math/big" "strings" + apiv1electra "github.com/attestantio/go-eth2-client/api/v1/electra" + client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" @@ -158,6 +160,14 @@ func (s *Service) beaconBlockProposalFromSSZ(res *httpResponse) (*api.Response[* response.Data.Deneb = &apiv1deneb.BlockContents{} err = response.Data.Deneb.UnmarshalSSZ(res.body) } + case spec.DataVersionElectra: + if response.Data.Blinded { + response.Data.ElectraBlinded = &apiv1electra.BlindedBeaconBlock{} + err = response.Data.ElectraBlinded.UnmarshalSSZ(res.body) + } else { + response.Data.Electra = &apiv1electra.BlockContents{} + err = response.Data.Electra.UnmarshalSSZ(res.body) + } default: return nil, fmt.Errorf("unhandled block proposal version %s", res.consensusVersion) } @@ -206,6 +216,12 @@ func (s *Service) beaconBlockProposalFromJSON(res *httpResponse) (*api.Response[ } else { response.Data.Deneb, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &apiv1deneb.BlockContents{}) } + case spec.DataVersionElectra: + if response.Data.Blinded { + response.Data.ElectraBlinded, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &apiv1electra.BlindedBeaconBlock{}) + } else { + response.Data.Electra, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &apiv1electra.BlockContents{}) + } default: err = fmt.Errorf("unsupported version %s", res.consensusVersion) } diff --git a/http/signedbeaconblock.go b/http/signedbeaconblock.go index 2070fa92..643f0fcf 100644 --- a/http/signedbeaconblock.go +++ b/http/signedbeaconblock.go @@ -19,6 +19,8 @@ import ( "errors" "fmt" + "github.com/attestantio/go-eth2-client/spec/electra" + client "github.com/attestantio/go-eth2-client" "github.com/attestantio/go-eth2-client/api" "github.com/attestantio/go-eth2-client/spec" @@ -99,6 +101,11 @@ func (*Service) signedBeaconBlockFromSSZ(res *httpResponse) (*api.Response[*spec if err := response.Data.Deneb.UnmarshalSSZ(res.body); err != nil { return nil, errors.Join(errors.New("failed to decode deneb signed block contents"), err) } + case spec.DataVersionElectra: + response.Data.Electra = &electra.SignedBeaconBlock{} + if err := response.Data.Electra.UnmarshalSSZ(res.body); err != nil { + return nil, errors.Join(errors.New("failed to decode electra signed block contents"), err) + } default: return nil, fmt.Errorf("unhandled block version %s", res.consensusVersion) } @@ -125,6 +132,8 @@ func (*Service) signedBeaconBlockFromJSON(res *httpResponse) (*api.Response[*spe response.Data.Capella, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &capella.SignedBeaconBlock{}) case spec.DataVersionDeneb: response.Data.Deneb, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &deneb.SignedBeaconBlock{}) + case spec.DataVersionElectra: + response.Data.Electra, response.Metadata, err = decodeJSONResponse(bytes.NewReader(res.body), &electra.SignedBeaconBlock{}) default: return nil, fmt.Errorf("unhandled version %s", res.consensusVersion) } diff --git a/http/submitbeaconblock.go b/http/submitbeaconblock.go index c168cfd2..f63554c4 100644 --- a/http/submitbeaconblock.go +++ b/http/submitbeaconblock.go @@ -49,6 +49,8 @@ func (s *Service) SubmitBeaconBlock(ctx context.Context, block *spec.VersionedSi specJSON, err = json.Marshal(block.Capella) case spec.DataVersionDeneb: specJSON, err = json.Marshal(block.Deneb) + case spec.DataVersionElectra: + specJSON, err = json.Marshal(block.Electra) default: err = errors.New("unknown block version") } diff --git a/http/submitblindedbeaconblock.go b/http/submitblindedbeaconblock.go index cc893d09..7cf1bc27 100644 --- a/http/submitblindedbeaconblock.go +++ b/http/submitblindedbeaconblock.go @@ -50,6 +50,8 @@ func (s *Service) SubmitBlindedBeaconBlock(ctx context.Context, block *api.Versi specJSON, err = json.Marshal(block.Capella) case spec.DataVersionDeneb: specJSON, err = json.Marshal(block.Deneb) + case spec.DataVersionElectra: + specJSON, err = json.Marshal(block.Electra) default: err = errors.New("unknown block version") } diff --git a/http/submitblindedproposal.go b/http/submitblindedproposal.go index ee1f2354..60e3e703 100644 --- a/http/submitblindedproposal.go +++ b/http/submitblindedproposal.go @@ -53,6 +53,8 @@ func (s *Service) SubmitBlindedProposal(ctx context.Context, specJSON, err = json.Marshal(opts.Proposal.Capella) case spec.DataVersionDeneb: specJSON, err = json.Marshal(opts.Proposal.Deneb) + case spec.DataVersionElectra: + specJSON, err = json.Marshal(opts.Proposal.Electra) default: err = errors.New("unknown proposal version") } diff --git a/http/submitproposal.go b/http/submitproposal.go index 0343093c..635c079c 100644 --- a/http/submitproposal.go +++ b/http/submitproposal.go @@ -116,6 +116,8 @@ func (*Service) submitProposalJSON(_ context.Context, specJSON, err = json.Marshal(proposal.Capella) case spec.DataVersionDeneb: specJSON, err = json.Marshal(proposal.Deneb) + case spec.DataVersionElectra: + specJSON, err = json.Marshal(proposal.Electra) default: err = errors.New("unknown proposal version") } @@ -150,6 +152,8 @@ func (*Service) submitProposalSSZ(_ context.Context, specSSZ, err = proposal.Capella.MarshalSSZ() case spec.DataVersionDeneb: specSSZ, err = proposal.Deneb.MarshalSSZ() + case spec.DataVersionElectra: + specSSZ, err = proposal.Electra.MarshalSSZ() default: err = errors.New("unknown proposal version") } diff --git a/spec/dataversion.go b/spec/dataversion.go index ef09d06e..0d3fe4e1 100644 --- a/spec/dataversion.go +++ b/spec/dataversion.go @@ -34,6 +34,8 @@ const ( DataVersionCapella // DataVersionDeneb is data applicable for the Deneb release of the beacon chain. DataVersionDeneb + // DataVersionElectra is data applicable for the Electra release of the beacon chain. + DataVersionElectra ) var dataVersionStrings = [...]string{ @@ -43,6 +45,7 @@ var dataVersionStrings = [...]string{ "bellatrix", "capella", "deneb", + "electra", } // MarshalJSON implements json.Marshaler. @@ -64,6 +67,8 @@ func (d *DataVersion) UnmarshalJSON(input []byte) error { *d = DataVersionCapella case `"deneb"`: *d = DataVersionDeneb + case `"electra"`: + *d = DataVersionElectra default: err = fmt.Errorf("unrecognised data version %s", string(input)) } diff --git a/spec/electra/attestation.go b/spec/electra/attestation.go new file mode 100644 index 00000000..0354aba6 --- /dev/null +++ b/spec/electra/attestation.go @@ -0,0 +1,142 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/attestantio/go-eth2-client/spec/phase0" + + "github.com/goccy/go-yaml" + "github.com/pkg/errors" + bitfield "github.com/prysmaticlabs/go-bitfield" +) + +// Attestation is the Ethereum 2 attestation structure. +type Attestation struct { + AggregationBits bitfield.Bitlist `ssz-max:"131072"` + Data *phase0.AttestationData + CommitteeBits bitfield.Bitvector64 `ssz-size:"1"` + Signature phase0.BLSSignature `ssz-size:"96"` +} + +// attestationJSON is a raw representation of the struct. +type attestationJSON struct { + AggregationBits string `json:"aggregation_bits"` + Data *phase0.AttestationData `json:"data"` + CommitteeBits string `json:"committee_bits"` + Signature string `json:"signature"` +} + +// attestationYAML is a raw representation of the struct. +type attestationYAML struct { + AggregationBits string `yaml:"aggregation_bits"` + Data *phase0.AttestationData `yaml:"data"` + CommitteeBits string `yaml:"committee_bits"` + Signature string `yaml:"signature"` +} + +// MarshalJSON implements json.Marshaler. +func (a *Attestation) MarshalJSON() ([]byte, error) { + return json.Marshal(&attestationJSON{ + AggregationBits: fmt.Sprintf("%#x", []byte(a.AggregationBits)), + Data: a.Data, + CommitteeBits: fmt.Sprintf("%#x", a.CommitteeBits), + Signature: fmt.Sprintf("%#x", a.Signature), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (a *Attestation) UnmarshalJSON(input []byte) error { + var attestationJSON attestationJSON + err := json.Unmarshal(input, &attestationJSON) + if err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return a.unpack(&attestationJSON) +} + +func (a *Attestation) unpack(attestationJSON *attestationJSON) error { + var err error + if attestationJSON.AggregationBits == "" { + return errors.New("aggregation bits missing") + } + if a.AggregationBits, err = hex.DecodeString(strings.TrimPrefix(attestationJSON.AggregationBits, "0x")); err != nil { + return errors.Wrap(err, "invalid value for aggregation bits") + } + a.Data = attestationJSON.Data + if a.Data == nil { + return errors.New("data missing") + } + if attestationJSON.CommitteeBits == "" { + return errors.New("committee bits missing") + } + if a.CommitteeBits, err = hex.DecodeString(strings.TrimPrefix(attestationJSON.CommitteeBits, "0x")); err != nil { + return errors.Wrap(err, "invalid value for committee bits") + } + if attestationJSON.Signature == "" { + return errors.New("signature missing") + } + signature, err := hex.DecodeString(strings.TrimPrefix(attestationJSON.Signature, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for signature") + } + if len(signature) != phase0.SignatureLength { + return errors.New("incorrect length for signature") + } + copy(a.Signature[:], signature) + + return nil +} + +// MarshalYAML implements yaml.Marshaler. +func (a *Attestation) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&attestationYAML{ + AggregationBits: fmt.Sprintf("%#x", []byte(a.AggregationBits)), + Data: a.Data, + CommitteeBits: fmt.Sprintf("%#x", []byte(a.CommitteeBits)), + Signature: fmt.Sprintf("%#x", a.Signature), + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (a *Attestation) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var attestationJSON attestationJSON + if err := yaml.Unmarshal(input, &attestationJSON); err != nil { + return err + } + + return a.unpack(&attestationJSON) +} + +// String returns a string version of the structure. +func (a *Attestation) String() string { + data, err := yaml.Marshal(a) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/attestation_ssz.go b/spec/electra/attestation_ssz.go new file mode 100644 index 00000000..f70c8eb6 --- /dev/null +++ b/spec/electra/attestation_ssz.go @@ -0,0 +1,155 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the Attestation object +func (a *Attestation) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(a) +} + +// MarshalSSZTo ssz marshals the Attestation object to a target array +func (a *Attestation) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(229) + + // Offset (0) 'AggregationBits' + dst = ssz.WriteOffset(dst, offset) + offset += len(a.AggregationBits) + + // Field (1) 'Data' + if a.Data == nil { + a.Data = new(phase0.AttestationData) + } + if dst, err = a.Data.MarshalSSZTo(dst); err != nil { + return + } + + // Field (2) 'CommitteeBits' + if size := len(a.CommitteeBits); size != 1 { + err = ssz.ErrBytesLengthFn("Attestation.CommitteeBits", size, 1) + return + } + dst = append(dst, a.CommitteeBits...) + + // Field (3) 'Signature' + dst = append(dst, a.Signature[:]...) + + // Field (0) 'AggregationBits' + if size := len(a.AggregationBits); size > 131072 { + err = ssz.ErrBytesLengthFn("Attestation.AggregationBits", size, 131072) + return + } + dst = append(dst, a.AggregationBits...) + + return +} + +// UnmarshalSSZ ssz unmarshals the Attestation object +func (a *Attestation) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 229 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'AggregationBits' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 229 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Data' + if a.Data == nil { + a.Data = new(phase0.AttestationData) + } + if err = a.Data.UnmarshalSSZ(buf[4:132]); err != nil { + return err + } + + // Field (2) 'CommitteeBits' + if cap(a.CommitteeBits) == 0 { + a.CommitteeBits = make([]byte, 0, len(buf[132:133])) + } + a.CommitteeBits = append(a.CommitteeBits, buf[132:133]...) + + // Field (3) 'Signature' + copy(a.Signature[:], buf[133:229]) + + // Field (0) 'AggregationBits' + { + buf = tail[o0:] + if err = ssz.ValidateBitlist(buf, 131072); err != nil { + return err + } + if cap(a.AggregationBits) == 0 { + a.AggregationBits = make([]byte, 0, len(buf)) + } + a.AggregationBits = append(a.AggregationBits, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Attestation object +func (a *Attestation) SizeSSZ() (size int) { + size = 229 + + // Field (0) 'AggregationBits' + size += len(a.AggregationBits) + + return +} + +// HashTreeRoot ssz hashes the Attestation object +func (a *Attestation) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(a) +} + +// HashTreeRootWith ssz hashes the Attestation object with a hasher +func (a *Attestation) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'AggregationBits' + if len(a.AggregationBits) == 0 { + err = ssz.ErrEmptyBitlist + return + } + hh.PutBitlist(a.AggregationBits, 131072) + + // Field (1) 'Data' + if a.Data == nil { + a.Data = new(phase0.AttestationData) + } + if err = a.Data.HashTreeRootWith(hh); err != nil { + return + } + + // Field (2) 'CommitteeBits' + if size := len(a.CommitteeBits); size != 1 { + err = ssz.ErrBytesLengthFn("Attestation.CommitteeBits", size, 1) + return + } + hh.PutBytes(a.CommitteeBits) + + // Field (3) 'Signature' + hh.PutBytes(a.Signature[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Attestation object +func (a *Attestation) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(a) +} diff --git a/spec/electra/beaconblock.go b/spec/electra/beaconblock.go new file mode 100644 index 00000000..70d2c5ca --- /dev/null +++ b/spec/electra/beaconblock.go @@ -0,0 +1,40 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// BeaconBlock represents a beacon block. +type BeaconBlock struct { + Slot phase0.Slot + ProposerIndex phase0.ValidatorIndex + ParentRoot phase0.Root `ssz-size:"32"` + StateRoot phase0.Root `ssz-size:"32"` + Body *BeaconBlockBody +} + +// String returns a string version of the structure. +func (b *BeaconBlock) String() string { + data, err := yaml.Marshal(b) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/beaconblock_json.go b/spec/electra/beaconblock_json.go new file mode 100644 index 00000000..7cd74677 --- /dev/null +++ b/spec/electra/beaconblock_json.go @@ -0,0 +1,73 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + "fmt" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/pkg/errors" +) + +// beaconBlockJSON is the spec representation of the struct. +type beaconBlockJSON struct { + Slot string `json:"slot"` + ProposerIndex string `json:"proposer_index"` + ParentRoot string `json:"parent_root"` + StateRoot string `json:"state_root"` + Body *BeaconBlockBody `json:"body"` +} + +// MarshalJSON implements json.Marshaler. +func (b *BeaconBlock) MarshalJSON() ([]byte, error) { + return json.Marshal(&beaconBlockJSON{ + Slot: fmt.Sprintf("%d", b.Slot), + ProposerIndex: fmt.Sprintf("%d", b.ProposerIndex), + ParentRoot: b.ParentRoot.String(), + StateRoot: b.StateRoot.String(), + Body: b.Body, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (b *BeaconBlock) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&beaconBlockJSON{}, input) + if err != nil { + return err + } + + if err := b.Slot.UnmarshalJSON(raw["slot"]); err != nil { + return errors.Wrap(err, "slot") + } + + if err := b.ProposerIndex.UnmarshalJSON(raw["proposer_index"]); err != nil { + return errors.Wrap(err, "proposer_index") + } + + if err := b.ParentRoot.UnmarshalJSON(raw["parent_root"]); err != nil { + return errors.Wrap(err, "parent_root") + } + + if err := b.StateRoot.UnmarshalJSON(raw["state_root"]); err != nil { + return errors.Wrap(err, "state_root") + } + + b.Body = &BeaconBlockBody{} + if err := b.Body.UnmarshalJSON(raw["body"]); err != nil { + return errors.Wrap(err, "body") + } + + return nil +} diff --git a/spec/electra/beaconblock_ssz.go b/spec/electra/beaconblock_ssz.go new file mode 100644 index 00000000..55666fc8 --- /dev/null +++ b/spec/electra/beaconblock_ssz.go @@ -0,0 +1,139 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the BeaconBlock object +func (b *BeaconBlock) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BeaconBlock object to a target array +func (b *BeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(84) + + // Field (0) 'Slot' + dst = ssz.MarshalUint64(dst, uint64(b.Slot)) + + // Field (1) 'ProposerIndex' + dst = ssz.MarshalUint64(dst, uint64(b.ProposerIndex)) + + // Field (2) 'ParentRoot' + dst = append(dst, b.ParentRoot[:]...) + + // Field (3) 'StateRoot' + dst = append(dst, b.StateRoot[:]...) + + // Offset (4) 'Body' + dst = ssz.WriteOffset(dst, offset) + if b.Body == nil { + b.Body = new(BeaconBlockBody) + } + offset += b.Body.SizeSSZ() + + // Field (4) 'Body' + if dst, err = b.Body.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BeaconBlock object +func (b *BeaconBlock) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 84 { + return ssz.ErrSize + } + + tail := buf + var o4 uint64 + + // Field (0) 'Slot' + b.Slot = phase0.Slot(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'ProposerIndex' + b.ProposerIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[8:16])) + + // Field (2) 'ParentRoot' + copy(b.ParentRoot[:], buf[16:48]) + + // Field (3) 'StateRoot' + copy(b.StateRoot[:], buf[48:80]) + + // Offset (4) 'Body' + if o4 = ssz.ReadOffset(buf[80:84]); o4 > size { + return ssz.ErrOffset + } + + if o4 < 84 { + return ssz.ErrInvalidVariableOffset + } + + // Field (4) 'Body' + { + buf = tail[o4:] + if b.Body == nil { + b.Body = new(BeaconBlockBody) + } + if err = b.Body.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BeaconBlock object +func (b *BeaconBlock) SizeSSZ() (size int) { + size = 84 + + // Field (4) 'Body' + if b.Body == nil { + b.Body = new(BeaconBlockBody) + } + size += b.Body.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the BeaconBlock object +func (b *BeaconBlock) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BeaconBlock object with a hasher +func (b *BeaconBlock) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Slot' + hh.PutUint64(uint64(b.Slot)) + + // Field (1) 'ProposerIndex' + hh.PutUint64(uint64(b.ProposerIndex)) + + // Field (2) 'ParentRoot' + hh.PutBytes(b.ParentRoot[:]) + + // Field (3) 'StateRoot' + hh.PutBytes(b.StateRoot[:]) + + // Field (4) 'Body' + if err = b.Body.HashTreeRootWith(hh); err != nil { + return + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the BeaconBlock object +func (b *BeaconBlock) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} diff --git a/spec/electra/beaconblock_yaml.go b/spec/electra/beaconblock_yaml.go new file mode 100644 index 00000000..25b2119d --- /dev/null +++ b/spec/electra/beaconblock_yaml.go @@ -0,0 +1,63 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// beaconBlockYAML is the spec representation of the struct. +type beaconBlockYAML struct { + Slot uint64 `yaml:"slot"` + ProposerIndex uint64 `yaml:"proposer_index"` + ParentRoot string `yaml:"parent_root"` + StateRoot string `yaml:"state_root"` + Body *BeaconBlockBody `yaml:"body"` +} + +// MarshalYAML implements yaml.Marshaler. +func (b *BeaconBlock) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&beaconBlockYAML{ + Slot: uint64(b.Slot), + ProposerIndex: uint64(b.ProposerIndex), + ParentRoot: b.ParentRoot.String(), + StateRoot: b.StateRoot.String(), + Body: b.Body, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (b *BeaconBlock) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled beaconBlockJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return b.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/beaconblockbody.go b/spec/electra/beaconblockbody.go new file mode 100644 index 00000000..aa2a5af9 --- /dev/null +++ b/spec/electra/beaconblockbody.go @@ -0,0 +1,52 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/deneb" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// BeaconBlockBody represents the body of a beacon block. +type BeaconBlockBody struct { + RANDAOReveal phase0.BLSSignature `ssz-size:"96"` + ETH1Data *phase0.ETH1Data + Graffiti [32]byte `ssz-size:"32"` + ProposerSlashings []*phase0.ProposerSlashing `ssz-max:"16"` + AttesterSlashings []*phase0.AttesterSlashing `ssz-max:"1"` + Attestations []*Attestation `ssz-max:"8"` + Deposits []*phase0.Deposit `ssz-max:"16"` + VoluntaryExits []*phase0.SignedVoluntaryExit `ssz-max:"16"` + SyncAggregate *altair.SyncAggregate + ExecutionPayload *ExecutionPayload + BLSToExecutionChanges []*capella.SignedBLSToExecutionChange `ssz-max:"16"` + BlobKZGCommitments []deneb.KZGCommitment `ssz-max:"4096" ssz-size:"?,48"` + Consolidations []*SignedConsolidation `ssz-max:"1"` +} + +// String returns a string version of the structure. +func (b *BeaconBlockBody) String() string { + data, err := yaml.Marshal(b) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/beaconblockbody_json.go b/spec/electra/beaconblockbody_json.go new file mode 100644 index 00000000..2b41f220 --- /dev/null +++ b/spec/electra/beaconblockbody_json.go @@ -0,0 +1,176 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// beaconBlockBodyJSON is the spec representation of the struct. +type beaconBlockBodyJSON struct { + RANDAOReveal phase0.BLSSignature `json:"randao_reveal"` + ETH1Data *phase0.ETH1Data `json:"eth1_data"` + Graffiti string `json:"graffiti"` + ProposerSlashings []*phase0.ProposerSlashing `json:"proposer_slashings"` + AttesterSlashings []*phase0.AttesterSlashing `json:"attester_slashings"` + Attestations []*Attestation `json:"attestations"` + Deposits []*phase0.Deposit `json:"deposits"` + VoluntaryExits []*phase0.SignedVoluntaryExit `json:"voluntary_exits"` + SyncAggregate *altair.SyncAggregate `json:"sync_aggregate"` + ExecutionPayload *ExecutionPayload `json:"execution_payload"` + BLSToExecutionChanges []*capella.SignedBLSToExecutionChange `json:"bls_to_execution_changes"` + BlobKZGCommitments []string `json:"blob_kzg_commitments"` + Consolidations []*SignedConsolidation `json:"consolidations"` +} + +// MarshalJSON implements json.Marshaler. +func (b *BeaconBlockBody) MarshalJSON() ([]byte, error) { + blobKZGCommitments := make([]string, len(b.BlobKZGCommitments)) + for i := range b.BlobKZGCommitments { + blobKZGCommitments[i] = b.BlobKZGCommitments[i].String() + } + + return json.Marshal(&beaconBlockBodyJSON{ + RANDAOReveal: b.RANDAOReveal, + ETH1Data: b.ETH1Data, + Graffiti: fmt.Sprintf("%#x", b.Graffiti), + ProposerSlashings: b.ProposerSlashings, + AttesterSlashings: b.AttesterSlashings, + Attestations: b.Attestations, + Deposits: b.Deposits, + VoluntaryExits: b.VoluntaryExits, + SyncAggregate: b.SyncAggregate, + ExecutionPayload: b.ExecutionPayload, + BLSToExecutionChanges: b.BLSToExecutionChanges, + BlobKZGCommitments: blobKZGCommitments, + Consolidations: b.Consolidations, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +// +//nolint:gocyclo +func (b *BeaconBlockBody) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&beaconBlockBodyJSON{}, input) + if err != nil { + return err + } + + if err := b.RANDAOReveal.UnmarshalJSON(raw["randao_reveal"]); err != nil { + return errors.Wrap(err, "randao_reveal") + } + + if err := json.Unmarshal(raw["eth1_data"], &b.ETH1Data); err != nil { + return errors.Wrap(err, "eth1_data") + } + + graffiti := raw["graffiti"] + if !bytes.HasPrefix(graffiti, []byte{'"', '0', 'x'}) { + return errors.New("graffiti: invalid prefix") + } + if !bytes.HasSuffix(graffiti, []byte{'"'}) { + return errors.New("graffiti: invalid suffix") + } + if len(graffiti) != 1+2+32*2+1 { + return errors.New("graffiti: incorrect length") + } + length, err := hex.Decode(b.Graffiti[:], graffiti[3:3+32*2]) + if err != nil { + return errors.Wrap(err, "graffiti") + } + if length != 32 { + return errors.New("graffiti: incorrect length") + } + + if err := json.Unmarshal(raw["proposer_slashings"], &b.ProposerSlashings); err != nil { + return errors.Wrap(err, "proposer_slashings") + } + for i := range b.ProposerSlashings { + if b.ProposerSlashings[i] == nil { + return fmt.Errorf("proposer slashings entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["attester_slashings"], &b.AttesterSlashings); err != nil { + return errors.Wrap(err, "attester_slashings") + } + for i := range b.AttesterSlashings { + if b.AttesterSlashings[i] == nil { + return fmt.Errorf("attester slashings entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["attestations"], &b.Attestations); err != nil { + return errors.Wrap(err, "attestations") + } + for i := range b.Attestations { + if b.Attestations[i] == nil { + return fmt.Errorf("attestations entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["deposits"], &b.Deposits); err != nil { + return errors.Wrap(err, "deposits") + } + for i := range b.Deposits { + if b.Deposits[i] == nil { + return fmt.Errorf("deposits entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["voluntary_exits"], &b.VoluntaryExits); err != nil { + return errors.Wrap(err, "voluntary_exits") + } + for i := range b.VoluntaryExits { + if b.VoluntaryExits[i] == nil { + return fmt.Errorf("voluntary exits entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["sync_aggregate"], &b.SyncAggregate); err != nil { + return errors.Wrap(err, "sync_aggregate") + } + + if err := json.Unmarshal(raw["execution_payload"], &b.ExecutionPayload); err != nil { + return errors.Wrap(err, "execution_payload") + } + + if err := json.Unmarshal(raw["bls_to_execution_changes"], &b.BLSToExecutionChanges); err != nil { + return errors.Wrap(err, "bls_to_execution_changes") + } + for i := range b.BLSToExecutionChanges { + if b.BLSToExecutionChanges[i] == nil { + return fmt.Errorf("bls to execution changes entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["blob_kzg_commitments"], &b.BlobKZGCommitments); err != nil { + return errors.Wrap(err, "blob_kzg_commitments") + } + + if err := json.Unmarshal(raw["consolidations"], &b.BlobKZGCommitments); err != nil { + return errors.Wrap(err, "consolidations") + } + + return nil +} diff --git a/spec/electra/beaconblockbody_ssz.go b/spec/electra/beaconblockbody_ssz.go new file mode 100644 index 00000000..9e9e3d93 --- /dev/null +++ b/spec/electra/beaconblockbody_ssz.go @@ -0,0 +1,653 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the BeaconBlockBody object +func (b *BeaconBlockBody) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BeaconBlockBody object to a target array +func (b *BeaconBlockBody) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(396) + + // Field (0) 'RANDAOReveal' + dst = append(dst, b.RANDAOReveal[:]...) + + // Field (1) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if dst, err = b.ETH1Data.MarshalSSZTo(dst); err != nil { + return + } + + // Field (2) 'Graffiti' + dst = append(dst, b.Graffiti[:]...) + + // Offset (3) 'ProposerSlashings' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.ProposerSlashings) * 416 + + // Offset (4) 'AttesterSlashings' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + offset += 4 + offset += b.AttesterSlashings[ii].SizeSSZ() + } + + // Offset (5) 'Attestations' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(b.Attestations); ii++ { + offset += 4 + offset += b.Attestations[ii].SizeSSZ() + } + + // Offset (6) 'Deposits' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Deposits) * 1240 + + // Offset (7) 'VoluntaryExits' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.VoluntaryExits) * 112 + + // Field (8) 'SyncAggregate' + if b.SyncAggregate == nil { + b.SyncAggregate = new(altair.SyncAggregate) + } + if dst, err = b.SyncAggregate.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (9) 'ExecutionPayload' + dst = ssz.WriteOffset(dst, offset) + if b.ExecutionPayload == nil { + b.ExecutionPayload = new(ExecutionPayload) + } + offset += b.ExecutionPayload.SizeSSZ() + + // Offset (10) 'BLSToExecutionChanges' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.BLSToExecutionChanges) * 172 + + // Offset (11) 'BlobKZGCommitments' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.BlobKZGCommitments) * 48 + + // Offset (12) 'Consolidations' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Consolidations) * 120 + + // Field (3) 'ProposerSlashings' + if size := len(b.ProposerSlashings); size > 16 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.ProposerSlashings", size, 16) + return + } + for ii := 0; ii < len(b.ProposerSlashings); ii++ { + if dst, err = b.ProposerSlashings[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (4) 'AttesterSlashings' + if size := len(b.AttesterSlashings); size > 1 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.AttesterSlashings", size, 1) + return + } + { + offset = 4 * len(b.AttesterSlashings) + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += b.AttesterSlashings[ii].SizeSSZ() + } + } + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + if dst, err = b.AttesterSlashings[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (5) 'Attestations' + if size := len(b.Attestations); size > 8 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.Attestations", size, 8) + return + } + { + offset = 4 * len(b.Attestations) + for ii := 0; ii < len(b.Attestations); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += b.Attestations[ii].SizeSSZ() + } + } + for ii := 0; ii < len(b.Attestations); ii++ { + if dst, err = b.Attestations[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (6) 'Deposits' + if size := len(b.Deposits); size > 16 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.Deposits", size, 16) + return + } + for ii := 0; ii < len(b.Deposits); ii++ { + if dst, err = b.Deposits[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (7) 'VoluntaryExits' + if size := len(b.VoluntaryExits); size > 16 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.VoluntaryExits", size, 16) + return + } + for ii := 0; ii < len(b.VoluntaryExits); ii++ { + if dst, err = b.VoluntaryExits[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (9) 'ExecutionPayload' + if dst, err = b.ExecutionPayload.MarshalSSZTo(dst); err != nil { + return + } + + // Field (10) 'BLSToExecutionChanges' + if size := len(b.BLSToExecutionChanges); size > 16 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.BLSToExecutionChanges", size, 16) + return + } + for ii := 0; ii < len(b.BLSToExecutionChanges); ii++ { + if dst, err = b.BLSToExecutionChanges[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (11) 'BlobKZGCommitments' + if size := len(b.BlobKZGCommitments); size > 4096 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.BlobKZGCommitments", size, 4096) + return + } + for ii := 0; ii < len(b.BlobKZGCommitments); ii++ { + dst = append(dst, b.BlobKZGCommitments[ii][:]...) + } + + // Field (12) 'Consolidations' + if size := len(b.Consolidations); size > 1 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.Consolidations", size, 1) + return + } + for ii := 0; ii < len(b.Consolidations); ii++ { + if dst, err = b.Consolidations[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BeaconBlockBody object +func (b *BeaconBlockBody) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 396 { + return ssz.ErrSize + } + + tail := buf + var o3, o4, o5, o6, o7, o9, o10, o11, o12 uint64 + + // Field (0) 'RANDAOReveal' + copy(b.RANDAOReveal[:], buf[0:96]) + + // Field (1) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if err = b.ETH1Data.UnmarshalSSZ(buf[96:168]); err != nil { + return err + } + + // Field (2) 'Graffiti' + copy(b.Graffiti[:], buf[168:200]) + + // Offset (3) 'ProposerSlashings' + if o3 = ssz.ReadOffset(buf[200:204]); o3 > size { + return ssz.ErrOffset + } + + if o3 < 396 { + return ssz.ErrInvalidVariableOffset + } + + // Offset (4) 'AttesterSlashings' + if o4 = ssz.ReadOffset(buf[204:208]); o4 > size || o3 > o4 { + return ssz.ErrOffset + } + + // Offset (5) 'Attestations' + if o5 = ssz.ReadOffset(buf[208:212]); o5 > size || o4 > o5 { + return ssz.ErrOffset + } + + // Offset (6) 'Deposits' + if o6 = ssz.ReadOffset(buf[212:216]); o6 > size || o5 > o6 { + return ssz.ErrOffset + } + + // Offset (7) 'VoluntaryExits' + if o7 = ssz.ReadOffset(buf[216:220]); o7 > size || o6 > o7 { + return ssz.ErrOffset + } + + // Field (8) 'SyncAggregate' + if b.SyncAggregate == nil { + b.SyncAggregate = new(altair.SyncAggregate) + } + if err = b.SyncAggregate.UnmarshalSSZ(buf[220:380]); err != nil { + return err + } + + // Offset (9) 'ExecutionPayload' + if o9 = ssz.ReadOffset(buf[380:384]); o9 > size || o7 > o9 { + return ssz.ErrOffset + } + + // Offset (10) 'BLSToExecutionChanges' + if o10 = ssz.ReadOffset(buf[384:388]); o10 > size || o9 > o10 { + return ssz.ErrOffset + } + + // Offset (11) 'BlobKZGCommitments' + if o11 = ssz.ReadOffset(buf[388:392]); o11 > size || o10 > o11 { + return ssz.ErrOffset + } + + // Offset (12) 'Consolidations' + if o12 = ssz.ReadOffset(buf[392:396]); o12 > size || o11 > o12 { + return ssz.ErrOffset + } + + // Field (3) 'ProposerSlashings' + { + buf = tail[o3:o4] + num, err := ssz.DivideInt2(len(buf), 416, 16) + if err != nil { + return err + } + b.ProposerSlashings = make([]*phase0.ProposerSlashing, num) + for ii := 0; ii < num; ii++ { + if b.ProposerSlashings[ii] == nil { + b.ProposerSlashings[ii] = new(phase0.ProposerSlashing) + } + if err = b.ProposerSlashings[ii].UnmarshalSSZ(buf[ii*416 : (ii+1)*416]); err != nil { + return err + } + } + } + + // Field (4) 'AttesterSlashings' + { + buf = tail[o4:o5] + num, err := ssz.DecodeDynamicLength(buf, 1) + if err != nil { + return err + } + b.AttesterSlashings = make([]*phase0.AttesterSlashing, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if b.AttesterSlashings[indx] == nil { + b.AttesterSlashings[indx] = new(phase0.AttesterSlashing) + } + if err = b.AttesterSlashings[indx].UnmarshalSSZ(buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + + // Field (5) 'Attestations' + { + buf = tail[o5:o6] + num, err := ssz.DecodeDynamicLength(buf, 8) + if err != nil { + return err + } + b.Attestations = make([]*Attestation, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if b.Attestations[indx] == nil { + b.Attestations[indx] = new(Attestation) + } + if err = b.Attestations[indx].UnmarshalSSZ(buf); err != nil { + return err + } + return nil + }) + if err != nil { + return err + } + } + + // Field (6) 'Deposits' + { + buf = tail[o6:o7] + num, err := ssz.DivideInt2(len(buf), 1240, 16) + if err != nil { + return err + } + b.Deposits = make([]*phase0.Deposit, num) + for ii := 0; ii < num; ii++ { + if b.Deposits[ii] == nil { + b.Deposits[ii] = new(phase0.Deposit) + } + if err = b.Deposits[ii].UnmarshalSSZ(buf[ii*1240 : (ii+1)*1240]); err != nil { + return err + } + } + } + + // Field (7) 'VoluntaryExits' + { + buf = tail[o7:o9] + num, err := ssz.DivideInt2(len(buf), 112, 16) + if err != nil { + return err + } + b.VoluntaryExits = make([]*phase0.SignedVoluntaryExit, num) + for ii := 0; ii < num; ii++ { + if b.VoluntaryExits[ii] == nil { + b.VoluntaryExits[ii] = new(phase0.SignedVoluntaryExit) + } + if err = b.VoluntaryExits[ii].UnmarshalSSZ(buf[ii*112 : (ii+1)*112]); err != nil { + return err + } + } + } + + // Field (9) 'ExecutionPayload' + { + buf = tail[o9:o10] + if b.ExecutionPayload == nil { + b.ExecutionPayload = new(ExecutionPayload) + } + if err = b.ExecutionPayload.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (10) 'BLSToExecutionChanges' + { + buf = tail[o10:o11] + num, err := ssz.DivideInt2(len(buf), 172, 16) + if err != nil { + return err + } + b.BLSToExecutionChanges = make([]*capella.SignedBLSToExecutionChange, num) + for ii := 0; ii < num; ii++ { + if b.BLSToExecutionChanges[ii] == nil { + b.BLSToExecutionChanges[ii] = new(capella.SignedBLSToExecutionChange) + } + if err = b.BLSToExecutionChanges[ii].UnmarshalSSZ(buf[ii*172 : (ii+1)*172]); err != nil { + return err + } + } + } + + // Field (11) 'BlobKZGCommitments' + { + buf = tail[o11:o12] + num, err := ssz.DivideInt2(len(buf), 48, 4096) + if err != nil { + return err + } + b.BlobKZGCommitments = make([]deneb.KZGCommitment, num) + for ii := 0; ii < num; ii++ { + copy(b.BlobKZGCommitments[ii][:], buf[ii*48:(ii+1)*48]) + } + } + + // Field (12) 'Consolidations' + { + buf = tail[o12:] + num, err := ssz.DivideInt2(len(buf), 120, 1) + if err != nil { + return err + } + b.Consolidations = make([]*SignedConsolidation, num) + for ii := 0; ii < num; ii++ { + if b.Consolidations[ii] == nil { + b.Consolidations[ii] = new(SignedConsolidation) + } + if err = b.Consolidations[ii].UnmarshalSSZ(buf[ii*120 : (ii+1)*120]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BeaconBlockBody object +func (b *BeaconBlockBody) SizeSSZ() (size int) { + size = 396 + + // Field (3) 'ProposerSlashings' + size += len(b.ProposerSlashings) * 416 + + // Field (4) 'AttesterSlashings' + for ii := 0; ii < len(b.AttesterSlashings); ii++ { + size += 4 + size += b.AttesterSlashings[ii].SizeSSZ() + } + + // Field (5) 'Attestations' + for ii := 0; ii < len(b.Attestations); ii++ { + size += 4 + size += b.Attestations[ii].SizeSSZ() + } + + // Field (6) 'Deposits' + size += len(b.Deposits) * 1240 + + // Field (7) 'VoluntaryExits' + size += len(b.VoluntaryExits) * 112 + + // Field (9) 'ExecutionPayload' + if b.ExecutionPayload == nil { + b.ExecutionPayload = new(ExecutionPayload) + } + size += b.ExecutionPayload.SizeSSZ() + + // Field (10) 'BLSToExecutionChanges' + size += len(b.BLSToExecutionChanges) * 172 + + // Field (11) 'BlobKZGCommitments' + size += len(b.BlobKZGCommitments) * 48 + + // Field (12) 'Consolidations' + size += len(b.Consolidations) * 120 + + return +} + +// HashTreeRoot ssz hashes the BeaconBlockBody object +func (b *BeaconBlockBody) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BeaconBlockBody object with a hasher +func (b *BeaconBlockBody) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'RANDAOReveal' + hh.PutBytes(b.RANDAOReveal[:]) + + // Field (1) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if err = b.ETH1Data.HashTreeRootWith(hh); err != nil { + return + } + + // Field (2) 'Graffiti' + hh.PutBytes(b.Graffiti[:]) + + // Field (3) 'ProposerSlashings' + { + subIndx := hh.Index() + num := uint64(len(b.ProposerSlashings)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.ProposerSlashings { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (4) 'AttesterSlashings' + { + subIndx := hh.Index() + num := uint64(len(b.AttesterSlashings)) + if num > 1 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.AttesterSlashings { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 1) + } + + // Field (5) 'Attestations' + { + subIndx := hh.Index() + num := uint64(len(b.Attestations)) + if num > 8 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Attestations { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 8) + } + + // Field (6) 'Deposits' + { + subIndx := hh.Index() + num := uint64(len(b.Deposits)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Deposits { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (7) 'VoluntaryExits' + { + subIndx := hh.Index() + num := uint64(len(b.VoluntaryExits)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.VoluntaryExits { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (8) 'SyncAggregate' + if b.SyncAggregate == nil { + b.SyncAggregate = new(altair.SyncAggregate) + } + if err = b.SyncAggregate.HashTreeRootWith(hh); err != nil { + return + } + + // Field (9) 'ExecutionPayload' + if err = b.ExecutionPayload.HashTreeRootWith(hh); err != nil { + return + } + + // Field (10) 'BLSToExecutionChanges' + { + subIndx := hh.Index() + num := uint64(len(b.BLSToExecutionChanges)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.BLSToExecutionChanges { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (11) 'BlobKZGCommitments' + { + if size := len(b.BlobKZGCommitments); size > 4096 { + err = ssz.ErrListTooBigFn("BeaconBlockBody.BlobKZGCommitments", size, 4096) + return + } + subIndx := hh.Index() + for _, i := range b.BlobKZGCommitments { + hh.PutBytes(i[:]) + } + numItems := uint64(len(b.BlobKZGCommitments)) + hh.MerkleizeWithMixin(subIndx, numItems, 4096) + } + + // Field (12) 'Consolidations' + { + subIndx := hh.Index() + num := uint64(len(b.Consolidations)) + if num > 1 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Consolidations { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 1) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the BeaconBlockBody object +func (b *BeaconBlockBody) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} diff --git a/spec/electra/beaconblockbody_yaml.go b/spec/electra/beaconblockbody_yaml.go new file mode 100644 index 00000000..8722bbbf --- /dev/null +++ b/spec/electra/beaconblockbody_yaml.go @@ -0,0 +1,88 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// beaconBlockBodyYAML is the spec representation of the struct. +type beaconBlockBodyYAML struct { + RANDAOReveal string `yaml:"randao_reveal"` + ETH1Data *phase0.ETH1Data `yaml:"eth1_data"` + Graffiti string `yaml:"graffiti"` + ProposerSlashings []*phase0.ProposerSlashing `yaml:"proposer_slashings"` + AttesterSlashings []*phase0.AttesterSlashing `yaml:"attester_slashings"` + Attestations []*Attestation `yaml:"attestations"` + Deposits []*phase0.Deposit `yaml:"deposits"` + VoluntaryExits []*phase0.SignedVoluntaryExit `yaml:"voluntary_exits"` + SyncAggregate *altair.SyncAggregate `yaml:"sync_aggregate"` + ExecutionPayload *ExecutionPayload `yaml:"execution_payload"` + BLSToExecutionChanges []*capella.SignedBLSToExecutionChange `yaml:"bls_to_execution_changes"` + BlobKZGCommitments []string `yaml:"blob_kzg_commitments"` + Consolidations []*SignedConsolidation `json:"consolidations"` +} + +// MarshalYAML implements yaml.Marshaler. +func (b *BeaconBlockBody) MarshalYAML() ([]byte, error) { + blobKZGCommitments := make([]string, len(b.BlobKZGCommitments)) + for i := range b.BlobKZGCommitments { + blobKZGCommitments[i] = b.BlobKZGCommitments[i].String() + } + + yamlBytes, err := yaml.MarshalWithOptions(&beaconBlockBodyYAML{ + RANDAOReveal: b.RANDAOReveal.String(), + ETH1Data: b.ETH1Data, + Graffiti: fmt.Sprintf("%#x", b.Graffiti), + ProposerSlashings: b.ProposerSlashings, + AttesterSlashings: b.AttesterSlashings, + Attestations: b.Attestations, + Deposits: b.Deposits, + VoluntaryExits: b.VoluntaryExits, + SyncAggregate: b.SyncAggregate, + ExecutionPayload: b.ExecutionPayload, + BLSToExecutionChanges: b.BLSToExecutionChanges, + BlobKZGCommitments: blobKZGCommitments, + Consolidations: b.Consolidations, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (b *BeaconBlockBody) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled beaconBlockBodyJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return b.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/beaconstate.go b/spec/electra/beaconstate.go new file mode 100644 index 00000000..8ee1463b --- /dev/null +++ b/spec/electra/beaconstate.go @@ -0,0 +1,75 @@ +// Copyright © 2023 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + bitfield "github.com/prysmaticlabs/go-bitfield" +) + +// BeaconState represents a beacon state. +type BeaconState struct { + GenesisTime uint64 + GenesisValidatorsRoot phase0.Root `ssz-size:"32"` + Slot phase0.Slot + Fork *phase0.Fork + LatestBlockHeader *phase0.BeaconBlockHeader + BlockRoots []phase0.Root `ssz-size:"8192,32"` + StateRoots []phase0.Root `ssz-size:"8192,32"` + HistoricalRoots []phase0.Root `ssz-max:"16777216" ssz-size:"?,32"` + ETH1Data *phase0.ETH1Data + ETH1DataVotes []*phase0.ETH1Data `ssz-max:"2048"` + ETH1DepositIndex uint64 + Validators []*phase0.Validator `ssz-max:"1099511627776"` + Balances []phase0.Gwei `ssz-max:"1099511627776"` + RANDAOMixes []phase0.Root `ssz-size:"65536,32"` + Slashings []phase0.Gwei `ssz-size:"8192"` + PreviousEpochParticipation []altair.ParticipationFlags `ssz-max:"1099511627776"` + CurrentEpochParticipation []altair.ParticipationFlags `ssz-max:"1099511627776"` + JustificationBits bitfield.Bitvector4 `ssz-size:"1"` + PreviousJustifiedCheckpoint *phase0.Checkpoint + CurrentJustifiedCheckpoint *phase0.Checkpoint + FinalizedCheckpoint *phase0.Checkpoint + InactivityScores []uint64 `ssz-max:"1099511627776"` + CurrentSyncCommittee *altair.SyncCommittee + NextSyncCommittee *altair.SyncCommittee + LatestExecutionPayloadHeader *ExecutionPayloadHeader + NextWithdrawalIndex capella.WithdrawalIndex + NextWithdrawalValidatorIndex phase0.ValidatorIndex + HistoricalSummaries []*capella.HistoricalSummary `ssz-max:"16777216"` + DepositReceiptsStartIndex uint64 + DepositBalanceToConsume phase0.Gwei + ExitBalanceToConsume phase0.Gwei + EarliestExitEpoch phase0.Epoch + ConsolidationBalanceToConsume phase0.Gwei + EarliestConsolidationEpoch phase0.Epoch + PendingBalanceDeposits []*PendingBalanceDeposit `ssz-max:"134217728"` + PendingPartialWithdrawals []*PendingPartialWithdrawal `ssz-max:"134217728"` + PendingConsolidations []*PendingConsolidation `ssz-max:"262144"` +} + +// String returns a string version of the structure. +func (b *BeaconState) String() string { + data, err := yaml.Marshal(b) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/beaconstate_json.go b/spec/electra/beaconstate_json.go new file mode 100644 index 00000000..dfb59d02 --- /dev/null +++ b/spec/electra/beaconstate_json.go @@ -0,0 +1,351 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// beaconStateJSON is the spec representation of the struct. +type beaconStateJSON struct { + GenesisTime string `json:"genesis_time"` + GenesisValidatorsRoot phase0.Root `json:"genesis_validators_root"` + Slot phase0.Slot `json:"slot"` + Fork *phase0.Fork `json:"fork"` + LatestBlockHeader *phase0.BeaconBlockHeader `json:"latest_block_header"` + BlockRoots []phase0.Root `json:"block_roots"` + StateRoots []phase0.Root `json:"state_roots"` + HistoricalRoots []phase0.Root `json:"historical_roots"` + ETH1Data *phase0.ETH1Data `json:"eth1_data"` + //nolint:staticcheck + ETH1DataVotes []*phase0.ETH1Data `json:"eth1_data_votes,allowempty"` + ETH1DepositIndex string `json:"eth1_deposit_index"` + Validators []*phase0.Validator `json:"validators"` + Balances []string `json:"balances"` + RANDAOMixes []string `json:"randao_mixes"` + Slashings []string `json:"slashings"` + PreviousEpochParticipation []string `json:"previous_epoch_participation"` + CurrentEpochParticipation []string `json:"current_epoch_participation"` + JustificationBits string `json:"justification_bits"` + PreviousJustifiedCheckpoint *phase0.Checkpoint `json:"previous_justified_checkpoint"` + CurrentJustifiedCheckpoint *phase0.Checkpoint `json:"current_justified_checkpoint"` + FinalizedCheckpoint *phase0.Checkpoint `json:"finalized_checkpoint"` + InactivityScores []string `json:"inactivity_scores"` + CurrentSyncCommittee *altair.SyncCommittee `json:"current_sync_committee"` + NextSyncCommittee *altair.SyncCommittee `json:"next_sync_committee"` + LatestExecutionPayloadHeader *ExecutionPayloadHeader `json:"latest_execution_payload_header"` + NextWithdrawalIndex string `json:"next_withdrawal_index"` + NextWithdrawalValidatorIndex string `json:"next_withdrawal_validator_index"` + HistoricalSummaries []*capella.HistoricalSummary `json:"historical_summaries"` + DepositReceiptsStartIndex string `json:"deposit_receipts_start_index"` + DepositBalanceToConsume phase0.Gwei `json:"deposit_balance_to_consume"` + ExitBalanceToConsume phase0.Gwei `json:"exit_balance_to_consume"` + EarliestExitEpoch phase0.Epoch `json:"earliest_exit_epoch"` + ConsolidationBalanceToConsume phase0.Gwei `json:"consolidation_balance_to_consume"` + EarliestConsolidationEpoch phase0.Epoch `json:"earliest_consolidation_epoch"` + PendingBalanceDeposits []*PendingBalanceDeposit `json:"pending_balance_deposits"` + PendingPartialWithdrawals []*PendingPartialWithdrawal `json:"pending_partial_withdrawals"` + PendingConsolidations []*PendingConsolidation `json:"pending_consolidations"` +} + +// MarshalJSON implements json.Marshaler. +func (b *BeaconState) MarshalJSON() ([]byte, error) { + balances := make([]string, len(b.Balances)) + for i := range b.Balances { + balances[i] = fmt.Sprintf("%d", b.Balances[i]) + } + randaoMixes := make([]string, len(b.RANDAOMixes)) + for i := range b.RANDAOMixes { + randaoMixes[i] = fmt.Sprintf("%#x", b.RANDAOMixes[i]) + } + slashings := make([]string, len(b.Slashings)) + for i := range b.Slashings { + slashings[i] = fmt.Sprintf("%d", b.Slashings[i]) + } + PreviousEpochParticipation := make([]string, len(b.PreviousEpochParticipation)) + for i := range b.PreviousEpochParticipation { + PreviousEpochParticipation[i] = fmt.Sprintf("%d", b.PreviousEpochParticipation[i]) + } + CurrentEpochParticipation := make([]string, len(b.CurrentEpochParticipation)) + for i := range b.CurrentEpochParticipation { + CurrentEpochParticipation[i] = fmt.Sprintf("%d", b.CurrentEpochParticipation[i]) + } + inactivityScores := make([]string, len(b.InactivityScores)) + for i := range b.InactivityScores { + inactivityScores[i] = strconv.FormatUint(b.InactivityScores[i], 10) + } + + return json.Marshal(&beaconStateJSON{ + GenesisTime: strconv.FormatUint(b.GenesisTime, 10), + GenesisValidatorsRoot: b.GenesisValidatorsRoot, + Slot: b.Slot, + Fork: b.Fork, + LatestBlockHeader: b.LatestBlockHeader, + BlockRoots: b.BlockRoots, + StateRoots: b.StateRoots, + HistoricalRoots: b.HistoricalRoots, + ETH1Data: b.ETH1Data, + ETH1DataVotes: b.ETH1DataVotes, + ETH1DepositIndex: strconv.FormatUint(b.ETH1DepositIndex, 10), + Validators: b.Validators, + Balances: balances, + RANDAOMixes: randaoMixes, + Slashings: slashings, + PreviousEpochParticipation: PreviousEpochParticipation, + CurrentEpochParticipation: CurrentEpochParticipation, + JustificationBits: fmt.Sprintf("%#x", b.JustificationBits.Bytes()), + PreviousJustifiedCheckpoint: b.PreviousJustifiedCheckpoint, + CurrentJustifiedCheckpoint: b.CurrentJustifiedCheckpoint, + FinalizedCheckpoint: b.FinalizedCheckpoint, + InactivityScores: inactivityScores, + CurrentSyncCommittee: b.CurrentSyncCommittee, + NextSyncCommittee: b.NextSyncCommittee, + LatestExecutionPayloadHeader: b.LatestExecutionPayloadHeader, + NextWithdrawalIndex: fmt.Sprintf("%d", b.NextWithdrawalIndex), + NextWithdrawalValidatorIndex: fmt.Sprintf("%d", b.NextWithdrawalValidatorIndex), + HistoricalSummaries: b.HistoricalSummaries, + DepositReceiptsStartIndex: fmt.Sprintf("%d", b.DepositReceiptsStartIndex), + DepositBalanceToConsume: b.DepositBalanceToConsume, + ExitBalanceToConsume: b.ExitBalanceToConsume, + EarliestExitEpoch: b.EarliestExitEpoch, + ConsolidationBalanceToConsume: b.ConsolidationBalanceToConsume, + EarliestConsolidationEpoch: b.EarliestConsolidationEpoch, + PendingBalanceDeposits: b.PendingBalanceDeposits, + PendingPartialWithdrawals: b.PendingPartialWithdrawals, + PendingConsolidations: b.PendingConsolidations, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +// +//nolint:gocyclo +func (b *BeaconState) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&beaconStateJSON{}, input) + if err != nil { + return err + } + + genesisTime := string(bytes.Trim(raw["genesis_time"], `"`)) + if b.GenesisTime, err = strconv.ParseUint(genesisTime, 10, 64); err != nil { + return errors.Wrap(err, "genesis_time") + } + + if err := b.GenesisValidatorsRoot.UnmarshalJSON(raw["genesis_validators_root"]); err != nil { + return errors.Wrap(err, "genesis_validators_root") + } + + if err := b.Slot.UnmarshalJSON(raw["slot"]); err != nil { + return errors.Wrap(err, "slot") + } + + b.Fork = &phase0.Fork{} + if err := b.Fork.UnmarshalJSON(raw["fork"]); err != nil { + return errors.Wrap(err, "fork") + } + + b.LatestBlockHeader = &phase0.BeaconBlockHeader{} + if err := b.LatestBlockHeader.UnmarshalJSON(raw["latest_block_header"]); err != nil { + return errors.Wrap(err, "latest_block_header") + } + + if err := json.Unmarshal(raw["block_roots"], &b.BlockRoots); err != nil { + return errors.Wrap(err, "block_roots") + } + + if err := json.Unmarshal(raw["state_roots"], &b.StateRoots); err != nil { + return errors.Wrap(err, "state_roots") + } + + if err := json.Unmarshal(raw["historical_roots"], &b.HistoricalRoots); err != nil { + return errors.Wrap(err, "historical_roots") + } + + b.ETH1Data = &phase0.ETH1Data{} + if err := b.ETH1Data.UnmarshalJSON(raw["eth1_data"]); err != nil { + return errors.Wrap(err, "eth1_data") + } + + if err := json.Unmarshal(raw["eth1_data_votes"], &b.ETH1DataVotes); err != nil { + return errors.Wrap(err, "eth1_data_votes") + } + for i := range b.ETH1DataVotes { + if b.ETH1DataVotes[i] == nil { + return fmt.Errorf("eth1 data votes entry %d missing", i) + } + } + + eth1DepositIndex := string(bytes.Trim(raw["eth1_deposit_index"], `"`)) + if b.ETH1DepositIndex, err = strconv.ParseUint(eth1DepositIndex, 10, 64); err != nil { + return errors.Wrap(err, "eth1_deposit_index") + } + + if err := json.Unmarshal(raw["validators"], &b.Validators); err != nil { + return errors.Wrap(err, "validators") + } + for i := range b.Validators { + if b.Validators[i] == nil { + return fmt.Errorf("validators entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["balances"], &b.Balances); err != nil { + return errors.Wrap(err, "balances") + } + + if err := json.Unmarshal(raw["randao_mixes"], &b.RANDAOMixes); err != nil { + return errors.Wrap(err, "randao_mixes") + } + + if err := json.Unmarshal(raw["slashings"], &b.Slashings); err != nil { + return errors.Wrap(err, "slashings") + } + + if err := json.Unmarshal(raw["previous_epoch_participation"], &b.PreviousEpochParticipation); err != nil { + return errors.Wrap(err, "previous_epoch_participation") + } + + if err := json.Unmarshal(raw["current_epoch_participation"], &b.CurrentEpochParticipation); err != nil { + return errors.Wrap(err, "current_epoch_participation") + } + + justificationBits := string(bytes.TrimPrefix(bytes.Trim(raw["justification_bits"], `"`), []byte{'0', 'x'})) + if b.JustificationBits, err = hex.DecodeString(justificationBits); err != nil { + return errors.Wrap(err, "justification_bits") + } + + b.PreviousJustifiedCheckpoint = &phase0.Checkpoint{} + if err := b.PreviousJustifiedCheckpoint.UnmarshalJSON(raw["previous_justified_checkpoint"]); err != nil { + return errors.Wrap(err, "previous_justified_checkpoint") + } + + b.CurrentJustifiedCheckpoint = &phase0.Checkpoint{} + if err := b.CurrentJustifiedCheckpoint.UnmarshalJSON(raw["current_justified_checkpoint"]); err != nil { + return errors.Wrap(err, "current_justified_checkpoint") + } + + b.FinalizedCheckpoint = &phase0.Checkpoint{} + if err := b.FinalizedCheckpoint.UnmarshalJSON(raw["finalized_checkpoint"]); err != nil { + return errors.Wrap(err, "finalized_checkpoint") + } + + inactivityScores := make([]string, 0) + if err := json.Unmarshal(raw["inactivity_scores"], &inactivityScores); err != nil { + return errors.Wrap(err, "inactivity_scores") + } + b.InactivityScores = make([]uint64, len(inactivityScores)) + for i := range inactivityScores { + if inactivityScores[i] == "" { + return fmt.Errorf("inactivity score %d missing", i) + } + if b.InactivityScores[i], err = strconv.ParseUint(inactivityScores[i], 10, 64); err != nil { + return errors.Wrap(err, fmt.Sprintf("invalid value for inactivity score %d", i)) + } + } + + b.CurrentSyncCommittee = &altair.SyncCommittee{} + if err := b.CurrentSyncCommittee.UnmarshalJSON(raw["current_sync_committee"]); err != nil { + return errors.Wrap(err, "current_sync_committee") + } + + b.NextSyncCommittee = &altair.SyncCommittee{} + if err := b.NextSyncCommittee.UnmarshalJSON(raw["next_sync_committee"]); err != nil { + return errors.Wrap(err, "next_sync_committee") + } + + b.LatestExecutionPayloadHeader = &ExecutionPayloadHeader{} + if err := b.LatestExecutionPayloadHeader.UnmarshalJSON(raw["latest_execution_payload_header"]); err != nil { + return errors.Wrap(err, "latest_execution_payload_header") + } + + if err := b.NextWithdrawalIndex.UnmarshalJSON(raw["next_withdrawal_index"]); err != nil { + return errors.Wrap(err, "next_withdrawal_index") + } + + if err := b.NextWithdrawalValidatorIndex.UnmarshalJSON(raw["next_withdrawal_validator_index"]); err != nil { + return errors.Wrap(err, "next_withdrawal_validator_index") + } + + if err := json.Unmarshal(raw["historical_summaries"], &b.HistoricalSummaries); err != nil { + return errors.Wrap(err, "historical_summaries") + } + for i := range b.HistoricalSummaries { + if b.HistoricalSummaries[i] == nil { + return fmt.Errorf("historical summaries entry %d missing", i) + } + } + + depositReceiptsStartIndex := string(bytes.Trim(raw["deposit_receipts_start_index"], `"`)) + if b.DepositReceiptsStartIndex, err = strconv.ParseUint(depositReceiptsStartIndex, 10, 64); err != nil { + return errors.Wrap(err, "deposit_receipts_start_index") + } + + if err := b.DepositBalanceToConsume.UnmarshalJSON(raw["deposit_balance_to_consume"]); err != nil { + return errors.Wrap(err, "deposit_balance_to_consume") + } + + if err := b.ExitBalanceToConsume.UnmarshalJSON(raw["exit_balance_to_consume"]); err != nil { + return errors.Wrap(err, "exit_balance_to_consume") + } + + if err := b.EarliestExitEpoch.UnmarshalJSON(raw["earliest_exit_epoch"]); err != nil { + return errors.Wrap(err, "earliest_exit_epoch") + } + + if err := b.ConsolidationBalanceToConsume.UnmarshalJSON(raw["consolidation_balance_to_consume"]); err != nil { + return errors.Wrap(err, "consolidation_balance_to_consume") + } + + if err := b.EarliestConsolidationEpoch.UnmarshalJSON(raw["earliest_consolidation_epoch"]); err != nil { + return errors.Wrap(err, "earliest_consolidation_epoch") + } + + if err := json.Unmarshal(raw["pending_balance_deposits"], &b.PendingBalanceDeposits); err != nil { + return errors.Wrap(err, "pending_balance_deposits") + } + for i := range b.PendingBalanceDeposits { + if b.PendingBalanceDeposits[i] == nil { + return fmt.Errorf("pending balance deposits entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["pending_partial_withdrawals"], &b.PendingPartialWithdrawals); err != nil { + return errors.Wrap(err, "pending_partial_withdrawals") + } + for i := range b.PendingPartialWithdrawals { + if b.PendingPartialWithdrawals[i] == nil { + return fmt.Errorf("pending partial withdrawals entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["pending_consolidations"], &b.PendingConsolidations); err != nil { + return errors.Wrap(err, "pending_consolidations") + } + for i := range b.PendingConsolidations { + if b.PendingConsolidations[i] == nil { + return fmt.Errorf("pending consolidations entry %d missing", i) + } + } + + return nil +} diff --git a/spec/electra/beaconstate_ssz.go b/spec/electra/beaconstate_ssz.go new file mode 100644 index 00000000..0294101f --- /dev/null +++ b/spec/electra/beaconstate_ssz.go @@ -0,0 +1,1123 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: adabd63e74fd1d7901414007d2d918638b3a939bb92180df274f553891838d1e +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the BeaconState object +func (b *BeaconState) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(b) +} + +// MarshalSSZTo ssz marshals the BeaconState object to a target array +func (b *BeaconState) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(2736713) + + // Field (0) 'GenesisTime' + dst = ssz.MarshalUint64(dst, b.GenesisTime) + + // Field (1) 'GenesisValidatorsRoot' + dst = append(dst, b.GenesisValidatorsRoot[:]...) + + // Field (2) 'Slot' + dst = ssz.MarshalUint64(dst, uint64(b.Slot)) + + // Field (3) 'Fork' + if b.Fork == nil { + b.Fork = new(phase0.Fork) + } + if dst, err = b.Fork.MarshalSSZTo(dst); err != nil { + return + } + + // Field (4) 'LatestBlockHeader' + if b.LatestBlockHeader == nil { + b.LatestBlockHeader = new(phase0.BeaconBlockHeader) + } + if dst, err = b.LatestBlockHeader.MarshalSSZTo(dst); err != nil { + return + } + + // Field (5) 'BlockRoots' + if size := len(b.BlockRoots); size != 8192 { + err = ssz.ErrVectorLengthFn("BeaconState.BlockRoots", size, 8192) + return + } + for ii := 0; ii < 8192; ii++ { + dst = append(dst, b.BlockRoots[ii][:]...) + } + + // Field (6) 'StateRoots' + if size := len(b.StateRoots); size != 8192 { + err = ssz.ErrVectorLengthFn("BeaconState.StateRoots", size, 8192) + return + } + for ii := 0; ii < 8192; ii++ { + dst = append(dst, b.StateRoots[ii][:]...) + } + + // Offset (7) 'HistoricalRoots' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.HistoricalRoots) * 32 + + // Field (8) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if dst, err = b.ETH1Data.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (9) 'ETH1DataVotes' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.ETH1DataVotes) * 72 + + // Field (10) 'ETH1DepositIndex' + dst = ssz.MarshalUint64(dst, b.ETH1DepositIndex) + + // Offset (11) 'Validators' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Validators) * 121 + + // Offset (12) 'Balances' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.Balances) * 8 + + // Field (13) 'RANDAOMixes' + if size := len(b.RANDAOMixes); size != 65536 { + err = ssz.ErrVectorLengthFn("BeaconState.RANDAOMixes", size, 65536) + return + } + for ii := 0; ii < 65536; ii++ { + dst = append(dst, b.RANDAOMixes[ii][:]...) + } + + // Field (14) 'Slashings' + if size := len(b.Slashings); size != 8192 { + err = ssz.ErrVectorLengthFn("BeaconState.Slashings", size, 8192) + return + } + for ii := 0; ii < 8192; ii++ { + dst = ssz.MarshalUint64(dst, uint64(b.Slashings[ii])) + } + + // Offset (15) 'PreviousEpochParticipation' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.PreviousEpochParticipation) * 1 + + // Offset (16) 'CurrentEpochParticipation' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.CurrentEpochParticipation) * 1 + + // Field (17) 'JustificationBits' + if size := len(b.JustificationBits); size != 1 { + err = ssz.ErrBytesLengthFn("BeaconState.JustificationBits", size, 1) + return + } + dst = append(dst, b.JustificationBits...) + + // Field (18) 'PreviousJustifiedCheckpoint' + if b.PreviousJustifiedCheckpoint == nil { + b.PreviousJustifiedCheckpoint = new(phase0.Checkpoint) + } + if dst, err = b.PreviousJustifiedCheckpoint.MarshalSSZTo(dst); err != nil { + return + } + + // Field (19) 'CurrentJustifiedCheckpoint' + if b.CurrentJustifiedCheckpoint == nil { + b.CurrentJustifiedCheckpoint = new(phase0.Checkpoint) + } + if dst, err = b.CurrentJustifiedCheckpoint.MarshalSSZTo(dst); err != nil { + return + } + + // Field (20) 'FinalizedCheckpoint' + if b.FinalizedCheckpoint == nil { + b.FinalizedCheckpoint = new(phase0.Checkpoint) + } + if dst, err = b.FinalizedCheckpoint.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (21) 'InactivityScores' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.InactivityScores) * 8 + + // Field (22) 'CurrentSyncCommittee' + if b.CurrentSyncCommittee == nil { + b.CurrentSyncCommittee = new(altair.SyncCommittee) + } + if dst, err = b.CurrentSyncCommittee.MarshalSSZTo(dst); err != nil { + return + } + + // Field (23) 'NextSyncCommittee' + if b.NextSyncCommittee == nil { + b.NextSyncCommittee = new(altair.SyncCommittee) + } + if dst, err = b.NextSyncCommittee.MarshalSSZTo(dst); err != nil { + return + } + + // Offset (24) 'LatestExecutionPayloadHeader' + dst = ssz.WriteOffset(dst, offset) + if b.LatestExecutionPayloadHeader == nil { + b.LatestExecutionPayloadHeader = new(ExecutionPayloadHeader) + } + offset += b.LatestExecutionPayloadHeader.SizeSSZ() + + // Field (25) 'NextWithdrawalIndex' + dst = ssz.MarshalUint64(dst, uint64(b.NextWithdrawalIndex)) + + // Field (26) 'NextWithdrawalValidatorIndex' + dst = ssz.MarshalUint64(dst, uint64(b.NextWithdrawalValidatorIndex)) + + // Offset (27) 'HistoricalSummaries' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.HistoricalSummaries) * 64 + + // Field (28) 'DepositReceiptsStartIndex' + dst = ssz.MarshalUint64(dst, b.DepositReceiptsStartIndex) + + // Field (29) 'DepositBalanceToConsume' + dst = ssz.MarshalUint64(dst, uint64(b.DepositBalanceToConsume)) + + // Field (30) 'ExitBalanceToConsume' + dst = ssz.MarshalUint64(dst, uint64(b.ExitBalanceToConsume)) + + // Field (31) 'EarliestExitEpoch' + dst = ssz.MarshalUint64(dst, uint64(b.EarliestExitEpoch)) + + // Field (32) 'ConsolidationBalanceToConsume' + dst = ssz.MarshalUint64(dst, uint64(b.ConsolidationBalanceToConsume)) + + // Field (33) 'EarliestConsolidationEpoch' + dst = ssz.MarshalUint64(dst, uint64(b.EarliestConsolidationEpoch)) + + // Offset (34) 'PendingBalanceDeposits' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.PendingBalanceDeposits) * 16 + + // Offset (35) 'PendingPartialWithdrawals' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.PendingPartialWithdrawals) * 24 + + // Offset (36) 'PendingConsolidations' + dst = ssz.WriteOffset(dst, offset) + offset += len(b.PendingConsolidations) * 16 + + // Field (7) 'HistoricalRoots' + if size := len(b.HistoricalRoots); size > 16777216 { + err = ssz.ErrListTooBigFn("BeaconState.HistoricalRoots", size, 16777216) + return + } + for ii := 0; ii < len(b.HistoricalRoots); ii++ { + dst = append(dst, b.HistoricalRoots[ii][:]...) + } + + // Field (9) 'ETH1DataVotes' + if size := len(b.ETH1DataVotes); size > 2048 { + err = ssz.ErrListTooBigFn("BeaconState.ETH1DataVotes", size, 2048) + return + } + for ii := 0; ii < len(b.ETH1DataVotes); ii++ { + if dst, err = b.ETH1DataVotes[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (11) 'Validators' + if size := len(b.Validators); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.Validators", size, 1099511627776) + return + } + for ii := 0; ii < len(b.Validators); ii++ { + if dst, err = b.Validators[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (12) 'Balances' + if size := len(b.Balances); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.Balances", size, 1099511627776) + return + } + for ii := 0; ii < len(b.Balances); ii++ { + dst = ssz.MarshalUint64(dst, uint64(b.Balances[ii])) + } + + // Field (15) 'PreviousEpochParticipation' + if size := len(b.PreviousEpochParticipation); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.PreviousEpochParticipation", size, 1099511627776) + return + } + for ii := 0; ii < len(b.PreviousEpochParticipation); ii++ { + dst = ssz.MarshalUint8(dst, uint8(b.PreviousEpochParticipation[ii])) + } + + // Field (16) 'CurrentEpochParticipation' + if size := len(b.CurrentEpochParticipation); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.CurrentEpochParticipation", size, 1099511627776) + return + } + for ii := 0; ii < len(b.CurrentEpochParticipation); ii++ { + dst = ssz.MarshalUint8(dst, uint8(b.CurrentEpochParticipation[ii])) + } + + // Field (21) 'InactivityScores' + if size := len(b.InactivityScores); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.InactivityScores", size, 1099511627776) + return + } + for ii := 0; ii < len(b.InactivityScores); ii++ { + dst = ssz.MarshalUint64(dst, b.InactivityScores[ii]) + } + + // Field (24) 'LatestExecutionPayloadHeader' + if dst, err = b.LatestExecutionPayloadHeader.MarshalSSZTo(dst); err != nil { + return + } + + // Field (27) 'HistoricalSummaries' + if size := len(b.HistoricalSummaries); size > 16777216 { + err = ssz.ErrListTooBigFn("BeaconState.HistoricalSummaries", size, 16777216) + return + } + for ii := 0; ii < len(b.HistoricalSummaries); ii++ { + if dst, err = b.HistoricalSummaries[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (34) 'PendingBalanceDeposits' + if size := len(b.PendingBalanceDeposits); size > 134217728 { + err = ssz.ErrListTooBigFn("BeaconState.PendingBalanceDeposits", size, 134217728) + return + } + for ii := 0; ii < len(b.PendingBalanceDeposits); ii++ { + if dst, err = b.PendingBalanceDeposits[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (35) 'PendingPartialWithdrawals' + if size := len(b.PendingPartialWithdrawals); size > 134217728 { + err = ssz.ErrListTooBigFn("BeaconState.PendingPartialWithdrawals", size, 134217728) + return + } + for ii := 0; ii < len(b.PendingPartialWithdrawals); ii++ { + if dst, err = b.PendingPartialWithdrawals[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (36) 'PendingConsolidations' + if size := len(b.PendingConsolidations); size > 262144 { + err = ssz.ErrListTooBigFn("BeaconState.PendingConsolidations", size, 262144) + return + } + for ii := 0; ii < len(b.PendingConsolidations); ii++ { + if dst, err = b.PendingConsolidations[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the BeaconState object +func (b *BeaconState) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 2736713 { + return ssz.ErrSize + } + + tail := buf + var o7, o9, o11, o12, o15, o16, o21, o24, o27, o34, o35, o36 uint64 + + // Field (0) 'GenesisTime' + b.GenesisTime = ssz.UnmarshallUint64(buf[0:8]) + + // Field (1) 'GenesisValidatorsRoot' + copy(b.GenesisValidatorsRoot[:], buf[8:40]) + + // Field (2) 'Slot' + b.Slot = phase0.Slot(ssz.UnmarshallUint64(buf[40:48])) + + // Field (3) 'Fork' + if b.Fork == nil { + b.Fork = new(phase0.Fork) + } + if err = b.Fork.UnmarshalSSZ(buf[48:64]); err != nil { + return err + } + + // Field (4) 'LatestBlockHeader' + if b.LatestBlockHeader == nil { + b.LatestBlockHeader = new(phase0.BeaconBlockHeader) + } + if err = b.LatestBlockHeader.UnmarshalSSZ(buf[64:176]); err != nil { + return err + } + + // Field (5) 'BlockRoots' + b.BlockRoots = make([]phase0.Root, 8192) + for ii := 0; ii < 8192; ii++ { + copy(b.BlockRoots[ii][:], buf[176:262320][ii*32:(ii+1)*32]) + } + + // Field (6) 'StateRoots' + b.StateRoots = make([]phase0.Root, 8192) + for ii := 0; ii < 8192; ii++ { + copy(b.StateRoots[ii][:], buf[262320:524464][ii*32:(ii+1)*32]) + } + + // Offset (7) 'HistoricalRoots' + if o7 = ssz.ReadOffset(buf[524464:524468]); o7 > size { + return ssz.ErrOffset + } + + if o7 < 2736713 { + return ssz.ErrInvalidVariableOffset + } + + // Field (8) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if err = b.ETH1Data.UnmarshalSSZ(buf[524468:524540]); err != nil { + return err + } + + // Offset (9) 'ETH1DataVotes' + if o9 = ssz.ReadOffset(buf[524540:524544]); o9 > size || o7 > o9 { + return ssz.ErrOffset + } + + // Field (10) 'ETH1DepositIndex' + b.ETH1DepositIndex = ssz.UnmarshallUint64(buf[524544:524552]) + + // Offset (11) 'Validators' + if o11 = ssz.ReadOffset(buf[524552:524556]); o11 > size || o9 > o11 { + return ssz.ErrOffset + } + + // Offset (12) 'Balances' + if o12 = ssz.ReadOffset(buf[524556:524560]); o12 > size || o11 > o12 { + return ssz.ErrOffset + } + + // Field (13) 'RANDAOMixes' + b.RANDAOMixes = make([]phase0.Root, 65536) + for ii := 0; ii < 65536; ii++ { + copy(b.RANDAOMixes[ii][:], buf[524560:2621712][ii*32:(ii+1)*32]) + } + + // Field (14) 'Slashings' + b.Slashings = make([]phase0.Gwei, 8192) + for ii := 0; ii < 8192; ii++ { + b.Slashings[ii] = phase0.Gwei(ssz.UnmarshallUint64(buf[2621712:2687248][ii*8 : (ii+1)*8])) + } + + // Offset (15) 'PreviousEpochParticipation' + if o15 = ssz.ReadOffset(buf[2687248:2687252]); o15 > size || o12 > o15 { + return ssz.ErrOffset + } + + // Offset (16) 'CurrentEpochParticipation' + if o16 = ssz.ReadOffset(buf[2687252:2687256]); o16 > size || o15 > o16 { + return ssz.ErrOffset + } + + // Field (17) 'JustificationBits' + if cap(b.JustificationBits) == 0 { + b.JustificationBits = make([]byte, 0, len(buf[2687256:2687257])) + } + b.JustificationBits = append(b.JustificationBits, buf[2687256:2687257]...) + + // Field (18) 'PreviousJustifiedCheckpoint' + if b.PreviousJustifiedCheckpoint == nil { + b.PreviousJustifiedCheckpoint = new(phase0.Checkpoint) + } + if err = b.PreviousJustifiedCheckpoint.UnmarshalSSZ(buf[2687257:2687297]); err != nil { + return err + } + + // Field (19) 'CurrentJustifiedCheckpoint' + if b.CurrentJustifiedCheckpoint == nil { + b.CurrentJustifiedCheckpoint = new(phase0.Checkpoint) + } + if err = b.CurrentJustifiedCheckpoint.UnmarshalSSZ(buf[2687297:2687337]); err != nil { + return err + } + + // Field (20) 'FinalizedCheckpoint' + if b.FinalizedCheckpoint == nil { + b.FinalizedCheckpoint = new(phase0.Checkpoint) + } + if err = b.FinalizedCheckpoint.UnmarshalSSZ(buf[2687337:2687377]); err != nil { + return err + } + + // Offset (21) 'InactivityScores' + if o21 = ssz.ReadOffset(buf[2687377:2687381]); o21 > size || o16 > o21 { + return ssz.ErrOffset + } + + // Field (22) 'CurrentSyncCommittee' + if b.CurrentSyncCommittee == nil { + b.CurrentSyncCommittee = new(altair.SyncCommittee) + } + if err = b.CurrentSyncCommittee.UnmarshalSSZ(buf[2687381:2712005]); err != nil { + return err + } + + // Field (23) 'NextSyncCommittee' + if b.NextSyncCommittee == nil { + b.NextSyncCommittee = new(altair.SyncCommittee) + } + if err = b.NextSyncCommittee.UnmarshalSSZ(buf[2712005:2736629]); err != nil { + return err + } + + // Offset (24) 'LatestExecutionPayloadHeader' + if o24 = ssz.ReadOffset(buf[2736629:2736633]); o24 > size || o21 > o24 { + return ssz.ErrOffset + } + + // Field (25) 'NextWithdrawalIndex' + b.NextWithdrawalIndex = capella.WithdrawalIndex(ssz.UnmarshallUint64(buf[2736633:2736641])) + + // Field (26) 'NextWithdrawalValidatorIndex' + b.NextWithdrawalValidatorIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[2736641:2736649])) + + // Offset (27) 'HistoricalSummaries' + if o27 = ssz.ReadOffset(buf[2736649:2736653]); o27 > size || o24 > o27 { + return ssz.ErrOffset + } + + // Field (28) 'DepositReceiptsStartIndex' + b.DepositReceiptsStartIndex = ssz.UnmarshallUint64(buf[2736653:2736661]) + + // Field (29) 'DepositBalanceToConsume' + b.DepositBalanceToConsume = phase0.Gwei(ssz.UnmarshallUint64(buf[2736661:2736669])) + + // Field (30) 'ExitBalanceToConsume' + b.ExitBalanceToConsume = phase0.Gwei(ssz.UnmarshallUint64(buf[2736669:2736677])) + + // Field (31) 'EarliestExitEpoch' + b.EarliestExitEpoch = phase0.Epoch(ssz.UnmarshallUint64(buf[2736677:2736685])) + + // Field (32) 'ConsolidationBalanceToConsume' + b.ConsolidationBalanceToConsume = phase0.Gwei(ssz.UnmarshallUint64(buf[2736685:2736693])) + + // Field (33) 'EarliestConsolidationEpoch' + b.EarliestConsolidationEpoch = phase0.Epoch(ssz.UnmarshallUint64(buf[2736693:2736701])) + + // Offset (34) 'PendingBalanceDeposits' + if o34 = ssz.ReadOffset(buf[2736701:2736705]); o34 > size || o27 > o34 { + return ssz.ErrOffset + } + + // Offset (35) 'PendingPartialWithdrawals' + if o35 = ssz.ReadOffset(buf[2736705:2736709]); o35 > size || o34 > o35 { + return ssz.ErrOffset + } + + // Offset (36) 'PendingConsolidations' + if o36 = ssz.ReadOffset(buf[2736709:2736713]); o36 > size || o35 > o36 { + return ssz.ErrOffset + } + + // Field (7) 'HistoricalRoots' + { + buf = tail[o7:o9] + num, err := ssz.DivideInt2(len(buf), 32, 16777216) + if err != nil { + return err + } + b.HistoricalRoots = make([]phase0.Root, num) + for ii := 0; ii < num; ii++ { + copy(b.HistoricalRoots[ii][:], buf[ii*32:(ii+1)*32]) + } + } + + // Field (9) 'ETH1DataVotes' + { + buf = tail[o9:o11] + num, err := ssz.DivideInt2(len(buf), 72, 2048) + if err != nil { + return err + } + b.ETH1DataVotes = make([]*phase0.ETH1Data, num) + for ii := 0; ii < num; ii++ { + if b.ETH1DataVotes[ii] == nil { + b.ETH1DataVotes[ii] = new(phase0.ETH1Data) + } + if err = b.ETH1DataVotes[ii].UnmarshalSSZ(buf[ii*72 : (ii+1)*72]); err != nil { + return err + } + } + } + + // Field (11) 'Validators' + { + buf = tail[o11:o12] + num, err := ssz.DivideInt2(len(buf), 121, 1099511627776) + if err != nil { + return err + } + b.Validators = make([]*phase0.Validator, num) + for ii := 0; ii < num; ii++ { + if b.Validators[ii] == nil { + b.Validators[ii] = new(phase0.Validator) + } + if err = b.Validators[ii].UnmarshalSSZ(buf[ii*121 : (ii+1)*121]); err != nil { + return err + } + } + } + + // Field (12) 'Balances' + { + buf = tail[o12:o15] + num, err := ssz.DivideInt2(len(buf), 8, 1099511627776) + if err != nil { + return err + } + b.Balances = make([]phase0.Gwei, num) + for ii := 0; ii < num; ii++ { + b.Balances[ii] = phase0.Gwei(ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8])) + } + } + + // Field (15) 'PreviousEpochParticipation' + { + buf = tail[o15:o16] + num, err := ssz.DivideInt2(len(buf), 1, 1099511627776) + if err != nil { + return err + } + b.PreviousEpochParticipation = make([]altair.ParticipationFlags, num) + for ii := 0; ii < num; ii++ { + b.PreviousEpochParticipation[ii] = altair.ParticipationFlags(ssz.UnmarshallUint8(buf[ii*1 : (ii+1)*1])) + } + } + + // Field (16) 'CurrentEpochParticipation' + { + buf = tail[o16:o21] + num, err := ssz.DivideInt2(len(buf), 1, 1099511627776) + if err != nil { + return err + } + b.CurrentEpochParticipation = make([]altair.ParticipationFlags, num) + for ii := 0; ii < num; ii++ { + b.CurrentEpochParticipation[ii] = altair.ParticipationFlags(ssz.UnmarshallUint8(buf[ii*1 : (ii+1)*1])) + } + } + + // Field (21) 'InactivityScores' + { + buf = tail[o21:o24] + num, err := ssz.DivideInt2(len(buf), 8, 1099511627776) + if err != nil { + return err + } + b.InactivityScores = ssz.ExtendUint64(b.InactivityScores, num) + for ii := 0; ii < num; ii++ { + b.InactivityScores[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) + } + } + + // Field (24) 'LatestExecutionPayloadHeader' + { + buf = tail[o24:o27] + if b.LatestExecutionPayloadHeader == nil { + b.LatestExecutionPayloadHeader = new(ExecutionPayloadHeader) + } + if err = b.LatestExecutionPayloadHeader.UnmarshalSSZ(buf); err != nil { + return err + } + } + + // Field (27) 'HistoricalSummaries' + { + buf = tail[o27:o34] + num, err := ssz.DivideInt2(len(buf), 64, 16777216) + if err != nil { + return err + } + b.HistoricalSummaries = make([]*capella.HistoricalSummary, num) + for ii := 0; ii < num; ii++ { + if b.HistoricalSummaries[ii] == nil { + b.HistoricalSummaries[ii] = new(capella.HistoricalSummary) + } + if err = b.HistoricalSummaries[ii].UnmarshalSSZ(buf[ii*64 : (ii+1)*64]); err != nil { + return err + } + } + } + + // Field (34) 'PendingBalanceDeposits' + { + buf = tail[o34:o35] + num, err := ssz.DivideInt2(len(buf), 16, 134217728) + if err != nil { + return err + } + b.PendingBalanceDeposits = make([]*PendingBalanceDeposit, num) + for ii := 0; ii < num; ii++ { + if b.PendingBalanceDeposits[ii] == nil { + b.PendingBalanceDeposits[ii] = new(PendingBalanceDeposit) + } + if err = b.PendingBalanceDeposits[ii].UnmarshalSSZ(buf[ii*16 : (ii+1)*16]); err != nil { + return err + } + } + } + + // Field (35) 'PendingPartialWithdrawals' + { + buf = tail[o35:o36] + num, err := ssz.DivideInt2(len(buf), 24, 134217728) + if err != nil { + return err + } + b.PendingPartialWithdrawals = make([]*PendingPartialWithdrawal, num) + for ii := 0; ii < num; ii++ { + if b.PendingPartialWithdrawals[ii] == nil { + b.PendingPartialWithdrawals[ii] = new(PendingPartialWithdrawal) + } + if err = b.PendingPartialWithdrawals[ii].UnmarshalSSZ(buf[ii*24 : (ii+1)*24]); err != nil { + return err + } + } + } + + // Field (36) 'PendingConsolidations' + { + buf = tail[o36:] + num, err := ssz.DivideInt2(len(buf), 16, 262144) + if err != nil { + return err + } + b.PendingConsolidations = make([]*PendingConsolidation, num) + for ii := 0; ii < num; ii++ { + if b.PendingConsolidations[ii] == nil { + b.PendingConsolidations[ii] = new(PendingConsolidation) + } + if err = b.PendingConsolidations[ii].UnmarshalSSZ(buf[ii*16 : (ii+1)*16]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the BeaconState object +func (b *BeaconState) SizeSSZ() (size int) { + size = 2736713 + + // Field (7) 'HistoricalRoots' + size += len(b.HistoricalRoots) * 32 + + // Field (9) 'ETH1DataVotes' + size += len(b.ETH1DataVotes) * 72 + + // Field (11) 'Validators' + size += len(b.Validators) * 121 + + // Field (12) 'Balances' + size += len(b.Balances) * 8 + + // Field (15) 'PreviousEpochParticipation' + size += len(b.PreviousEpochParticipation) * 1 + + // Field (16) 'CurrentEpochParticipation' + size += len(b.CurrentEpochParticipation) * 1 + + // Field (21) 'InactivityScores' + size += len(b.InactivityScores) * 8 + + // Field (24) 'LatestExecutionPayloadHeader' + if b.LatestExecutionPayloadHeader == nil { + b.LatestExecutionPayloadHeader = new(ExecutionPayloadHeader) + } + size += b.LatestExecutionPayloadHeader.SizeSSZ() + + // Field (27) 'HistoricalSummaries' + size += len(b.HistoricalSummaries) * 64 + + // Field (34) 'PendingBalanceDeposits' + size += len(b.PendingBalanceDeposits) * 16 + + // Field (35) 'PendingPartialWithdrawals' + size += len(b.PendingPartialWithdrawals) * 24 + + // Field (36) 'PendingConsolidations' + size += len(b.PendingConsolidations) * 16 + + return +} + +// HashTreeRoot ssz hashes the BeaconState object +func (b *BeaconState) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// HashTreeRootWith ssz hashes the BeaconState object with a hasher +func (b *BeaconState) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'GenesisTime' + hh.PutUint64(b.GenesisTime) + + // Field (1) 'GenesisValidatorsRoot' + hh.PutBytes(b.GenesisValidatorsRoot[:]) + + // Field (2) 'Slot' + hh.PutUint64(uint64(b.Slot)) + + // Field (3) 'Fork' + if b.Fork == nil { + b.Fork = new(phase0.Fork) + } + if err = b.Fork.HashTreeRootWith(hh); err != nil { + return + } + + // Field (4) 'LatestBlockHeader' + if b.LatestBlockHeader == nil { + b.LatestBlockHeader = new(phase0.BeaconBlockHeader) + } + if err = b.LatestBlockHeader.HashTreeRootWith(hh); err != nil { + return + } + + // Field (5) 'BlockRoots' + { + if size := len(b.BlockRoots); size != 8192 { + err = ssz.ErrVectorLengthFn("BeaconState.BlockRoots", size, 8192) + return + } + subIndx := hh.Index() + for _, i := range b.BlockRoots { + hh.Append(i[:]) + } + hh.Merkleize(subIndx) + } + + // Field (6) 'StateRoots' + { + if size := len(b.StateRoots); size != 8192 { + err = ssz.ErrVectorLengthFn("BeaconState.StateRoots", size, 8192) + return + } + subIndx := hh.Index() + for _, i := range b.StateRoots { + hh.Append(i[:]) + } + hh.Merkleize(subIndx) + } + + // Field (7) 'HistoricalRoots' + { + if size := len(b.HistoricalRoots); size > 16777216 { + err = ssz.ErrListTooBigFn("BeaconState.HistoricalRoots", size, 16777216) + return + } + subIndx := hh.Index() + for _, i := range b.HistoricalRoots { + hh.Append(i[:]) + } + numItems := uint64(len(b.HistoricalRoots)) + hh.MerkleizeWithMixin(subIndx, numItems, 16777216) + } + + // Field (8) 'ETH1Data' + if b.ETH1Data == nil { + b.ETH1Data = new(phase0.ETH1Data) + } + if err = b.ETH1Data.HashTreeRootWith(hh); err != nil { + return + } + + // Field (9) 'ETH1DataVotes' + { + subIndx := hh.Index() + num := uint64(len(b.ETH1DataVotes)) + if num > 2048 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.ETH1DataVotes { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 2048) + } + + // Field (10) 'ETH1DepositIndex' + hh.PutUint64(b.ETH1DepositIndex) + + // Field (11) 'Validators' + { + subIndx := hh.Index() + num := uint64(len(b.Validators)) + if num > 1099511627776 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.Validators { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 1099511627776) + } + + // Field (12) 'Balances' + { + if size := len(b.Balances); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.Balances", size, 1099511627776) + return + } + subIndx := hh.Index() + for _, i := range b.Balances { + hh.AppendUint64(uint64(i)) + } + hh.FillUpTo32() + numItems := uint64(len(b.Balances)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(1099511627776, numItems, 8)) + } + + // Field (13) 'RANDAOMixes' + { + if size := len(b.RANDAOMixes); size != 65536 { + err = ssz.ErrVectorLengthFn("BeaconState.RANDAOMixes", size, 65536) + return + } + subIndx := hh.Index() + for _, i := range b.RANDAOMixes { + hh.Append(i[:]) + } + hh.Merkleize(subIndx) + } + + // Field (14) 'Slashings' + { + if size := len(b.Slashings); size != 8192 { + err = ssz.ErrVectorLengthFn("BeaconState.Slashings", size, 8192) + return + } + subIndx := hh.Index() + for _, i := range b.Slashings { + hh.AppendUint64(uint64(i)) + } + hh.Merkleize(subIndx) + } + + // Field (15) 'PreviousEpochParticipation' + { + if size := len(b.PreviousEpochParticipation); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.PreviousEpochParticipation", size, 1099511627776) + return + } + subIndx := hh.Index() + for _, i := range b.PreviousEpochParticipation { + hh.AppendUint8(uint8(i)) + } + hh.FillUpTo32() + numItems := uint64(len(b.PreviousEpochParticipation)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(1099511627776, numItems, 1)) + } + + // Field (16) 'CurrentEpochParticipation' + { + if size := len(b.CurrentEpochParticipation); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.CurrentEpochParticipation", size, 1099511627776) + return + } + subIndx := hh.Index() + for _, i := range b.CurrentEpochParticipation { + hh.AppendUint8(uint8(i)) + } + hh.FillUpTo32() + numItems := uint64(len(b.CurrentEpochParticipation)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(1099511627776, numItems, 1)) + } + + // Field (17) 'JustificationBits' + if size := len(b.JustificationBits); size != 1 { + err = ssz.ErrBytesLengthFn("BeaconState.JustificationBits", size, 1) + return + } + hh.PutBytes(b.JustificationBits) + + // Field (18) 'PreviousJustifiedCheckpoint' + if b.PreviousJustifiedCheckpoint == nil { + b.PreviousJustifiedCheckpoint = new(phase0.Checkpoint) + } + if err = b.PreviousJustifiedCheckpoint.HashTreeRootWith(hh); err != nil { + return + } + + // Field (19) 'CurrentJustifiedCheckpoint' + if b.CurrentJustifiedCheckpoint == nil { + b.CurrentJustifiedCheckpoint = new(phase0.Checkpoint) + } + if err = b.CurrentJustifiedCheckpoint.HashTreeRootWith(hh); err != nil { + return + } + + // Field (20) 'FinalizedCheckpoint' + if b.FinalizedCheckpoint == nil { + b.FinalizedCheckpoint = new(phase0.Checkpoint) + } + if err = b.FinalizedCheckpoint.HashTreeRootWith(hh); err != nil { + return + } + + // Field (21) 'InactivityScores' + { + if size := len(b.InactivityScores); size > 1099511627776 { + err = ssz.ErrListTooBigFn("BeaconState.InactivityScores", size, 1099511627776) + return + } + subIndx := hh.Index() + for _, i := range b.InactivityScores { + hh.AppendUint64(i) + } + hh.FillUpTo32() + numItems := uint64(len(b.InactivityScores)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(1099511627776, numItems, 8)) + } + + // Field (22) 'CurrentSyncCommittee' + if b.CurrentSyncCommittee == nil { + b.CurrentSyncCommittee = new(altair.SyncCommittee) + } + if err = b.CurrentSyncCommittee.HashTreeRootWith(hh); err != nil { + return + } + + // Field (23) 'NextSyncCommittee' + if b.NextSyncCommittee == nil { + b.NextSyncCommittee = new(altair.SyncCommittee) + } + if err = b.NextSyncCommittee.HashTreeRootWith(hh); err != nil { + return + } + + // Field (24) 'LatestExecutionPayloadHeader' + if err = b.LatestExecutionPayloadHeader.HashTreeRootWith(hh); err != nil { + return + } + + // Field (25) 'NextWithdrawalIndex' + hh.PutUint64(uint64(b.NextWithdrawalIndex)) + + // Field (26) 'NextWithdrawalValidatorIndex' + hh.PutUint64(uint64(b.NextWithdrawalValidatorIndex)) + + // Field (27) 'HistoricalSummaries' + { + subIndx := hh.Index() + num := uint64(len(b.HistoricalSummaries)) + if num > 16777216 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.HistoricalSummaries { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16777216) + } + + // Field (28) 'DepositReceiptsStartIndex' + hh.PutUint64(b.DepositReceiptsStartIndex) + + // Field (29) 'DepositBalanceToConsume' + hh.PutUint64(uint64(b.DepositBalanceToConsume)) + + // Field (30) 'ExitBalanceToConsume' + hh.PutUint64(uint64(b.ExitBalanceToConsume)) + + // Field (31) 'EarliestExitEpoch' + hh.PutUint64(uint64(b.EarliestExitEpoch)) + + // Field (32) 'ConsolidationBalanceToConsume' + hh.PutUint64(uint64(b.ConsolidationBalanceToConsume)) + + // Field (33) 'EarliestConsolidationEpoch' + hh.PutUint64(uint64(b.EarliestConsolidationEpoch)) + + // Field (34) 'PendingBalanceDeposits' + { + subIndx := hh.Index() + num := uint64(len(b.PendingBalanceDeposits)) + if num > 134217728 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.PendingBalanceDeposits { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 134217728) + } + + // Field (35) 'PendingPartialWithdrawals' + { + subIndx := hh.Index() + num := uint64(len(b.PendingPartialWithdrawals)) + if num > 134217728 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.PendingPartialWithdrawals { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 134217728) + } + + // Field (36) 'PendingConsolidations' + { + subIndx := hh.Index() + num := uint64(len(b.PendingConsolidations)) + if num > 262144 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range b.PendingConsolidations { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 262144) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the BeaconState object +func (b *BeaconState) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} diff --git a/spec/electra/beaconstate_yaml.go b/spec/electra/beaconstate_yaml.go new file mode 100644 index 00000000..7ff889e0 --- /dev/null +++ b/spec/electra/beaconstate_yaml.go @@ -0,0 +1,131 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/attestantio/go-eth2-client/spec/altair" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// beaconStateYAML is the spec representation of the struct. +type beaconStateYAML struct { + GenesisTime uint64 `yaml:"genesis_time"` + GenesisValidatorsRoot phase0.Root `yaml:"genesis_validators_root"` + Slot phase0.Slot `yaml:"slot"` + Fork *phase0.Fork `yaml:"fork"` + LatestBlockHeader *phase0.BeaconBlockHeader `yaml:"latest_block_header"` + BlockRoots []phase0.Root `yaml:"block_roots"` + StateRoots []phase0.Root `yaml:"state_roots"` + HistoricalRoots []phase0.Root `yaml:"historical_roots"` + ETH1Data *phase0.ETH1Data `yaml:"eth1_data"` + ETH1DataVotes []*phase0.ETH1Data `yaml:"eth1_data_votes"` + ETH1DepositIndex uint64 `yaml:"eth1_deposit_index"` + Validators []*phase0.Validator `yaml:"validators"` + Balances []phase0.Gwei `yaml:"balances"` + RANDAOMixes []phase0.Root `yaml:"randao_mixes"` + Slashings []phase0.Gwei `yaml:"slashings"` + PreviousEpochParticipation []altair.ParticipationFlags `yaml:"previous_epoch_participation"` + CurrentEpochParticipation []altair.ParticipationFlags `yaml:"current_epoch_participation"` + JustificationBits string `yaml:"justification_bits"` + PreviousJustifiedCheckpoint *phase0.Checkpoint `yaml:"previous_justified_checkpoint"` + CurrentJustifiedCheckpoint *phase0.Checkpoint `yaml:"current_justified_checkpoint"` + FinalizedCheckpoint *phase0.Checkpoint `yaml:"finalized_checkpoint"` + InactivityScores []uint64 `yaml:"inactivity_scores"` + CurrentSyncCommittee *altair.SyncCommittee `yaml:"current_sync_committee"` + NextSyncCommittee *altair.SyncCommittee `yaml:"next_sync_committee"` + LatestExecutionPayloadHeader *ExecutionPayloadHeader `yaml:"latest_execution_payload_header"` + NextWithdrawalIndex capella.WithdrawalIndex `yaml:"next_withdrawal_index"` + NextWithdrawalValidatorIndex phase0.ValidatorIndex `yaml:"next_withdrawal_validator_index"` + HistoricalSummaries []*capella.HistoricalSummary `yaml:"historical_summaries"` + DepositReceiptsStartIndex uint64 `yaml:"deposit_receipts_start_index"` + DepositBalanceToConsume phase0.Gwei `yaml:"deposit_balance_to_consume"` + ExitBalanceToConsume phase0.Gwei `yaml:"exit_balance_to_consume"` + EarliestExitEpoch phase0.Epoch `yaml:"earliest_exit_epoch"` + ConsolidationBalanceToConsume phase0.Gwei `yaml:"consolidation_balance_to_consume"` + EarliestConsolidationEpoch phase0.Epoch `yaml:"earliest_consolidation_epoch"` + PendingBalanceDeposits []*PendingBalanceDeposit `yaml:"pending_balance_deposits"` + PendingPartialWithdrawals []*PendingPartialWithdrawal `yaml:"pending_partial_withdrawals"` + PendingConsolidations []*PendingConsolidation `yaml:"pending_consolidations"` +} + +// MarshalYAML implements yaml.Marshaler. +func (b *BeaconState) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&beaconStateYAML{ + GenesisTime: b.GenesisTime, + GenesisValidatorsRoot: b.GenesisValidatorsRoot, + Slot: b.Slot, + Fork: b.Fork, + LatestBlockHeader: b.LatestBlockHeader, + BlockRoots: b.BlockRoots, + StateRoots: b.StateRoots, + HistoricalRoots: b.HistoricalRoots, + ETH1Data: b.ETH1Data, + ETH1DataVotes: b.ETH1DataVotes, + ETH1DepositIndex: b.ETH1DepositIndex, + Validators: b.Validators, + Balances: b.Balances, + RANDAOMixes: b.RANDAOMixes, + Slashings: b.Slashings, + PreviousEpochParticipation: b.PreviousEpochParticipation, + CurrentEpochParticipation: b.CurrentEpochParticipation, + JustificationBits: fmt.Sprintf("%#x", b.JustificationBits.Bytes()), + PreviousJustifiedCheckpoint: b.PreviousJustifiedCheckpoint, + CurrentJustifiedCheckpoint: b.CurrentJustifiedCheckpoint, + FinalizedCheckpoint: b.FinalizedCheckpoint, + InactivityScores: b.InactivityScores, + CurrentSyncCommittee: b.CurrentSyncCommittee, + NextSyncCommittee: b.NextSyncCommittee, + LatestExecutionPayloadHeader: b.LatestExecutionPayloadHeader, + NextWithdrawalIndex: b.NextWithdrawalIndex, + NextWithdrawalValidatorIndex: b.NextWithdrawalValidatorIndex, + HistoricalSummaries: b.HistoricalSummaries, + DepositReceiptsStartIndex: b.DepositReceiptsStartIndex, + DepositBalanceToConsume: b.DepositBalanceToConsume, + ExitBalanceToConsume: b.ExitBalanceToConsume, + EarliestExitEpoch: b.EarliestExitEpoch, + ConsolidationBalanceToConsume: b.ConsolidationBalanceToConsume, + EarliestConsolidationEpoch: b.EarliestConsolidationEpoch, + PendingBalanceDeposits: b.PendingBalanceDeposits, + PendingPartialWithdrawals: b.PendingPartialWithdrawals, + PendingConsolidations: b.PendingConsolidations, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (b *BeaconState) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled beaconStateJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return b.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/consolidation.go b/spec/electra/consolidation.go new file mode 100644 index 00000000..9cda3cc5 --- /dev/null +++ b/spec/electra/consolidation.go @@ -0,0 +1,38 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// Consolidation represents a consolidation. +type Consolidation struct { + SourceIndex phase0.ValidatorIndex + TargetIndex phase0.ValidatorIndex + Epoch phase0.Epoch +} + +// String returns a string version of the structure. +func (c *Consolidation) String() string { + data, err := yaml.Marshal(c) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/consolidation_json.go b/spec/electra/consolidation_json.go new file mode 100644 index 00000000..e7ca474f --- /dev/null +++ b/spec/electra/consolidation_json.go @@ -0,0 +1,58 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// consolidationJSON is the spec representation of the struct. +type consolidationJSON struct { + SourceIndex phase0.ValidatorIndex `json:"source_index"` + TargetIndex phase0.ValidatorIndex `json:"target_index"` + Epoch phase0.Epoch `json:"epoch"` +} + +// MarshalJSON implements json.Marshaler. +func (c *Consolidation) MarshalJSON() ([]byte, error) { + return json.Marshal(&consolidationJSON{ + SourceIndex: c.SourceIndex, + TargetIndex: c.TargetIndex, + Epoch: c.Epoch, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (c *Consolidation) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&consolidationJSON{}, input) + if err != nil { + return err + } + + if err := c.SourceIndex.UnmarshalJSON(raw["source_index"]); err != nil { + return errors.Wrap(err, "source_index") + } + if err := c.TargetIndex.UnmarshalJSON(raw["target_index"]); err != nil { + return errors.Wrap(err, "target_index") + } + if err := c.Epoch.UnmarshalJSON(raw["epoch"]); err != nil { + return errors.Wrap(err, "epoch") + } + + return nil +} diff --git a/spec/electra/consolidation_ssz.go b/spec/electra/consolidation_ssz.go new file mode 100644 index 00000000..abd78a12 --- /dev/null +++ b/spec/electra/consolidation_ssz.go @@ -0,0 +1,83 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the Consolidation object +func (c *Consolidation) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(c) +} + +// MarshalSSZTo ssz marshals the Consolidation object to a target array +func (c *Consolidation) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'SourceIndex' + dst = ssz.MarshalUint64(dst, uint64(c.SourceIndex)) + + // Field (1) 'TargetIndex' + dst = ssz.MarshalUint64(dst, uint64(c.TargetIndex)) + + // Field (2) 'Epoch' + dst = ssz.MarshalUint64(dst, uint64(c.Epoch)) + + return +} + +// UnmarshalSSZ ssz unmarshals the Consolidation object +func (c *Consolidation) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 24 { + return ssz.ErrSize + } + + // Field (0) 'SourceIndex' + c.SourceIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'TargetIndex' + c.TargetIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[8:16])) + + // Field (2) 'Epoch' + c.Epoch = phase0.Epoch(ssz.UnmarshallUint64(buf[16:24])) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the Consolidation object +func (c *Consolidation) SizeSSZ() (size int) { + size = 24 + return +} + +// HashTreeRoot ssz hashes the Consolidation object +func (c *Consolidation) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(c) +} + +// HashTreeRootWith ssz hashes the Consolidation object with a hasher +func (c *Consolidation) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'SourceIndex' + hh.PutUint64(uint64(c.SourceIndex)) + + // Field (1) 'TargetIndex' + hh.PutUint64(uint64(c.TargetIndex)) + + // Field (2) 'Epoch' + hh.PutUint64(uint64(c.Epoch)) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the Consolidation object +func (c *Consolidation) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(c) +} diff --git a/spec/electra/consolidation_yaml.go b/spec/electra/consolidation_yaml.go new file mode 100644 index 00000000..f31a3e55 --- /dev/null +++ b/spec/electra/consolidation_yaml.go @@ -0,0 +1,60 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// consolidationYAML is the spec representation of the struct. +type consolidationYAML struct { + SourceIndex phase0.ValidatorIndex `yaml:"source_index"` + TargetIndex phase0.ValidatorIndex `yaml:"target_index"` + Epoch phase0.Epoch `yaml:"epoch"` +} + +// MarshalYAML implements yaml.Marshaler. +func (c *Consolidation) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&consolidationYAML{ + SourceIndex: c.SourceIndex, + TargetIndex: c.TargetIndex, + Epoch: c.Epoch, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (c *Consolidation) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled consolidationJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(&unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return c.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/depositreceipt.go b/spec/electra/depositreceipt.go new file mode 100644 index 00000000..0b5689bb --- /dev/null +++ b/spec/electra/depositreceipt.go @@ -0,0 +1,40 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// DepositReceipt represents a deposit receipt. +type DepositReceipt struct { + Pubkey phase0.BLSPubKey `ssz-size:"48"` + WithdrawalCredentials []byte `ssz-size:"32"` + Amount phase0.Gwei + Signature phase0.BLSSignature `ssz-size:"96"` + Index uint64 +} + +// String returns a string version of the structure. +func (d *DepositReceipt) String() string { + data, err := yaml.Marshal(d) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/depositreceipt_json.go b/spec/electra/depositreceipt_json.go new file mode 100644 index 00000000..d9239915 --- /dev/null +++ b/spec/electra/depositreceipt_json.go @@ -0,0 +1,111 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// depositReceiptJSON is the spec representation of the struct. +type depositReceiptJSON struct { + Pubkey string `json:"pubkey"` + WithdrawalCredentials string `json:"withdrawal_credentials"` + Amount string `json:"amount"` + Signature string `json:"signature"` + Index string `json:"index"` +} + +// MarshalJSON implements json.Marshaler. +func (d *DepositReceipt) MarshalJSON() ([]byte, error) { + return json.Marshal(&depositReceiptJSON{ + Pubkey: fmt.Sprintf("%#x", d.Pubkey), + WithdrawalCredentials: fmt.Sprintf("%#x", d.WithdrawalCredentials), + Amount: fmt.Sprintf("%d", d.Amount), + Signature: fmt.Sprintf("%#x", d.Signature), + Index: fmt.Sprintf("%d", d.Index), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (d *DepositReceipt) UnmarshalJSON(input []byte) error { + var depositReceipt depositReceiptJSON + if err := json.Unmarshal(input, &depositReceipt); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return d.unpack(&depositReceipt) +} + +func (d *DepositReceipt) unpack(depositReceipt *depositReceiptJSON) error { + if depositReceipt.Pubkey == "" { + return errors.New("public key missing") + } + pubkey, err := hex.DecodeString(strings.TrimPrefix(depositReceipt.Pubkey, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for public key") + } + if len(pubkey) != phase0.PublicKeyLength { + return errors.New("incorrect length for public key") + } + copy(d.Pubkey[:], pubkey) + + if depositReceipt.WithdrawalCredentials == "" { + return errors.New("withdrawal credentials missing") + } + if d.WithdrawalCredentials, err = hex.DecodeString(strings.TrimPrefix(depositReceipt.WithdrawalCredentials, "0x")); err != nil { + return errors.Wrap(err, "invalid value for withdrawal credentials") + } + if len(d.WithdrawalCredentials) != phase0.HashLength { + return errors.New("incorrect length for withdrawal credentials") + } + + if depositReceipt.Amount == "" { + return errors.New("amount missing") + } + amount, err := strconv.ParseUint(depositReceipt.Amount, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for amount") + } + d.Amount = phase0.Gwei(amount) + + if depositReceipt.Signature == "" { + return errors.New("signature missing") + } + signature, err := hex.DecodeString(strings.TrimPrefix(depositReceipt.Signature, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for signature") + } + if len(signature) != phase0.SignatureLength { + return errors.New("incorrect length for signature") + } + copy(d.Signature[:], signature) + + if depositReceipt.Index == "" { + return errors.New("index missing") + } + index, err := strconv.ParseUint(depositReceipt.Index, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for index") + } + d.Index = index + + return nil +} diff --git a/spec/electra/depositreceipt_ssz.go b/spec/electra/depositreceipt_ssz.go new file mode 100644 index 00000000..bc403af9 --- /dev/null +++ b/spec/electra/depositreceipt_ssz.go @@ -0,0 +1,112 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 0ba659c6f75c98d67fde663b42e7c0d93368dc54eb9bdec5d3b8cadf0f43e416 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the DepositReceipt object +func (d *DepositReceipt) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(d) +} + +// MarshalSSZTo ssz marshals the DepositReceipt object to a target array +func (d *DepositReceipt) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Pubkey' + dst = append(dst, d.Pubkey[:]...) + + // Field (1) 'WithdrawalCredentials' + if size := len(d.WithdrawalCredentials); size != 32 { + err = ssz.ErrBytesLengthFn("DepositReceipt.WithdrawalCredentials", size, 32) + return + } + dst = append(dst, d.WithdrawalCredentials...) + + // Field (2) 'Amount' + dst = ssz.MarshalUint64(dst, uint64(d.Amount)) + + // Field (3) 'Signature' + dst = append(dst, d.Signature[:]...) + + // Field (4) 'Index' + dst = ssz.MarshalUint64(dst, d.Index) + + return +} + +// UnmarshalSSZ ssz unmarshals the DepositReceipt object +func (d *DepositReceipt) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 192 { + return ssz.ErrSize + } + + // Field (0) 'Pubkey' + copy(d.Pubkey[:], buf[0:48]) + + // Field (1) 'WithdrawalCredentials' + if cap(d.WithdrawalCredentials) == 0 { + d.WithdrawalCredentials = make([]byte, 0, len(buf[48:80])) + } + d.WithdrawalCredentials = append(d.WithdrawalCredentials, buf[48:80]...) + + // Field (2) 'Amount' + d.Amount = phase0.Gwei(ssz.UnmarshallUint64(buf[80:88])) + + // Field (3) 'Signature' + copy(d.Signature[:], buf[88:184]) + + // Field (4) 'Index' + d.Index = ssz.UnmarshallUint64(buf[184:192]) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the DepositReceipt object +func (d *DepositReceipt) SizeSSZ() (size int) { + size = 192 + return +} + +// HashTreeRoot ssz hashes the DepositReceipt object +func (d *DepositReceipt) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(d) +} + +// HashTreeRootWith ssz hashes the DepositReceipt object with a hasher +func (d *DepositReceipt) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Pubkey' + hh.PutBytes(d.Pubkey[:]) + + // Field (1) 'WithdrawalCredentials' + if size := len(d.WithdrawalCredentials); size != 32 { + err = ssz.ErrBytesLengthFn("DepositReceipt.WithdrawalCredentials", size, 32) + return + } + hh.PutBytes(d.WithdrawalCredentials) + + // Field (2) 'Amount' + hh.PutUint64(uint64(d.Amount)) + + // Field (3) 'Signature' + hh.PutBytes(d.Signature[:]) + + // Field (4) 'Index' + hh.PutUint64(d.Index) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the DepositReceipt object +func (d *DepositReceipt) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(d) +} diff --git a/spec/electra/depositreceipt_yaml.go b/spec/electra/depositreceipt_yaml.go new file mode 100644 index 00000000..8efd082c --- /dev/null +++ b/spec/electra/depositreceipt_yaml.go @@ -0,0 +1,57 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "fmt" + + "github.com/goccy/go-yaml" +) + +// depositReceiptYAML is the spec representation of the struct. +type depositReceiptYAML struct { + Pubkey string `yaml:"pubkey"` + WithdrawalCredentials string `yaml:"withdrawal_credentials"` + Amount uint64 `yaml:"amount"` + Signature string `yaml:"signature"` + Index uint64 `yaml:"index"` +} + +// MarshalYAML implements yaml.Marshaler. +func (d *DepositReceipt) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&depositReceiptYAML{ + Pubkey: fmt.Sprintf("%#x", d.Pubkey), + WithdrawalCredentials: fmt.Sprintf("%#x", d.WithdrawalCredentials), + Amount: uint64(d.Amount), + Signature: fmt.Sprintf("%#x", d.Signature), + Index: d.Index, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (d *DepositReceipt) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var depositReceipt depositReceiptJSON + if err := yaml.Unmarshal(input, &depositReceipt); err != nil { + return err + } + + return d.unpack(&depositReceipt) +} diff --git a/spec/electra/executionlayerwithdrawalrequest.go b/spec/electra/executionlayerwithdrawalrequest.go new file mode 100644 index 00000000..45d3e826 --- /dev/null +++ b/spec/electra/executionlayerwithdrawalrequest.go @@ -0,0 +1,39 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// ExecutionLayerWithdrawalRequest represents an execution layer withdraw request. +type ExecutionLayerWithdrawalRequest struct { + SourceAddress bellatrix.ExecutionAddress `ssz-size:"20"` + ValidatorPubkey phase0.BLSPubKey `ssz-size:"48"` + Amount phase0.Gwei +} + +// String returns a string version of the structure. +func (e *ExecutionLayerWithdrawalRequest) String() string { + data, err := yaml.Marshal(e) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/executionlayerwithdrawalrequest_json.go b/spec/electra/executionlayerwithdrawalrequest_json.go new file mode 100644 index 00000000..e4fab2d7 --- /dev/null +++ b/spec/electra/executionlayerwithdrawalrequest_json.go @@ -0,0 +1,59 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// executionLayerWithdrawalRequestJSON is the spec representation of the struct. +type executionLayerWithdrawalRequestJSON struct { + SourceAddress bellatrix.ExecutionAddress `json:"source_address"` + ValidatorPubkey phase0.BLSPubKey `json:"validator_pubkey"` + Amount phase0.Gwei `json:"amount"` +} + +// MarshalJSON implements json.Marshaler. +func (e *ExecutionLayerWithdrawalRequest) MarshalJSON() ([]byte, error) { + return json.Marshal(&executionLayerWithdrawalRequestJSON{ + SourceAddress: e.SourceAddress, + ValidatorPubkey: e.ValidatorPubkey, + Amount: e.Amount, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (e *ExecutionLayerWithdrawalRequest) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&executionLayerWithdrawalRequestJSON{}, input) + if err != nil { + return err + } + + if err := e.SourceAddress.UnmarshalJSON(raw["source_address"]); err != nil { + return errors.Wrap(err, "source_address") + } + if err := e.ValidatorPubkey.UnmarshalJSON(raw["validator_pubkey"]); err != nil { + return errors.Wrap(err, "validator_pubkey") + } + if err := e.Amount.UnmarshalJSON(raw["amount"]); err != nil { + return errors.Wrap(err, "amount") + } + + return nil +} diff --git a/spec/electra/executionlayerwithdrawalrequest_ssz.go b/spec/electra/executionlayerwithdrawalrequest_ssz.go new file mode 100644 index 00000000..0145fd91 --- /dev/null +++ b/spec/electra/executionlayerwithdrawalrequest_ssz.go @@ -0,0 +1,83 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the ExecutionLayerWithdrawalRequest object +func (e *ExecutionLayerWithdrawalRequest) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(e) +} + +// MarshalSSZTo ssz marshals the ExecutionLayerWithdrawalRequest object to a target array +func (e *ExecutionLayerWithdrawalRequest) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'SourceAddress' + dst = append(dst, e.SourceAddress[:]...) + + // Field (1) 'ValidatorPubkey' + dst = append(dst, e.ValidatorPubkey[:]...) + + // Field (2) 'Amount' + dst = ssz.MarshalUint64(dst, uint64(e.Amount)) + + return +} + +// UnmarshalSSZ ssz unmarshals the ExecutionLayerWithdrawalRequest object +func (e *ExecutionLayerWithdrawalRequest) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 76 { + return ssz.ErrSize + } + + // Field (0) 'SourceAddress' + copy(e.SourceAddress[:], buf[0:20]) + + // Field (1) 'ValidatorPubkey' + copy(e.ValidatorPubkey[:], buf[20:68]) + + // Field (2) 'Amount' + e.Amount = phase0.Gwei(ssz.UnmarshallUint64(buf[68:76])) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ExecutionLayerWithdrawalRequest object +func (e *ExecutionLayerWithdrawalRequest) SizeSSZ() (size int) { + size = 76 + return +} + +// HashTreeRoot ssz hashes the ExecutionLayerWithdrawalRequest object +func (e *ExecutionLayerWithdrawalRequest) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(e) +} + +// HashTreeRootWith ssz hashes the ExecutionLayerWithdrawalRequest object with a hasher +func (e *ExecutionLayerWithdrawalRequest) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'SourceAddress' + hh.PutBytes(e.SourceAddress[:]) + + // Field (1) 'ValidatorPubkey' + hh.PutBytes(e.ValidatorPubkey[:]) + + // Field (2) 'Amount' + hh.PutUint64(uint64(e.Amount)) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the ExecutionLayerWithdrawalRequest object +func (e *ExecutionLayerWithdrawalRequest) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(e) +} diff --git a/spec/electra/executionlayerwithdrawalrequest_yaml.go b/spec/electra/executionlayerwithdrawalrequest_yaml.go new file mode 100644 index 00000000..3511edf3 --- /dev/null +++ b/spec/electra/executionlayerwithdrawalrequest_yaml.go @@ -0,0 +1,61 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// executionLayerWithdrawalRequestYAML is the spec representation of the struct. +type executionLayerWithdrawalRequestYAML struct { + SourceAddress bellatrix.ExecutionAddress `yaml:"source_address"` + ValidatorPubkey phase0.BLSPubKey `yaml:"validator_pubkey"` + Amount phase0.Gwei `yaml:"amount"` +} + +// MarshalYAML implements yaml.Marshaler. +func (e *ExecutionLayerWithdrawalRequest) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&executionLayerWithdrawalRequestYAML{ + SourceAddress: e.SourceAddress, + ValidatorPubkey: e.ValidatorPubkey, + Amount: e.Amount, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (e *ExecutionLayerWithdrawalRequest) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled executionLayerWithdrawalRequestJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(&unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return e.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/executionpayload.go b/spec/electra/executionpayload.go new file mode 100644 index 00000000..ab4cf132 --- /dev/null +++ b/spec/electra/executionpayload.go @@ -0,0 +1,57 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/holiman/uint256" +) + +// ExecutionPayload represents an execution layer payload. +type ExecutionPayload struct { + ParentHash phase0.Hash32 `ssz-size:"32"` + FeeRecipient bellatrix.ExecutionAddress `ssz-size:"20"` + StateRoot phase0.Root `ssz-size:"32"` + ReceiptsRoot phase0.Root `ssz-size:"32"` + LogsBloom [256]byte `ssz-size:"256"` + PrevRandao [32]byte `ssz-size:"32"` + BlockNumber uint64 + GasLimit uint64 + GasUsed uint64 + Timestamp uint64 + ExtraData []byte `ssz-max:"32"` + BaseFeePerGas *uint256.Int `ssz-size:"32"` + BlockHash phase0.Hash32 `ssz-size:"32"` + Transactions []bellatrix.Transaction `ssz-max:"1048576,1073741824" ssz-size:"?,?"` + Withdrawals []*capella.Withdrawal `ssz-max:"16"` + BlobGasUsed uint64 + ExcessBlobGas uint64 + DepositReceipts []*DepositReceipt `ssz-max:"8192"` + WithdrawalRequests []*ExecutionLayerWithdrawalRequest `ssz-max:"16"` +} + +// String returns a string version of the structure. +func (e *ExecutionPayload) String() string { + data, err := yaml.Marshal(e) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/executionpayload_json.go b/spec/electra/executionpayload_json.go new file mode 100644 index 00000000..2a89e3c6 --- /dev/null +++ b/spec/electra/executionpayload_json.go @@ -0,0 +1,271 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/holiman/uint256" + "github.com/pkg/errors" +) + +// executionPayloadJSON is the spec representation of the struct. +type executionPayloadJSON struct { + ParentHash phase0.Hash32 `json:"parent_hash"` + FeeRecipient bellatrix.ExecutionAddress `json:"fee_recipient"` + StateRoot phase0.Root `json:"state_root"` + ReceiptsRoot phase0.Root `json:"receipts_root"` + LogsBloom string `json:"logs_bloom"` + PrevRandao string `json:"prev_randao"` + BlockNumber string `json:"block_number"` + GasLimit string `json:"gas_limit"` + GasUsed string `json:"gas_used"` + Timestamp string `json:"timestamp"` + ExtraData string `json:"extra_data"` + BaseFeePerGas string `json:"base_fee_per_gas"` + BlockHash phase0.Hash32 `json:"block_hash"` + Transactions []string `json:"transactions"` + Withdrawals []*capella.Withdrawal `json:"withdrawals"` + BlobGasUsed string `json:"blob_gas_used"` + ExcessBlobGas string `json:"excess_blob_gas"` + DepositReceipts []*DepositReceipt `json:"deposit_receipts"` + WithdrawalRequests []*ExecutionLayerWithdrawalRequest `json:"withdrawal_requests"` +} + +// MarshalJSON implements json.Marshaler. +func (e *ExecutionPayload) MarshalJSON() ([]byte, error) { + transactions := make([]string, len(e.Transactions)) + for i := range e.Transactions { + transactions[i] = fmt.Sprintf("%#x", e.Transactions[i]) + } + + extraData := "0x" + if len(e.ExtraData) > 0 { + extraData = fmt.Sprintf("%#x", e.ExtraData) + } + + return json.Marshal(&executionPayloadJSON{ + ParentHash: e.ParentHash, + FeeRecipient: e.FeeRecipient, + StateRoot: e.StateRoot, + ReceiptsRoot: e.ReceiptsRoot, + LogsBloom: fmt.Sprintf("%#x", e.LogsBloom), + PrevRandao: fmt.Sprintf("%#x", e.PrevRandao), + BlockNumber: strconv.FormatUint(e.BlockNumber, 10), + GasLimit: strconv.FormatUint(e.GasLimit, 10), + GasUsed: strconv.FormatUint(e.GasUsed, 10), + Timestamp: strconv.FormatUint(e.Timestamp, 10), + ExtraData: extraData, + BaseFeePerGas: e.BaseFeePerGas.Dec(), + BlockHash: e.BlockHash, + Transactions: transactions, + Withdrawals: e.Withdrawals, + BlobGasUsed: strconv.FormatUint(e.BlobGasUsed, 10), + ExcessBlobGas: strconv.FormatUint(e.ExcessBlobGas, 10), + DepositReceipts: e.DepositReceipts, + WithdrawalRequests: e.WithdrawalRequests, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +// +//nolint:gocyclo +func (e *ExecutionPayload) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&executionPayloadJSON{}, input) + if err != nil { + return err + } + + if err := e.ParentHash.UnmarshalJSON(raw["parent_hash"]); err != nil { + return errors.Wrap(err, "parent_hash") + } + + if err := e.FeeRecipient.UnmarshalJSON(raw["fee_recipient"]); err != nil { + return errors.Wrap(err, "fee_recipient") + } + + if err := e.StateRoot.UnmarshalJSON(raw["state_root"]); err != nil { + return errors.Wrap(err, "state_root") + } + + if err := e.ReceiptsRoot.UnmarshalJSON(raw["receipts_root"]); err != nil { + return errors.Wrap(err, "receipts_root") + } + + logsBloom := raw["logs_bloom"] + if !bytes.HasPrefix(logsBloom, []byte{'"', '0', 'x'}) { + return errors.New("logs_bloom: invalid prefix") + } + if !bytes.HasSuffix(logsBloom, []byte{'"'}) { + return errors.New("logs_bloom: invalid suffix") + } + if len(logsBloom) != 1+2+256*2+1 { + return errors.New("logs_bloom: incorrect length") + } + length, err := hex.Decode(e.LogsBloom[:], logsBloom[3:3+256*2]) + if err != nil { + return errors.Wrap(err, "logs_bloom") + } + if length != 256 { + return errors.New("logs_bloom: incorrect length") + } + + prevRandao := raw["prev_randao"] + if !bytes.HasPrefix(prevRandao, []byte{'"', '0', 'x'}) { + return errors.New("prev_randao: invalid prefix") + } + if !bytes.HasSuffix(prevRandao, []byte{'"'}) { + return errors.New("prev_randao: invalid suffix") + } + if len(prevRandao) != 1+2+32*2+1 { + return errors.New("prev_randao: incorrect length") + } + length, err = hex.Decode(e.PrevRandao[:], prevRandao[3:3+32*2]) + if err != nil { + return errors.Wrap(err, "prev_randao") + } + if length != 32 { + return errors.New("prev_randao: incorrect length") + } + + tmpUint, err := strconv.ParseUint(string(bytes.Trim(raw["block_number"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "block_number") + } + e.BlockNumber = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["gas_limit"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "gas_limit") + } + e.GasLimit = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["gas_used"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "gas_used") + } + e.GasUsed = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["timestamp"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "timestamp") + } + e.Timestamp = tmpUint + + var tmpBytes []byte + switch { + case bytes.Equal(raw["extra_data"], []byte{'0', 'x'}), bytes.Equal(raw["extra_data"], []byte{'0'}): + // Empty. + default: + tmpBytes = bytes.TrimPrefix(bytes.Trim(raw["extra_data"], `"`), []byte{'0', 'x'}) + if len(tmpBytes)%2 == 1 { + tmpBytes = []byte(fmt.Sprintf("0%s", string(tmpBytes))) + } + tmp, err := hex.DecodeString(string(tmpBytes)) + if err != nil { + return errors.Wrap(err, "extra_data") + } + if len(tmp) > 32 { + return errors.New("extra_data: incorrect length") + } + e.ExtraData = tmp + } + + tmpBytes = bytes.Trim(raw["base_fee_per_gas"], `"`) + tmpBytes = bytes.TrimPrefix(tmpBytes, []byte{'0', 'x'}) + if bytes.HasPrefix(tmpBytes, []byte{'0', 'x'}) { + e.BaseFeePerGas, err = uint256.FromHex(string(tmpBytes)) + } else { + e.BaseFeePerGas, err = uint256.FromDecimal(string(tmpBytes)) + } + if err != nil { + return errors.Wrap(err, "base_fee_per_gas") + } + + if err := e.BlockHash.UnmarshalJSON(raw["block_hash"]); err != nil { + return errors.Wrap(err, "block_hash") + } + + transactions := make([]json.RawMessage, 0) + if err := json.Unmarshal(raw["transactions"], &transactions); err != nil { + return errors.Wrap(err, "transactions") + } + if len(transactions) > bellatrix.MaxTransactionsPerPayload { + return errors.Wrap(err, "incorrect length for transactions") + } + e.Transactions = make([]bellatrix.Transaction, len(transactions)) + for i := range transactions { + if len(transactions[i]) == 0 || + bytes.Equal(transactions[i], []byte{'"', '"'}) || + bytes.Equal(transactions[i], []byte{'"', '0', 'x', '"'}) { + return fmt.Errorf("transaction %d: missing", i) + } + e.Transactions[i] = make([]byte, (len(transactions[i])-4)/2) + if err := json.Unmarshal(transactions[i], &e.Transactions[i]); err != nil { + return errors.Wrapf(err, "transaction %d", i) + } + if len(e.Transactions[i]) > bellatrix.MaxBytesPerTransaction { + return errors.Wrapf(err, "incorrect length for transaction %d", i) + } + } + + if err := json.Unmarshal(raw["withdrawals"], &e.Withdrawals); err != nil { + return errors.Wrap(err, "withdrawals") + } + for i := range e.Withdrawals { + if e.Withdrawals[i] == nil { + return fmt.Errorf("withdrawals entry %d missing", i) + } + } + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["blob_gas_used"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "blob_gas_used") + } + e.BlobGasUsed = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["excess_blob_gas"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "excess_blob_gas") + } + e.ExcessBlobGas = tmpUint + + if err := json.Unmarshal(raw["deposit_receipts"], &e.DepositReceipts); err != nil { + return errors.Wrap(err, "deposit_receipts") + } + for i := range e.DepositReceipts { + if e.DepositReceipts[i] == nil { + return fmt.Errorf("deposit receipts entry %d missing", i) + } + } + + if err := json.Unmarshal(raw["withdrawal_requests"], &e.WithdrawalRequests); err != nil { + return errors.Wrap(err, "withdrawal_requests") + } + for i := range e.WithdrawalRequests { + if e.WithdrawalRequests[i] == nil { + return fmt.Errorf("withdraw requests entry %d missing", i) + } + } + + return nil +} diff --git a/spec/electra/executionpayload_ssz.go b/spec/electra/executionpayload_ssz.go new file mode 100644 index 00000000..d675e644 --- /dev/null +++ b/spec/electra/executionpayload_ssz.go @@ -0,0 +1,503 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: a63387b6d1f288be86bb43febdcd67174ea6fd3cbf23b63c8656748b54289b53 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/capella" + ssz "github.com/ferranbt/fastssz" + "github.com/holiman/uint256" +) + +// MarshalSSZ ssz marshals the ExecutionPayload object +func (e *ExecutionPayload) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(e) +} + +// MarshalSSZTo ssz marshals the ExecutionPayload object to a target array +func (e *ExecutionPayload) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(536) + + // Field (0) 'ParentHash' + dst = append(dst, e.ParentHash[:]...) + + // Field (1) 'FeeRecipient' + dst = append(dst, e.FeeRecipient[:]...) + + // Field (2) 'StateRoot' + dst = append(dst, e.StateRoot[:]...) + + // Field (3) 'ReceiptsRoot' + dst = append(dst, e.ReceiptsRoot[:]...) + + // Field (4) 'LogsBloom' + dst = append(dst, e.LogsBloom[:]...) + + // Field (5) 'PrevRandao' + dst = append(dst, e.PrevRandao[:]...) + + // Field (6) 'BlockNumber' + dst = ssz.MarshalUint64(dst, e.BlockNumber) + + // Field (7) 'GasLimit' + dst = ssz.MarshalUint64(dst, e.GasLimit) + + // Field (8) 'GasUsed' + dst = ssz.MarshalUint64(dst, e.GasUsed) + + // Field (9) 'Timestamp' + dst = ssz.MarshalUint64(dst, e.Timestamp) + + // Offset (10) 'ExtraData' + dst = ssz.WriteOffset(dst, offset) + offset += len(e.ExtraData) + + // Field (11) 'BaseFeePerGas' + baseFeePerGas := e.BaseFeePerGas.Bytes32() + for i := 0; i < 32; i++ { + dst = append(dst, baseFeePerGas[31-i]) + } + + // Field (12) 'BlockHash' + dst = append(dst, e.BlockHash[:]...) + + // Offset (13) 'Transactions' + dst = ssz.WriteOffset(dst, offset) + for ii := 0; ii < len(e.Transactions); ii++ { + offset += 4 + offset += len(e.Transactions[ii]) + } + + // Offset (14) 'Withdrawals' + dst = ssz.WriteOffset(dst, offset) + offset += len(e.Withdrawals) * 44 + + // Field (15) 'BlobGasUsed' + dst = ssz.MarshalUint64(dst, e.BlobGasUsed) + + // Field (16) 'ExcessBlobGas' + dst = ssz.MarshalUint64(dst, e.ExcessBlobGas) + + // Offset (17) 'DepositReceipts' + dst = ssz.WriteOffset(dst, offset) + offset += len(e.DepositReceipts) * 192 + + // Offset (18) 'WithdrawalRequests' + dst = ssz.WriteOffset(dst, offset) + offset += len(e.WithdrawalRequests) * 76 + + // Field (10) 'ExtraData' + if size := len(e.ExtraData); size > 32 { + err = ssz.ErrBytesLengthFn("ExecutionPayload.ExtraData", size, 32) + return + } + dst = append(dst, e.ExtraData...) + + // Field (13) 'Transactions' + if size := len(e.Transactions); size > 1073741824 { + err = ssz.ErrListTooBigFn("ExecutionPayload.Transactions", size, 1073741824) + return + } + { + offset = 4 * len(e.Transactions) + for ii := 0; ii < len(e.Transactions); ii++ { + dst = ssz.WriteOffset(dst, offset) + offset += len(e.Transactions[ii]) + } + } + for ii := 0; ii < len(e.Transactions); ii++ { + if size := len(e.Transactions[ii]); size > 1073741824 { + err = ssz.ErrBytesLengthFn("ExecutionPayload.Transactions[ii]", size, 1073741824) + return + } + dst = append(dst, e.Transactions[ii]...) + } + + // Field (14) 'Withdrawals' + if size := len(e.Withdrawals); size > 16 { + err = ssz.ErrListTooBigFn("ExecutionPayload.Withdrawals", size, 16) + return + } + for ii := 0; ii < len(e.Withdrawals); ii++ { + if dst, err = e.Withdrawals[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (17) 'DepositReceipts' + if size := len(e.DepositReceipts); size > 8192 { + err = ssz.ErrListTooBigFn("ExecutionPayload.DepositReceipts", size, 8192) + return + } + for ii := 0; ii < len(e.DepositReceipts); ii++ { + if dst, err = e.DepositReceipts[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + // Field (18) 'WithdrawalRequests' + if size := len(e.WithdrawalRequests); size > 16 { + err = ssz.ErrListTooBigFn("ExecutionPayload.WithdrawalRequests", size, 16) + return + } + for ii := 0; ii < len(e.WithdrawalRequests); ii++ { + if dst, err = e.WithdrawalRequests[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the ExecutionPayload object +func (e *ExecutionPayload) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 536 { + return ssz.ErrSize + } + + tail := buf + var o10, o13, o14, o17, o18 uint64 + + // Field (0) 'ParentHash' + copy(e.ParentHash[:], buf[0:32]) + + // Field (1) 'FeeRecipient' + copy(e.FeeRecipient[:], buf[32:52]) + + // Field (2) 'StateRoot' + copy(e.StateRoot[:], buf[52:84]) + + // Field (3) 'ReceiptsRoot' + copy(e.ReceiptsRoot[:], buf[84:116]) + + // Field (4) 'LogsBloom' + copy(e.LogsBloom[:], buf[116:372]) + + // Field (5) 'PrevRandao' + copy(e.PrevRandao[:], buf[372:404]) + + // Field (6) 'BlockNumber' + e.BlockNumber = ssz.UnmarshallUint64(buf[404:412]) + + // Field (7) 'GasLimit' + e.GasLimit = ssz.UnmarshallUint64(buf[412:420]) + + // Field (8) 'GasUsed' + e.GasUsed = ssz.UnmarshallUint64(buf[420:428]) + + // Field (9) 'Timestamp' + e.Timestamp = ssz.UnmarshallUint64(buf[428:436]) + + // Offset (10) 'ExtraData' + if o10 = ssz.ReadOffset(buf[436:440]); o10 > size { + return ssz.ErrOffset + } + + if o10 < 536 { + return ssz.ErrInvalidVariableOffset + } + + // Field (11) 'BaseFeePerGas' + baseFeePerGasBE := make([]byte, 32) + for i := 0; i < 32; i++ { + baseFeePerGasBE[i] = buf[471-i] + } + e.BaseFeePerGas = &uint256.Int{} + e.BaseFeePerGas.SetBytes32(baseFeePerGasBE) + + // Field (12) 'BlockHash' + copy(e.BlockHash[:], buf[472:504]) + + // Offset (13) 'Transactions' + if o13 = ssz.ReadOffset(buf[504:508]); o13 > size || o10 > o13 { + return ssz.ErrOffset + } + + // Offset (14) 'Withdrawals' + if o14 = ssz.ReadOffset(buf[508:512]); o14 > size || o13 > o14 { + return ssz.ErrOffset + } + + // Field (15) 'BlobGasUsed' + e.BlobGasUsed = ssz.UnmarshallUint64(buf[512:520]) + + // Field (16) 'ExcessBlobGas' + e.ExcessBlobGas = ssz.UnmarshallUint64(buf[520:528]) + + // Offset (17) 'DepositReceipts' + if o17 = ssz.ReadOffset(buf[528:532]); o17 > size || o14 > o17 { + return ssz.ErrOffset + } + + // Offset (18) 'WithdrawalRequests' + if o18 = ssz.ReadOffset(buf[532:536]); o18 > size || o17 > o18 { + return ssz.ErrOffset + } + + // Field (10) 'ExtraData' + { + buf = tail[o10:o13] + if len(buf) > 32 { + return ssz.ErrBytesLength + } + if cap(e.ExtraData) == 0 { + e.ExtraData = make([]byte, 0, len(buf)) + } + e.ExtraData = append(e.ExtraData, buf...) + } + + // Field (13) 'Transactions' + { + buf = tail[o13:o14] + num, err := ssz.DecodeDynamicLength(buf, 1073741824) + if err != nil { + return err + } + e.Transactions = make([]bellatrix.Transaction, num) + err = ssz.UnmarshalDynamic(buf, num, func(indx int, buf []byte) (err error) { + if len(buf) > 1073741824 { + return ssz.ErrBytesLength + } + if cap(e.Transactions[indx]) == 0 { + e.Transactions[indx] = bellatrix.Transaction(make([]byte, 0, len(buf))) + } + e.Transactions[indx] = append(e.Transactions[indx], buf...) + return nil + }) + if err != nil { + return err + } + } + + // Field (14) 'Withdrawals' + { + buf = tail[o14:o17] + num, err := ssz.DivideInt2(len(buf), 44, 16) + if err != nil { + return err + } + e.Withdrawals = make([]*capella.Withdrawal, num) + for ii := 0; ii < num; ii++ { + if e.Withdrawals[ii] == nil { + e.Withdrawals[ii] = new(capella.Withdrawal) + } + if err = e.Withdrawals[ii].UnmarshalSSZ(buf[ii*44 : (ii+1)*44]); err != nil { + return err + } + } + } + + // Field (17) 'DepositReceipts' + { + buf = tail[o17:o18] + num, err := ssz.DivideInt2(len(buf), 192, 8192) + if err != nil { + return err + } + e.DepositReceipts = make([]*DepositReceipt, num) + for ii := 0; ii < num; ii++ { + if e.DepositReceipts[ii] == nil { + e.DepositReceipts[ii] = new(DepositReceipt) + } + if err = e.DepositReceipts[ii].UnmarshalSSZ(buf[ii*192 : (ii+1)*192]); err != nil { + return err + } + } + } + + // Field (18) 'WithdrawalRequests' + { + buf = tail[o18:] + num, err := ssz.DivideInt2(len(buf), 76, 16) + if err != nil { + return err + } + e.WithdrawalRequests = make([]*ExecutionLayerWithdrawalRequest, num) + for ii := 0; ii < num; ii++ { + if e.WithdrawalRequests[ii] == nil { + e.WithdrawalRequests[ii] = new(ExecutionLayerWithdrawalRequest) + } + if err = e.WithdrawalRequests[ii].UnmarshalSSZ(buf[ii*76 : (ii+1)*76]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ExecutionPayload object +func (e *ExecutionPayload) SizeSSZ() (size int) { + size = 536 + + // Field (10) 'ExtraData' + size += len(e.ExtraData) + + // Field (13) 'Transactions' + for ii := 0; ii < len(e.Transactions); ii++ { + size += 4 + size += len(e.Transactions[ii]) + } + + // Field (14) 'Withdrawals' + size += len(e.Withdrawals) * 44 + + // Field (17) 'DepositReceipts' + size += len(e.DepositReceipts) * 192 + + // Field (18) 'WithdrawalRequests' + size += len(e.WithdrawalRequests) * 76 + + return +} + +// HashTreeRoot ssz hashes the ExecutionPayload object +func (e *ExecutionPayload) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(e) +} + +// HashTreeRootWith ssz hashes the ExecutionPayload object with a hasher +func (e *ExecutionPayload) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'ParentHash' + hh.PutBytes(e.ParentHash[:]) + + // Field (1) 'FeeRecipient' + hh.PutBytes(e.FeeRecipient[:]) + + // Field (2) 'StateRoot' + hh.PutBytes(e.StateRoot[:]) + + // Field (3) 'ReceiptsRoot' + hh.PutBytes(e.ReceiptsRoot[:]) + + // Field (4) 'LogsBloom' + hh.PutBytes(e.LogsBloom[:]) + + // Field (5) 'PrevRandao' + hh.PutBytes(e.PrevRandao[:]) + + // Field (6) 'BlockNumber' + hh.PutUint64(e.BlockNumber) + + // Field (7) 'GasLimit' + hh.PutUint64(e.GasLimit) + + // Field (8) 'GasUsed' + hh.PutUint64(e.GasUsed) + + // Field (9) 'Timestamp' + hh.PutUint64(e.Timestamp) + + // Field (10) 'ExtraData' + { + elemIndx := hh.Index() + byteLen := uint64(len(e.ExtraData)) + if byteLen > 32 { + err = ssz.ErrIncorrectListSize + return + } + hh.Append(e.ExtraData) + hh.MerkleizeWithMixin(elemIndx, byteLen, (32+31)/32) + } + + // Field (11) 'BaseFeePerGas' + baseFeePerGas := make([]byte, 32) + baseFeePerGasBE := e.BaseFeePerGas.Bytes32() + for i := 0; i < 32; i++ { + baseFeePerGas[i] = baseFeePerGasBE[31-i] + } + hh.PutBytes(baseFeePerGas) + + // Field (12) 'BlockHash' + hh.PutBytes(e.BlockHash[:]) + + // Field (13) 'Transactions' + { + subIndx := hh.Index() + num := uint64(len(e.Transactions)) + if num > 1073741824 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range e.Transactions { + { + elemIndx := hh.Index() + byteLen := uint64(len(elem)) + if byteLen > 1073741824 { + err = ssz.ErrIncorrectListSize + return + } + hh.AppendBytes32(elem) + hh.MerkleizeWithMixin(elemIndx, byteLen, (1073741824+31)/32) + } + } + hh.MerkleizeWithMixin(subIndx, num, 1073741824) + } + + // Field (14) 'Withdrawals' + { + subIndx := hh.Index() + num := uint64(len(e.Withdrawals)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range e.Withdrawals { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + // Field (15) 'BlobGasUsed' + hh.PutUint64(e.BlobGasUsed) + + // Field (16) 'ExcessBlobGas' + hh.PutUint64(e.ExcessBlobGas) + + // Field (17) 'DepositReceipts' + { + subIndx := hh.Index() + num := uint64(len(e.DepositReceipts)) + if num > 8192 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range e.DepositReceipts { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 8192) + } + + // Field (18) 'WithdrawalRequests' + { + subIndx := hh.Index() + num := uint64(len(e.WithdrawalRequests)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range e.WithdrawalRequests { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the ExecutionPayload object +func (e *ExecutionPayload) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(e) +} diff --git a/spec/electra/executionpayload_yaml.go b/spec/electra/executionpayload_yaml.go new file mode 100644 index 00000000..f4409320 --- /dev/null +++ b/spec/electra/executionpayload_yaml.go @@ -0,0 +1,103 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/attestantio/go-eth2-client/spec/capella" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// executionPayloadYAML is the spec representation of the struct. +type executionPayloadYAML struct { + ParentHash string `yaml:"parent_hash"` + FeeRecipient string `yaml:"fee_recipient"` + StateRoot string `yaml:"state_root"` + ReceiptsRoot string `yaml:"receipts_root"` + LogsBloom string `yaml:"logs_bloom"` + PrevRandao string `yaml:"prev_randao"` + BlockNumber uint64 `yaml:"block_number"` + GasLimit uint64 `yaml:"gas_limit"` + GasUsed uint64 `yaml:"gas_used"` + Timestamp uint64 `yaml:"timestamp"` + ExtraData string `yaml:"extra_data"` + BaseFeePerGas string `yaml:"base_fee_per_gas"` + BlockHash string `yaml:"block_hash"` + Transactions []string `yaml:"transactions"` + Withdrawals []*capella.Withdrawal `yaml:"withdrawals"` + BlobGasUsed uint64 `yaml:"blob_gas_used"` + ExcessBlobGas uint64 `yaml:"excess_blob_gas"` + DepositReceipts []*DepositReceipt `yaml:"deposit_receipts"` + WithdrawalRequests []*ExecutionLayerWithdrawalRequest `yaml:"withdrawal_requests"` +} + +// MarshalYAML implements yaml.Marshaler. +func (e *ExecutionPayload) MarshalYAML() ([]byte, error) { + transactions := make([]string, len(e.Transactions)) + for i := range e.Transactions { + transactions[i] = fmt.Sprintf("%#x", e.Transactions[i]) + } + + extraData := "0x" + if len(e.ExtraData) > 0 { + extraData = fmt.Sprintf("%#x", e.ExtraData) + } + + yamlBytes, err := yaml.MarshalWithOptions(&executionPayloadYAML{ + ParentHash: e.ParentHash.String(), + FeeRecipient: e.FeeRecipient.String(), + StateRoot: e.StateRoot.String(), + ReceiptsRoot: e.ReceiptsRoot.String(), + LogsBloom: fmt.Sprintf("%#x", e.LogsBloom), + PrevRandao: fmt.Sprintf("%#x", e.PrevRandao), + BlockNumber: e.BlockNumber, + GasLimit: e.GasLimit, + GasUsed: e.GasUsed, + Timestamp: e.Timestamp, + ExtraData: extraData, + BaseFeePerGas: e.BaseFeePerGas.Dec(), + BlockHash: fmt.Sprintf("%#x", e.BlockHash), + Transactions: transactions, + Withdrawals: e.Withdrawals, + BlobGasUsed: e.BlobGasUsed, + ExcessBlobGas: e.ExcessBlobGas, + DepositReceipts: e.DepositReceipts, + WithdrawalRequests: e.WithdrawalRequests, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (e *ExecutionPayload) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled executionPayloadJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return e.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/executionpayloadheader.go b/spec/electra/executionpayloadheader.go new file mode 100644 index 00000000..1df1e001 --- /dev/null +++ b/spec/electra/executionpayloadheader.go @@ -0,0 +1,56 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/holiman/uint256" +) + +// ExecutionPayloadHeader represents an execution layer payload header. +type ExecutionPayloadHeader struct { + ParentHash phase0.Hash32 `ssz-size:"32"` + FeeRecipient bellatrix.ExecutionAddress `ssz-size:"20"` + StateRoot phase0.Root `ssz-size:"32"` + ReceiptsRoot phase0.Root `ssz-size:"32"` + LogsBloom [256]byte `ssz-size:"256"` + PrevRandao [32]byte `ssz-size:"32"` + BlockNumber uint64 + GasLimit uint64 + GasUsed uint64 + Timestamp uint64 + ExtraData []byte `ssz-max:"32"` + BaseFeePerGas *uint256.Int `ssz-size:"32"` + BlockHash phase0.Hash32 `ssz-size:"32"` + TransactionsRoot phase0.Root `ssz-size:"32"` + WithdrawalsRoot phase0.Root `ssz-size:"32"` + BlobGasUsed uint64 + ExcessBlobGas uint64 + DepositReceiptsRoot phase0.Root `ssz-size:"32"` + WithdrawalRequestsRoot phase0.Root `ssz-size:"32"` +} + +// String returns a string version of the structure. +func (e *ExecutionPayloadHeader) String() string { + data, err := yaml.Marshal(e) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/executionpayloadheader_json.go b/spec/electra/executionpayloadheader_json.go new file mode 100644 index 00000000..915b17c9 --- /dev/null +++ b/spec/electra/executionpayloadheader_json.go @@ -0,0 +1,230 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/holiman/uint256" + "github.com/pkg/errors" +) + +// executionPayloadHeaderJSON is the spec representation of the struct. +type executionPayloadHeaderJSON struct { + ParentHash phase0.Hash32 `json:"parent_hash"` + FeeRecipient bellatrix.ExecutionAddress `json:"fee_recipient"` + StateRoot phase0.Root `json:"state_root"` + ReceiptsRoot phase0.Root `json:"receipts_root"` + LogsBloom string `json:"logs_bloom"` + PrevRandao string `json:"prev_randao"` + BlockNumber string `json:"block_number"` + GasLimit string `json:"gas_limit"` + GasUsed string `json:"gas_used"` + Timestamp string `json:"timestamp"` + ExtraData string `json:"extra_data"` + BaseFeePerGas string `json:"base_fee_per_gas"` + BlockHash phase0.Hash32 `json:"block_hash"` + TransactionsRoot phase0.Root `json:"transactions_root"` + WithdrawalsRoot phase0.Root `json:"withdrawals_root"` + BlobGasUsed string `json:"blob_gas_used"` + ExcessBlobGas string `json:"excess_blob_gas"` + DepositReceiptsRoot phase0.Root `json:"deposit_receipts_root"` + WithdrawalRequestsRoot phase0.Root `json:"withdrawal_requests_root"` +} + +// MarshalJSON implements json.Marshaler. +func (e *ExecutionPayloadHeader) MarshalJSON() ([]byte, error) { + extraData := "0x" + if len(e.ExtraData) > 0 { + extraData = fmt.Sprintf("%#x", e.ExtraData) + } + + return json.Marshal(&executionPayloadHeaderJSON{ + ParentHash: e.ParentHash, + FeeRecipient: e.FeeRecipient, + StateRoot: e.StateRoot, + ReceiptsRoot: e.ReceiptsRoot, + LogsBloom: fmt.Sprintf("%#x", e.LogsBloom), + PrevRandao: fmt.Sprintf("%#x", e.PrevRandao), + BlockNumber: strconv.FormatUint(e.BlockNumber, 10), + GasLimit: strconv.FormatUint(e.GasLimit, 10), + GasUsed: strconv.FormatUint(e.GasUsed, 10), + Timestamp: strconv.FormatUint(e.Timestamp, 10), + ExtraData: extraData, + BaseFeePerGas: e.BaseFeePerGas.Dec(), + BlockHash: e.BlockHash, + TransactionsRoot: e.TransactionsRoot, + WithdrawalsRoot: e.WithdrawalsRoot, + BlobGasUsed: strconv.FormatUint(e.BlobGasUsed, 10), + ExcessBlobGas: strconv.FormatUint(e.ExcessBlobGas, 10), + DepositReceiptsRoot: e.DepositReceiptsRoot, + WithdrawalRequestsRoot: e.WithdrawalRequestsRoot, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +// +//nolint:gocyclo +func (e *ExecutionPayloadHeader) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&executionPayloadHeaderJSON{}, input) + if err != nil { + return err + } + + if err := e.ParentHash.UnmarshalJSON(raw["parent_hash"]); err != nil { + return errors.Wrap(err, "parent_hash") + } + + if err := e.FeeRecipient.UnmarshalJSON(raw["fee_recipient"]); err != nil { + return errors.Wrap(err, "fee_recipient") + } + + if err := e.StateRoot.UnmarshalJSON(raw["state_root"]); err != nil { + return errors.Wrap(err, "state_root") + } + + if err := e.ReceiptsRoot.UnmarshalJSON(raw["receipts_root"]); err != nil { + return errors.Wrap(err, "receipts_root") + } + + logsBloom := raw["logs_bloom"] + if !bytes.HasPrefix(logsBloom, []byte{'"', '0', 'x'}) { + return errors.New("logs_bloom: invalid prefix") + } + if !bytes.HasSuffix(logsBloom, []byte{'"'}) { + return errors.New("logs_bloom: invalid suffix") + } + if len(logsBloom) != 1+2+256*2+1 { + return errors.New("logs_bloom: incorrect length") + } + length, err := hex.Decode(e.LogsBloom[:], logsBloom[3:3+256*2]) + if err != nil { + return errors.Wrap(err, "logs_bloom") + } + if length != 256 { + return errors.New("logs_bloom: incorrect length") + } + + prevRandao := raw["prev_randao"] + if !bytes.HasPrefix(prevRandao, []byte{'"', '0', 'x'}) { + return errors.New("prev_randao: invalid prefix") + } + if !bytes.HasSuffix(prevRandao, []byte{'"'}) { + return errors.New("prev_randao: invalid suffix") + } + if len(prevRandao) != 1+2+32*2+1 { + return errors.New("prev_randao: incorrect length") + } + length, err = hex.Decode(e.PrevRandao[:], prevRandao[3:3+32*2]) + if err != nil { + return errors.Wrap(err, "prev_randao") + } + if length != 32 { + return errors.New("prev_randao: incorrect length") + } + + tmpUint, err := strconv.ParseUint(string(bytes.Trim(raw["block_number"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "block_number") + } + e.BlockNumber = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["gas_limit"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "gas_limit") + } + e.GasLimit = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["gas_used"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "gas_used") + } + e.GasUsed = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["timestamp"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "timestamp") + } + e.Timestamp = tmpUint + + var tmpBytes []byte + switch { + case bytes.Equal(raw["extra_data"], []byte{'0', 'x'}), bytes.Equal(raw["extra_data"], []byte{'0'}): + // Empty. + default: + tmpBytes = bytes.TrimPrefix(bytes.Trim(raw["extra_data"], `"`), []byte{'0', 'x'}) + if len(tmpBytes)%2 == 1 { + tmpBytes = []byte(fmt.Sprintf("0%s", string(tmpBytes))) + } + tmp, err := hex.DecodeString(string(tmpBytes)) + if err != nil { + return errors.Wrap(err, "extra_data") + } + if len(tmp) > 32 { + return errors.New("extra_data: incorrect length") + } + e.ExtraData = tmp + } + + tmpBytes = bytes.Trim(raw["base_fee_per_gas"], `"`) + if bytes.HasPrefix(tmpBytes, []byte{'0', 'x'}) { + e.BaseFeePerGas, err = uint256.FromHex(string(tmpBytes)) + } else { + e.BaseFeePerGas, err = uint256.FromDecimal(string(tmpBytes)) + } + if err != nil { + return errors.Wrap(err, "base_fee_per_gas") + } + + if err := e.BlockHash.UnmarshalJSON(raw["block_hash"]); err != nil { + return errors.Wrap(err, "block_hash") + } + + if err := e.TransactionsRoot.UnmarshalJSON(raw["transactions_root"]); err != nil { + return errors.Wrap(err, "transactions_root") + } + + if err := e.WithdrawalsRoot.UnmarshalJSON(raw["withdrawals_root"]); err != nil { + return errors.Wrap(err, "withdrawals_root") + } + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["blob_gas_used"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "blob_gas_used") + } + e.BlobGasUsed = tmpUint + + tmpUint, err = strconv.ParseUint(string(bytes.Trim(raw["excess_blob_gas"], `"`)), 10, 64) + if err != nil { + return errors.Wrap(err, "excess_blob_gas") + } + e.ExcessBlobGas = tmpUint + + if err := e.DepositReceiptsRoot.UnmarshalJSON(raw["deposit_receipts_root"]); err != nil { + return errors.Wrap(err, "deposit_receipts_root") + } + + if err := e.WithdrawalRequestsRoot.UnmarshalJSON(raw["withdrawal_requests_root"]); err != nil { + return errors.Wrap(err, "withdrawal_requests_root") + } + + return nil +} diff --git a/spec/electra/executionpayloadheader_ssz.go b/spec/electra/executionpayloadheader_ssz.go new file mode 100644 index 00000000..d33d1fed --- /dev/null +++ b/spec/electra/executionpayloadheader_ssz.go @@ -0,0 +1,282 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: a63387b6d1f288be86bb43febdcd67174ea6fd3cbf23b63c8656748b54289b53 +// Version: 0.1.3 +package electra + +import ( + ssz "github.com/ferranbt/fastssz" + "github.com/holiman/uint256" +) + +// MarshalSSZ ssz marshals the ExecutionPayloadHeader object +func (e *ExecutionPayloadHeader) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(e) +} + +// MarshalSSZTo ssz marshals the ExecutionPayloadHeader object to a target array +func (e *ExecutionPayloadHeader) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(648) + + // Field (0) 'ParentHash' + dst = append(dst, e.ParentHash[:]...) + + // Field (1) 'FeeRecipient' + dst = append(dst, e.FeeRecipient[:]...) + + // Field (2) 'StateRoot' + dst = append(dst, e.StateRoot[:]...) + + // Field (3) 'ReceiptsRoot' + dst = append(dst, e.ReceiptsRoot[:]...) + + // Field (4) 'LogsBloom' + dst = append(dst, e.LogsBloom[:]...) + + // Field (5) 'PrevRandao' + dst = append(dst, e.PrevRandao[:]...) + + // Field (6) 'BlockNumber' + dst = ssz.MarshalUint64(dst, e.BlockNumber) + + // Field (7) 'GasLimit' + dst = ssz.MarshalUint64(dst, e.GasLimit) + + // Field (8) 'GasUsed' + dst = ssz.MarshalUint64(dst, e.GasUsed) + + // Field (9) 'Timestamp' + dst = ssz.MarshalUint64(dst, e.Timestamp) + + // Offset (10) 'ExtraData' + dst = ssz.WriteOffset(dst, offset) + offset += len(e.ExtraData) + + // Field (11) 'BaseFeePerGas' + baseFeePerGas := e.BaseFeePerGas.Bytes32() + for i := 0; i < 32; i++ { + dst = append(dst, baseFeePerGas[31-i]) + } + + // Field (12) 'BlockHash' + dst = append(dst, e.BlockHash[:]...) + + // Field (13) 'TransactionsRoot' + dst = append(dst, e.TransactionsRoot[:]...) + + // Field (14) 'WithdrawalsRoot' + dst = append(dst, e.WithdrawalsRoot[:]...) + + // Field (15) 'BlobGasUsed' + dst = ssz.MarshalUint64(dst, e.BlobGasUsed) + + // Field (16) 'ExcessBlobGas' + dst = ssz.MarshalUint64(dst, e.ExcessBlobGas) + + // Field (17) 'DepositReceiptsRoot' + dst = append(dst, e.DepositReceiptsRoot[:]...) + + // Field (18) 'WithdrawalRequestsRoot' + dst = append(dst, e.WithdrawalRequestsRoot[:]...) + + // Field (10) 'ExtraData' + if size := len(e.ExtraData); size > 32 { + err = ssz.ErrBytesLengthFn("ExecutionPayloadHeader.ExtraData", size, 32) + return + } + dst = append(dst, e.ExtraData...) + + return +} + +// UnmarshalSSZ ssz unmarshals the ExecutionPayloadHeader object +func (e *ExecutionPayloadHeader) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 648 { + return ssz.ErrSize + } + + tail := buf + var o10 uint64 + + // Field (0) 'ParentHash' + copy(e.ParentHash[:], buf[0:32]) + + // Field (1) 'FeeRecipient' + copy(e.FeeRecipient[:], buf[32:52]) + + // Field (2) 'StateRoot' + copy(e.StateRoot[:], buf[52:84]) + + // Field (3) 'ReceiptsRoot' + copy(e.ReceiptsRoot[:], buf[84:116]) + + // Field (4) 'LogsBloom' + copy(e.LogsBloom[:], buf[116:372]) + + // Field (5) 'PrevRandao' + copy(e.PrevRandao[:], buf[372:404]) + + // Field (6) 'BlockNumber' + e.BlockNumber = ssz.UnmarshallUint64(buf[404:412]) + + // Field (7) 'GasLimit' + e.GasLimit = ssz.UnmarshallUint64(buf[412:420]) + + // Field (8) 'GasUsed' + e.GasUsed = ssz.UnmarshallUint64(buf[420:428]) + + // Field (9) 'Timestamp' + e.Timestamp = ssz.UnmarshallUint64(buf[428:436]) + + // Offset (10) 'ExtraData' + if o10 = ssz.ReadOffset(buf[436:440]); o10 > size { + return ssz.ErrOffset + } + + if o10 < 648 { + return ssz.ErrInvalidVariableOffset + } + + // Field (11) 'BaseFeePerGas' + baseFeePerGasBE := make([]byte, 32) + for i := 0; i < 32; i++ { + baseFeePerGasBE[i] = buf[471-i] + } + e.BaseFeePerGas = &uint256.Int{} + e.BaseFeePerGas.SetBytes32(baseFeePerGasBE) + + // Field (12) 'BlockHash' + copy(e.BlockHash[:], buf[472:504]) + + // Field (13) 'TransactionsRoot' + copy(e.TransactionsRoot[:], buf[504:536]) + + // Field (14) 'WithdrawalsRoot' + copy(e.WithdrawalsRoot[:], buf[536:568]) + + // Field (15) 'BlobGasUsed' + e.BlobGasUsed = ssz.UnmarshallUint64(buf[568:576]) + + // Field (16) 'ExcessBlobGas' + e.ExcessBlobGas = ssz.UnmarshallUint64(buf[576:584]) + + // Field (17) 'DepositReceiptsRoot' + copy(e.DepositReceiptsRoot[:], buf[584:616]) + + // Field (18) 'WithdrawalRequestsRoot' + copy(e.WithdrawalRequestsRoot[:], buf[616:648]) + + // Field (10) 'ExtraData' + { + buf = tail[o10:] + if len(buf) > 32 { + return ssz.ErrBytesLength + } + if cap(e.ExtraData) == 0 { + e.ExtraData = make([]byte, 0, len(buf)) + } + e.ExtraData = append(e.ExtraData, buf...) + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ExecutionPayloadHeader object +func (e *ExecutionPayloadHeader) SizeSSZ() (size int) { + size = 648 + + // Field (10) 'ExtraData' + size += len(e.ExtraData) + + return +} + +// HashTreeRoot ssz hashes the ExecutionPayloadHeader object +func (e *ExecutionPayloadHeader) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(e) +} + +// HashTreeRootWith ssz hashes the ExecutionPayloadHeader object with a hasher +func (e *ExecutionPayloadHeader) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'ParentHash' + hh.PutBytes(e.ParentHash[:]) + + // Field (1) 'FeeRecipient' + hh.PutBytes(e.FeeRecipient[:]) + + // Field (2) 'StateRoot' + hh.PutBytes(e.StateRoot[:]) + + // Field (3) 'ReceiptsRoot' + hh.PutBytes(e.ReceiptsRoot[:]) + + // Field (4) 'LogsBloom' + hh.PutBytes(e.LogsBloom[:]) + + // Field (5) 'PrevRandao' + hh.PutBytes(e.PrevRandao[:]) + + // Field (6) 'BlockNumber' + hh.PutUint64(e.BlockNumber) + + // Field (7) 'GasLimit' + hh.PutUint64(e.GasLimit) + + // Field (8) 'GasUsed' + hh.PutUint64(e.GasUsed) + + // Field (9) 'Timestamp' + hh.PutUint64(e.Timestamp) + + // Field (10) 'ExtraData' + { + elemIndx := hh.Index() + byteLen := uint64(len(e.ExtraData)) + if byteLen > 32 { + err = ssz.ErrIncorrectListSize + return + } + hh.Append(e.ExtraData) + hh.MerkleizeWithMixin(elemIndx, byteLen, (32+31)/32) + } + + // Field (11) 'BaseFeePerGas' + baseFeePerGas := make([]byte, 32) + baseFeePerGasBE := e.BaseFeePerGas.Bytes32() + for i := 0; i < 32; i++ { + baseFeePerGas[i] = baseFeePerGasBE[31-i] + } + hh.PutBytes(baseFeePerGas) + + // Field (12) 'BlockHash' + hh.PutBytes(e.BlockHash[:]) + + // Field (13) 'TransactionsRoot' + hh.PutBytes(e.TransactionsRoot[:]) + + // Field (14) 'WithdrawalsRoot' + hh.PutBytes(e.WithdrawalsRoot[:]) + + // Field (15) 'BlobGasUsed' + hh.PutUint64(e.BlobGasUsed) + + // Field (16) 'ExcessBlobGas' + hh.PutUint64(e.ExcessBlobGas) + + // Field (17) 'DepositReceiptsRoot' + hh.PutBytes(e.DepositReceiptsRoot[:]) + + // Field (18) 'WithdrawalRequestsRoot' + hh.PutBytes(e.WithdrawalRequestsRoot[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the ExecutionPayloadHeader object +func (e *ExecutionPayloadHeader) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(e) +} diff --git a/spec/electra/executionpayloadheader_yaml.go b/spec/electra/executionpayloadheader_yaml.go new file mode 100644 index 00000000..e1081b90 --- /dev/null +++ b/spec/electra/executionpayloadheader_yaml.go @@ -0,0 +1,100 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// executionPayloadHeaderYAML is the spec representation of the struct. +type executionPayloadHeaderYAML struct { + ParentHash phase0.Hash32 `yaml:"parent_hash"` + FeeRecipient bellatrix.ExecutionAddress `yaml:"fee_recipient"` + StateRoot phase0.Root `yaml:"state_root"` + ReceiptsRoot phase0.Root `yaml:"receipts_root"` + LogsBloom string `yaml:"logs_bloom"` + PrevRandao string `yaml:"prev_randao"` + BlockNumber uint64 `yaml:"block_number"` + GasLimit uint64 `yaml:"gas_limit"` + GasUsed uint64 `yaml:"gas_used"` + Timestamp uint64 `yaml:"timestamp"` + ExtraData string `yaml:"extra_data"` + BaseFeePerGas string `yaml:"base_fee_per_gas"` + BlockHash phase0.Hash32 `yaml:"block_hash"` + TransactionsRoot phase0.Root `yaml:"transactions_root"` + WithdrawalsRoot phase0.Root `yaml:"withdrawals_root"` + BlobGasUsed uint64 `yaml:"blob_gas_used"` + ExcessBlobGas uint64 `yaml:"excess_blob_gas"` + DepositReceiptsRoot phase0.Root `yaml:"deposit_receipts_root"` + ExitsRoot phase0.Root `yaml:"exits_root"` + WithdrawalRequestsRoot phase0.Root `yaml:"withdrawal_requests_root"` +} + +// MarshalYAML implements yaml.Marshaler. +func (e *ExecutionPayloadHeader) MarshalYAML() ([]byte, error) { + extraData := "0x" + if len(e.ExtraData) > 0 { + extraData = fmt.Sprintf("%#x", e.ExtraData) + } + + yamlBytes, err := yaml.MarshalWithOptions(&executionPayloadHeaderYAML{ + ParentHash: e.ParentHash, + FeeRecipient: e.FeeRecipient, + StateRoot: e.StateRoot, + ReceiptsRoot: e.ReceiptsRoot, + LogsBloom: fmt.Sprintf("%#x", e.LogsBloom), + PrevRandao: fmt.Sprintf("%#x", e.PrevRandao), + BlockNumber: e.BlockNumber, + GasLimit: e.GasLimit, + GasUsed: e.GasUsed, + Timestamp: e.Timestamp, + ExtraData: extraData, + BaseFeePerGas: e.BaseFeePerGas.Dec(), + BlockHash: e.BlockHash, + TransactionsRoot: e.TransactionsRoot, + WithdrawalsRoot: e.WithdrawalsRoot, + BlobGasUsed: e.BlobGasUsed, + ExcessBlobGas: e.ExcessBlobGas, + DepositReceiptsRoot: e.DepositReceiptsRoot, + WithdrawalRequestsRoot: e.WithdrawalRequestsRoot, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (e *ExecutionPayloadHeader) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled executionPayloadHeaderJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return e.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/generate.go b/spec/electra/generate.go new file mode 100644 index 00000000..377ebc9b --- /dev/null +++ b/spec/electra/generate.go @@ -0,0 +1,19 @@ +// Copyright © 2023 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +// Need to `go install github.com/ferranbt/fastssz/sszgen@latest` for this to work. +//go:generate rm -f attestation_ssz.go beaconblockbody_ssz.go beaconblock_ssz.go beaconstate_ssz.go consolidation_ssz.go depositreceipt_ssz.go executionlayerwithdrawalrequest_ssz.go executionpayload_ssz.go executionpayloadheader_ssz.go pendingbalancedeposit_ssz.go pendingconsolidation_ssz.go pendingpartialwithdrawal_ssz.go signedbeaconblock_ssz.go signedconsolidation_ssz.go +//go:generate sszgen --suffix=ssz --path . --include ../phase0,../altair,../bellatrix,../capella,../deneb --objs Attestation,BeaconBlockBody,BeaconBlock,BeaconState,Consolidation,DepositReceipt,ExecutionLayerWithdrawalRequest,ExecutionPayload,ExecutionPayloadHeader,PendingBalanceDeposit,PendingConsolidation,PendingPartialWithdrawal,SignedBeaconBlock,SignedConsolidation +//go:generate goimports -w attestation_ssz.go beaconblockbody_ssz.go beaconblock_ssz.go beaconstate_ssz.go consolidation_ssz.go depositreceipt_ssz.go executionlayerwithdrawalrequest_ssz.go executionpayload_ssz.go executionpayloadheader_ssz.go pendingbalancedeposit_ssz.go pendingconsolidation_ssz.go pendingpartialwithdrawal_ssz.go signedbeaconblock_ssz.go signedconsolidation_ssz.go diff --git a/spec/electra/indexedattestation.go b/spec/electra/indexedattestation.go new file mode 100644 index 00000000..0858043a --- /dev/null +++ b/spec/electra/indexedattestation.go @@ -0,0 +1,143 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/hex" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/attestantio/go-eth2-client/spec/phase0" + + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// IndexedAttestation provides a signed attestation with a list of attesting indices. +type IndexedAttestation struct { + // Currently using primitives as sszgen does not handle []ValidatorIndex + AttestingIndices []uint64 `ssz-max:"131072"` + Data *phase0.AttestationData + Signature phase0.BLSSignature `ssz-size:"96"` +} + +// indexedAttestationJSON is the spec representation of the struct. +type indexedAttestationJSON struct { + AttestingIndices []string `json:"attesting_indices"` + Data *phase0.AttestationData `json:"data"` + Signature string `json:"signature"` +} + +// indexedAttestationYAML is a raw representation of the struct. +type indexedAttestationYAML struct { + AttestingIndices []uint64 `yaml:"attesting_indices"` + Data *phase0.AttestationData `yaml:"data"` + Signature string `yaml:"signature"` +} + +// MarshalJSON implements json.Marshaler. +func (i *IndexedAttestation) MarshalJSON() ([]byte, error) { + attestingIndices := make([]string, len(i.AttestingIndices)) + for j := range i.AttestingIndices { + attestingIndices[j] = strconv.FormatUint(i.AttestingIndices[j], 10) + } + + return json.Marshal(&indexedAttestationJSON{ + AttestingIndices: attestingIndices, + Data: i.Data, + Signature: fmt.Sprintf("%#x", i.Signature), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (i *IndexedAttestation) UnmarshalJSON(input []byte) error { + var indexedAttestationJSON indexedAttestationJSON + if err := json.Unmarshal(input, &indexedAttestationJSON); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return i.unpack(&indexedAttestationJSON) +} + +func (i *IndexedAttestation) unpack(indexedAttestationJSON *indexedAttestationJSON) error { + var err error + // Spec tests contain indexed attestations with empty attesting indices. + // if indexedAttestationJSON.AttestingIndices == nil { + // return errors.New("attesting indices missing") + // } + // if len(indexedAttestationJSON.AttestingIndices) == 0 { + // return errors.New("attesting indices missing") + // } + i.AttestingIndices = make([]uint64, len(indexedAttestationJSON.AttestingIndices)) + for j := range indexedAttestationJSON.AttestingIndices { + if i.AttestingIndices[j], err = strconv.ParseUint(indexedAttestationJSON.AttestingIndices[j], 10, 64); err != nil { + return errors.Wrap(err, "failed to parse attesting index") + } + } + if indexedAttestationJSON.Data == nil { + return errors.New("data missing") + } + i.Data = indexedAttestationJSON.Data + if indexedAttestationJSON.Signature == "" { + return errors.New("signature missing") + } + signature, err := hex.DecodeString(strings.TrimPrefix(indexedAttestationJSON.Signature, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for signature") + } + if len(signature) != phase0.SignatureLength { + return errors.New("incorrect length for signature") + } + copy(i.Signature[:], signature) + + return nil +} + +// MarshalYAML implements yaml.Marshaler. +func (i *IndexedAttestation) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&indexedAttestationYAML{ + AttestingIndices: i.AttestingIndices, + Data: i.Data, + Signature: fmt.Sprintf("%#x", i.Signature), + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (i *IndexedAttestation) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var indexedAttestationJSON indexedAttestationJSON + if err := yaml.Unmarshal(input, &indexedAttestationJSON); err != nil { + return err + } + + return i.unpack(&indexedAttestationJSON) +} + +// String returns a string version of the structure. +func (i *IndexedAttestation) String() string { + data, err := yaml.Marshal(i) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/indexedattestation_ssz.go b/spec/electra/indexedattestation_ssz.go new file mode 100644 index 00000000..ce68437e --- /dev/null +++ b/spec/electra/indexedattestation_ssz.go @@ -0,0 +1,146 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the IndexedAttestation object +func (i *IndexedAttestation) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(i) +} + +// MarshalSSZTo ssz marshals the IndexedAttestation object to a target array +func (i *IndexedAttestation) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(228) + + // Offset (0) 'AttestingIndices' + dst = ssz.WriteOffset(dst, offset) + offset += len(i.AttestingIndices) * 8 + + // Field (1) 'Data' + if i.Data == nil { + i.Data = new(phase0.AttestationData) + } + if dst, err = i.Data.MarshalSSZTo(dst); err != nil { + return + } + + // Field (2) 'Signature' + dst = append(dst, i.Signature[:]...) + + // Field (0) 'AttestingIndices' + if size := len(i.AttestingIndices); size > 131072 { + err = ssz.ErrListTooBigFn("IndexedAttestation.AttestingIndices", size, 131072) + return + } + for ii := 0; ii < len(i.AttestingIndices); ii++ { + dst = ssz.MarshalUint64(dst, i.AttestingIndices[ii]) + } + + return +} + +// UnmarshalSSZ ssz unmarshals the IndexedAttestation object +func (i *IndexedAttestation) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 228 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'AttestingIndices' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 228 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Data' + if i.Data == nil { + i.Data = new(phase0.AttestationData) + } + if err = i.Data.UnmarshalSSZ(buf[4:132]); err != nil { + return err + } + + // Field (2) 'Signature' + copy(i.Signature[:], buf[132:228]) + + // Field (0) 'AttestingIndices' + { + buf = tail[o0:] + num, err := ssz.DivideInt2(len(buf), 8, 131072) + if err != nil { + return err + } + i.AttestingIndices = ssz.ExtendUint64(i.AttestingIndices, num) + for ii := 0; ii < num; ii++ { + i.AttestingIndices[ii] = ssz.UnmarshallUint64(buf[ii*8 : (ii+1)*8]) + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the IndexedAttestation object +func (i *IndexedAttestation) SizeSSZ() (size int) { + size = 228 + + // Field (0) 'AttestingIndices' + size += len(i.AttestingIndices) * 8 + + return +} + +// HashTreeRoot ssz hashes the IndexedAttestation object +func (i *IndexedAttestation) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(i) +} + +// HashTreeRootWith ssz hashes the IndexedAttestation object with a hasher +func (i *IndexedAttestation) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'AttestingIndices' + { + if size := len(i.AttestingIndices); size > 131072 { + err = ssz.ErrListTooBigFn("IndexedAttestation.AttestingIndices", size, 131072) + return + } + subIndx := hh.Index() + for _, i := range i.AttestingIndices { + hh.AppendUint64(i) + } + hh.FillUpTo32() + numItems := uint64(len(i.AttestingIndices)) + hh.MerkleizeWithMixin(subIndx, numItems, ssz.CalculateLimit(131072, numItems, 8)) + } + + // Field (1) 'Data' + if i.Data == nil { + i.Data = new(phase0.AttestationData) + } + if err = i.Data.HashTreeRootWith(hh); err != nil { + return + } + + // Field (2) 'Signature' + hh.PutBytes(i.Signature[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the IndexedAttestation object +func (i *IndexedAttestation) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(i) +} diff --git a/spec/electra/pendingbalancedeposit.go b/spec/electra/pendingbalancedeposit.go new file mode 100644 index 00000000..10a5f228 --- /dev/null +++ b/spec/electra/pendingbalancedeposit.go @@ -0,0 +1,37 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// PendingBalanceDeposit represents a pending balance deposit. +type PendingBalanceDeposit struct { + Index phase0.ValidatorIndex + Amount phase0.Gwei +} + +// String returns a string version of the structure. +func (p *PendingBalanceDeposit) String() string { + data, err := yaml.Marshal(p) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/pendingbalancedeposit_json.go b/spec/electra/pendingbalancedeposit_json.go new file mode 100644 index 00000000..d8304ac3 --- /dev/null +++ b/spec/electra/pendingbalancedeposit_json.go @@ -0,0 +1,53 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// pendingBalanceDepositJSON is the spec representation of the struct. +type pendingBalanceDepositJSON struct { + Index phase0.ValidatorIndex `json:"index"` + Amount phase0.Gwei `json:"amount"` +} + +// MarshalJSON implements json.Marshaler. +func (p *PendingBalanceDeposit) MarshalJSON() ([]byte, error) { + return json.Marshal(&pendingBalanceDepositJSON{ + Index: p.Index, + Amount: p.Amount, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (p *PendingBalanceDeposit) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&pendingBalanceDepositJSON{}, input) + if err != nil { + return err + } + + if err := p.Index.UnmarshalJSON(raw["index"]); err != nil { + return errors.Wrap(err, "index") + } + if err := p.Amount.UnmarshalJSON(raw["amount"]); err != nil { + return errors.Wrap(err, "amount") + } + + return nil +} diff --git a/spec/electra/pendingbalancedeposit_ssz.go b/spec/electra/pendingbalancedeposit_ssz.go new file mode 100644 index 00000000..27983f0e --- /dev/null +++ b/spec/electra/pendingbalancedeposit_ssz.go @@ -0,0 +1,74 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the PendingBalanceDeposit object +func (p *PendingBalanceDeposit) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the PendingBalanceDeposit object to a target array +func (p *PendingBalanceDeposit) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Index' + dst = ssz.MarshalUint64(dst, uint64(p.Index)) + + // Field (1) 'Amount' + dst = ssz.MarshalUint64(dst, uint64(p.Amount)) + + return +} + +// UnmarshalSSZ ssz unmarshals the PendingBalanceDeposit object +func (p *PendingBalanceDeposit) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 16 { + return ssz.ErrSize + } + + // Field (0) 'Index' + p.Index = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'Amount' + p.Amount = phase0.Gwei(ssz.UnmarshallUint64(buf[8:16])) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the PendingBalanceDeposit object +func (p *PendingBalanceDeposit) SizeSSZ() (size int) { + size = 16 + return +} + +// HashTreeRoot ssz hashes the PendingBalanceDeposit object +func (p *PendingBalanceDeposit) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the PendingBalanceDeposit object with a hasher +func (p *PendingBalanceDeposit) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Index' + hh.PutUint64(uint64(p.Index)) + + // Field (1) 'Amount' + hh.PutUint64(uint64(p.Amount)) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the PendingBalanceDeposit object +func (p *PendingBalanceDeposit) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(p) +} diff --git a/spec/electra/pendingbalancedeposit_yaml.go b/spec/electra/pendingbalancedeposit_yaml.go new file mode 100644 index 00000000..24b8f328 --- /dev/null +++ b/spec/electra/pendingbalancedeposit_yaml.go @@ -0,0 +1,58 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// pendingBalanceDepositYAML is the spec representation of the struct. +type pendingBalanceDepositYAML struct { + Index phase0.ValidatorIndex `yaml:"index"` + Amount phase0.Gwei `yaml:"amount"` +} + +// MarshalYAML implements yaml.Marshaler. +func (p *PendingBalanceDeposit) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&pendingBalanceDepositYAML{ + Index: p.Index, + Amount: p.Amount, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (p *PendingBalanceDeposit) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled pendingBalanceDepositJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(&unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return p.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/pendingconsolidation.go b/spec/electra/pendingconsolidation.go new file mode 100644 index 00000000..d45cf563 --- /dev/null +++ b/spec/electra/pendingconsolidation.go @@ -0,0 +1,37 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// PendingConsolidation represents a pending consolidation. +type PendingConsolidation struct { + SourceIndex phase0.ValidatorIndex + TargetIndex phase0.ValidatorIndex +} + +// String returns a string version of the structure. +func (p *PendingConsolidation) String() string { + data, err := yaml.Marshal(p) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/pendingconsolidation_json.go b/spec/electra/pendingconsolidation_json.go new file mode 100644 index 00000000..3108e200 --- /dev/null +++ b/spec/electra/pendingconsolidation_json.go @@ -0,0 +1,53 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// pendingConsolidationJSON is the spec representation of the struct. +type pendingConsolidationJSON struct { + SourceIndex phase0.ValidatorIndex `json:"source_index"` + TargetIndex phase0.ValidatorIndex `json:"target_index"` +} + +// MarshalJSON implements json.Marshaler. +func (p *PendingConsolidation) MarshalJSON() ([]byte, error) { + return json.Marshal(&pendingConsolidationJSON{ + SourceIndex: p.SourceIndex, + TargetIndex: p.TargetIndex, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (p *PendingConsolidation) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&pendingConsolidationJSON{}, input) + if err != nil { + return err + } + + if err := p.SourceIndex.UnmarshalJSON(raw["source_index"]); err != nil { + return errors.Wrap(err, "source_index") + } + if err := p.TargetIndex.UnmarshalJSON(raw["target_index"]); err != nil { + return errors.Wrap(err, "target_index") + } + + return nil +} diff --git a/spec/electra/pendingconsolidation_ssz.go b/spec/electra/pendingconsolidation_ssz.go new file mode 100644 index 00000000..cabc71d9 --- /dev/null +++ b/spec/electra/pendingconsolidation_ssz.go @@ -0,0 +1,74 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the PendingConsolidation object +func (p *PendingConsolidation) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the PendingConsolidation object to a target array +func (p *PendingConsolidation) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'SourceIndex' + dst = ssz.MarshalUint64(dst, uint64(p.SourceIndex)) + + // Field (1) 'TargetIndex' + dst = ssz.MarshalUint64(dst, uint64(p.TargetIndex)) + + return +} + +// UnmarshalSSZ ssz unmarshals the PendingConsolidation object +func (p *PendingConsolidation) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 16 { + return ssz.ErrSize + } + + // Field (0) 'SourceIndex' + p.SourceIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'TargetIndex' + p.TargetIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[8:16])) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the PendingConsolidation object +func (p *PendingConsolidation) SizeSSZ() (size int) { + size = 16 + return +} + +// HashTreeRoot ssz hashes the PendingConsolidation object +func (p *PendingConsolidation) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the PendingConsolidation object with a hasher +func (p *PendingConsolidation) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'SourceIndex' + hh.PutUint64(uint64(p.SourceIndex)) + + // Field (1) 'TargetIndex' + hh.PutUint64(uint64(p.TargetIndex)) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the PendingConsolidation object +func (p *PendingConsolidation) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(p) +} diff --git a/spec/electra/pendingconsolidation_yaml.go b/spec/electra/pendingconsolidation_yaml.go new file mode 100644 index 00000000..2cacc029 --- /dev/null +++ b/spec/electra/pendingconsolidation_yaml.go @@ -0,0 +1,58 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// pendingConsolidationYAML is the spec representation of the struct. +type pendingConsolidationYAML struct { + SourceIndex phase0.ValidatorIndex `yaml:"source_index"` + TargetIndex phase0.ValidatorIndex `yaml:"target_index"` +} + +// MarshalYAML implements yaml.Marshaler. +func (p *PendingConsolidation) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&pendingConsolidationYAML{ + SourceIndex: p.SourceIndex, + TargetIndex: p.TargetIndex, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (p *PendingConsolidation) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled pendingConsolidationJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(&unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return p.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/pendingpartialwithdrawal.go b/spec/electra/pendingpartialwithdrawal.go new file mode 100644 index 00000000..5e15b149 --- /dev/null +++ b/spec/electra/pendingpartialwithdrawal.go @@ -0,0 +1,38 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// PendingPartialWithdrawal represents a pending partial withdrawal. +type PendingPartialWithdrawal struct { + Index phase0.ValidatorIndex + Amount phase0.Gwei + WithdrawableEpoch phase0.Epoch +} + +// String returns a string version of the structure. +func (p *PendingPartialWithdrawal) String() string { + data, err := yaml.Marshal(p) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/pendingpartialwithdrawal_json.go b/spec/electra/pendingpartialwithdrawal_json.go new file mode 100644 index 00000000..ad307a99 --- /dev/null +++ b/spec/electra/pendingpartialwithdrawal_json.go @@ -0,0 +1,58 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// pendingPartialWithdrawalJSON is the spec representation of the struct. +type pendingPartialWithdrawalJSON struct { + Index phase0.ValidatorIndex `json:"index"` + Amount phase0.Gwei `json:"amount"` + WithdrawableEpoch phase0.Epoch `json:"withdrawable_epoch"` +} + +// MarshalJSON implements json.Marshaler. +func (p *PendingPartialWithdrawal) MarshalJSON() ([]byte, error) { + return json.Marshal(&pendingPartialWithdrawalJSON{ + Index: p.Index, + Amount: p.Amount, + WithdrawableEpoch: p.WithdrawableEpoch, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (p *PendingPartialWithdrawal) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&pendingPartialWithdrawalJSON{}, input) + if err != nil { + return err + } + + if err := p.Index.UnmarshalJSON(raw["index"]); err != nil { + return errors.Wrap(err, "index") + } + if err := p.Amount.UnmarshalJSON(raw["amount"]); err != nil { + return errors.Wrap(err, "amount") + } + if err := p.WithdrawableEpoch.UnmarshalJSON(raw["withdrawable_epoch"]); err != nil { + return errors.Wrap(err, "withdrawable_epoch") + } + + return nil +} diff --git a/spec/electra/pendingpartialwithdrawal_ssz.go b/spec/electra/pendingpartialwithdrawal_ssz.go new file mode 100644 index 00000000..88ced33f --- /dev/null +++ b/spec/electra/pendingpartialwithdrawal_ssz.go @@ -0,0 +1,83 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the PendingPartialWithdrawal object +func (p *PendingPartialWithdrawal) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(p) +} + +// MarshalSSZTo ssz marshals the PendingPartialWithdrawal object to a target array +func (p *PendingPartialWithdrawal) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Index' + dst = ssz.MarshalUint64(dst, uint64(p.Index)) + + // Field (1) 'Amount' + dst = ssz.MarshalUint64(dst, uint64(p.Amount)) + + // Field (2) 'WithdrawableEpoch' + dst = ssz.MarshalUint64(dst, uint64(p.WithdrawableEpoch)) + + return +} + +// UnmarshalSSZ ssz unmarshals the PendingPartialWithdrawal object +func (p *PendingPartialWithdrawal) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 24 { + return ssz.ErrSize + } + + // Field (0) 'Index' + p.Index = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'Amount' + p.Amount = phase0.Gwei(ssz.UnmarshallUint64(buf[8:16])) + + // Field (2) 'WithdrawableEpoch' + p.WithdrawableEpoch = phase0.Epoch(ssz.UnmarshallUint64(buf[16:24])) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the PendingPartialWithdrawal object +func (p *PendingPartialWithdrawal) SizeSSZ() (size int) { + size = 24 + return +} + +// HashTreeRoot ssz hashes the PendingPartialWithdrawal object +func (p *PendingPartialWithdrawal) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(p) +} + +// HashTreeRootWith ssz hashes the PendingPartialWithdrawal object with a hasher +func (p *PendingPartialWithdrawal) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Index' + hh.PutUint64(uint64(p.Index)) + + // Field (1) 'Amount' + hh.PutUint64(uint64(p.Amount)) + + // Field (2) 'WithdrawableEpoch' + hh.PutUint64(uint64(p.WithdrawableEpoch)) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the PendingPartialWithdrawal object +func (p *PendingPartialWithdrawal) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(p) +} diff --git a/spec/electra/pendingpartialwithdrawal_yaml.go b/spec/electra/pendingpartialwithdrawal_yaml.go new file mode 100644 index 00000000..d9a6fc9c --- /dev/null +++ b/spec/electra/pendingpartialwithdrawal_yaml.go @@ -0,0 +1,60 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// pendingPartialWithdrawalYAML is the spec representation of the struct. +type pendingPartialWithdrawalYAML struct { + Index phase0.ValidatorIndex `yaml:"index"` + Amount phase0.Gwei `yaml:"amount"` + WithdrawableEpoch phase0.Epoch `yaml:"withdrawable_epoch"` +} + +// MarshalYAML implements yaml.Marshaler. +func (p *PendingPartialWithdrawal) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&pendingPartialWithdrawalYAML{ + Index: p.Index, + Amount: p.Amount, + WithdrawableEpoch: p.WithdrawableEpoch, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (p *PendingPartialWithdrawal) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled pendingPartialWithdrawalJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(&unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return p.UnmarshalJSON(marshaled) +} diff --git a/spec/electra/signedbeaconblock.go b/spec/electra/signedbeaconblock.go new file mode 100644 index 00000000..2c200195 --- /dev/null +++ b/spec/electra/signedbeaconblock.go @@ -0,0 +1,37 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// SignedBeaconBlock is a signed beacon block. +type SignedBeaconBlock struct { + Message *BeaconBlock + Signature phase0.BLSSignature `ssz-size:"96"` +} + +// String returns a string version of the structure. +func (s *SignedBeaconBlock) String() string { + data, err := yaml.Marshal(s) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/signedbeaconblock_json.go b/spec/electra/signedbeaconblock_json.go new file mode 100644 index 00000000..fa23dfee --- /dev/null +++ b/spec/electra/signedbeaconblock_json.go @@ -0,0 +1,68 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "strings" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// signedBeaconBlockJSON is the spec representation of the struct. +type signedBeaconBlockJSON struct { + Message *BeaconBlock `json:"message"` + Signature string `json:"signature"` +} + +// MarshalJSON implements json.Marshaler. +func (s *SignedBeaconBlock) MarshalJSON() ([]byte, error) { + return json.Marshal(&signedBeaconBlockJSON{ + Message: s.Message, + Signature: fmt.Sprintf("%#x", s.Signature), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *SignedBeaconBlock) UnmarshalJSON(input []byte) error { + var data signedBeaconBlockJSON + if err := json.Unmarshal(input, &data); err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return s.unpack(&data) +} + +func (s *SignedBeaconBlock) unpack(data *signedBeaconBlockJSON) error { + if data.Message == nil { + return errors.New("message missing") + } + s.Message = data.Message + if data.Signature == "" { + return errors.New("signature missing") + } + signature, err := hex.DecodeString(strings.TrimPrefix(data.Signature, "0x")) + if err != nil { + return errors.Wrap(err, "invalid value for signature") + } + if len(signature) != phase0.SignatureLength { + return fmt.Errorf("incorrect length %d for signature", len(signature)) + } + copy(s.Signature[:], signature) + + return nil +} diff --git a/spec/electra/signedbeaconblock_ssz.go b/spec/electra/signedbeaconblock_ssz.go new file mode 100644 index 00000000..89a95f1b --- /dev/null +++ b/spec/electra/signedbeaconblock_ssz.go @@ -0,0 +1,111 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the SignedBeaconBlock object +func (s *SignedBeaconBlock) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SignedBeaconBlock object to a target array +func (s *SignedBeaconBlock) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(100) + + // Offset (0) 'Message' + dst = ssz.WriteOffset(dst, offset) + if s.Message == nil { + s.Message = new(BeaconBlock) + } + offset += s.Message.SizeSSZ() + + // Field (1) 'Signature' + dst = append(dst, s.Signature[:]...) + + // Field (0) 'Message' + if dst, err = s.Message.MarshalSSZTo(dst); err != nil { + return + } + + return +} + +// UnmarshalSSZ ssz unmarshals the SignedBeaconBlock object +func (s *SignedBeaconBlock) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 100 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'Message' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 100 { + return ssz.ErrInvalidVariableOffset + } + + // Field (1) 'Signature' + copy(s.Signature[:], buf[4:100]) + + // Field (0) 'Message' + { + buf = tail[o0:] + if s.Message == nil { + s.Message = new(BeaconBlock) + } + if err = s.Message.UnmarshalSSZ(buf); err != nil { + return err + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SignedBeaconBlock object +func (s *SignedBeaconBlock) SizeSSZ() (size int) { + size = 100 + + // Field (0) 'Message' + if s.Message == nil { + s.Message = new(BeaconBlock) + } + size += s.Message.SizeSSZ() + + return +} + +// HashTreeRoot ssz hashes the SignedBeaconBlock object +func (s *SignedBeaconBlock) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SignedBeaconBlock object with a hasher +func (s *SignedBeaconBlock) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Message' + if err = s.Message.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Signature' + hh.PutBytes(s.Signature[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SignedBeaconBlock object +func (s *SignedBeaconBlock) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/spec/electra/signedbeaconblock_yaml.go b/spec/electra/signedbeaconblock_yaml.go new file mode 100644 index 00000000..bd44c064 --- /dev/null +++ b/spec/electra/signedbeaconblock_yaml.go @@ -0,0 +1,51 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "fmt" + + "github.com/goccy/go-yaml" +) + +// signedBeaconBlockYAML is the spec representation of the struct. +type signedBeaconBlockYAML struct { + Message *BeaconBlock `yaml:"message"` + Signature string `yaml:"signature"` +} + +// MarshalYAML implements yaml.Marshaler. +func (s *SignedBeaconBlock) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&signedBeaconBlockYAML{ + Message: s.Message, + Signature: fmt.Sprintf("%#x", s.Signature), + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (s *SignedBeaconBlock) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var data signedBeaconBlockJSON + if err := yaml.Unmarshal(input, &data); err != nil { + return err + } + + return s.unpack(&data) +} diff --git a/spec/electra/signedconsolidation.go b/spec/electra/signedconsolidation.go new file mode 100644 index 00000000..f8fe30da --- /dev/null +++ b/spec/electra/signedconsolidation.go @@ -0,0 +1,37 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// SignedConsolidation is a signed consolidation. +type SignedConsolidation struct { + Message Consolidation + Signature phase0.BLSSignature `ssz-size:"96"` +} + +// String returns a string version of the structure. +func (s *SignedConsolidation) String() string { + data, err := yaml.Marshal(s) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/signedconsolidation_json.go b/spec/electra/signedconsolidation_json.go new file mode 100644 index 00000000..7ff53caf --- /dev/null +++ b/spec/electra/signedconsolidation_json.go @@ -0,0 +1,53 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "encoding/json" + + "github.com/attestantio/go-eth2-client/codecs" + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" +) + +// signedConsolidationJSON is the spec representation of the struct. +type signedConsolidationJSON struct { + Message Consolidation `json:"message"` + Signature phase0.BLSSignature `json:"signature"` +} + +// MarshalJSON implements json.Marshaler. +func (s *SignedConsolidation) MarshalJSON() ([]byte, error) { + return json.Marshal(&signedConsolidationJSON{ + Message: s.Message, + Signature: s.Signature, + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (s *SignedConsolidation) UnmarshalJSON(input []byte) error { + raw, err := codecs.RawJSON(&signedConsolidationJSON{}, input) + if err != nil { + return err + } + + if err := s.Message.UnmarshalJSON(raw["message"]); err != nil { + return errors.Wrap(err, "message") + } + if err := s.Signature.UnmarshalJSON(raw["signature"]); err != nil { + return errors.Wrap(err, "signature") + } + + return nil +} diff --git a/spec/electra/signedconsolidation_ssz.go b/spec/electra/signedconsolidation_ssz.go new file mode 100644 index 00000000..7921ae41 --- /dev/null +++ b/spec/electra/signedconsolidation_ssz.go @@ -0,0 +1,79 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: 17d4c9180818d70e873edf284079b326d586a16686d17c7c974a8a2fd19ec3e9 +// Version: 0.1.3 +package electra + +import ( + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the SignedConsolidation object +func (s *SignedConsolidation) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SignedConsolidation object to a target array +func (s *SignedConsolidation) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'Message' + if dst, err = s.Message.MarshalSSZTo(dst); err != nil { + return + } + + // Field (1) 'Signature' + dst = append(dst, s.Signature[:]...) + + return +} + +// UnmarshalSSZ ssz unmarshals the SignedConsolidation object +func (s *SignedConsolidation) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 120 { + return ssz.ErrSize + } + + // Field (0) 'Message' + if err = s.Message.UnmarshalSSZ(buf[0:24]); err != nil { + return err + } + + // Field (1) 'Signature' + copy(s.Signature[:], buf[24:120]) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SignedConsolidation object +func (s *SignedConsolidation) SizeSSZ() (size int) { + size = 120 + return +} + +// HashTreeRoot ssz hashes the SignedConsolidation object +func (s *SignedConsolidation) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SignedConsolidation object with a hasher +func (s *SignedConsolidation) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'Message' + if err = s.Message.HashTreeRootWith(hh); err != nil { + return + } + + // Field (1) 'Signature' + hh.PutBytes(s.Signature[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SignedConsolidation object +func (s *SignedConsolidation) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/spec/electra/signedconsolidation_yaml.go b/spec/electra/signedconsolidation_yaml.go new file mode 100644 index 00000000..11d45d86 --- /dev/null +++ b/spec/electra/signedconsolidation_yaml.go @@ -0,0 +1,58 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import ( + "bytes" + "encoding/json" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" + "github.com/pkg/errors" +) + +// signedConsolidationYAML is the spec representation of the struct. +type signedConsolidationYAML struct { + Message Consolidation `yaml:"message"` + Signature phase0.BLSSignature `yaml:"signature"` +} + +// MarshalYAML implements yaml.Marshaler. +func (s *SignedConsolidation) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&signedConsolidationYAML{ + Message: s.Message, + Signature: s.Signature, + }, yaml.Flow(true)) + if err != nil { + return nil, err + } + + return bytes.ReplaceAll(yamlBytes, []byte(`"`), []byte(`'`)), nil +} + +// UnmarshalYAML implements yaml.Unmarshaler. +func (s *SignedConsolidation) UnmarshalYAML(input []byte) error { + // This is very inefficient, but YAML is only used for spec tests so we do this + // rather than maintain a custom YAML unmarshaller. + var unmarshaled signedConsolidationJSON + if err := yaml.Unmarshal(input, &unmarshaled); err != nil { + return errors.Wrap(err, "failed to unmarshal YAML") + } + marshaled, err := json.Marshal(&unmarshaled) + if err != nil { + return errors.Wrap(err, "failed to marshal JSON") + } + + return s.UnmarshalJSON(marshaled) +} diff --git a/spec/versionedattestation.go b/spec/versionedattestation.go new file mode 100644 index 00000000..e7dc593c --- /dev/null +++ b/spec/versionedattestation.go @@ -0,0 +1,231 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package spec + +import ( + "errors" + + "github.com/prysmaticlabs/go-bitfield" + + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/phase0" +) + +// VersionedAttestation contains a versioned attestation. +type VersionedAttestation struct { + Version DataVersion + Phase0 *phase0.Attestation + Altair *phase0.Attestation + Bellatrix *phase0.Attestation + Capella *phase0.Attestation + Deneb *phase0.Attestation + Electra *electra.Attestation +} + +// IsEmpty returns true if there is no block. +func (v *VersionedAttestation) IsEmpty() bool { + return v.Phase0 == nil && v.Altair == nil && v.Bellatrix == nil && v.Capella == nil && v.Deneb == nil && v.Electra == nil +} + +// AggregationBits returns the aggregation bits of the attestation. +func (v *VersionedAttestation) AggregationBits() (bitfield.Bitlist, error) { + switch v.Version { + case DataVersionPhase0: + if v.Phase0 == nil { + return nil, errors.New("no Phase0 attestation") + } + + return v.Phase0.AggregationBits, nil + case DataVersionAltair: + if v.Altair == nil { + return nil, errors.New("no Altair attestation") + } + + return v.Altair.AggregationBits, nil + case DataVersionBellatrix: + if v.Bellatrix == nil { + return nil, errors.New("no Bellatrix attestation") + } + + return v.Bellatrix.AggregationBits, nil + case DataVersionCapella: + if v.Capella == nil { + return nil, errors.New("no Capella attestation") + } + + return v.Capella.AggregationBits, nil + case DataVersionDeneb: + if v.Deneb == nil { + return nil, errors.New("no Deneb attestation") + } + + return v.Deneb.AggregationBits, nil + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra attestation") + } + + return v.Electra.AggregationBits, nil + default: + return nil, errors.New("unknown version") + } +} + +// Data returns the data of the attestation. +func (v *VersionedAttestation) Data() (*phase0.AttestationData, error) { + switch v.Version { + case DataVersionPhase0: + if v.Phase0 == nil { + return nil, errors.New("no Phase0 attestation") + } + + return v.Phase0.Data, nil + case DataVersionAltair: + if v.Altair == nil { + return nil, errors.New("no Altair attestation") + } + + return v.Altair.Data, nil + case DataVersionBellatrix: + if v.Bellatrix == nil { + return nil, errors.New("no Bellatrix attestation") + } + + return v.Bellatrix.Data, nil + case DataVersionCapella: + if v.Capella == nil { + return nil, errors.New("no Capella attestation") + } + + return v.Capella.Data, nil + case DataVersionDeneb: + if v.Deneb == nil { + return nil, errors.New("no Deneb attestation") + } + + return v.Deneb.Data, nil + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra attestation") + } + + return v.Electra.Data, nil + default: + return nil, errors.New("unknown version") + } +} + +// CommitteeBits returns the committee bits of the attestation. +func (v *VersionedAttestation) CommitteeBits() (bitfield.Bitvector64, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return nil, errors.New("attestation does not provide committee bits") + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra attestation") + } + + return v.Electra.CommitteeBits, nil + default: + return nil, errors.New("unknown version") + } +} + +// Signature returns the signature of the attestation. +func (v *VersionedAttestation) Signature() (phase0.BLSSignature, error) { + switch v.Version { + case DataVersionPhase0: + if v.Phase0 == nil { + return phase0.BLSSignature{}, errors.New("no Phase0 attestation") + } + + return v.Phase0.Signature, nil + case DataVersionAltair: + if v.Altair == nil { + return phase0.BLSSignature{}, errors.New("no Altair attestation") + } + + return v.Altair.Signature, nil + case DataVersionBellatrix: + if v.Bellatrix == nil { + return phase0.BLSSignature{}, errors.New("no Bellatrix attestation") + } + + return v.Bellatrix.Signature, nil + case DataVersionCapella: + if v.Capella == nil { + return phase0.BLSSignature{}, errors.New("no Capella attestation") + } + + return v.Capella.Signature, nil + case DataVersionDeneb: + if v.Deneb == nil { + return phase0.BLSSignature{}, errors.New("no Deneb attestation") + } + + return v.Deneb.Signature, nil + case DataVersionElectra: + if v.Electra == nil { + return phase0.BLSSignature{}, errors.New("no Electra attestation") + } + + return v.Electra.Signature, nil + default: + return phase0.BLSSignature{}, errors.New("unknown version") + } +} + +// String returns a string version of the structure. +func (v *VersionedAttestation) String() string { + switch v.Version { + case DataVersionPhase0: + if v.Phase0 == nil { + return "" + } + + return v.Phase0.String() + case DataVersionAltair: + if v.Altair == nil { + return "" + } + + return v.Altair.String() + case DataVersionBellatrix: + if v.Bellatrix == nil { + return "" + } + + return v.Bellatrix.String() + case DataVersionCapella: + if v.Capella == nil { + return "" + } + + return v.Capella.String() + case DataVersionDeneb: + if v.Deneb == nil { + return "" + } + + return v.Deneb.String() + case DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() + default: + return "unknown version" + } +} diff --git a/spec/versionedbeaconblock.go b/spec/versionedbeaconblock.go index c0c833e1..341b992d 100644 --- a/spec/versionedbeaconblock.go +++ b/spec/versionedbeaconblock.go @@ -1,4 +1,4 @@ -// Copyright © 2021 - 2023 Attestant Limited. +// Copyright © 2021 - 2024 Attestant Limited. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -16,6 +16,8 @@ package spec import ( "errors" + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" @@ -31,6 +33,7 @@ type VersionedBeaconBlock struct { Bellatrix *bellatrix.BeaconBlock Capella *capella.BeaconBlock Deneb *deneb.BeaconBlock + Electra *electra.BeaconBlock } // IsEmpty returns true if there is no block. @@ -71,6 +74,12 @@ func (v *VersionedBeaconBlock) Slot() (phase0.Slot, error) { } return v.Deneb.Slot, nil + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no electra block") + } + + return v.Electra.Slot, nil default: return 0, errors.New("unknown version") } @@ -124,6 +133,15 @@ func (v *VersionedBeaconBlock) RandaoReveal() (phase0.BLSSignature, error) { } return v.Deneb.Body.RANDAOReveal, nil + case DataVersionElectra: + if v.Electra == nil { + return phase0.BLSSignature{}, errors.New("no electra block") + } + if v.Electra.Body == nil { + return phase0.BLSSignature{}, errors.New("no electra block body") + } + + return v.Electra.Body.RANDAOReveal, nil default: return phase0.BLSSignature{}, errors.New("unknown version") } @@ -177,6 +195,15 @@ func (v *VersionedBeaconBlock) Graffiti() ([32]byte, error) { } return v.Deneb.Body.Graffiti, nil + case DataVersionElectra: + if v.Electra == nil { + return [32]byte{}, errors.New("no electra block") + } + if v.Electra.Body == nil { + return [32]byte{}, errors.New("no electra block body") + } + + return v.Electra.Body.Graffiti, nil default: return [32]byte{}, errors.New("unknown version") } @@ -215,6 +242,12 @@ func (v *VersionedBeaconBlock) ProposerIndex() (phase0.ValidatorIndex, error) { } return v.Deneb.ProposerIndex, nil + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no electra block") + } + + return v.Electra.ProposerIndex, nil default: return 0, errors.New("unknown version") } @@ -253,6 +286,12 @@ func (v *VersionedBeaconBlock) Root() (phase0.Root, error) { } return v.Deneb.HashTreeRoot() + case DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.HashTreeRoot() default: return phase0.Root{}, errors.New("unknown version") } @@ -306,6 +345,15 @@ func (v *VersionedBeaconBlock) BodyRoot() (phase0.Root, error) { } return v.Deneb.Body.HashTreeRoot() + case DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, errors.New("no electra block") + } + if v.Electra.Body == nil { + return phase0.Root{}, errors.New("no electra block body") + } + + return v.Electra.Body.HashTreeRoot() default: return phase0.Root{}, errors.New("unknown version") } @@ -344,6 +392,12 @@ func (v *VersionedBeaconBlock) ParentRoot() (phase0.Root, error) { } return v.Deneb.ParentRoot, nil + case DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.ParentRoot, nil default: return phase0.Root{}, errors.New("unknown version") } @@ -382,44 +436,104 @@ func (v *VersionedBeaconBlock) StateRoot() (phase0.Root, error) { } return v.Deneb.StateRoot, nil + case DataVersionElectra: + if v.Electra == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.StateRoot, nil default: return phase0.Root{}, errors.New("unknown version") } } // Attestations returns the attestations of the beacon block. -func (v *VersionedBeaconBlock) Attestations() ([]*phase0.Attestation, error) { +func (v *VersionedBeaconBlock) Attestations() ([]VersionedAttestation, error) { switch v.Version { case DataVersionPhase0: if v.Phase0 == nil || v.Phase0.Body == nil { return nil, errors.New("no phase0 block") } - return v.Phase0.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Phase0.Body.Attestations)) + for i, attestation := range v.Phase0.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionPhase0, + Phase0: attestation, + } + } + + return versionedAttestations, nil case DataVersionAltair: if v.Altair == nil || v.Altair.Body == nil { return nil, errors.New("no altair block") } - return v.Altair.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Altair.Body.Attestations)) + for i, attestation := range v.Altair.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionAltair, + Altair: attestation, + } + } + + return versionedAttestations, nil case DataVersionBellatrix: if v.Bellatrix == nil || v.Bellatrix.Body == nil { return nil, errors.New("no bellatrix block") } - return v.Bellatrix.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Bellatrix.Body.Attestations)) + for i, attestation := range v.Bellatrix.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case DataVersionCapella: if v.Capella == nil || v.Capella.Body == nil { return nil, errors.New("no capella block") } - return v.Capella.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Capella.Body.Attestations)) + for i, attestation := range v.Capella.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case DataVersionDeneb: if v.Deneb == nil || v.Deneb.Body == nil { return nil, errors.New("no deneb block") } - return v.Deneb.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Deneb.Body.Attestations)) + for i, attestation := range v.Deneb.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Body == nil { + return nil, errors.New("no electra block") + } + + versionedAttestations := make([]VersionedAttestation, len(v.Electra.Body.Attestations)) + for i, attestation := range v.Electra.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, errors.New("unknown version") } @@ -458,6 +572,12 @@ func (v *VersionedBeaconBlock) AttesterSlashings() ([]*phase0.AttesterSlashing, } return v.Deneb.Body.AttesterSlashings, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Body.AttesterSlashings, nil default: return nil, errors.New("unknown version") } @@ -496,6 +616,12 @@ func (v *VersionedBeaconBlock) ProposerSlashings() ([]*phase0.ProposerSlashing, } return v.Deneb.Body.ProposerSlashings, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Body.ProposerSlashings, nil default: return nil, errors.New("unknown version") } @@ -534,6 +660,12 @@ func (v *VersionedBeaconBlock) String() string { } return v.Deneb.String() + case DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } diff --git a/spec/versionedbeaconblockbody.go b/spec/versionedbeaconblockbody.go index ae7fbf3e..1e5dbc20 100644 --- a/spec/versionedbeaconblockbody.go +++ b/spec/versionedbeaconblockbody.go @@ -1,4 +1,4 @@ -// Copyright © 2021 - 2023 Attestant Limited. +// Copyright © 2021 - 2024 Attestant Limited. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -18,6 +18,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -29,6 +30,7 @@ type VersionedBeaconBlockBody struct { Bellatrix *bellatrix.BeaconBlockBody Capella *capella.BeaconBlockBody Deneb *deneb.BeaconBlockBody + Electra *electra.BeaconBlockBody } // String returns a string version of the structure. @@ -64,6 +66,12 @@ func (v *VersionedBeaconBlockBody) String() string { } return v.Deneb.String() + case DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } diff --git a/spec/versionedbeaconstate.go b/spec/versionedbeaconstate.go index 22c8c87b..3582233e 100644 --- a/spec/versionedbeaconstate.go +++ b/spec/versionedbeaconstate.go @@ -1,4 +1,4 @@ -// Copyright © 2021 - 2023 Attestant Limited. +// Copyright © 2021 - 2024 Attestant Limited. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -20,6 +20,7 @@ import ( "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/deneb" + "github.com/attestantio/go-eth2-client/spec/electra" "github.com/attestantio/go-eth2-client/spec/phase0" ) @@ -31,11 +32,12 @@ type VersionedBeaconState struct { Bellatrix *bellatrix.BeaconState Capella *capella.BeaconState Deneb *deneb.BeaconState + Electra *electra.BeaconState } // IsEmpty returns true if there is no block. func (v *VersionedBeaconState) IsEmpty() bool { - return v.Phase0 == nil && v.Altair == nil && v.Bellatrix == nil && v.Capella == nil && v.Deneb == nil + return v.Phase0 == nil && v.Altair == nil && v.Bellatrix == nil && v.Capella == nil && v.Deneb == nil && v.Electra == nil } // Slot returns the slot of the state. @@ -71,6 +73,12 @@ func (v *VersionedBeaconState) Slot() (phase0.Slot, error) { } return v.Deneb.Slot, nil + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.Slot, nil default: return 0, errors.New("unknown version") } @@ -93,6 +101,12 @@ func (v *VersionedBeaconState) NextWithdrawalValidatorIndex() (phase0.ValidatorI } return v.Deneb.NextWithdrawalValidatorIndex, nil + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.NextWithdrawalValidatorIndex, nil default: return 0, errors.New("unknown version") } @@ -131,6 +145,12 @@ func (v *VersionedBeaconState) Validators() ([]*phase0.Validator, error) { } return v.Deneb.Validators, nil + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra state") + } + + return v.Electra.Validators, nil default: return nil, errors.New("unknown version") } @@ -169,6 +189,156 @@ func (v *VersionedBeaconState) ValidatorBalances() ([]phase0.Gwei, error) { } return v.Deneb.Balances, nil + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra state") + } + + return v.Electra.Balances, nil + default: + return nil, errors.New("unknown version") + } +} + +// DepositReceiptsStartIndex returns the deposit receipts start index of the state. +func (v *VersionedBeaconState) DepositReceiptsStartIndex() (uint64, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return 0, errors.New("state does not provide deposit receipts start index") + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.DepositReceiptsStartIndex, nil + default: + return 0, errors.New("unknown version") + } +} + +// DepositBalanceToConsume returns the deposit balance to consume of the state. +func (v *VersionedBeaconState) DepositBalanceToConsume() (phase0.Gwei, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return 0, errors.New("state does not provide deposit balance to consume") + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.DepositBalanceToConsume, nil + default: + return 0, errors.New("unknown version") + } +} + +// ExitBalanceToConsume returns the deposit balance to consume of the state. +func (v *VersionedBeaconState) ExitBalanceToConsume() (phase0.Gwei, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return 0, errors.New("state does not provide exit balance to consume") + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.ExitBalanceToConsume, nil + default: + return 0, errors.New("unknown version") + } +} + +// EarliestExitEpoch returns the earliest exit epoch of the state. +func (v *VersionedBeaconState) EarliestExitEpoch() (phase0.Epoch, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return 0, errors.New("state does not provide earliest exit epoch") + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.EarliestExitEpoch, nil + default: + return 0, errors.New("unknown version") + } +} + +// ConsolidationBalanceToConsume returns the consolidation balance to consume of the state. +func (v *VersionedBeaconState) ConsolidationBalanceToConsume() (phase0.Gwei, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return 0, errors.New("state does not provide consolidation balance to consume") + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.ConsolidationBalanceToConsume, nil + default: + return 0, errors.New("unknown version") + } +} + +// EarliestConsolidationEpoch returns the earliest consolidation epoch of the state. +func (v *VersionedBeaconState) EarliestConsolidationEpoch() (phase0.Epoch, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return 0, errors.New("state does not provide earliest consolidation epoch") + case DataVersionElectra: + if v.Electra == nil { + return 0, errors.New("no Electra state") + } + + return v.Electra.EarliestConsolidationEpoch, nil + default: + return 0, errors.New("unknown version") + } +} + +// PendingBalanceDeposits returns the pending balance deposits of the state. +func (v *VersionedBeaconState) PendingBalanceDeposits() ([]*electra.PendingBalanceDeposit, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return nil, errors.New("state does not provide pending balance deposits") + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra state") + } + + return v.Electra.PendingBalanceDeposits, nil + default: + return nil, errors.New("unknown version") + } +} + +// PendingPartialWithdrawals returns the pending partial withdrawals of the state. +func (v *VersionedBeaconState) PendingPartialWithdrawals() ([]*electra.PendingPartialWithdrawal, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return nil, errors.New("state does not provide pending partial withdrawals") + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra state") + } + + return v.Electra.PendingPartialWithdrawals, nil + default: + return nil, errors.New("unknown version") + } +} + +// PendingConsolidations returns the pending consolidations of the state. +func (v *VersionedBeaconState) PendingConsolidations() ([]*electra.PendingConsolidation, error) { + switch v.Version { + case DataVersionPhase0, DataVersionAltair, DataVersionBellatrix, DataVersionCapella, DataVersionDeneb: + return nil, errors.New("state does not provide pending consolidations") + case DataVersionElectra: + if v.Electra == nil { + return nil, errors.New("no Electra state") + } + + return v.Electra.PendingConsolidations, nil default: return nil, errors.New("unknown version") } @@ -207,6 +377,12 @@ func (v *VersionedBeaconState) String() string { } return v.Deneb.String() + case DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } diff --git a/spec/versionedsignedbeaconblock.go b/spec/versionedsignedbeaconblock.go index 7a099c1b..b5ff055e 100644 --- a/spec/versionedsignedbeaconblock.go +++ b/spec/versionedsignedbeaconblock.go @@ -1,4 +1,4 @@ -// Copyright © 2021 - 2023 Attestant Limited. +// Copyright © 2021 - 2024 Attestant Limited. // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -16,6 +16,8 @@ package spec import ( "errors" + "github.com/attestantio/go-eth2-client/spec/electra" + "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/attestantio/go-eth2-client/spec/capella" @@ -31,6 +33,7 @@ type VersionedSignedBeaconBlock struct { Bellatrix *bellatrix.SignedBeaconBlock Capella *capella.SignedBeaconBlock Deneb *deneb.SignedBeaconBlock + Electra *electra.SignedBeaconBlock } // Slot returns the slot of the signed beacon block. @@ -66,6 +69,12 @@ func (v *VersionedSignedBeaconBlock) Slot() (phase0.Slot, error) { } return v.Deneb.Message.Slot, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil { + return 0, errors.New("no electra block") + } + + return v.Electra.Message.Slot, nil default: return 0, errors.New("unknown version") } @@ -104,6 +113,12 @@ func (v *VersionedSignedBeaconBlock) ProposerIndex() (phase0.ValidatorIndex, err } return v.Deneb.Message.ProposerIndex, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil { + return 0, errors.New("no electra block") + } + + return v.Electra.Message.ProposerIndex, nil default: return 0, errors.New("unknown version") } @@ -130,6 +145,12 @@ func (v *VersionedSignedBeaconBlock) ExecutionBlockHash() (phase0.Hash32, error) } return v.Deneb.Message.Body.ExecutionPayload.BlockHash, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil || v.Electra.Message.Body.ExecutionPayload == nil { + return phase0.Hash32{}, errors.New("no deneb block") + } + + return v.Electra.Message.Body.ExecutionPayload.BlockHash, nil default: return phase0.Hash32{}, errors.New("unknown version") } @@ -156,6 +177,12 @@ func (v *VersionedSignedBeaconBlock) ExecutionBlockNumber() (uint64, error) { } return v.Deneb.Message.Body.ExecutionPayload.BlockNumber, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil || v.Electra.Message.Body.ExecutionPayload == nil { + return 0, errors.New("no electra block") + } + + return v.Electra.Message.Body.ExecutionPayload.BlockNumber, nil default: return 0, errors.New("unknown version") } @@ -186,6 +213,12 @@ func (v *VersionedSignedBeaconBlock) ExecutionTransactions() ([]bellatrix.Transa } return v.Deneb.Message.Body.ExecutionPayload.Transactions, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil || v.Electra.Message.Body.ExecutionPayload == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.ExecutionPayload.Transactions, nil default: return nil, errors.New("unknown version") } @@ -224,44 +257,106 @@ func (v *VersionedSignedBeaconBlock) Graffiti() ([32]byte, error) { } return v.Deneb.Message.Body.Graffiti, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return [32]byte{}, errors.New("no electra block") + } + + return v.Electra.Message.Body.Graffiti, nil default: return [32]byte{}, errors.New("unknown version") } } // Attestations returns the attestations of the beacon block. -func (v *VersionedSignedBeaconBlock) Attestations() ([]*phase0.Attestation, error) { +// +//nolint:gocyclo +func (v *VersionedSignedBeaconBlock) Attestations() ([]VersionedAttestation, error) { switch v.Version { case DataVersionPhase0: if v.Phase0 == nil || v.Phase0.Message == nil || v.Phase0.Message.Body == nil { return nil, errors.New("no phase0 block") } - return v.Phase0.Message.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Phase0.Message.Body.Attestations)) + for i, attestation := range v.Phase0.Message.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionPhase0, + Phase0: attestation, + } + } + + return versionedAttestations, nil case DataVersionAltair: if v.Altair == nil || v.Altair.Message == nil || v.Altair.Message.Body == nil { return nil, errors.New("no altair block") } - return v.Altair.Message.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Altair.Message.Body.Attestations)) + for i, attestation := range v.Altair.Message.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionAltair, + Altair: attestation, + } + } + + return versionedAttestations, nil case DataVersionBellatrix: if v.Bellatrix == nil || v.Bellatrix.Message == nil || v.Bellatrix.Message.Body == nil { return nil, errors.New("no bellatrix block") } - return v.Bellatrix.Message.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Bellatrix.Message.Body.Attestations)) + for i, attestation := range v.Bellatrix.Message.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionBellatrix, + Bellatrix: attestation, + } + } + + return versionedAttestations, nil case DataVersionCapella: if v.Capella == nil || v.Capella.Message == nil || v.Capella.Message.Body == nil { return nil, errors.New("no capella block") } - return v.Capella.Message.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Capella.Message.Body.Attestations)) + for i, attestation := range v.Capella.Message.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionCapella, + Capella: attestation, + } + } + + return versionedAttestations, nil case DataVersionDeneb: if v.Deneb == nil || v.Deneb.Message == nil || v.Deneb.Message.Body == nil { return nil, errors.New("no deneb block") } - return v.Deneb.Message.Body.Attestations, nil + versionedAttestations := make([]VersionedAttestation, len(v.Deneb.Message.Body.Attestations)) + for i, attestation := range v.Deneb.Message.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionDeneb, + Deneb: attestation, + } + } + + return versionedAttestations, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + versionedAttestations := make([]VersionedAttestation, len(v.Electra.Message.Body.Attestations)) + for i, attestation := range v.Electra.Message.Body.Attestations { + versionedAttestations[i] = VersionedAttestation{ + Version: DataVersionElectra, + Electra: attestation, + } + } + + return versionedAttestations, nil default: return nil, errors.New("unknown version") } @@ -300,6 +395,12 @@ func (v *VersionedSignedBeaconBlock) Root() (phase0.Root, error) { } return v.Deneb.Message.HashTreeRoot() + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.Message.HashTreeRoot() default: return phase0.Root{}, errors.New("unknown version") } @@ -338,6 +439,12 @@ func (v *VersionedSignedBeaconBlock) BodyRoot() (phase0.Root, error) { } return v.Deneb.Message.Body.HashTreeRoot() + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.Message.Body.HashTreeRoot() default: return phase0.Root{}, errors.New("unknown version") } @@ -376,6 +483,12 @@ func (v *VersionedSignedBeaconBlock) ParentRoot() (phase0.Root, error) { } return v.Deneb.Message.ParentRoot, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.Message.ParentRoot, nil default: return phase0.Root{}, errors.New("unknown version") } @@ -414,6 +527,12 @@ func (v *VersionedSignedBeaconBlock) StateRoot() (phase0.Root, error) { } return v.Deneb.Message.StateRoot, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil { + return phase0.Root{}, errors.New("no electra block") + } + + return v.Electra.Message.StateRoot, nil default: return phase0.Root{}, errors.New("unknown version") } @@ -452,6 +571,12 @@ func (v *VersionedSignedBeaconBlock) RandaoReveal() (phase0.BLSSignature, error) } return v.Deneb.Message.Body.RANDAOReveal, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return phase0.BLSSignature{}, errors.New("no electra block") + } + + return v.Electra.Message.Body.RANDAOReveal, nil default: return phase0.BLSSignature{}, errors.New("unknown version") } @@ -490,6 +615,12 @@ func (v *VersionedSignedBeaconBlock) ETH1Data() (*phase0.ETH1Data, error) { } return v.Deneb.Message.Body.ETH1Data, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.ETH1Data, nil default: return nil, errors.New("unknown version") } @@ -528,6 +659,12 @@ func (v *VersionedSignedBeaconBlock) Deposits() ([]*phase0.Deposit, error) { } return v.Deneb.Message.Body.Deposits, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.Deposits, nil default: return nil, errors.New("unknown version") } @@ -566,6 +703,12 @@ func (v *VersionedSignedBeaconBlock) VoluntaryExits() ([]*phase0.SignedVoluntary } return v.Deneb.Message.Body.VoluntaryExits, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.VoluntaryExits, nil default: return nil, errors.New("unknown version") } @@ -604,6 +747,12 @@ func (v *VersionedSignedBeaconBlock) AttesterSlashings() ([]*phase0.AttesterSlas } return v.Deneb.Message.Body.AttesterSlashings, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.AttesterSlashings, nil default: return nil, errors.New("unknown version") } @@ -642,6 +791,12 @@ func (v *VersionedSignedBeaconBlock) ProposerSlashings() ([]*phase0.ProposerSlas } return v.Deneb.Message.Body.ProposerSlashings, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.ProposerSlashings, nil default: return nil, errors.New("unknown version") } @@ -676,6 +831,12 @@ func (v *VersionedSignedBeaconBlock) SyncAggregate() (*altair.SyncAggregate, err } return v.Deneb.Message.Body.SyncAggregate, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.SyncAggregate, nil default: return nil, errors.New("unknown version") } @@ -702,6 +863,12 @@ func (v *VersionedSignedBeaconBlock) BLSToExecutionChanges() ([]*capella.SignedB } return v.Deneb.Message.Body.BLSToExecutionChanges, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.BLSToExecutionChanges, nil default: return nil, errors.New("unknown version") } @@ -728,6 +895,12 @@ func (v *VersionedSignedBeaconBlock) Withdrawals() ([]*capella.Withdrawal, error } return v.Deneb.Message.Body.ExecutionPayload.Withdrawals, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil || v.Electra.Message.Body.ExecutionPayload == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.ExecutionPayload.Withdrawals, nil default: return nil, errors.New("unknown version") } @@ -750,6 +923,36 @@ func (v *VersionedSignedBeaconBlock) BlobKZGCommitments() ([]deneb.KZGCommitment } return v.Deneb.Message.Body.BlobKZGCommitments, nil + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.BlobKZGCommitments, nil + default: + return nil, errors.New("unknown version") + } +} + +// Consolidations returns the consolidations of the beacon block. +func (v *VersionedSignedBeaconBlock) Consolidations() ([]*electra.SignedConsolidation, error) { + switch v.Version { + case DataVersionPhase0: + return nil, errors.New("phase0 block does not have consolidations") + case DataVersionAltair: + return nil, errors.New("altair block does not have consolidations") + case DataVersionBellatrix: + return nil, errors.New("bellatrix block does not have consolidations") + case DataVersionCapella: + return nil, errors.New("capella block does not have consolidations") + case DataVersionDeneb: + return nil, errors.New("deneb block does not have consolidations") + case DataVersionElectra: + if v.Electra == nil || v.Electra.Message == nil || v.Electra.Message.Body == nil { + return nil, errors.New("no electra block") + } + + return v.Electra.Message.Body.Consolidations, nil default: return nil, errors.New("unknown version") } @@ -788,6 +991,12 @@ func (v *VersionedSignedBeaconBlock) String() string { } return v.Deneb.String() + case DataVersionElectra: + if v.Electra == nil { + return "" + } + + return v.Electra.String() default: return "unknown version" } diff --git a/util/electra/depositreceipts.go b/util/electra/depositreceipts.go new file mode 100644 index 00000000..623a5126 --- /dev/null +++ b/util/electra/depositreceipts.go @@ -0,0 +1,21 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import "github.com/attestantio/go-eth2-client/spec/electra" + +// DepositReceipts provides information about deposit receipts. +type DepositReceipts struct { + DepositReceipts []*electra.DepositReceipt `ssz-max:"8192"` +} diff --git a/util/electra/depositreceipts_ssz.go b/util/electra/depositreceipts_ssz.go new file mode 100644 index 00000000..27fe0eaf --- /dev/null +++ b/util/electra/depositreceipts_ssz.go @@ -0,0 +1,121 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: bd021322a0585214e3e2b5d17c3251ad136909024b45304057c72b6c80c84729 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/electra" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the DepositReceipts object +func (d *DepositReceipts) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(d) +} + +// MarshalSSZTo ssz marshals the DepositReceipts object to a target array +func (d *DepositReceipts) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'DepositReceipts' + dst = ssz.WriteOffset(dst, offset) + offset += len(d.DepositReceipts) * 192 + + // Field (0) 'DepositReceipts' + if size := len(d.DepositReceipts); size > 8192 { + err = ssz.ErrListTooBigFn("DepositReceipts.DepositReceipts", size, 8192) + return + } + for ii := 0; ii < len(d.DepositReceipts); ii++ { + if dst, err = d.DepositReceipts[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the DepositReceipts object +func (d *DepositReceipts) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'DepositReceipts' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'DepositReceipts' + { + buf = tail[o0:] + num, err := ssz.DivideInt2(len(buf), 192, 8192) + if err != nil { + return err + } + d.DepositReceipts = make([]*electra.DepositReceipt, num) + for ii := 0; ii < num; ii++ { + if d.DepositReceipts[ii] == nil { + d.DepositReceipts[ii] = new(electra.DepositReceipt) + } + if err = d.DepositReceipts[ii].UnmarshalSSZ(buf[ii*192 : (ii+1)*192]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the DepositReceipts object +func (d *DepositReceipts) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'DepositReceipts' + size += len(d.DepositReceipts) * 192 + + return +} + +// HashTreeRoot ssz hashes the DepositReceipts object +func (d *DepositReceipts) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(d) +} + +// HashTreeRootWith ssz hashes the DepositReceipts object with a hasher +func (d *DepositReceipts) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'DepositReceipts' + { + subIndx := hh.Index() + num := uint64(len(d.DepositReceipts)) + if num > 8192 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range d.DepositReceipts { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 8192) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the DepositReceipts object +func (d *DepositReceipts) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(d) +} diff --git a/util/electra/generate.go b/util/electra/generate.go new file mode 100644 index 00000000..cbd61a7d --- /dev/null +++ b/util/electra/generate.go @@ -0,0 +1,19 @@ +// Copyright © 2021 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +// Need to `go install github.com/ferranbt/fastssz/sszgen@latest` for this to work. +//go:generate rm -f depositreceipts_ssz.go withdrawalrequests_ssz.go +//go:generate sszgen -suffix ssz -include ../../spec/phase0,../../spec/bellatrix,../../spec/electra -path . -objs DepositReceipts,ExecutionPayloadWithdrawalRequests +//go:generate goimports -w depositreceipts_ssz.go withdrawalrequests_ssz.go diff --git a/util/electra/withdrawalrequests.go b/util/electra/withdrawalrequests.go new file mode 100644 index 00000000..d48ad83e --- /dev/null +++ b/util/electra/withdrawalrequests.go @@ -0,0 +1,21 @@ +// Copyright © 2024 Attestant Limited. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package electra + +import "github.com/attestantio/go-eth2-client/spec/electra" + +// ExecutionPayloadWithdrawalRequests provides information about execution layer withdrawal requests. +type ExecutionPayloadWithdrawalRequests struct { + WithdrawalRequests []*electra.ExecutionLayerWithdrawalRequest `ssz-max:"16"` +} diff --git a/util/electra/withdrawalrequests_ssz.go b/util/electra/withdrawalrequests_ssz.go new file mode 100644 index 00000000..7ec9e4ed --- /dev/null +++ b/util/electra/withdrawalrequests_ssz.go @@ -0,0 +1,121 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: bd021322a0585214e3e2b5d17c3251ad136909024b45304057c72b6c80c84729 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/electra" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the ExecutionPayloadWithdrawalRequests object +func (e *ExecutionPayloadWithdrawalRequests) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(e) +} + +// MarshalSSZTo ssz marshals the ExecutionPayloadWithdrawalRequests object to a target array +func (e *ExecutionPayloadWithdrawalRequests) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + offset := int(4) + + // Offset (0) 'WithdrawalRequests' + dst = ssz.WriteOffset(dst, offset) + offset += len(e.WithdrawalRequests) * 76 + + // Field (0) 'WithdrawalRequests' + if size := len(e.WithdrawalRequests); size > 16 { + err = ssz.ErrListTooBigFn("ExecutionPayloadWithdrawalRequests.WithdrawalRequests", size, 16) + return + } + for ii := 0; ii < len(e.WithdrawalRequests); ii++ { + if dst, err = e.WithdrawalRequests[ii].MarshalSSZTo(dst); err != nil { + return + } + } + + return +} + +// UnmarshalSSZ ssz unmarshals the ExecutionPayloadWithdrawalRequests object +func (e *ExecutionPayloadWithdrawalRequests) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size < 4 { + return ssz.ErrSize + } + + tail := buf + var o0 uint64 + + // Offset (0) 'WithdrawalRequests' + if o0 = ssz.ReadOffset(buf[0:4]); o0 > size { + return ssz.ErrOffset + } + + if o0 < 4 { + return ssz.ErrInvalidVariableOffset + } + + // Field (0) 'WithdrawalRequests' + { + buf = tail[o0:] + num, err := ssz.DivideInt2(len(buf), 76, 16) + if err != nil { + return err + } + e.WithdrawalRequests = make([]*electra.ExecutionLayerWithdrawalRequest, num) + for ii := 0; ii < num; ii++ { + if e.WithdrawalRequests[ii] == nil { + e.WithdrawalRequests[ii] = new(electra.ExecutionLayerWithdrawalRequest) + } + if err = e.WithdrawalRequests[ii].UnmarshalSSZ(buf[ii*76 : (ii+1)*76]); err != nil { + return err + } + } + } + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the ExecutionPayloadWithdrawalRequests object +func (e *ExecutionPayloadWithdrawalRequests) SizeSSZ() (size int) { + size = 4 + + // Field (0) 'WithdrawalRequests' + size += len(e.WithdrawalRequests) * 76 + + return +} + +// HashTreeRoot ssz hashes the ExecutionPayloadWithdrawalRequests object +func (e *ExecutionPayloadWithdrawalRequests) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(e) +} + +// HashTreeRootWith ssz hashes the ExecutionPayloadWithdrawalRequests object with a hasher +func (e *ExecutionPayloadWithdrawalRequests) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'WithdrawalRequests' + { + subIndx := hh.Index() + num := uint64(len(e.WithdrawalRequests)) + if num > 16 { + err = ssz.ErrIncorrectListSize + return + } + for _, elem := range e.WithdrawalRequests { + if err = elem.HashTreeRootWith(hh); err != nil { + return + } + } + hh.MerkleizeWithMixin(subIndx, num, 16) + } + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the ExecutionPayloadWithdrawalRequests object +func (e *ExecutionPayloadWithdrawalRequests) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(e) +}