From 8d1395f534aeb22446c2c09b9d90536af5e60a95 Mon Sep 17 00:00:00 2001 From: olegshmuelov <45327364+olegshmuelov@users.noreply.github.com> Date: Tue, 28 Mar 2023 11:34:03 +0300 Subject: [PATCH] Capella block support (#92) * VersionedValidatorRegistration * Add real block and sig data tests for capella --- core/slashing_protection.go | 7 +- eth1_deposit/eth1_deposit.go | 26 +++- go.mod | 2 - go.sum | 7 - signer/sign_aggregate_and_proof.go | 3 +- signer/sign_attestation.go | 3 +- signer/sign_attestation_test.go | 3 +- signer/sign_beacon_block_test.go | 66 +++++++-- signer/sign_blinded_beacon_block_test.go | 47 +++++++ signer/sign_block.go | 10 +- signer/sign_epoch.go | 3 +- signer/sign_registration.go | 20 ++- signer/sign_registration_test.go | 14 +- signer/sign_slot.go | 4 +- signer/sign_sync_committee.go | 9 +- signer/slashing_protection.go | 20 --- signer/ssz.go | 53 ++++++++ signer/validator_signer.go | 17 ++- .../attestation_protection_test.go | 59 +++++++- slashing_protection/no_protection.go | 11 +- slashing_protection/normal_protection.go | 70 ++++++---- .../proposal_protection_test.go | 90 +++++++----- stores/inmemory/marshalable_test.go | 9 +- stores/inmemory/slashing_store.go | 36 ++++- stores/inmemory/slashing_test.go | 128 +++++++++--------- 25 files changed, 502 insertions(+), 215 deletions(-) delete mode 100644 signer/slashing_protection.go create mode 100644 signer/ssz.go diff --git a/core/slashing_protection.go b/core/slashing_protection.go index ef6c611..3e17245 100644 --- a/core/slashing_protection.go +++ b/core/slashing_protection.go @@ -10,13 +10,14 @@ type SlashingProtector interface { IsSlashableProposal(pubKey []byte, slot phase0.Slot) (*ProposalSlashStatus, error) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error UpdateHighestProposal(pubKey []byte, slot phase0.Slot) error - RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, error) + FetchHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) + FetchHighestProposal(pubKey []byte) (phase0.Slot, bool, error) } // SlashingStore represents the behavior of the slashing store type SlashingStore interface { SaveHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error - RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, error) + RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) SaveHighestProposal(pubKey []byte, slot phase0.Slot) error - RetrieveHighestProposal(pubKey []byte) (phase0.Slot, error) + RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) } diff --git a/eth1_deposit/eth1_deposit.go b/eth1_deposit/eth1_deposit.go index e4be10c..28e2d61 100644 --- a/eth1_deposit/eth1_deposit.go +++ b/eth1_deposit/eth1_deposit.go @@ -2,7 +2,6 @@ package eth1deposit import ( "github.com/attestantio/go-eth2-client/spec/phase0" - ssvtypes "github.com/bloxapp/ssv-spec/types" "github.com/pkg/errors" util "github.com/wealdtech/go-eth2-util" @@ -17,6 +16,12 @@ const ( BLSWithdrawalPrefixByte = byte(0) ) +// GenesisValidatorsRoot genesis validators root of the chain. +var ( + GenesisValidatorsRoot = phase0.Root{} + DomainDeposit = [4]byte{0x03, 0x00, 0x00, 0x00} +) + // IsSupportedDepositNetwork returns true if the given network is supported var IsSupportedDepositNetwork = func(network core.Network) bool { return network == core.PyrmontNetwork || network == core.PraterNetwork || network == core.MainNetwork @@ -40,7 +45,7 @@ func DepositData(validationKey *core.HDKey, withdrawalPubKey []byte, network cor } // Create domain - domain, err := ssvtypes.ComputeETHDomain(ssvtypes.DomainDeposit, network.ForkVersion(), ssvtypes.GenesisValidatorsRoot) + domain, err := ComputeETHDomain(DomainDeposit, network.ForkVersion(), GenesisValidatorsRoot) if err != nil { return nil, [32]byte{}, errors.Wrap(err, "failed to calculate domain") } @@ -83,3 +88,20 @@ func withdrawalCredentialsHash(withdrawalPubKey []byte) []byte { h := util.SHA256(withdrawalPubKey) return append([]byte{BLSWithdrawalPrefixByte}, h[1:]...)[:32] } + +// ComputeETHDomain returns computed domain +func ComputeETHDomain(domain phase0.DomainType, fork phase0.Version, genesisValidatorRoot phase0.Root) (phase0.Domain, error) { + ret := phase0.Domain{} + copy(ret[0:4], domain[:]) + + forkData := phase0.ForkData{ + CurrentVersion: fork, + GenesisValidatorsRoot: genesisValidatorRoot, + } + forkDataRoot, err := forkData.HashTreeRoot() + if err != nil { + return ret, err + } + copy(ret[4:32], forkDataRoot[0:28]) + return ret, nil +} diff --git a/go.mod b/go.mod index c256db5..43eaeb8 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( github.com/attestantio/go-eth2-client v0.15.2 - github.com/bloxapp/ssv-spec v0.2.8-0.20230116160450-3526f3880cb9 github.com/btcsuite/btcd/btcec/v2 v2.2.1 github.com/ferranbt/fastssz v0.1.2 github.com/google/uuid v1.3.0 @@ -23,7 +22,6 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect - github.com/ethereum/go-ethereum v1.10.23 // indirect github.com/fatih/color v1.13.0 // indirect github.com/goccy/go-yaml v1.9.5 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect diff --git a/go.sum b/go.sum index f8e52a0..70088fb 100644 --- a/go.sum +++ b/go.sum @@ -43,12 +43,8 @@ github.com/attestantio/go-eth2-client v0.15.2/go.mod h1:/Oh6YTuHmHhgLN/ZnQRKHGc7 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bloxapp/ssv-spec v0.2.8-0.20230116160450-3526f3880cb9 h1:2tPKHBFQO8bzESNZPBe5UfrLQiVGajou+4xQ6prTHng= -github.com/bloxapp/ssv-spec v0.2.8-0.20230116160450-3526f3880cb9/go.mod h1:w0GcW7zn+EGU9YTUPM0W9L0w8Q7N3kVjIlO2Ha/htqw= -github.com/btcsuite/btcd v0.22.1 h1:CnwP9LM/M9xuRrGSCGeMVs9iv09uMqwsVX7EeIpgV2c= github.com/btcsuite/btcd/btcec/v2 v2.2.1 h1:xP60mv8fvp+0khmrN0zTdPC3cNm24rfeE6lh2R/Yv3E= github.com/btcsuite/btcd/btcec/v2 v2.2.1/go.mod h1:9/CSmJxmuvqzX9Wh2fXMWToLOHhPd11lSPuIupwTkI8= -github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -62,15 +58,12 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.23 h1:Xk8XAT4/UuqcjMLIMF+7imjkg32kfVFKoeyQDaO2yWM= -github.com/ethereum/go-ethereum v1.10.23/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= diff --git a/signer/sign_aggregate_and_proof.go b/signer/sign_aggregate_and_proof.go index acada27..942907a 100644 --- a/signer/sign_aggregate_and_proof.go +++ b/signer/sign_aggregate_and_proof.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" "github.com/pkg/errors" ) @@ -23,7 +22,7 @@ func (signer *SimpleSigner) SignAggregateAndProof(agg *phase0.AggregateAndProof, return nil, nil, err } - root, err := types.ComputeETHSigningRoot(agg, domain) + root, err := ComputeETHSigningRoot(agg, domain) if err != nil { return nil, nil, err } diff --git a/signer/sign_attestation.go b/signer/sign_attestation.go index 089411b..7b6ce16 100644 --- a/signer/sign_attestation.go +++ b/signer/sign_attestation.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" "github.com/pkg/errors" ) @@ -47,7 +46,7 @@ func (signer *SimpleSigner) SignBeaconAttestation(attestation *phase0.Attestatio } // 6. Prepare and sign data - root, err := types.ComputeETHSigningRoot(attestation, domain) + root, err := ComputeETHSigningRoot(attestation, domain) if err != nil { return nil, nil, err } diff --git a/signer/sign_attestation_test.go b/signer/sign_attestation_test.go index e5db3c3..f381e22 100644 --- a/signer/sign_attestation_test.go +++ b/signer/sign_attestation_test.go @@ -353,7 +353,8 @@ func TestAttestationSignaturesNoSlashingData(t *testing.T) { _byteArray32("01000000f071c66c6561d0b939feb15f513a019d99a84bd85635221e3ad42dac"), _byteArray("95087182937f6982ae99f9b06bd116f463f414513032e33a3d175d9662eddf162101fcf6ca2a9fedaded74b8047c5dcf")) require.Nil(t, res) - require.EqualError(t, err, "highest attestation data is nil, can't determine if attestation is slashable") + require.Error(t, err) + require.EqualError(t, err, "highest attestation data is not found, can't determine if attestation is slashable") } func TestAttestationSignatures(t *testing.T) { diff --git a/signer/sign_beacon_block_test.go b/signer/sign_beacon_block_test.go index 6a08732..2ba3939 100644 --- a/signer/sign_beacon_block_test.go +++ b/signer/sign_beacon_block_test.go @@ -11,6 +11,7 @@ import ( "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/bellatrix" + "github.com/attestantio/go-eth2-client/spec/capella" "github.com/attestantio/go-eth2-client/spec/phase0" "github.com/stretchr/testify/require" @@ -27,6 +28,47 @@ func testBlock(t *testing.T) *phase0.BeaconBlock { return blk } +// tested against a block and sig generated from https://github.com/prysmaticlabs/prysm/blob/master/shared/testutil/block.go#L86 +func TestBenchmarkBlockProposal(t *testing.T) { + require.NoError(t, core.InitBLS()) + + // fixture + sk := "5470813f7deef638dc531188ca89e36976d536f680e89849cd9077fd096e20bc" + pk := "a3862121db5914d7272b0b705e6e3c5336b79e316735661873566245207329c30f9a33d4fb5f5857fc6fd0a368186972" + domain := "0000000081509579e35e84020ad8751eca180b44df470332d3ad17fc6fd52459" + blockByts := []byte(`{"slot":"1","proposer_index":"2","parent_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","state_root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","body":{"randao_reveal":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","eth1_data":{"deposit_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","deposit_count":"10","block_hash":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"graffiti":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","proposer_slashings":[{"signed_header_1":{"message":{"slot":"1","proposer_index":"2","parent_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","state_root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","body_root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"},"signed_header_2":{"message":{"slot":"1","proposer_index":"2","parent_root":"0x010102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","state_root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","body_root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["1","2","3"],"data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"},"attestation_2":{"attesting_indices":["1","2","3"],"data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}}],"attestations":[{"aggregation_bits":"0x010203","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}],"deposits":[{"proof":["0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"],"data":{"pubkey":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f","withdrawal_credentials":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","amount":"32000000000","signature":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"}}],"voluntary_exits":[{"message":{"epoch":"1","validator_index":"2"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}]}}`) + sigByts := "aab8d1dc7e9fdea39b9a46cde64759138372a8d1226d56d01fa9e2df9e45e39d67bacf5794bb6841f5ea143aa124a33211089d4cc045e0605380bf4ae03291b1ee8082e55274ce5dc513cc0bebdb8e8a5276ad39a0cea3edd321d9cf854f695c" + + // setup KeyVault + store := inmemStorage() + options := ð2keymanager.KeyVaultOptions{} + options.SetStorage(store) + options.SetWalletType(core.NDWallet) + vault, err := eth2keymanager.NewKeyVault(options) + require.NoError(t, err) + wallet, err := vault.Wallet() + require.NoError(t, err) + k, err := core.NewHDKeyFromPrivateKey(_byteArray(sk), "") + require.NoError(t, err) + acc := wallets.NewValidatorAccount("1", k, nil, "", vault.Context) + require.NoError(t, wallet.AddValidatorAccount(acc)) + + // setup signer + signer := NewSimpleSigner(wallet, &prot.NoProtection{}, core.PraterNetwork) + + // decode block + blk := &phase0.BeaconBlock{} + require.NoError(t, json.Unmarshal(blockByts, blk)) + + versionedBeaconBlock := &spec.VersionedBeaconBlock{ + Version: spec.DataVersionPhase0, + Phase0: blk, + } + sig, _, err := signer.SignBeaconBlock(versionedBeaconBlock, _byteArray32(domain), _byteArray(pk)) + require.NoError(t, err) + require.EqualValues(t, _byteArray(sigByts), sig) +} + // tested against a block and sig generated from https://github.com/prysmaticlabs/prysm/blob/hf1/shared/testutil/altair.go#L313-L438 func TestBenchmarkBlockProposalAltair(t *testing.T) { require.NoError(t, core.InitBLS()) @@ -112,16 +154,16 @@ func TestBenchmarkBlockProposalBellatrix(t *testing.T) { require.EqualValues(t, _byteArray(sigByts), sig) } -// tested against a block and sig generated from https://github.com/prysmaticlabs/prysm/blob/master/shared/testutil/block.go#L86 -func TestBenchmarkBlockProposal(t *testing.T) { +// tested against a real block and sig from the Sepolia testnet (slot 1861337) +func TestBenchmarkBlockProposalCapella(t *testing.T) { require.NoError(t, core.InitBLS()) // fixture - sk := "5470813f7deef638dc531188ca89e36976d536f680e89849cd9077fd096e20bc" - pk := "a3862121db5914d7272b0b705e6e3c5336b79e316735661873566245207329c30f9a33d4fb5f5857fc6fd0a368186972" - domain := "0000000081509579e35e84020ad8751eca180b44df470332d3ad17fc6fd52459" - blockByts := []byte(`{"slot":"1","proposer_index":"2","parent_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","state_root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","body":{"randao_reveal":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","eth1_data":{"deposit_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","deposit_count":"10","block_hash":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"graffiti":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","proposer_slashings":[{"signed_header_1":{"message":{"slot":"1","proposer_index":"2","parent_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","state_root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","body_root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"},"signed_header_2":{"message":{"slot":"1","proposer_index":"2","parent_root":"0x010102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","state_root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","body_root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}}],"attester_slashings":[{"attestation_1":{"attesting_indices":["1","2","3"],"data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"},"attestation_2":{"attesting_indices":["1","2","3"],"data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}}],"attestations":[{"aggregation_bits":"0x010203","data":{"slot":"100","index":"1","beacon_block_root":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","source":{"epoch":"1","root":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"},"target":{"epoch":"2","root":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}},"signature":"0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebf"}],"deposits":[{"proof":["0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f","0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f","0x606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f"],"data":{"pubkey":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f","withdrawal_credentials":"0x202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f","amount":"32000000000","signature":"0x404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f"}}],"voluntary_exits":[{"message":{"epoch":"1","validator_index":"2"},"signature":"0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"}]}}`) - sigByts := "aab8d1dc7e9fdea39b9a46cde64759138372a8d1226d56d01fa9e2df9e45e39d67bacf5794bb6841f5ea143aa124a33211089d4cc045e0605380bf4ae03291b1ee8082e55274ce5dc513cc0bebdb8e8a5276ad39a0cea3edd321d9cf854f695c" + sk := "17f8b2dbb824318970a9c82b4f5456d598342dac248f89c252dd30bb9e85e43c" + pk := "81d6fc2f01633e8eab3ba4d72588e14f45b00e68ab887bdd4ec5e8558965db21189310df973837106216777b07fc0805" + domain := "0000000047eb72b3be36f08feffcaba760f0a2ed78c1a85f0654941a0d19d0fa" + blkJSON := []byte(`{"slot":"1861337","proposer_index":"1610","parent_root":"0x353f35e506b07b6a20dd5d24c92b6dfda1b9e221848963d7a40cb7fe4e23c35a","state_root":"0x8e9cfedb909c4c9bbc52ca4174b279d31ffe40a667077c7b31a66bbd1a66d0be","body":{"randao_reveal":"0xb019a2ef4b7763c7bd5a9b3dbe00ba38cdfb6ee0fcecfe4a7301687973717f7c193072a3ebd70de3aed153f960b11fc40037b8164542e1364a31489ff7a0a1119ef46bc2ea099886446cba3acc4721a2534c292f6eeb650719f32477707aab3e","eth1_data":{"deposit_root":"0x9df92d765b5aa041fd4bbe8d5878eb89290efa78e444c1a603eecfae2ea05fa4","deposit_count":"403","block_hash":"0x1ee29c95ea816db342e04fbd1f00cb14940a45d025d2d14bf0d33187d8f4d5ea"},"graffiti":"0x626c6f787374616b696e672e636f6d0000000000000000000000000000000000","proposer_slashings":[],"attester_slashings":[],"attestations":[{"aggregation_bits":"0xffffffffffffff37","data":{"slot":"1861336","index":"0","beacon_block_root":"0x353f35e506b07b6a20dd5d24c92b6dfda1b9e221848963d7a40cb7fe4e23c35a","source":{"epoch":"58165","root":"0x5c0a59daecb2f509e0d5a038da44c55ef5827b0b979e6e397cc7f5846f9f3f4c"},"target":{"epoch":"58166","root":"0x835469d3b4348c2f5c2964afaac731acc0117eb0f5cec64bf166ab26847f77a8"}},"signature":"0x991b97e124a8dc672d135c167f030ceff9ab500dfd9a8bb79e199a303bfa54cebc205e396a16466e51d54cb7e0d981eb0eb03295bdb39b69d9559421aeea22838d22732d66ea79d3bebb8a8a335679491b60ad6d815318cbfd92a1a867c70255"}],"deposits":[],"voluntary_exits":[],"sync_aggregate":{"sync_committee_bits":"0xfffffbfffffffffffffffcffffdfffffffffffffffefffffffffffff7ffdffffbfffffffffffefffffebfffffffffffffffffffdff7fffffffffffffffffffff","sync_committee_signature":"0x91713624034f3c1e37baa3de1d082883c6e44cc830f4ecf0ca58cc06153aba97ca4539edfdb0f49a0d18b354f585422e114feb07b689db0a47ee72231d8e6fb2970663f518ca0a3fda0e701cd4662405c4f2fa9fdac0533c790f92404240432e"},"execution_payload":{"parent_hash":"0x4b37c3a4f001b91cfcac61b7f6bdfd2218525e4f421a4ab52fa513b00e76ab6f","fee_recipient":"0x0f35b0753e261375c9a6cb44316b4bdc7e765509","state_root":"0xc9d0b31cc38141d01998220dcc0e7830994e6a1d11fa3a26d7d113df82c1a53a","receipts_root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0x251fdfd6ebef77085f24016900debbd334e67cf51ee48d959f01ef12698a49a5","block_number":"3031542","gas_limit":"30000000","gas_used":"0","timestamp":"1678069644","extra_data":"0xd883010b02846765746888676f312e32302e31856c696e7578","base_fee_per_gas":"7","block_hash":"0x4cb1ca5a2947972cb018416fe4489b9daf674d5be97fd2ebb6de1a1608226733","transactions":[],"withdrawals":[{"index":"645759","validator_index":"425","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"2886"},{"index":"645760","validator_index":"428","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"2886"},{"index":"645761","validator_index":"429","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"2886"},{"index":"645762","validator_index":"431","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"2886"},{"index":"645763","validator_index":"434","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"2886"},{"index":"645764","validator_index":"437","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"2886"},{"index":"645765","validator_index":"440","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645766","validator_index":"441","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645767","validator_index":"448","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645768","validator_index":"450","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645769","validator_index":"451","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645770","validator_index":"456","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645771","validator_index":"458","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645772","validator_index":"465","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645773","validator_index":"467","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"},{"index":"645774","validator_index":"468","address":"0x25c4a76e7d118705e7ea2e9b7d8c59930d8acd3b","amount":"1924"}]},"bls_to_execution_changes":[]}}`) + sigByts := "9321eb1d373e75bb0b746a8c4b6f62285cccb17f2105a1f4f968534902131a29fffd5b54a0012c34c8600639609c46ab05a23fe8f89cdd252df680883ccb94073d9b852680bba6c5f5166edcd6024c9ef489c555d1cde994fdd7010fb394e45d" // setup KeyVault store := inmemStorage() @@ -140,13 +182,13 @@ func TestBenchmarkBlockProposal(t *testing.T) { // setup signer signer := NewSimpleSigner(wallet, &prot.NoProtection{}, core.PraterNetwork) - // decode block - blk := &phase0.BeaconBlock{} - require.NoError(t, json.Unmarshal(blockByts, blk)) + // decode json block + blk := &capella.BeaconBlock{} + require.NoError(t, json.Unmarshal(blkJSON, blk)) versionedBeaconBlock := &spec.VersionedBeaconBlock{ - Version: spec.DataVersionPhase0, - Phase0: blk, + Version: spec.DataVersionCapella, + Capella: blk, } sig, _, err := signer.SignBeaconBlock(versionedBeaconBlock, _byteArray32(domain), _byteArray(pk)) require.NoError(t, err) diff --git a/signer/sign_blinded_beacon_block_test.go b/signer/sign_blinded_beacon_block_test.go index 33b60fa..593bf21 100644 --- a/signer/sign_blinded_beacon_block_test.go +++ b/signer/sign_blinded_beacon_block_test.go @@ -2,10 +2,14 @@ package signer import ( "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" "testing" "github.com/attestantio/go-eth2-client/api" apiv1bellatrix "github.com/attestantio/go-eth2-client/api/v1/bellatrix" + apiv1capella "github.com/attestantio/go-eth2-client/api/v1/capella" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/bellatrix" "github.com/stretchr/testify/require" @@ -60,6 +64,49 @@ func TestBenchmarkBlindedBlockProposalBellatrix(t *testing.T) { require.EqualValues(t, _byteArray(sigByts), sig) } +// tested against a real block and sig from the Sepolia testnet (slot 1861337) +func TestBenchmarkBlindedBlockProposalCapella(t *testing.T) { + require.NoError(t, core.InitBLS()) + + // fixture + sk := "17f8b2dbb824318970a9c82b4f5456d598342dac248f89c252dd30bb9e85e43c" + pk := "81d6fc2f01633e8eab3ba4d72588e14f45b00e68ab887bdd4ec5e8558965db21189310df973837106216777b07fc0805" + domain := "0000000047eb72b3be36f08feffcaba760f0a2ed78c1a85f0654941a0d19d0fa" + blkJSON := []byte(`{"slot":"1861337","proposer_index":"1610","parent_root":"0x353f35e506b07b6a20dd5d24c92b6dfda1b9e221848963d7a40cb7fe4e23c35a","state_root":"0x8e9cfedb909c4c9bbc52ca4174b279d31ffe40a667077c7b31a66bbd1a66d0be","body":{"randao_reveal":"0xb019a2ef4b7763c7bd5a9b3dbe00ba38cdfb6ee0fcecfe4a7301687973717f7c193072a3ebd70de3aed153f960b11fc40037b8164542e1364a31489ff7a0a1119ef46bc2ea099886446cba3acc4721a2534c292f6eeb650719f32477707aab3e","eth1_data":{"deposit_root":"0x9df92d765b5aa041fd4bbe8d5878eb89290efa78e444c1a603eecfae2ea05fa4","deposit_count":"403","block_hash":"0x1ee29c95ea816db342e04fbd1f00cb14940a45d025d2d14bf0d33187d8f4d5ea"},"graffiti":"0x626c6f787374616b696e672e636f6d0000000000000000000000000000000000","proposer_slashings":[],"attester_slashings":[],"attestations":[{"aggregation_bits":"0xffffffffffffff37","data":{"slot":"1861336","index":"0","beacon_block_root":"0x353f35e506b07b6a20dd5d24c92b6dfda1b9e221848963d7a40cb7fe4e23c35a","source":{"epoch":"58165","root":"0x5c0a59daecb2f509e0d5a038da44c55ef5827b0b979e6e397cc7f5846f9f3f4c"},"target":{"epoch":"58166","root":"0x835469d3b4348c2f5c2964afaac731acc0117eb0f5cec64bf166ab26847f77a8"}},"signature":"0x991b97e124a8dc672d135c167f030ceff9ab500dfd9a8bb79e199a303bfa54cebc205e396a16466e51d54cb7e0d981eb0eb03295bdb39b69d9559421aeea22838d22732d66ea79d3bebb8a8a335679491b60ad6d815318cbfd92a1a867c70255"}],"deposits":[],"voluntary_exits":[],"sync_aggregate":{"sync_committee_bits":"0xfffffbfffffffffffffffcffffdfffffffffffffffefffffffffffff7ffdffffbfffffffffffefffffebfffffffffffffffffffdff7fffffffffffffffffffff","sync_committee_signature":"0x91713624034f3c1e37baa3de1d082883c6e44cc830f4ecf0ca58cc06153aba97ca4539edfdb0f49a0d18b354f585422e114feb07b689db0a47ee72231d8e6fb2970663f518ca0a3fda0e701cd4662405c4f2fa9fdac0533c790f92404240432e"},"execution_payload_header":{"parent_hash":"0x4b37c3a4f001b91cfcac61b7f6bdfd2218525e4f421a4ab52fa513b00e76ab6f","fee_recipient":"0x0f35b0753e261375c9a6cb44316b4bdc7e765509","state_root":"0xc9d0b31cc38141d01998220dcc0e7830994e6a1d11fa3a26d7d113df82c1a53a","receipts_root":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logs_bloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prev_randao":"0x251fdfd6ebef77085f24016900debbd334e67cf51ee48d959f01ef12698a49a5","block_number":"3031542","gas_limit":"30000000","gas_used":"0","timestamp":"1678069644","extra_data":"0xd883010b02846765746888676f312e32302e31856c696e7578","base_fee_per_gas":"7","block_hash":"0x4cb1ca5a2947972cb018416fe4489b9daf674d5be97fd2ebb6de1a1608226733","transactions_root":"0x7ffe241ea60187fdb0187bfa22de35d1f9bed7ab061d9401fd47e34a54fbede1","withdrawals_root":"0xb8edfc6f0dc2564cff3102471bef1e02db94346c692eb81decf0dec63c1c69f5"},"bls_to_execution_changes":[]}}`) + sigByts := "9321eb1d373e75bb0b746a8c4b6f62285cccb17f2105a1f4f968534902131a29fffd5b54a0012c34c8600639609c46ab05a23fe8f89cdd252df680883ccb94073d9b852680bba6c5f5166edcd6024c9ef489c555d1cde994fdd7010fb394e45d" + + // setup KeyVault + store := inmemStorage() + options := ð2keymanager.KeyVaultOptions{} + options.SetStorage(store) + options.SetWalletType(core.NDWallet) + vault, err := eth2keymanager.NewKeyVault(options) + require.NoError(t, err) + wallet, err := vault.Wallet() + require.NoError(t, err) + k, err := core.NewHDKeyFromPrivateKey(_byteArray(sk), "") + require.NoError(t, err) + acc := wallets.NewValidatorAccount("1", k, nil, "", vault.Context) + require.NoError(t, wallet.AddValidatorAccount(acc)) + + // setup signer + signer := NewSimpleSigner(wallet, &prot.NoProtection{}, core.PraterNetwork) + + // decode block + blk := &apiv1capella.BlindedBeaconBlock{} + require.NoError(t, json.Unmarshal(blkJSON, blk)) + + versionedBlindedBeaconBlock := &api.VersionedBlindedBeaconBlock{ + Version: spec.DataVersionCapella, + Capella: blk, + } + + sig, _, err := signer.SignBlindedBeaconBlock(versionedBlindedBeaconBlock, _byteArray32(domain), _byteArray(pk)) + fmt.Println(hex.EncodeToString(sig)) + require.NoError(t, err) + require.EqualValues(t, _byteArray(sigByts), sig) +} + // Test slashing by signing first beacon block and then blinded beacon block func TestDoubleProposalsSigning_Regular_Blinded(t *testing.T) { require.NoError(t, core.InitBLS()) diff --git a/signer/sign_block.go b/signer/sign_block.go index 5c68399..1817d18 100644 --- a/signer/sign_block.go +++ b/signer/sign_block.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" ssz "github.com/ferranbt/fastssz" "github.com/pkg/errors" @@ -33,7 +32,7 @@ func (signer *SimpleSigner) SignBlock(block ssz.HashRoot, slot phase0.Slot, doma } // 4. check we can even sign this - status, err := signer.verifySlashableAndUpdate(pubKey, slot) + status, err := signer.slashingProtector.IsSlashableProposal(pubKey, slot) if err != nil { return nil, nil, err } @@ -41,7 +40,12 @@ func (signer *SimpleSigner) SignBlock(block ssz.HashRoot, slot phase0.Slot, doma return nil, nil, errors.Errorf("slashable proposal (%s), not signing", status.Status) } - root, err := types.ComputeETHSigningRoot(block, domain) + // 5. add to protection storage + if err = signer.slashingProtector.UpdateHighestProposal(pubKey, slot); err != nil { + return nil, nil, err + } + + root, err := ComputeETHSigningRoot(block, domain) if err != nil { return nil, nil, errors.Wrap(err, "could not get signing root") } diff --git a/signer/sign_epoch.go b/signer/sign_epoch.go index d6caf23..5537d52 100644 --- a/signer/sign_epoch.go +++ b/signer/sign_epoch.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" "github.com/pkg/errors" ) @@ -23,7 +22,7 @@ func (signer *SimpleSigner) SignEpoch(epoch phase0.Epoch, domain phase0.Domain, return nil, nil, err } - root, err := types.ComputeETHSigningRoot(types.SSZUint64(epoch), domain) + root, err := ComputeETHSigningRoot(SSZUint64(epoch), domain) if err != nil { return nil, nil, err } diff --git a/signer/sign_registration.go b/signer/sign_registration.go index 503ec5d..34e02ac 100644 --- a/signer/sign_registration.go +++ b/signer/sign_registration.go @@ -3,14 +3,15 @@ package signer import ( "encoding/hex" - apiv1 "github.com/attestantio/go-eth2-client/api/v1" + "github.com/attestantio/go-eth2-client/api" + "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" + ssz "github.com/ferranbt/fastssz" "github.com/pkg/errors" ) // SignRegistration signs the given ValidatorRegistration. -func (signer *SimpleSigner) SignRegistration(registration *apiv1.ValidatorRegistration, domain phase0.Domain, pubKey []byte) ([]byte, []byte, error) { +func (signer *SimpleSigner) SignRegistration(registration *api.VersionedValidatorRegistration, domain phase0.Domain, pubKey []byte) ([]byte, []byte, error) { // Validate the registration. if registration == nil { return nil, nil, errors.New("registration data is nil") @@ -25,8 +26,19 @@ func (signer *SimpleSigner) SignRegistration(registration *apiv1.ValidatorRegist return nil, nil, err } + var reg ssz.HashRoot + switch registration.Version { + case spec.BuilderVersionV1: + if registration.V1 == nil { + return nil, nil, errors.New("no validator registration") + } + reg = registration.V1 + default: + return nil, nil, errors.Errorf("unsupported registration version %d", registration.Version) + } + // Produce the signature. - root, err := types.ComputeETHSigningRoot(registration, domain) + root, err := ComputeETHSigningRoot(reg, domain) if err != nil { return nil, nil, err } diff --git a/signer/sign_registration_test.go b/signer/sign_registration_test.go index edd69b0..f943b20 100644 --- a/signer/sign_registration_test.go +++ b/signer/sign_registration_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/attestantio/go-eth2-client/api" apiv1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/pkg/errors" "github.com/stretchr/testify/require" @@ -15,16 +16,21 @@ func TestSimpleSigner_SignRegistration(t *testing.T) { signer, err := setupNoSlashingProtectionSK(_byteArray("659e875e1b062c03f2f2a57332974d475b97df6cfc581d322e79642d39aca8fd")) require.NoError(t, err) - registrationMock := &apiv1.ValidatorRegistration{ + valRegistrationMock := &apiv1.ValidatorRegistration{ GasLimit: 123456, Timestamp: time.UnixMilli(1658313712), } - copy(registrationMock.FeeRecipient[:], "9831EeF7A86C19E32bEcDad091c1DbC974cf452a") - copy(registrationMock.Pubkey[:], "a27c45f7afe6c63363acf886cdad282539fb2cf58b304f2caa95f2ea53048b65a5d41d926c3562e3f18b8b61871375af") + copy(valRegistrationMock.FeeRecipient[:], "9831EeF7A86C19E32bEcDad091c1DbC974cf452a") + copy(valRegistrationMock.Pubkey[:], "a27c45f7afe6c63363acf886cdad282539fb2cf58b304f2caa95f2ea53048b65a5d41d926c3562e3f18b8b61871375af") + + registrationMock := &api.VersionedValidatorRegistration{ + Version: 0, + V1: valRegistrationMock, + } tests := []struct { name string - data *apiv1.ValidatorRegistration + data *api.VersionedValidatorRegistration pubKey []byte domain [32]byte expectedError error diff --git a/signer/sign_slot.go b/signer/sign_slot.go index 5a70af5..7037ce5 100644 --- a/signer/sign_slot.go +++ b/signer/sign_slot.go @@ -4,8 +4,6 @@ import ( "encoding/hex" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" - "github.com/pkg/errors" ) @@ -24,7 +22,7 @@ func (signer *SimpleSigner) SignSlot(slot phase0.Slot, domain phase0.Domain, pub return nil, nil, err } - root, err := types.ComputeETHSigningRoot(types.SSZUint64(slot), domain) + root, err := ComputeETHSigningRoot(SSZUint64(slot), domain) if err != nil { return nil, nil, err } diff --git a/signer/sign_sync_committee.go b/signer/sign_sync_committee.go index 687fee3..7f2b049 100644 --- a/signer/sign_sync_committee.go +++ b/signer/sign_sync_committee.go @@ -5,7 +5,6 @@ import ( "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/phase0" - "github.com/bloxapp/ssv-spec/types" "github.com/pkg/errors" ) @@ -26,8 +25,8 @@ func (signer *SimpleSigner) SignSyncCommittee(msgBlockRoot []byte, domain phase0 defer signer.unlock(account.ID(), "sync_committee") // 3. sign - sszRoot := types.SSZBytes(msgBlockRoot) - root, err := types.ComputeETHSigningRoot(&sszRoot, domain) + sszRoot := SSZBytes(msgBlockRoot) + root, err := ComputeETHSigningRoot(&sszRoot, domain) if err != nil { return nil, nil, errors.Wrap(err, "could not get signing root") } @@ -59,7 +58,7 @@ func (signer *SimpleSigner) SignSyncCommitteeSelectionData(data *altair.SyncAggr if data == nil { return nil, nil, errors.New("selection data nil") } - root, err := types.ComputeETHSigningRoot(data, domain) + root, err := ComputeETHSigningRoot(data, domain) if err != nil { return nil, nil, errors.Wrap(err, "could not get signing root") } @@ -91,7 +90,7 @@ func (signer *SimpleSigner) SignSyncCommitteeContributionAndProof(contribAndProo if contribAndProof == nil { return nil, nil, errors.New("contrib proof data nil") } - root, err := types.ComputeETHSigningRoot(contribAndProof, domain) + root, err := ComputeETHSigningRoot(contribAndProof, domain) if err != nil { return nil, nil, errors.Wrap(err, "could not get signing root") } diff --git a/signer/slashing_protection.go b/signer/slashing_protection.go deleted file mode 100644 index 07fb86a..0000000 --- a/signer/slashing_protection.go +++ /dev/null @@ -1,20 +0,0 @@ -package signer - -import ( - "github.com/attestantio/go-eth2-client/spec/phase0" - - "github.com/bloxapp/eth2-key-manager/core" -) - -// verifySlashableAndUpdate verified if block is slashable, if not saves it as the highest -func (signer *SimpleSigner) verifySlashableAndUpdate(pubKey []byte, slot phase0.Slot) (*core.ProposalSlashStatus, error) { - status, err := signer.slashingProtector.IsSlashableProposal(pubKey, slot) - if err != nil { - return nil, err - } - - if err := signer.slashingProtector.UpdateHighestProposal(pubKey, slot); err != nil { - return nil, err - } - return status, nil -} diff --git a/signer/ssz.go b/signer/ssz.go new file mode 100644 index 0000000..deb6f96 --- /dev/null +++ b/signer/ssz.go @@ -0,0 +1,53 @@ +package signer + +import ( + "encoding/binary" + + ssz "github.com/ferranbt/fastssz" +) + +// SSZBytes -- +type SSZBytes []byte + +// HashTreeRoot -- +func (b SSZBytes) HashTreeRoot() ([32]byte, error) { + return ssz.HashWithDefaultHasher(b) +} + +// GetTree -- +func (b SSZBytes) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(b) +} + +// HashTreeRootWith -- +func (b SSZBytes) HashTreeRootWith(hh ssz.HashWalker) error { + indx := hh.Index() + hh.PutBytes(b) + hh.Merkleize(indx) + return nil +} + +// SSZUint64 -- +type SSZUint64 uint64 + +// GetTree -- +func (s SSZUint64) GetTree() (*ssz.Node, error) { + return ssz.ProofTree(s) +} + +// HashTreeRootWith -- +func (s SSZUint64) HashTreeRootWith(hh ssz.HashWalker) error { + indx := hh.Index() + hh.PutUint64(uint64(s)) + hh.Merkleize(indx) + return nil +} + +// HashTreeRoot -- +func (s SSZUint64) HashTreeRoot() ([32]byte, error) { + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, uint64(s)) + var root [32]byte + copy(root[:], buf) + return root, nil +} diff --git a/signer/validator_signer.go b/signer/validator_signer.go index f5f50c2..6fdedae 100644 --- a/signer/validator_signer.go +++ b/signer/validator_signer.go @@ -4,10 +4,10 @@ import ( "sync" "github.com/attestantio/go-eth2-client/api" - apiv1 "github.com/attestantio/go-eth2-client/api/v1" "github.com/attestantio/go-eth2-client/spec" "github.com/attestantio/go-eth2-client/spec/altair" "github.com/attestantio/go-eth2-client/spec/phase0" + ssz "github.com/ferranbt/fastssz" "github.com/google/uuid" "github.com/bloxapp/eth2-key-manager/core" @@ -24,7 +24,7 @@ type ValidatorSigner interface { SignSyncCommittee(msgBlockRoot []byte, domain phase0.Domain, pubKey []byte) (sig []byte, root []byte, err error) SignSyncCommitteeSelectionData(data *altair.SyncAggregatorSelectionData, domain phase0.Domain, pubKey []byte) (sig []byte, root []byte, err error) SignSyncCommitteeContributionAndProof(contribAndProof *altair.ContributionAndProof, domain phase0.Domain, pubKey []byte) (sig []byte, root []byte, err error) - SignRegistration(registration *apiv1.ValidatorRegistration, domain phase0.Domain, pubKey []byte) (sig []byte, root []byte, err error) + SignRegistration(registration *api.VersionedValidatorRegistration, domain phase0.Domain, pubKey []byte) (sig []byte, root []byte, err error) } // SimpleSigner implements ValidatorSigner interface @@ -70,3 +70,16 @@ func (signer *SimpleSigner) unlock(accountID uuid.UUID, operation string) { val.Unlock() } } + +// ComputeETHSigningRoot returns computed root for eth signing +func ComputeETHSigningRoot(obj ssz.HashRoot, domain phase0.Domain) (phase0.Root, error) { + root, err := obj.HashTreeRoot() + if err != nil { + return phase0.Root{}, err + } + signingContainer := phase0.SigningData{ + ObjectRoot: root, + Domain: domain, + } + return signingContainer.HashTreeRoot() +} diff --git a/slashing_protection/attestation_protection_test.go b/slashing_protection/attestation_protection_test.go index 55dc88e..88c2741 100644 --- a/slashing_protection/attestation_protection_test.go +++ b/slashing_protection/attestation_protection_test.go @@ -276,8 +276,10 @@ func TestDoubleAttestationVote(t *testing.T) { func TestMinimalSlashingProtection(t *testing.T) { protector, accounts := setupAttestation(t, true) - at, err := protector.RetrieveHighestAttestation(accounts[0].ValidatorPublicKey()) + at, found, err := protector.FetchHighestAttestation(accounts[0].ValidatorPublicKey()) require.NoError(t, err) + require.True(t, found) + require.NotNil(t, at) fmt.Printf("%d", at.Target.Epoch) // 5,10 t.Run("source lower than highest source", func(t *testing.T) { @@ -417,10 +419,63 @@ func TestUpdateLatestAttestation(t *testing.T) { require.NoError(tt, err) // Validate highest. - highest, err := protector.RetrieveHighestAttestation(k) + highest, found, err := protector.FetchHighestAttestation(k) require.NoError(tt, err) + require.True(tt, found) require.EqualValues(tt, highest.Source.Epoch, test.expectedHighestSource) require.EqualValues(tt, highest.Target.Epoch, test.expectedHighestTarget) }) } } + +func TestAttestationData(t *testing.T) { + protector, accounts := setupAttestation(t, true) + + t.Run("public key nil on fetch", func(t *testing.T) { + res, found, err := protector.FetchHighestAttestation(nil) + require.Error(t, err) + require.Nil(t, res) + require.False(t, found) + require.EqualError(t, err, "public key could not be nil") + }) + + t.Run("public key nil on update", func(t *testing.T) { + err := protector.UpdateHighestAttestation(nil, &phase0.AttestationData{}) + require.Error(t, err) + require.EqualError(t, err, "could not retrieve highest attestation: public key could not be nil") + }) + + t.Run("public key nil on slashing check", func(t *testing.T) { + res, err := protector.IsSlashableAttestation(nil, &phase0.AttestationData{ + Slot: 30, + Index: 4, + BeaconBlockRoot: _byteArray32("A"), + Source: &phase0.Checkpoint{ + Epoch: 2, + Root: _byteArray32("B"), + }, + Target: &phase0.Checkpoint{ + Epoch: 5, + Root: _byteArray32("C"), + }, + }) + + require.Error(t, err) + require.Nil(t, res) + require.EqualError(t, err, "could not retrieve highest attestation: public key could not be nil") + }) + + t.Run("attestation data nil on update", func(t *testing.T) { + err := protector.UpdateHighestAttestation(accounts[0].ValidatorPublicKey(), nil) + require.Error(t, err) + require.EqualError(t, err, "attestation data could not be nil") + }) + + t.Run("attestation data nil on slashable check", func(t *testing.T) { + res, err := protector.IsSlashableAttestation(accounts[0].ValidatorPublicKey(), nil) + + require.Error(t, err) + require.Nil(t, res) + require.EqualError(t, err, "attestation data could not be nil") + }) +} diff --git a/slashing_protection/no_protection.go b/slashing_protection/no_protection.go index 85d0b0e..0c54b2e 100644 --- a/slashing_protection/no_protection.go +++ b/slashing_protection/no_protection.go @@ -33,7 +33,12 @@ func (p *NoProtection) UpdateHighestAttestation(pubKey []byte, attestation *phas return nil } -// RetrieveHighestAttestation does nothing -func (p *NoProtection) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, error) { - return nil, nil +// FetchHighestAttestation does nothing +func (p *NoProtection) FetchHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + return nil, false, nil +} + +// FetchHighestProposal returns highest proposal data +func (p *NoProtection) FetchHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + return 0, false, nil } diff --git a/slashing_protection/normal_protection.go b/slashing_protection/normal_protection.go index d6dcbef..17b9750 100644 --- a/slashing_protection/normal_protection.go +++ b/slashing_protection/normal_protection.go @@ -19,10 +19,17 @@ func NewNormalProtection(store core.SlashingStore) *NormalProtection { // IsSlashableAttestation detects double, surround and surrounded slashable events func (protector *NormalProtection) IsSlashableAttestation(pubKey []byte, attestation *phase0.AttestationData) (*core.AttestationSlashStatus, error) { + if attestation == nil { + return nil, errors.New("attestation data could not be nil") + } + // lookupEndEpoch should be the latest written attestation, if not than req.Data.Target.Epoch - highest, err := protector.RetrieveHighestAttestation(pubKey) + highest, found, err := protector.store.RetrieveHighestAttestation(pubKey) if err != nil { - return nil, err + return nil, errors.Wrap(err, "could not retrieve highest attestation") + } + if !found { + return nil, errors.New("highest attestation data is not found, can't determine if attestation is slashable") } if highest != nil { // Source epoch can't be lower than previously known highest source, it can be equal or higher. @@ -43,12 +50,16 @@ func (protector *NormalProtection) IsSlashableAttestation(pubKey []byte, attesta // IsSlashableProposal detects slashable proposal request func (protector *NormalProtection) IsSlashableProposal(pubKey []byte, slot phase0.Slot) (*core.ProposalSlashStatus, error) { - highest, err := protector.store.RetrieveHighestProposal(pubKey) + if slot == 0 { + return nil, errors.New("proposal slot can not be 0") + } + + highest, found, err := protector.store.RetrieveHighestProposal(pubKey) if err != nil { - return nil, errors.New("could not retrieve highest proposal") + return nil, errors.Wrap(err, "could not retrieve highest proposal") } - if highest == 0 { - return nil, errors.New("highest proposal data is nil, can't determine if proposal is slashable") + if !found { + return nil, errors.New("highest proposal data is not found, can't determine if proposal is slashable") } if slot > highest { @@ -66,15 +77,18 @@ func (protector *NormalProtection) IsSlashableProposal(pubKey []byte, slot phase // UpdateHighestAttestation potentially updates the highest attestation given this latest attestation. func (protector *NormalProtection) UpdateHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { + if attestation == nil { + return errors.New("attestation data could not be nil") + } + // if no previous highest attestation found, set current - highest, err := protector.store.RetrieveHighestAttestation(pubKey) + highest, found, err := protector.store.RetrieveHighestAttestation(pubKey) if err != nil { - return errors.New("could not retrieve highest attestation") + return errors.Wrap(err, "could not retrieve highest attestation") } - if highest == nil { - err := protector.store.SaveHighestAttestation(pubKey, attestation) - if err != nil { - return errors.New("could not save highest attestation") + if !found || highest == nil { + if err = protector.store.SaveHighestAttestation(pubKey, attestation); err != nil { + return errors.Wrap(err, "could not save highest attestation") } return nil } @@ -91,9 +105,9 @@ func (protector *NormalProtection) UpdateHighestAttestation(pubKey []byte, attes } if shouldUpdate { - err := protector.store.SaveHighestAttestation(pubKey, highest) + err = protector.store.SaveHighestAttestation(pubKey, highest) if err != nil { - return errors.New("could not save highest attestation") + return errors.Wrap(err, "could not save highest attestation") } } return nil @@ -101,27 +115,31 @@ func (protector *NormalProtection) UpdateHighestAttestation(pubKey []byte, attes // UpdateHighestProposal updates highest proposal func (protector *NormalProtection) UpdateHighestProposal(key []byte, slot phase0.Slot) error { + if slot == 0 { + return errors.New("proposal slot can not be 0") + } + // if no previous highest proposal found, set current - highest, err := protector.store.RetrieveHighestProposal(key) + highest, found, err := protector.store.RetrieveHighestProposal(key) if err != nil { - return errors.New("could not retrieve highest proposal") + return errors.Wrap(err, "could not retrieve highest proposal") } - if highest == 0 { - err := protector.store.SaveHighestProposal(key, slot) + if !found || highest < slot { + err = protector.store.SaveHighestProposal(key, slot) if err != nil { - return errors.New("could not save highest proposal") + return errors.Wrap(err, "could not save highest proposal") } - return nil - } - - if highest < slot { - return protector.store.SaveHighestProposal(key, slot) } return nil } -// RetrieveHighestAttestation returns highest attestation data -func (protector *NormalProtection) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, error) { +// FetchHighestAttestation returns highest attestation data +func (protector *NormalProtection) FetchHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { return protector.store.RetrieveHighestAttestation(pubKey) } + +// FetchHighestProposal returns highest proposal data +func (protector *NormalProtection) FetchHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + return protector.store.RetrieveHighestProposal(pubKey) +} diff --git a/slashing_protection/proposal_protection_test.go b/slashing_protection/proposal_protection_test.go index ce0e1af..cec90e0 100644 --- a/slashing_protection/proposal_protection_test.go +++ b/slashing_protection/proposal_protection_test.go @@ -59,14 +59,8 @@ func setupProposal(t *testing.T, updateHighestProposal bool) (core.SlashingProte protector := NewNormalProtection(vault.Context.Storage.(core.SlashingStore)) if updateHighestProposal { - blk := &phase0.BeaconBlock{ - Slot: 100, - ProposerIndex: 2, - ParentRoot: _byteArray32("A"), - StateRoot: _byteArray32("A"), - Body: &phase0.BeaconBlockBody{}, - } - require.NoError(t, protector.UpdateHighestProposal(account1.ValidatorPublicKey(), blk.Slot)) + highestProposal := phase0.Slot(100) + require.NoError(t, protector.UpdateHighestProposal(account1.ValidatorPublicKey(), highestProposal)) } return protector, []core.ValidatorAccount{account1, account2}, nil @@ -77,15 +71,7 @@ func TestProposalProtection(t *testing.T) { protector, accounts, err := setupProposal(t, true) require.NoError(t, err) - blk := &phase0.BeaconBlock{ - Slot: 101, - ProposerIndex: 2, - ParentRoot: _byteArray32("Z"), - StateRoot: _byteArray32("Z"), - Body: &phase0.BeaconBlockBody{}, - } - - res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), blk.Slot) + res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), phase0.Slot(101)) require.NoError(t, err) require.Equal(t, res.Status, core.ValidProposal) }) @@ -94,15 +80,8 @@ func TestProposalProtection(t *testing.T) { protector, accounts, err := setupProposal(t, false) require.NoError(t, err) - blk := &phase0.BeaconBlock{ - Slot: 99, - ProposerIndex: 2, - ParentRoot: _byteArray32("Z"), - StateRoot: _byteArray32("Z"), - Body: &phase0.BeaconBlockBody{}, - } - res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), blk.Slot) - require.EqualError(t, err, "highest proposal data is nil, can't determine if proposal is slashable") + res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), phase0.Slot(99)) + require.EqualError(t, err, "highest proposal data is not found, can't determine if proposal is slashable") require.Nil(t, res) }) @@ -110,16 +89,57 @@ func TestProposalProtection(t *testing.T) { protector, accounts, err := setupProposal(t, true) require.NoError(t, err) - blk := &phase0.BeaconBlock{ - Slot: 99, - ProposerIndex: 2, - ParentRoot: _byteArray32("Z"), - StateRoot: _byteArray32("Z"), - Body: &phase0.BeaconBlockBody{}, - } - - res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), blk.Slot) + res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), phase0.Slot(99)) require.NoError(t, err) require.Equal(t, res.Status, core.HighestProposalVote) }) + + t.Run("public key nil on fetch", func(t *testing.T) { + protector, _, err := setupProposal(t, false) + require.NoError(t, err) + + res, found, err := protector.FetchHighestProposal(nil) + require.Error(t, err) + require.False(t, found) + require.Equal(t, phase0.Slot(0), res) + require.EqualError(t, err, "public key could not be nil") + }) + + t.Run("public key nil on update", func(t *testing.T) { + protector, _, err := setupProposal(t, false) + require.NoError(t, err) + + err = protector.UpdateHighestProposal(nil, phase0.Slot(99)) + require.Error(t, err) + require.EqualError(t, err, "could not retrieve highest proposal: public key could not be nil") + }) + + t.Run("public key nil on slashing check", func(t *testing.T) { + protector, _, err := setupProposal(t, true) + require.NoError(t, err) + + res, err := protector.IsSlashableProposal(nil, phase0.Slot(99)) + require.Error(t, err) + require.Nil(t, res) + require.EqualError(t, err, "could not retrieve highest proposal: public key could not be nil") + }) + + t.Run("proposal slot 0 on update", func(t *testing.T) { + protector, accounts, err := setupProposal(t, false) + require.NoError(t, err) + + err = protector.UpdateHighestProposal(accounts[0].ValidatorPublicKey(), phase0.Slot(0)) + require.Error(t, err) + require.EqualError(t, err, "proposal slot can not be 0") + }) + + t.Run("proposal slot 0 on slashable check", func(t *testing.T) { + protector, accounts, err := setupProposal(t, false) + require.NoError(t, err) + + res, err := protector.IsSlashableProposal(accounts[0].ValidatorPublicKey(), phase0.Slot(0)) + require.Error(t, err) + require.Nil(t, res) + require.EqualError(t, err, "proposal slot can not be 0") + }) } diff --git a/stores/inmemory/marshalable_test.go b/stores/inmemory/marshalable_test.go index bfbe63c..125f1e7 100644 --- a/stores/inmemory/marshalable_test.go +++ b/stores/inmemory/marshalable_test.go @@ -96,7 +96,7 @@ func TestMarshaling(t *testing.T) { require.NoError(t, store.SaveHighestAttestation(acc.ValidatorPublicKey(), att)) // proposal - require.NoError(t, store.SaveHighestProposal(acc.ValidatorPublicKey(), 1)) + require.NoError(t, store.SaveHighestProposal(acc.ValidatorPublicKey(), phase0.Slot(1))) // marshal byts, err := json.Marshal(store) @@ -122,13 +122,16 @@ func TestMarshaling(t *testing.T) { require.Equal(t, acc.ID().String(), acc2.ID().String()) }) t.Run("verify attestation", func(t *testing.T) { - att2, err := store.RetrieveHighestAttestation(acc.ValidatorPublicKey()) + att2, found, err := store.RetrieveHighestAttestation(acc.ValidatorPublicKey()) require.NoError(t, err) + require.True(t, found) + require.NotNil(t, att2) require.Equal(t, att.BeaconBlockRoot, att2.BeaconBlockRoot) }) t.Run("verify proposal", func(t *testing.T) { - prop2, err := store.RetrieveHighestProposal(acc.ValidatorPublicKey()) + prop2, found, err := store.RetrieveHighestProposal(acc.ValidatorPublicKey()) require.NoError(t, err) + require.True(t, found) require.Equal(t, phase0.Slot(1), prop2) }) } diff --git a/stores/inmemory/slashing_store.go b/stores/inmemory/slashing_store.go index 04f62ae..305358d 100644 --- a/stores/inmemory/slashing_store.go +++ b/stores/inmemory/slashing_store.go @@ -4,10 +4,19 @@ import ( "encoding/hex" "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/pkg/errors" ) // SaveHighestAttestation saves the given highest attestation func (store *InMemStore) SaveHighestAttestation(pubKey []byte, attestation *phase0.AttestationData) error { + if pubKey == nil { + return errors.New("public key could not be nil") + } + + if attestation == nil { + return errors.New("attestation data could not be nil") + } + store.highestAttestationLock.Lock() store.highestAttestation[hex.EncodeToString(pubKey)] = attestation store.highestAttestationLock.Unlock() @@ -15,15 +24,26 @@ func (store *InMemStore) SaveHighestAttestation(pubKey []byte, attestation *phas } // RetrieveHighestAttestation retrieves highest attestation -func (store *InMemStore) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, error) { +func (store *InMemStore) RetrieveHighestAttestation(pubKey []byte) (*phase0.AttestationData, bool, error) { + if pubKey == nil { + return nil, false, errors.New("public key could not be nil") + } + store.highestAttestationLock.RLock() - val := store.highestAttestation[hex.EncodeToString(pubKey)] + val, found := store.highestAttestation[hex.EncodeToString(pubKey)] store.highestAttestationLock.RUnlock() - return val, nil + return val, found, nil } // SaveHighestProposal saves the given highest attestation func (store *InMemStore) SaveHighestProposal(pubKey []byte, slot phase0.Slot) error { + if pubKey == nil { + return errors.New("public key could not be nil") + } + if slot == 0 { + return errors.New("invalid proposal slot, slot could not be 0") + } + store.highestProposalLock.Lock() store.highestProposal[hex.EncodeToString(pubKey)] = slot store.highestProposalLock.Unlock() @@ -31,9 +51,13 @@ func (store *InMemStore) SaveHighestProposal(pubKey []byte, slot phase0.Slot) er } // RetrieveHighestProposal returns highest proposal -func (store *InMemStore) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, error) { +func (store *InMemStore) RetrieveHighestProposal(pubKey []byte) (phase0.Slot, bool, error) { + if pubKey == nil { + return 0, false, errors.New("public key could not be nil") + } + store.highestProposalLock.RLock() - val := store.highestProposal[hex.EncodeToString(pubKey)] + val, found := store.highestProposal[hex.EncodeToString(pubKey)] store.highestProposalLock.RUnlock() - return val, nil + return val, found, nil } diff --git a/stores/inmemory/slashing_test.go b/stores/inmemory/slashing_test.go index 72c42a5..dc203c2 100644 --- a/stores/inmemory/slashing_test.go +++ b/stores/inmemory/slashing_test.go @@ -37,23 +37,18 @@ func (a *mockAccount) ValidationKeySign(data []byte) ([]byte, error) { return func (a *mockAccount) GetDepositData() (map[string]interface{}, error) { return nil, nil } func (a *mockAccount) SetContext(ctx *core.WalletContext) {} -//func testBlock(t *testing.T) *spec.VersionedBeaconBlock { -// blockByts := "7b22736c6f74223a312c2270726f706f7365725f696e646578223a38352c22706172656e745f726f6f74223a224f6b4f6b767962375755666f43634879543333476858794b7741666c4e64534f626b374b49534c396432733d222c2273746174655f726f6f74223a227264584c666d704c2f396a4f662b6c7065753152466d4747486a4571315562633955674257576d505236553d222c22626f6479223a7b2272616e64616f5f72657665616c223a226f734657704c79554f664859583549764b727170626f4d5048464a684153456232333057394b32556b4b41774c38577473496e41573138572f555a5a597652384250777267616c4e45316f48775745397468555277584b4574522b767135684f56744e424868626b5831426f3855625a51532b5230787177386a667177396446222c22657468315f64617461223a7b226465706f7369745f726f6f74223a22704f564553434e6d764a31546876484e444576344e7a4a324257494c39417856464e55642f4b3352536b6f3d222c226465706f7369745f636f756e74223a3132382c22626c6f636b5f68617368223a22704f564553434e6d764a31546876484e444576344e7a4a324257494c39417856464e55642f4b3352536b6f3d227d2c226772616666697469223a22414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d222c2270726f706f7365725f736c617368696e6773223a6e756c6c2c2261747465737465725f736c617368696e6773223a6e756c6c2c226174746573746174696f6e73223a5b7b226167677265676174696f6e5f62697473223a2248773d3d222c2264617461223a7b22736c6f74223a302c22636f6d6d69747465655f696e646578223a302c22626561636f6e5f626c6f636b5f726f6f74223a224f6b4f6b767962375755666f43634879543333476858794b7741666c4e64534f626b374b49534c396432733d222c22736f75726365223a7b2265706f6368223a302c22726f6f74223a22414141414141414141414141414141414141414141414141414141414141414141414141414141414141413d227d2c22746172676574223a7b2265706f6368223a302c22726f6f74223a224f6b4f6b767962375755666f43634879543333476858794b7741666c4e64534f626b374b49534c396432733d227d7d2c227369676e6174757265223a226c37627963617732537751633147587a4c36662f6f5a39616752386562685278503550675a546676676e30344b367879384a6b4c68506738326276674269675641674347767965357a7446797a4772646936555a655a4850593030595a6d3964513939764352674d34676f31666b3046736e684543654d68522f45454b59626a227d5d2c226465706f73697473223a6e756c6c2c22766f6c756e746172795f6578697473223a6e756c6c7d7d" -// blk := &spec.VersionedBeaconBlock{} -// require.NoError(t, json.Unmarshal(_byteArray(blockByts), blk)) -// return blk -//} - func getSlashingStorage() core.SlashingStore { return NewInMemStore(core.MainNetwork) } -func TestSavingProposal(t *testing.T) { +func TestSavingHighestProposal(t *testing.T) { storage := getSlashingStorage() tests := []struct { - name string - proposal phase0.Slot - account core.ValidatorAccount + name string + proposal phase0.Slot + account core.ValidatorAccount + accountPubKeyNil bool + expectedErr string }{ { name: "simple save", @@ -63,29 +58,59 @@ func TestSavingProposal(t *testing.T) { validationKey: _bigInt("5467048590701165350380985526996487573957450279098876378395441669247373404218"), }, }, + { + name: "corrupted save - public key is nil", + proposal: 0, + account: &mockAccount{ + id: uuid.New(), + validationKey: _bigInt("5467048590701165350380985526996487573957450279098876378395441669247373404218"), + }, + accountPubKeyNil: true, + expectedErr: "public key could not be nil", + }, + { + name: "corrupted save - proposal is 0", + proposal: 0, + account: &mockAccount{ + id: uuid.New(), + validationKey: _bigInt("5467048590701165350380985526996487573957450279098876378395441669247373404218"), + }, + expectedErr: "invalid proposal slot, slot could not be 0", + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { // save - err := storage.SaveHighestProposal(test.account.ValidatorPublicKey(), test.proposal) + valPubKey := test.account.ValidatorPublicKey() + if test.accountPubKeyNil { + valPubKey = nil + } + err := storage.SaveHighestProposal(valPubKey, test.proposal) + if test.expectedErr != "" { + require.Error(t, err) + require.EqualError(t, err, test.expectedErr) + return + } require.NoError(t, err) // fetch - proposal, err := storage.RetrieveHighestProposal(test.account.ValidatorPublicKey()) + proposal, found, err := storage.RetrieveHighestProposal(valPubKey) require.NoError(t, err) - require.NotNil(t, proposal) + require.True(t, found) require.EqualValues(t, test.proposal, proposal) }) } } -func TestSavingAttestation(t *testing.T) { +func TestSavingHighestAttestation(t *testing.T) { storage := getSlashingStorage() tests := []struct { - name string - att *phase0.AttestationData - account core.ValidatorAccount + name string + att *phase0.AttestationData + account core.ValidatorAccount + accountPubKeyNil bool + expectedErr string }{ { name: "simple save", @@ -127,38 +152,8 @@ func TestSavingAttestation(t *testing.T) { validationKey: _bigInt("5467048590701165350380985526996487573957450279098876378395441669247373404218"), }, }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - // save - err := storage.SaveHighestAttestation(test.account.ValidatorPublicKey(), test.att) - require.NoError(t, err) - - // fetch - att, err := storage.RetrieveHighestAttestation(test.account.ValidatorPublicKey()) - require.NoError(t, err) - require.NotNil(t, att) - - // test equal - aRoot, err := att.HashTreeRoot() - require.NoError(t, err) - bRoot, err := test.att.HashTreeRoot() - require.NoError(t, err) - require.EqualValues(t, aRoot, bRoot) - }) - } -} - -func TestSavingHighestAttestation(t *testing.T) { - storage := getSlashingStorage() - tests := []struct { - name string - att *phase0.AttestationData - account core.ValidatorAccount - }{ { - name: "simple save", + name: "corrupted save - public key is nil", att: &phase0.AttestationData{ Slot: 30, Index: 1, @@ -168,7 +163,7 @@ func TestSavingHighestAttestation(t *testing.T) { Root: [32]byte{}, }, Target: &phase0.Checkpoint{ - Epoch: 4, + Epoch: 3, Root: [32]byte{}, }, }, @@ -176,38 +171,39 @@ func TestSavingHighestAttestation(t *testing.T) { id: uuid.New(), validationKey: _bigInt("5467048590701165350380985526996487573957450279098876378395441669247373404218"), }, + accountPubKeyNil: true, + expectedErr: "public key could not be nil", }, { - name: "simple save with no change to latest attestation target", - att: &phase0.AttestationData{ - Slot: 30, - Index: 1, - BeaconBlockRoot: [32]byte{}, - Source: &phase0.Checkpoint{ - Epoch: 1, - Root: [32]byte{}, - }, - Target: &phase0.Checkpoint{ - Epoch: 3, - Root: [32]byte{}, - }, - }, + name: "corrupted save - attestation data is nil", + att: nil, account: &mockAccount{ id: uuid.New(), validationKey: _bigInt("5467048590701165350380985526996487573957450279098876378395441669247373404218"), }, + expectedErr: "attestation data could not be nil", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { // save - err := storage.SaveHighestAttestation(test.account.ValidatorPublicKey(), test.att) + valPubKey := test.account.ValidatorPublicKey() + if test.accountPubKeyNil { + valPubKey = nil + } + err := storage.SaveHighestAttestation(valPubKey, test.att) + if test.expectedErr != "" { + require.Error(t, err) + require.EqualError(t, err, test.expectedErr) + return + } require.NoError(t, err) // fetch - att, err := storage.RetrieveHighestAttestation(test.account.ValidatorPublicKey()) + att, found, err := storage.RetrieveHighestAttestation(test.account.ValidatorPublicKey()) require.NoError(t, err) + require.True(t, found) require.NotNil(t, att) // test equal