From 33740bb806af825bd6c61ff05c38ed83250185d6 Mon Sep 17 00:00:00 2001 From: Chris Berry Date: Mon, 4 Nov 2024 12:00:19 +0000 Subject: [PATCH 1/3] Add SingleAttestation container --- .gitignore | 3 + spec/electra/generate.go | 6 +- spec/electra/singleattestation.go | 40 +++++++++ spec/electra/singleattestation_json.go | 80 ++++++++++++++++++ spec/electra/singleattestation_ssz.go | 107 +++++++++++++++++++++++++ spec/electra/singleattestation_yaml.go | 56 +++++++++++++ spec/phase0/attestationdata.go | 2 +- 7 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 spec/electra/singleattestation.go create mode 100644 spec/electra/singleattestation_json.go create mode 100644 spec/electra/singleattestation_ssz.go create mode 100644 spec/electra/singleattestation_yaml.go diff --git a/.gitignore b/.gitignore index 8d09c99e..325b59d3 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,8 @@ coverage.html # Vim *.sw? +# Intellij and friends +.idea/ + # Local TODO TODO.md diff --git a/spec/electra/generate.go b/spec/electra/generate.go index 1b4600f4..71b3b890 100644 --- a/spec/electra/generate.go +++ b/spec/electra/generate.go @@ -15,6 +15,6 @@ package electra //nolint:revive // Need to `go install github.com/ferranbt/fastssz/sszgen@latest` for this to work. -//go:generate rm -f aggregateandproof_ssz.go attestation_ssz.go attesterslashing_ssz.go beaconblockbody_ssz.go beaconblock_ssz.go beaconstate_ssz.go consolidation_ssz.go consolidationrequest_ssz.go depositrequest_ssz.go withdrawalrequest_ssz.go executionrequests_ssz.go pendingconsolidation_ssz.go pendingdeposit_ssz.go pendingpartialwithdrawal_ssz.go signedaggregateandproof_ssz.go signedbeaconblock_ssz.go -//go:generate sszgen --suffix=ssz --path . --include ../phase0,../altair,../bellatrix,../capella,../deneb --objs AggregateAndProof,Attestation,AttesterSlashing,BeaconBlockBody,BeaconBlock,BeaconState,Consolidation,ConsolidationRequest,DepositRequest,WithdrawalRequest,ExecutionRequests,PendingConsolidation,PendingDeposit,PendingPartialWithdrawal,SignedAggregateAndProof,SignedBeaconBlock -//go:generate goimports -w aggregateandproof_ssz.go attestation_ssz.go attesterslashing_ssz.go beaconblockbody_ssz.go beaconblock_ssz.go beaconstate_ssz.go consolidation_ssz.go consolidationrequest_ssz.go depositrequest_ssz.go withdrawalrequest_ssz.go executionrequests_ssz.go pendingconsolidation_ssz.go pendingdeposit_ssz.go pendingpartialwithdrawal_ssz.go signedaggregateandproof_ssz.go signedbeaconblock_ssz.go +//go:generate rm -f aggregateandproof_ssz.go attestation_ssz.go attesterslashing_ssz.go beaconblockbody_ssz.go beaconblock_ssz.go beaconstate_ssz.go consolidation_ssz.go consolidationrequest_ssz.go depositrequest_ssz.go withdrawalrequest_ssz.go executionrequests_ssz.go pendingconsolidation_ssz.go pendingdeposit_ssz.go pendingpartialwithdrawal_ssz.go signedaggregateandproof_ssz.go signedbeaconblock_ssz.go singleattestation_ssz.go +//go:generate sszgen --suffix=ssz --path . --include ../phase0,../altair,../bellatrix,../capella,../deneb --objs AggregateAndProof,Attestation,AttesterSlashing,BeaconBlockBody,BeaconBlock,BeaconState,Consolidation,ConsolidationRequest,DepositRequest,WithdrawalRequest,ExecutionRequests,PendingConsolidation,PendingDeposit,PendingPartialWithdrawal,SignedAggregateAndProof,SignedBeaconBlock,SingleAttestation +//go:generate goimports -w aggregateandproof_ssz.go attestation_ssz.go attesterslashing_ssz.go beaconblockbody_ssz.go beaconblock_ssz.go beaconstate_ssz.go consolidation_ssz.go consolidationrequest_ssz.go depositrequest_ssz.go withdrawalrequest_ssz.go executionrequests_ssz.go pendingconsolidation_ssz.go pendingdeposit_ssz.go pendingpartialwithdrawal_ssz.go signedaggregateandproof_ssz.go signedbeaconblock_ssz.go singleattestation_ssz.go diff --git a/spec/electra/singleattestation.go b/spec/electra/singleattestation.go new file mode 100644 index 00000000..3cdbe624 --- /dev/null +++ b/spec/electra/singleattestation.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" +) + +// SingleAttestation is a new struct in electra for propagating network only Attestations. See: +// https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md#singleattestation. +type SingleAttestation struct { + CommitteeIndex phase0.CommitteeIndex + AttesterIndex phase0.ValidatorIndex + Data *phase0.AttestationData + Signature phase0.BLSSignature `ssz-size:"96"` +} + +// String returns a string version of the structure. +func (a *SingleAttestation) String() string { + data, err := yaml.Marshal(a) + if err != nil { + return fmt.Sprintf("ERR: %v", err) + } + + return string(data) +} diff --git a/spec/electra/singleattestation_json.go b/spec/electra/singleattestation_json.go new file mode 100644 index 00000000..0c24bf04 --- /dev/null +++ b/spec/electra/singleattestation_json.go @@ -0,0 +1,80 @@ +// 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" +) + +// singleAttestationJSON is the spec representation of the struct. +type singleAttestationJSON struct { + CommitteeIndex string `json:"committee_index"` + AttesterIndex string `json:"attester_index"` + Data *phase0.AttestationData `json:"data"` + Signature string `json:"signature"` +} + +// MarshalJSON implements json.Marshaler. +func (a *SingleAttestation) MarshalJSON() ([]byte, error) { + return json.Marshal(&singleAttestationJSON{ + CommitteeIndex: fmt.Sprintf("%d", a.CommitteeIndex), + AttesterIndex: fmt.Sprintf("%d", a.AttesterIndex), + Data: a.Data, + Signature: fmt.Sprintf("%#x", a.Signature), + }) +} + +// UnmarshalJSON implements json.Unmarshaler. +func (a *SingleAttestation) UnmarshalJSON(input []byte) error { + var singleAttestationJSON singleAttestationJSON + err := json.Unmarshal(input, &singleAttestationJSON) + if err != nil { + return errors.Wrap(err, "invalid JSON") + } + + return a.unpack(&singleAttestationJSON) +} + +func (a *SingleAttestation) unpack(singleAttestationJSON *singleAttestationJSON) error { + var err error + if singleAttestationJSON.CommitteeIndex == "" { + return errors.New("committee index missing") + } + if singleAttestationJSON.AttesterIndex == "" { + return errors.New("attester index missing") + } + a.Data = singleAttestationJSON.Data + if a.Data == nil { + return errors.New("data missing") + } + if singleAttestationJSON.Signature == "" { + return errors.New("signature missing") + } + signature, err := hex.DecodeString(strings.TrimPrefix(singleAttestationJSON.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 +} diff --git a/spec/electra/singleattestation_ssz.go b/spec/electra/singleattestation_ssz.go new file mode 100644 index 00000000..97af586d --- /dev/null +++ b/spec/electra/singleattestation_ssz.go @@ -0,0 +1,107 @@ +// Code generated by fastssz. DO NOT EDIT. +// Hash: fb166eff3bbca34883327c3b21fe6565e8ee24248c1a3e60a67f63f7c380a6a6 +// Version: 0.1.3 +package electra + +import ( + "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" +) + +// MarshalSSZ ssz marshals the SingleAttestation object +func (s *SingleAttestation) MarshalSSZ() ([]byte, error) { + return ssz.MarshalSSZ(s) +} + +// MarshalSSZTo ssz marshals the SingleAttestation object to a target array +func (s *SingleAttestation) MarshalSSZTo(buf []byte) (dst []byte, err error) { + dst = buf + + // Field (0) 'CommitteeIndex' + dst = ssz.MarshalUint64(dst, uint64(s.CommitteeIndex)) + + // Field (1) 'AttesterIndex' + dst = ssz.MarshalUint64(dst, uint64(s.AttesterIndex)) + + // Field (2) 'Data' + if s.Data == nil { + s.Data = new(phase0.AttestationData) + } + if dst, err = s.Data.MarshalSSZTo(dst); err != nil { + return + } + + // Field (3) 'Signature' + dst = append(dst, s.Signature[:]...) + + return +} + +// UnmarshalSSZ ssz unmarshals the SingleAttestation object +func (s *SingleAttestation) UnmarshalSSZ(buf []byte) error { + var err error + size := uint64(len(buf)) + if size != 240 { + return ssz.ErrSize + } + + // Field (0) 'CommitteeIndex' + s.CommitteeIndex = phase0.CommitteeIndex(ssz.UnmarshallUint64(buf[0:8])) + + // Field (1) 'AttesterIndex' + s.AttesterIndex = phase0.ValidatorIndex(ssz.UnmarshallUint64(buf[8:16])) + + // Field (2) 'Data' + if s.Data == nil { + s.Data = new(phase0.AttestationData) + } + if err = s.Data.UnmarshalSSZ(buf[16:144]); err != nil { + return err + } + + // Field (3) 'Signature' + copy(s.Signature[:], buf[144:240]) + + return err +} + +// SizeSSZ returns the ssz encoded size in bytes for the SingleAttestation object +func (s *SingleAttestation) SizeSSZ() (size int) { + size = 240 + return +} + +// HashTreeRoot ssz hashes the SingleAttestation object +func (s *SingleAttestation) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(s) +} + +// HashTreeRootWith ssz hashes the SingleAttestation object with a hasher +func (s *SingleAttestation) HashTreeRootWith(hh ssz.HashWalker) (err error) { + indx := hh.Index() + + // Field (0) 'CommitteeIndex' + hh.PutUint64(uint64(s.CommitteeIndex)) + + // Field (1) 'AttesterIndex' + hh.PutUint64(uint64(s.AttesterIndex)) + + // Field (2) 'Data' + if s.Data == nil { + s.Data = new(phase0.AttestationData) + } + if err = s.Data.HashTreeRootWith(hh); err != nil { + return + } + + // Field (3) 'Signature' + hh.PutBytes(s.Signature[:]) + + hh.Merkleize(indx) + return +} + +// GetTree ssz hashes the SingleAttestation object +func (s *SingleAttestation) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} diff --git a/spec/electra/singleattestation_yaml.go b/spec/electra/singleattestation_yaml.go new file mode 100644 index 00000000..d3f627b1 --- /dev/null +++ b/spec/electra/singleattestation_yaml.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 ( + "bytes" + "fmt" + + "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/goccy/go-yaml" +) + +// singleAttestationYAML is the spec representation of the struct. +type singleAttestationYAML struct { + CommitteeIndex string `yaml:"committee_index"` + AttesterIndex string `yaml:"attester_index"` + Data *phase0.AttestationData `yaml:"data"` + Signature string `yaml:"signature"` +} + +// MarshalYAML implements yaml.Marshaller. +func (a *SingleAttestation) MarshalYAML() ([]byte, error) { + yamlBytes, err := yaml.MarshalWithOptions(&singleAttestationYAML{ + CommitteeIndex: fmt.Sprintf("%d", a.CommitteeIndex), + AttesterIndex: fmt.Sprintf("%d", a.AttesterIndex), + Data: a.Data, + 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 *SingleAttestation) UnmarshalYAML(input []byte) error { + // We unmarshal to the JSON struct to save on duplicate code. + var singleAttestationJSON singleAttestationJSON + if err := yaml.Unmarshal(input, &singleAttestationJSON); err != nil { + return err + } + + return a.unpack(&singleAttestationJSON) +} diff --git a/spec/phase0/attestationdata.go b/spec/phase0/attestationdata.go index 9b45a4c0..ac42b76d 100644 --- a/spec/phase0/attestationdata.go +++ b/spec/phase0/attestationdata.go @@ -140,7 +140,7 @@ func (a *AttestationData) UnmarshalYAML(input []byte) error { return a.unpack(&attestationDataJSON) } -// String provids a string representation of the struct. +// String provides a string representation of the struct. func (a *AttestationData) String() string { data, err := yaml.Marshal(a) if err != nil { From 43ddb3ec09f5f444708f54cae925517ed8b4db93 Mon Sep 17 00:00:00 2001 From: Chris Berry Date: Mon, 4 Nov 2024 17:07:26 +0000 Subject: [PATCH 2/3] Fix SingleAttestation assignment and add tests --- spec/electra/singleattestation_json.go | 11 +++ spec/electra/singleattestation_test.go | 125 +++++++++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 spec/electra/singleattestation_test.go diff --git a/spec/electra/singleattestation_json.go b/spec/electra/singleattestation_json.go index 0c24bf04..abd7152b 100644 --- a/spec/electra/singleattestation_json.go +++ b/spec/electra/singleattestation_json.go @@ -17,6 +17,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "strconv" "strings" "github.com/attestantio/go-eth2-client/spec/phase0" @@ -57,9 +58,19 @@ func (a *SingleAttestation) unpack(singleAttestationJSON *singleAttestationJSON) if singleAttestationJSON.CommitteeIndex == "" { return errors.New("committee index missing") } + committeeIndex, err := strconv.ParseUint(singleAttestationJSON.CommitteeIndex, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for committee index") + } + a.CommitteeIndex = phase0.CommitteeIndex(committeeIndex) if singleAttestationJSON.AttesterIndex == "" { return errors.New("attester index missing") } + attesterIndex, err := strconv.ParseUint(singleAttestationJSON.AttesterIndex, 10, 64) + if err != nil { + return errors.Wrap(err, "invalid value for attester index") + } + a.AttesterIndex = phase0.ValidatorIndex(attesterIndex) a.Data = singleAttestationJSON.Data if a.Data == nil { return errors.New("data missing") diff --git a/spec/electra/singleattestation_test.go b/spec/electra/singleattestation_test.go new file mode 100644 index 00000000..248209bc --- /dev/null +++ b/spec/electra/singleattestation_test.go @@ -0,0 +1,125 @@ +// Copyright © 2020, 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_test + +import ( + "encoding/json" + "github.com/attestantio/go-eth2-client/spec/electra" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestSingleAttestationJSON(t *testing.T) { + tests := []struct { + name string + input []byte + err string + }{ + { + name: "Empty", + err: "unexpected end of JSON input", + }, + { + name: "JSONBad", + input: []byte("[]"), + err: "invalid JSON: json: cannot unmarshal array into Go value of type electra.singleAttestationJSON", + }, + { + name: "CommitteeIndexMissing", + input: []byte(`{"attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "committee index missing", + }, + { + name: "CommitteeIndexWrongType", + input: []byte(`{"committee_index":true,"attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "invalid JSON: json: cannot unmarshal bool into Go struct field singleAttestationJSON.committee_index of type string", + }, + { + name: "CommitteeIndexInvalid", + input: []byte(`{"committee_index":"-1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: `invalid value for committee index: strconv.ParseUint: parsing "-1": invalid syntax`, + }, + { + name: "AttesterIndexMissing", + input: []byte(`{"committee_index":"1","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "attester index missing", + }, + { + name: "AttesterIndexWrongType", + input: []byte(`{"committee_index":"1","attester_index":200,"data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "invalid JSON: json: cannot unmarshal number into Go struct field singleAttestationJSON.attester_index of type string", + }, + { + name: "AttesterIndexInvalid", + input: []byte(`{"committee_index":"1","attester_index":"-1","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: `invalid value for attester index: strconv.ParseUint: parsing "-1": invalid syntax`, + }, + { + name: "DataMissing", + input: []byte(`{"committee_index":"1","attester_index":"200","signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "data missing", + }, + { + name: "DataInvalid", + input: []byte(`{"committee_index":"1","attester_index":"200","data":true,"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "invalid JSON: invalid JSON: json: cannot unmarshal bool into Go value of type phase0.attestationDataJSON", + }, + { + name: "SignatureMissing", + input: []byte(`{"committee_index":"1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}}}`), + err: "signature missing", + }, + { + name: "SignatureWrongType", + input: []byte(`{"committee_index":"1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":true}`), + err: "invalid JSON: json: cannot unmarshal bool into Go struct field singleAttestationJSON.signature of type string", + }, + { + name: "SignatureInvalid", + input: []byte(`{"committee_index":"1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"invalid"}`), + err: "invalid value for signature: encoding/hex: invalid byte: U+0069 'i'", + }, + { + name: "SignatureShort", + input: []byte(`{"committee_index":"1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x6162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "incorrect length for signature", + }, + { + name: "SignatureLong", + input: []byte(`{"committee_index":"1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x60606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + err: "incorrect length for signature", + }, + { + name: "Good", + input: []byte(`{"committee_index":"1","attester_index":"200","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var res electra.SingleAttestation + err := json.Unmarshal(test.input, &res) + if test.err != "" { + require.EqualError(t, err, test.err) + } else { + require.NoError(t, err) + rt, err := json.Marshal(&res) + require.NoError(t, err) + assert.Equal(t, string(test.input), string(rt)) + } + }) + } +} From ec86cf0192463e961c24849767fea4688cc2d533 Mon Sep 17 00:00:00 2001 From: Chris Berry Date: Mon, 4 Nov 2024 17:14:20 +0000 Subject: [PATCH 3/3] Add yaml test --- spec/electra/singleattestation_test.go | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/spec/electra/singleattestation_test.go b/spec/electra/singleattestation_test.go index 248209bc..ae68b0be 100644 --- a/spec/electra/singleattestation_test.go +++ b/spec/electra/singleattestation_test.go @@ -14,10 +14,12 @@ package electra_test import ( + "bytes" "encoding/json" "github.com/attestantio/go-eth2-client/spec/electra" "testing" + "github.com/goccy/go-yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -123,3 +125,34 @@ func TestSingleAttestationJSON(t *testing.T) { }) } } + +func TestSingleAttestationYAML(t *testing.T) { + tests := []struct { + name string + input []byte + root []byte + err string + }{ + { + name: "Good", + input: []byte(`{committee_index: '1', attester_index: '200', data: {slot: 100, index: 1, beacon_block_root: '0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f', source: {epoch: 1, root: '0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f'}, target: {epoch: 2, root: '0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f'}}, signature: '0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf'}`), + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var res electra.SingleAttestation + err := yaml.Unmarshal(test.input, &res) + if test.err != "" { + require.EqualError(t, err, test.err) + } else { + require.NoError(t, err) + rt, err := yaml.Marshal(&res) + require.NoError(t, err) + assert.Equal(t, string(rt), res.String()) + rt = bytes.TrimSuffix(rt, []byte("\n")) + assert.Equal(t, string(test.input), string(rt)) + } + }) + } +}