From 14d6b7c078a3f851159a4e71ce2cf80ebc0ca250 Mon Sep 17 00:00:00 2001 From: corver Date: Thu, 16 Feb 2023 17:43:32 +0200 Subject: [PATCH] cluster: draft v1.6 lock with deposit data (#1813) Adds a new draft lock version v1.6 which includes deposit data in the lock's distributed validators. category: feature ticket: #1775 --- cluster/cluster_internal_test.go | 9 +++ cluster/cluster_test.go | 29 ++++++- cluster/definition.go | 18 ++--- cluster/deposit.go | 60 ++++++++++++++ cluster/deposit_internal_test.go | 43 ++++++++++ cluster/distvalidator.go | 57 +++++++++++-- cluster/lock.go | 73 +++++++++++++---- cluster/ssz.go | 79 ++++++++++++++----- .../testdata/cluster_definition_v1_6_0.json | 40 ++++++++++ cluster/testdata/cluster_lock_v1_0_0.json | 8 +- cluster/testdata/cluster_lock_v1_1_0.json | 8 +- cluster/testdata/cluster_lock_v1_2_0.json | 8 +- cluster/testdata/cluster_lock_v1_3_0.json | 8 +- cluster/testdata/cluster_lock_v1_4_0.json | 8 +- cluster/testdata/cluster_lock_v1_5_0.json | 8 +- cluster/testdata/cluster_lock_v1_6_0.json | 72 +++++++++++++++++ cluster/version.go | 28 ++----- testutil/random.go | 7 ++ 18 files changed, 465 insertions(+), 98 deletions(-) create mode 100644 cluster/deposit.go create mode 100644 cluster/deposit_internal_test.go create mode 100644 cluster/testdata/cluster_definition_v1_6_0.json create mode 100644 cluster/testdata/cluster_lock_v1_6_0.json diff --git a/cluster/cluster_internal_test.go b/cluster/cluster_internal_test.go index 25c3de00e..968bdf0ae 100644 --- a/cluster/cluster_internal_test.go +++ b/cluster/cluster_internal_test.go @@ -265,3 +265,12 @@ func RandomValidatorAddresses(n int) []ValidatorAddresses { return resp } + +func RandomDepositData() DepositData { + return DepositData{ + PubKey: testutil.RandomBytes48(), + WithdrawalCredentials: testutil.RandomBytes32(), + Amount: rand.Int(), + Signature: testutil.RandomBytes96(), + } +} diff --git a/cluster/cluster_test.go b/cluster/cluster_test.go index 6733b5ef2..e787df9af 100644 --- a/cluster/cluster_test.go +++ b/cluster/cluster_test.go @@ -34,6 +34,8 @@ import ( //go:generate go test . -v -update -clean const ( + v1_6 = "v1.6.0" + v1_5 = "v1.5.0" v1_4 = "v1.4.0" v1_3 = "v1.3.0" v1_2 = "v1.2.0" @@ -60,7 +62,7 @@ func TestEncode(t *testing.T) { }, } // Add multiple validator address from v1.5 and later. - if version != v1_0 && version != v1_1 && version != v1_2 && version != v1_3 && version != v1_4 { + if !isAnyVersion(version, v1_0, v1_1, v1_2, v1_3, v1_4) { opts = append(opts, cluster.WithMultiVAddrs(cluster.RandomValidatorAddresses(numVals))) } @@ -92,10 +94,10 @@ func TestEncode(t *testing.T) { rand.New(rand.NewSource(0)), opts..., ) - require.NoError(t, err) + testutil.RequireNoError(t, err) // Definition version prior to v1.3.0 don't support EIP712 signatures. - if version == v1_0 || version == v1_1 || version == v1_2 { + if isAnyVersion(version, v1_0, v1_1, v1_2) { for i := range definition.Operators { // Set to empty values instead of nil to align with unmarshalled json. definition.Operators[i].ConfigSignature = []byte{} @@ -104,7 +106,7 @@ func TestEncode(t *testing.T) { } // Definition version prior to v1.4.0 don't support creator. - if version == v1_0 || version == v1_1 || version == v1_2 || version == v1_3 { + if isAnyVersion(version, v1_0, v1_1, v1_2, v1_3) { definition.Creator = cluster.Creator{} } @@ -139,16 +141,25 @@ func TestEncode(t *testing.T) { testutil.RandomBytes48(), testutil.RandomBytes48(), }, + DepositData: cluster.RandomDepositData(), }, { PubKey: testutil.RandomBytes48(), PubShares: [][]byte{ testutil.RandomBytes48(), testutil.RandomBytes48(), }, + DepositData: cluster.RandomDepositData(), }, }, } + // Lock version prior to v1.6.0 don't support DepositData. + if isAnyVersion(version, v1_0, v1_1, v1_2, v1_3, v1_4, v1_5) { + for i := range lock.Validators { + lock.Validators[i].DepositData = cluster.DepositData{} + } + } + t.Run("lock_json_"+vStr, func(t *testing.T) { testutil.RequireGoldenJSON(t, lock, testutil.WithFilename("cluster_lock_"+vStr+".json")) @@ -232,3 +243,13 @@ func TestDefinitionPeers(t *testing.T) { require.Equal(t, names[i], peer.Name) } } + +func isAnyVersion(version string, list ...string) bool { + for _, v := range list { + if version == v { + return true + } + } + + return false +} diff --git a/cluster/definition.go b/cluster/definition.go index 7d74f0bd4..99ae80125 100644 --- a/cluster/definition.go +++ b/cluster/definition.go @@ -350,14 +350,14 @@ func (d Definition) MarshalJSON() ([]byte, error) { } switch { - case isV1x0(d2.Version) || isV1x1(d2.Version): + case isAnyVersion(d2.Version, v1_0, v1_1): return marshalDefinitionV1x0or1(d2) - case isV1x2(d2.Version) || isV1x3(d2.Version): + case isAnyVersion(d2.Version, v1_2, v1_3): // v1.2 and v1.3 has the same json format. return marshalDefinitionV1x2or3(d2) - case isV1x4(d2.Version): + case isAnyVersion(d2.Version, v1_4): return marshalDefinitionV1x4(d2) - case isV1x5(d2.Version): + case isAnyVersion(d2.Version, v1_5, v1_6): return marshalDefinitionV1x5(d2) default: return nil, errors.New("unsupported version") @@ -383,22 +383,22 @@ func (d *Definition) UnmarshalJSON(data []byte) error { err error ) switch { - case isV1x0(version.Version) || isV1x1(version.Version): + case isAnyVersion(version.Version, v1_0, v1_1): def, err = unmarshalDefinitionV1x0or1(data) if err != nil { return err } - case isV1x2(version.Version) || isV1x3(version.Version): + case isAnyVersion(version.Version, v1_2, v1_3): def, err = unmarshalDefinitionV1x2or3(data) if err != nil { return err } - case isV1x4(version.Version): + case isAnyVersion(version.Version, v1_4): def, err = unmarshalDefinitionV1x4(data) if err != nil { return err } - case isV1x5(version.Version): + case isAnyVersion(version.Version, v1_5, v1_6): def, err = unmarshalDefinitionV1x5(data) if err != nil { return err @@ -681,7 +681,7 @@ func unmarshalDefinitionV1x5(data []byte) (def Definition, err error) { // supportEIP712Sigs returns true if the provided definition version supports EIP712 signatures. // Note that Definition versions prior to v1.3.0 don't support EIP712 signatures. func supportEIP712Sigs(version string) bool { - return !(isV1x0(version) || isV1x1(version) || isV1x2(version)) + return !isAnyVersion(version, v1_0, v1_1, v1_2) } func eip712SigsPresent(operators []Operator) bool { diff --git a/cluster/deposit.go b/cluster/deposit.go new file mode 100644 index 000000000..b8ef9f8fd --- /dev/null +++ b/cluster/deposit.go @@ -0,0 +1,60 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +package cluster + +// DepositData defines the deposit data to activate a validator. +// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositdata +type DepositData struct { + // PubKey is the validator public key. + PubKey []byte `json:"pubkey" ssz:"Bytes48" lock_hash:"0"` + + // WithdrawalCredentials included in the deposit. + WithdrawalCredentials []byte `json:"withdrawal_credentials" ssz:"Bytes32" lock_hash:"1"` + + // Amount is the amount in Gwei to be deposited. + Amount int `json:"amount" ssz:"uint64" lock_hash:"2"` + + // Signature is the BLS signature of the deposit message (above three fields). + Signature []byte `json:"signature" ssz:"Bytes96" lock_hash:"3"` +} + +// depositDataJSON is the json formatter of DepositData. +type depositDataJSON struct { + PubKey ethHex `json:"pubkey"` + WithdrawalCredentials ethHex `json:"withdrawal_credentials"` + Amount int `json:"amount,string"` + Signature ethHex `json:"signature"` +} + +// depositDataToJSON converts DepositData to depositDataJSON. +func depositDataToJSON(d DepositData) depositDataJSON { + return depositDataJSON{ + PubKey: d.PubKey, + WithdrawalCredentials: d.WithdrawalCredentials, + Amount: d.Amount, + Signature: d.Signature, + } +} + +// depositDataFromJSON converts depositDataJSON to DepositData. +func depositDataFromJSON(d depositDataJSON) DepositData { + return DepositData{ + PubKey: d.PubKey, + WithdrawalCredentials: d.WithdrawalCredentials, + Amount: d.Amount, + Signature: d.Signature, + } +} diff --git a/cluster/deposit_internal_test.go b/cluster/deposit_internal_test.go new file mode 100644 index 000000000..124e60a4c --- /dev/null +++ b/cluster/deposit_internal_test.go @@ -0,0 +1,43 @@ +// Copyright © 2022 Obol Labs Inc. +// +// This program is free software: you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation, either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . + +package cluster + +import ( + "encoding/json" + "testing" + + eth2p0 "github.com/attestantio/go-eth2-client/spec/phase0" + "github.com/stretchr/testify/require" +) + +func TestDepositJSON(t *testing.T) { + deposit := RandomDepositData() + depositJSON := depositDataToJSON(deposit) + + eth2Deposit := ð2p0.DepositData{ + PublicKey: *(*eth2p0.BLSPubKey)(deposit.PubKey), + WithdrawalCredentials: deposit.WithdrawalCredentials, + Amount: eth2p0.Gwei(deposit.Amount), + Signature: *(*eth2p0.BLSSignature)(deposit.Signature), + } + + b1, err := json.MarshalIndent(depositJSON, "", " ") + require.NoError(t, err) + b2, err := json.MarshalIndent(eth2Deposit, "", " ") + require.NoError(t, err) + + require.Equal(t, b1, b2) +} diff --git a/cluster/distvalidator.go b/cluster/distvalidator.go index 262bdc5dd..4323cd26e 100644 --- a/cluster/distvalidator.go +++ b/cluster/distvalidator.go @@ -28,6 +28,9 @@ type DistValidator struct { // PubShares are the public keys corresponding to each node's secret key share. // It can be used to verify a partial signature created by any node in the cluster. PubShares [][]byte `json:"public_shares,omitempty" ssz:"CompositeList[256],Bytes48" lock_hash:"1"` + + // DepositData is the validator deposit data. + DepositData DepositData `json:"deposit_data,omitempty" ssz:"Composite" lock_hash:"2"` } // PublicKey returns the validator BLS group public key. @@ -52,13 +55,20 @@ type distValidatorJSONv1x1 struct { FeeRecipientAddress ethHex `json:"fee_recipient_address,omitempty"` } -// distValidatorJSONv1x2 is the json formatter of DistValidator for versions v1.2.0 and later. -type distValidatorJSONv1x2 struct { +// distValidatorJSONv1x2to5 is the json formatter of DistValidator for versions v1.2.0 to v1.5.0. +type distValidatorJSONv1x2to5 struct { PubKey ethHex `json:"distributed_public_key"` PubShares []ethHex `json:"public_shares,omitempty"` FeeRecipientAddress ethHex `json:"fee_recipient_address,omitempty"` } +// distValidatorJSONv1x6 is the json formatter of DistValidator for versions v1.6.0 or later. +type distValidatorJSONv1x6 struct { + PubKey ethHex `json:"distributed_public_key"` + PubShares []ethHex `json:"public_shares,omitempty"` + DepositData depositDataJSON `json:"deposit_data,omitempty"` +} + func distValidatorsFromV1x1(distValidators []distValidatorJSONv1x1) []DistValidator { var resp []DistValidator for _, dv := range distValidators { @@ -83,7 +93,7 @@ func distValidatorsToV1x1(distValidators []DistValidator) []distValidatorJSONv1x return resp } -func distValidatorsFromV1x2orLater(distValidators []distValidatorJSONv1x2) []DistValidator { +func distValidatorsFromV1x2to5(distValidators []distValidatorJSONv1x2to5) []DistValidator { var resp []DistValidator for _, dv := range distValidators { var shares [][]byte @@ -99,15 +109,15 @@ func distValidatorsFromV1x2orLater(distValidators []distValidatorJSONv1x2) []Dis return resp } -func distValidatorsToV1x2orLater(distValidators []DistValidator) []distValidatorJSONv1x2 { - var resp []distValidatorJSONv1x2 +func distValidatorsToV1x2to5(distValidators []DistValidator) []distValidatorJSONv1x2to5 { + var resp []distValidatorJSONv1x2to5 for _, dv := range distValidators { var shares []ethHex for _, share := range dv.PubShares { shares = append(shares, share) } - resp = append(resp, distValidatorJSONv1x2{ + resp = append(resp, distValidatorJSONv1x2to5{ PubKey: dv.PubKey, PubShares: shares, }) @@ -115,3 +125,38 @@ func distValidatorsToV1x2orLater(distValidators []DistValidator) []distValidator return resp } + +func distValidatorsFromV1x6orLater(distValidators []distValidatorJSONv1x6) []DistValidator { + var resp []DistValidator + for _, dv := range distValidators { + var shares [][]byte + for _, share := range dv.PubShares { + shares = append(shares, share) + } + resp = append(resp, DistValidator{ + PubKey: dv.PubKey, + PubShares: shares, + DepositData: depositDataFromJSON(dv.DepositData), + }) + } + + return resp +} + +func distValidatorsToV1x6OrLater(distValidators []DistValidator) []distValidatorJSONv1x6 { + var resp []distValidatorJSONv1x6 + for _, dv := range distValidators { + var shares []ethHex + for _, share := range dv.PubShares { + shares = append(shares, share) + } + + resp = append(resp, distValidatorJSONv1x6{ + PubKey: dv.PubKey, + PubShares: shares, + DepositData: depositDataToJSON(dv.DepositData), + }) + } + + return resp +} diff --git a/cluster/lock.go b/cluster/lock.go index 892ef38b2..3a9e88866 100644 --- a/cluster/lock.go +++ b/cluster/lock.go @@ -51,10 +51,12 @@ func (l Lock) MarshalJSON() ([]byte, error) { } switch { - case isV1x0(l.Version) || isV1x1(l.Version): + case isAnyVersion(l.Version, v1_0, v1_1): return marshalLockV1x0or1(l, lockHash) - case isV1x2(l.Version) || isV1x3(l.Version) || isV1x4(l.Version) || isV1x5(l.Version): - return marshalLockV1x2orLater(l, lockHash) + case isAnyVersion(l.Version, v1_2, v1_3, v1_4, v1_5): + return marshalLockV1x2to5(l, lockHash) + case isAnyVersion(l.Version, v1_6): + return marshalLockV1x6orLater(l, lockHash) default: return nil, errors.New("unsupported version") } @@ -87,7 +89,12 @@ func (l *Lock) UnmarshalJSON(data []byte) error { return err } case isAnyVersion(version.Definition.Version, v1_2, v1_3, v1_4, v1_5): - lock, err = unmarshalLockV1x2orLater(data) + lock, err = unmarshalLockV1x2to5(data) + if err != nil { + return err + } + case isAnyVersion(version.Definition.Version, v1_6): + lock, err = unmarshalLockV1x6orLater(data) if err != nil { return err } @@ -138,7 +145,7 @@ func (l Lock) VerifySignatures() error { } if len(l.SignatureAggregate) == 0 { - if isV1x0(l.Version) || isV1x1(l.Version) { + if isAnyVersion(l.Version, v1_0, v1_1) { return nil // Earlier versions of `charon create cluster` didn't populate SignatureAggregate. } @@ -188,10 +195,24 @@ func marshalLockV1x0or1(lock Lock, lockHash [32]byte) ([]byte, error) { return resp, nil } -func marshalLockV1x2orLater(lock Lock, lockHash [32]byte) ([]byte, error) { - resp, err := json.Marshal(lockJSONv1x2orLater{ +func marshalLockV1x2to5(lock Lock, lockHash [32]byte) ([]byte, error) { + resp, err := json.Marshal(lockJSONv1x2to5{ Definition: lock.Definition, - Validators: distValidatorsToV1x2orLater(lock.Validators), + Validators: distValidatorsToV1x2to5(lock.Validators), + SignatureAggregate: lock.SignatureAggregate, + LockHash: lockHash[:], + }) + if err != nil { + return nil, errors.Wrap(err, "marshal definition v1_2") + } + + return resp, nil +} + +func marshalLockV1x6orLater(lock Lock, lockHash [32]byte) ([]byte, error) { + resp, err := json.Marshal(lockJSONv1x6orLater{ + Definition: lock.Definition, + Validators: distValidatorsToV1x6OrLater(lock.Validators), SignatureAggregate: lock.SignatureAggregate, LockHash: lockHash[:], }) @@ -224,8 +245,8 @@ func unmarshalLockV1x0or1(data []byte) (lock Lock, err error) { return lock, nil } -func unmarshalLockV1x2orLater(data []byte) (lock Lock, err error) { - var lockJSON lockJSONv1x2orLater +func unmarshalLockV1x2to5(data []byte) (lock Lock, err error) { + var lockJSON lockJSONv1x2to5 if err := json.Unmarshal(data, &lockJSON); err != nil { return Lock{}, errors.Wrap(err, "unmarshal definition") } @@ -238,7 +259,23 @@ func unmarshalLockV1x2orLater(data []byte) (lock Lock, err error) { lock = Lock{ Definition: lockJSON.Definition, - Validators: distValidatorsFromV1x2orLater(lockJSON.Validators), + Validators: distValidatorsFromV1x2to5(lockJSON.Validators), + SignatureAggregate: lockJSON.SignatureAggregate, + LockHash: lockJSON.LockHash, + } + + return lock, nil +} + +func unmarshalLockV1x6orLater(data []byte) (lock Lock, err error) { + var lockJSON lockJSONv1x6orLater + if err := json.Unmarshal(data, &lockJSON); err != nil { + return Lock{}, errors.Wrap(err, "unmarshal definition") + } + + lock = Lock{ + Definition: lockJSON.Definition, + Validators: distValidatorsFromV1x6orLater(lockJSON.Validators), SignatureAggregate: lockJSON.SignatureAggregate, LockHash: lockJSON.LockHash, } @@ -254,10 +291,18 @@ type lockJSONv1x0or1 struct { LockHash []byte `json:"lock_hash"` } -// lockJSONv1x2orLater is the json formatter of Lock for versions v1.2.0 and later. -type lockJSONv1x2orLater struct { +// lockJSONv1x2to5 is the json formatter of Lock for versions v1.2.0 to v1.5.0. +type lockJSONv1x2to5 struct { + Definition Definition `json:"cluster_definition"` + Validators []distValidatorJSONv1x2to5 `json:"distributed_validators"` + SignatureAggregate ethHex `json:"signature_aggregate"` + LockHash ethHex `json:"lock_hash"` +} + +// lockJSONv1x6orLater is the json formatter of Lock for versions v1.6.0 or later. +type lockJSONv1x6orLater struct { Definition Definition `json:"cluster_definition"` - Validators []distValidatorJSONv1x2 `json:"distributed_validators"` + Validators []distValidatorJSONv1x6 `json:"distributed_validators"` SignatureAggregate ethHex `json:"signature_aggregate"` LockHash ethHex `json:"lock_hash"` } diff --git a/cluster/ssz.go b/cluster/ssz.go index fca0ab483..79eba49d5 100644 --- a/cluster/ssz.go +++ b/cluster/ssz.go @@ -23,18 +23,20 @@ import ( ) const ( - sszMaxENR = 1024 - sszMaxName = 256 - sszMaxUUID = 64 - sszMaxVersion = 16 - sszMaxTimestamp = 32 - sszMaxDKGAlgorithm = 32 - sszMaxOperators = 256 - sszMaxValidators = 65536 - sszLenForkVersion = 4 - sszLenK1Sig = 65 - sszLenHash = 32 - sszLenPubKey = 48 + sszMaxENR = 1024 + sszMaxName = 256 + sszMaxUUID = 64 + sszMaxVersion = 16 + sszMaxTimestamp = 32 + sszMaxDKGAlgorithm = 32 + sszMaxOperators = 256 + sszMaxValidators = 65536 + sszLenForkVersion = 4 + sszLenK1Sig = 65 + sszLenBLSSig = 96 + sszLenHash = 32 + sszLenWithdrawCreds = 32 + sszLenPubKey = 48 ) // getDefinitionHashFunc returns the function to hash a definition based on the provided version. @@ -43,7 +45,7 @@ func getDefinitionHashFunc(version string) (func(Definition, ssz.HashWalker, boo return hashDefinitionLegacy, nil } else if isAnyVersion(version, v1_3, v1_4) { return hashDefinitionV1x3or4, nil - } else if isAnyVersion(version, v1_5) { + } else if isAnyVersion(version, v1_5, v1_6) { return hashDefinitionV1x5, nil } else { return nil, errors.New("unknown version", z.Str("version", version)) @@ -128,7 +130,7 @@ func hashDefinitionLegacy(d Definition, hh ssz.HashWalker, configOnly bool) erro // Field (1) 'ENR' hh.PutBytes([]byte(o.ENR)) - if isV1x0(d.Version) || isV1x1(d.Version) { + if isAnyVersion(d.Version, v1_0, v1_1) { // Field (2) 'Nonce' hh.PutUint64(zeroNonce) // Older versions had a zero nonce } @@ -410,11 +412,11 @@ func hashDefinitionV1x5(d Definition, hh ssz.HashWalker, configOnly bool) error // hashLock returns a lock hash. func hashLock(l Lock) ([32]byte, error) { var hashFunc func(Lock, ssz.HashWalker) error - if isV1x0(l.Version) || isV1x1(l.Version) || isV1x2(l.Version) { + if isAnyVersion(l.Version, v1_0, v1_1, v1_2) { hashFunc = hashLockLegacy } else if isAnyVersion(l.Version, v1_3, v1_4) { hashFunc = hashLockV1x3or4 - } else if isAnyVersion(l.Version, v1_5) { //nolint:revive // Early return not applicable to else if + } else if isAnyVersion(l.Version, v1_5, v1_6) { //nolint:revive // Early return not applicable to else if hashFunc = hashLockV1x5 } else { return [32]byte{}, errors.New("unknown version") @@ -485,7 +487,7 @@ func hashLockV1x5(l Lock, hh ssz.HashWalker) error { subIndx := hh.Index() num := uint64(len(l.Validators)) for _, validator := range l.Validators { - if err := hashValidatorV1x5(validator, hh); err != nil { + if err := hashValidatorV1x5(validator, hh, l.Version); err != nil { return err } } @@ -523,7 +525,7 @@ func hashValidatorV1x3Or4(v DistValidator, hh ssz.HashWalker) error { } // hashValidatorV1x5 hashes the distributed validator v1.5. -func hashValidatorV1x5(v DistValidator, hh ssz.HashWalker) error { +func hashValidatorV1x5(v DistValidator, hh ssz.HashWalker, version string) error { indx := hh.Index() // Field (0) 'PubKey' Bytes48 @@ -544,6 +546,16 @@ func hashValidatorV1x5(v DistValidator, hh ssz.HashWalker) error { hh.MerkleizeWithMixin(subIndx, num, sszMaxOperators) } + depositHashFunc, err := getDepositDataHashFunc(version) + if err != nil { + return err + } + + // Field (2) 'DepositData' Composite + if err := depositHashFunc(v.DepositData, hh); err != nil { + return err + } + hh.Merkleize(indx) return nil @@ -599,3 +611,34 @@ func hashValidatorLegacy(v DistValidator, hh ssz.HashWalker) error { return nil } + +// getDefinitionHashFunc returns the function to hash a definition based on the provided version. +func getDepositDataHashFunc(version string) (func(DepositData, ssz.HashWalker) error, error) { + if isAnyVersion(version, v1_0, v1_1, v1_2, v1_3, v1_4, v1_5) { + // Noop hash function for v1.0 to v1.5 that do not support deposit data. + return func(DepositData, ssz.HashWalker) error { return nil }, nil + } else if isAnyVersion(version, v1_6) { + return hashDepositData, nil + } else { + return nil, errors.New("unknown version", z.Str("version", version)) + } +} + +// hashDepositData hashes the latest deposit data. +func hashDepositData(d DepositData, hh ssz.HashWalker) error { + // Field (0) 'PubKey' Bytes48 + if err := putBytesN(hh, d.PubKey, sszLenPubKey); err != nil { + return err + } + + // Field (1) 'WithdrawalCredentials' Bytes32 + if err := putBytesN(hh, d.WithdrawalCredentials, sszLenWithdrawCreds); err != nil { + return err + } + + // Field (2) 'Amount' uint64 + hh.PutUint64(uint64(d.Amount)) + + // Field (3) 'Signature' Bytes96 + return putBytesN(hh, d.Signature, sszLenBLSSig) +} diff --git a/cluster/testdata/cluster_definition_v1_6_0.json b/cluster/testdata/cluster_definition_v1_6_0.json new file mode 100644 index 000000000..25262b462 --- /dev/null +++ b/cluster/testdata/cluster_definition_v1_6_0.json @@ -0,0 +1,40 @@ +{ + "name": "test definition", + "creator": { + "address": "0x6bf84c7174cb7476364cc3dbd968b0f7172ed857", + "config_signature": "0xff094279db1944ebd7a19d0f7bbacbe0255aa5b7d44bec40f84c892b9bffd43629b0223beea5f4f74391f445d15afd4294040374f6924b98cbf8713f8d962d7c1c" + }, + "operators": [ + { + "address": "0x019192c24224e2cafccae3a61fb586b14323a6bc", + "enr": "enr://ea6f5b3af6de0374366c4719e43a1b067d89bc7f01f1f573981659a44ff17a4c", + "config_signature": "0x7215a3b539eb1e5849c6077dbb5722f5717a289a266f97647981998ebea89c0b4b373970115e82ed6f4125c8fa7311e4d7defa922daae7786667f7e936cd4f241b", + "enr_signature": "0xf7df866baa56038367ad6145de1ee8f4a8b0993ebdf8883a0ad8be9c3978b04883e56a156a8de563afa467d49dec6a40e9a1d007f033c2823061bdd0eaa59f8e1c" + }, + { + "address": "0xa6430105220d0b29688b734b8ea0f3ca9936e846", + "enr": "enr://f6a63b7f3dfd2567c18979e4d60f26686d9bf2fb26c901ff354cde1607ee294b", + "config_signature": "0x39f32b7c7822ba64f84ab43ca0c6e6b91c1fd3be8990434179d3af4491a369012db92d184fc39d1734ff5716428953bb6865fcf92b0c3a17c9028be9914eb7641c", + "enr_signature": "0x6c9347800979d1830356f2a54c3deab2a4b4475d63afbe8fb56987c77f5818526f1814be823350eab13935f31d84484517e924aef78ae151c00755925836b7071b" + } + ], + "uuid": "0194FDC2-FA2F-FCC0-41D3-FF12045B73C8", + "version": "v1.6.0", + "timestamp": "2022-07-19T18:19:58+02:00", + "num_validators": 2, + "threshold": 3, + "validators": [ + { + "fee_recipient_address": "0x52fdfc072182654f163f5f0f9a621d729566c74d", + "withdrawal_address": "0x81855ad8681d0d86d1e91e00167939cb6694d2c4" + }, + { + "fee_recipient_address": "0xeb9d18a44784045d87f3c67cf22746e995af5a25", + "withdrawal_address": "0x5fb90badb37c5821b6d95526a41a9504680b4e7c" + } + ], + "dkg_algorithm": "default", + "fork_version": "0x90000069", + "config_hash": "0x63adb22a10930e78013c6fbb72dfc877346d455a9401a65cf58ab5c33f725c9d", + "definition_hash": "0x898990feec5df2288bcebe6b4e3474a2412dc8d49147d5353daddd7135353191" +} \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_0_0.json b/cluster/testdata/cluster_lock_v1_0_0.json index 48fe8e321..4a87aacc0 100644 --- a/cluster/testdata/cluster_lock_v1_0_0.json +++ b/cluster/testdata/cluster_lock_v1_0_0.json @@ -38,13 +38,13 @@ ] }, { - "distributed_public_key": "0xfd69d22ae5411947cb553d7694267aef4ebcea406b32d6108bd68584f57e37caac6e33feaa3263a399437024ba9c9b14", + "distributed_public_key": "0x143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193f", "public_shares": [ - "Z4onTwGpEK4pX277/l9av0TM3iY7VgZjPivwAG8oKV19OQafAaI5xDZYVMOvf2tB", - "1jH5K5qNEvQSVzJf/zMvdXawYgVWMEo+Pq4Uwo0M6jnSkBpScg2oXKHks46vP0TG" + "uJZnEKeWBzLKUs9Tw/UgyIm3m/UEz7V8dgEjLVibrM6p1uJj4lwndB0/bGLLuxXZ", + "r7y/f32kGrBAjjlpwuLNzyM0OL8XdKzncJpPCR6ag/3q4OxV6yM6m1OUyzx4VrVG" ] } ], "signature_aggregate": "K3x4Irpk+Eq0PKDG5rkcH9O+iZBDQXnTr0SRo2kBLbk=", - "lock_hash": "ux4dtUukqx5cH2m7Drg12LtUmBtF0WL1eYggAbn4/Eg=" + "lock_hash": "zD6bwojstGAONrfY8IQkSOb6NvQROyifYltc7d2vCkA=" } \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_1_0.json b/cluster/testdata/cluster_lock_v1_1_0.json index 587945b1f..1206141cf 100644 --- a/cluster/testdata/cluster_lock_v1_1_0.json +++ b/cluster/testdata/cluster_lock_v1_1_0.json @@ -38,13 +38,13 @@ ] }, { - "distributed_public_key": "0xfd69d22ae5411947cb553d7694267aef4ebcea406b32d6108bd68584f57e37caac6e33feaa3263a399437024ba9c9b14", + "distributed_public_key": "0x143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193f", "public_shares": [ - "Z4onTwGpEK4pX277/l9av0TM3iY7VgZjPivwAG8oKV19OQafAaI5xDZYVMOvf2tB", - "1jH5K5qNEvQSVzJf/zMvdXawYgVWMEo+Pq4Uwo0M6jnSkBpScg2oXKHks46vP0TG" + "uJZnEKeWBzLKUs9Tw/UgyIm3m/UEz7V8dgEjLVibrM6p1uJj4lwndB0/bGLLuxXZ", + "r7y/f32kGrBAjjlpwuLNzyM0OL8XdKzncJpPCR6ag/3q4OxV6yM6m1OUyzx4VrVG" ] } ], "signature_aggregate": "K3x4Irpk+Eq0PKDG5rkcH9O+iZBDQXnTr0SRo2kBLbk=", - "lock_hash": "hXEeMf1sE9fqoz7hTzbmTsf+EcCzN08rpNrSM5BHn3g=" + "lock_hash": "tTR/07EXBRNU9BE+wpjTNm1cfIMOWbKJhcjJmDpvq1s=" } \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_2_0.json b/cluster/testdata/cluster_lock_v1_2_0.json index cabdffadd..41ada774f 100644 --- a/cluster/testdata/cluster_lock_v1_2_0.json +++ b/cluster/testdata/cluster_lock_v1_2_0.json @@ -36,13 +36,13 @@ ] }, { - "distributed_public_key": "0xfd69d22ae5411947cb553d7694267aef4ebcea406b32d6108bd68584f57e37caac6e33feaa3263a399437024ba9c9b14", + "distributed_public_key": "0x143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193f", "public_shares": [ - "0x678a274f01a910ae295f6efbfe5f5abf44ccde263b5606633e2bf0006f28295d7d39069f01a239c4365854c3af7f6b41", - "0xd631f92b9a8d12f41257325fff332f7576b0620556304a3e3eae14c28d0cea39d2901a52720da85ca1e4b38eaf3f44c6" + "0xb8966710a7960732ca52cf53c3f520c889b79bf504cfb57c7601232d589baccea9d6e263e25c27741d3f6c62cbbb15d9", + "0xafbcbf7f7da41ab0408e3969c2e2cdcf233438bf1774ace7709a4f091e9a83fdeae0ec55eb233a9b5394cb3c7856b546" ] } ], "signature_aggregate": "0x2b7c7822ba64f84ab43ca0c6e6b91c1fd3be8990434179d3af4491a369012db9", - "lock_hash": "0xd619af0d79e8538917bc59b0833eb777630bc44adaf00403f5f13a24c728c05b" + "lock_hash": "0xc651a8b9ee937aab2272c286ef7107147d5dd603a8900150f877af506733cf69" } \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_3_0.json b/cluster/testdata/cluster_lock_v1_3_0.json index 17f216ea3..5805d7199 100644 --- a/cluster/testdata/cluster_lock_v1_3_0.json +++ b/cluster/testdata/cluster_lock_v1_3_0.json @@ -36,13 +36,13 @@ ] }, { - "distributed_public_key": "0xfd69d22ae5411947cb553d7694267aef4ebcea406b32d6108bd68584f57e37caac6e33feaa3263a399437024ba9c9b14", + "distributed_public_key": "0x143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193f", "public_shares": [ - "0x678a274f01a910ae295f6efbfe5f5abf44ccde263b5606633e2bf0006f28295d7d39069f01a239c4365854c3af7f6b41", - "0xd631f92b9a8d12f41257325fff332f7576b0620556304a3e3eae14c28d0cea39d2901a52720da85ca1e4b38eaf3f44c6" + "0xb8966710a7960732ca52cf53c3f520c889b79bf504cfb57c7601232d589baccea9d6e263e25c27741d3f6c62cbbb15d9", + "0xafbcbf7f7da41ab0408e3969c2e2cdcf233438bf1774ace7709a4f091e9a83fdeae0ec55eb233a9b5394cb3c7856b546" ] } ], "signature_aggregate": "0x2b7c7822ba64f84ab43ca0c6e6b91c1fd3be8990434179d3af4491a369012db9", - "lock_hash": "0xb58b5915d843653cc08e061d4a9f41aaced6e7ad2e6ec01617400225c210ead6" + "lock_hash": "0xc7445a02c905718926909f7ce41477608e5168c5fa17f96fe2446b4cd6ef4520" } \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_4_0.json b/cluster/testdata/cluster_lock_v1_4_0.json index 780f53af0..77a6e50ce 100644 --- a/cluster/testdata/cluster_lock_v1_4_0.json +++ b/cluster/testdata/cluster_lock_v1_4_0.json @@ -40,13 +40,13 @@ ] }, { - "distributed_public_key": "0xfd69d22ae5411947cb553d7694267aef4ebcea406b32d6108bd68584f57e37caac6e33feaa3263a399437024ba9c9b14", + "distributed_public_key": "0x143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522b5ffa17604193f", "public_shares": [ - "0x678a274f01a910ae295f6efbfe5f5abf44ccde263b5606633e2bf0006f28295d7d39069f01a239c4365854c3af7f6b41", - "0xd631f92b9a8d12f41257325fff332f7576b0620556304a3e3eae14c28d0cea39d2901a52720da85ca1e4b38eaf3f44c6" + "0xb8966710a7960732ca52cf53c3f520c889b79bf504cfb57c7601232d589baccea9d6e263e25c27741d3f6c62cbbb15d9", + "0xafbcbf7f7da41ab0408e3969c2e2cdcf233438bf1774ace7709a4f091e9a83fdeae0ec55eb233a9b5394cb3c7856b546" ] } ], "signature_aggregate": "0x2b7c7822ba64f84ab43ca0c6e6b91c1fd3be8990434179d3af4491a369012db9", - "lock_hash": "0xe1c9c14a0ba1fabdfe0f01942585e661ed0d786d8404e52c62a99ed345c33e2c" + "lock_hash": "0xf257f203f95fbe0e2af304013d39c69a950398f2020ff27c0edb44ed8cf25a55" } \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_5_0.json b/cluster/testdata/cluster_lock_v1_5_0.json index efbbe2c72..610c6a2d8 100644 --- a/cluster/testdata/cluster_lock_v1_5_0.json +++ b/cluster/testdata/cluster_lock_v1_5_0.json @@ -48,13 +48,13 @@ ] }, { - "distributed_public_key": "0xd2901a52720da85ca1e4b38eaf3f44c6c6ef8362f2f54fc00e09d6fc25640854c15dfcacaa8a2cecce5a3aba53ab705b", + "distributed_public_key": "0xeae0ec55eb233a9b5394cb3c7856b546d313c8a3b4c1c0e05447f4ba370eb36dbcfdec90b302dcdc3b9ef522e2a6f1ed", "public_shares": [ - "0x18db94b4d338a5143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c4160f38ee9e2a9f3fb4ffb0019b454d522", - "0xb5ffa17604193fb8966710a7960732ca52cf53c3f520c889b79bf504cfb57c7601232d589baccea9d6e263e25c27741d" + "0x0afec1f8e20faabedf6b162e717d3a748a58677a0c56348f8921a266b11d0f334c62fe52ba53af19779cb2948b6570ff", + "0xa0b773963c130ad797ddeafe4e3ad29b5125210f0ef1c314090f07c79a6f571c246f3e9ac0b7413ef110bd58b00ce73b" ] } ], "signature_aggregate": "0x85650c30ec29a3703934bf50a28da102975deda77e758579ea3dfe4136abf752", - "lock_hash": "0xd2880980169ee4a0000f23feb8fad9a6c70f38312956fe67aa89e118f5b0e048" + "lock_hash": "0x902b7afc390e00a5aeb428d6fa17d5c8341082b38d1eb1514440c2f9be743c82" } \ No newline at end of file diff --git a/cluster/testdata/cluster_lock_v1_6_0.json b/cluster/testdata/cluster_lock_v1_6_0.json new file mode 100644 index 000000000..24a699d13 --- /dev/null +++ b/cluster/testdata/cluster_lock_v1_6_0.json @@ -0,0 +1,72 @@ +{ + "cluster_definition": { + "name": "test definition", + "creator": { + "address": "0x6bf84c7174cb7476364cc3dbd968b0f7172ed857", + "config_signature": "0xff094279db1944ebd7a19d0f7bbacbe0255aa5b7d44bec40f84c892b9bffd43629b0223beea5f4f74391f445d15afd4294040374f6924b98cbf8713f8d962d7c1c" + }, + "operators": [ + { + "address": "0x019192c24224e2cafccae3a61fb586b14323a6bc", + "enr": "enr://ea6f5b3af6de0374366c4719e43a1b067d89bc7f01f1f573981659a44ff17a4c", + "config_signature": "0x7215a3b539eb1e5849c6077dbb5722f5717a289a266f97647981998ebea89c0b4b373970115e82ed6f4125c8fa7311e4d7defa922daae7786667f7e936cd4f241b", + "enr_signature": "0xf7df866baa56038367ad6145de1ee8f4a8b0993ebdf8883a0ad8be9c3978b04883e56a156a8de563afa467d49dec6a40e9a1d007f033c2823061bdd0eaa59f8e1c" + }, + { + "address": "0xa6430105220d0b29688b734b8ea0f3ca9936e846", + "enr": "enr://f6a63b7f3dfd2567c18979e4d60f26686d9bf2fb26c901ff354cde1607ee294b", + "config_signature": "0x39f32b7c7822ba64f84ab43ca0c6e6b91c1fd3be8990434179d3af4491a369012db92d184fc39d1734ff5716428953bb6865fcf92b0c3a17c9028be9914eb7641c", + "enr_signature": "0x6c9347800979d1830356f2a54c3deab2a4b4475d63afbe8fb56987c77f5818526f1814be823350eab13935f31d84484517e924aef78ae151c00755925836b7071b" + } + ], + "uuid": "0194FDC2-FA2F-FCC0-41D3-FF12045B73C8", + "version": "v1.6.0", + "timestamp": "2022-07-19T18:19:58+02:00", + "num_validators": 2, + "threshold": 3, + "validators": [ + { + "fee_recipient_address": "0x52fdfc072182654f163f5f0f9a621d729566c74d", + "withdrawal_address": "0x81855ad8681d0d86d1e91e00167939cb6694d2c4" + }, + { + "fee_recipient_address": "0xeb9d18a44784045d87f3c67cf22746e995af5a25", + "withdrawal_address": "0x5fb90badb37c5821b6d95526a41a9504680b4e7c" + } + ], + "dkg_algorithm": "default", + "fork_version": "0x90000069", + "config_hash": "0x63adb22a10930e78013c6fbb72dfc877346d455a9401a65cf58ab5c33f725c9d", + "definition_hash": "0x898990feec5df2288bcebe6b4e3474a2412dc8d49147d5353daddd7135353191" + }, + "distributed_validators": [ + { + "distributed_public_key": "0xb3b8271d03e944b3c9db366b75045f8efd69d22ae5411947cb553d7694267aef4ebcea406b32d6108bd68584f57e37ca", + "public_shares": [ + "0xac6e33feaa3263a399437024ba9c9b14678a274f01a910ae295f6efbfe5f5abf44ccde263b5606633e2bf0006f28295d", + "0x7d39069f01a239c4365854c3af7f6b41d631f92b9a8d12f41257325fff332f7576b0620556304a3e3eae14c28d0cea39" + ], + "deposit_data": { + "pubkey": "0xd2901a52720da85ca1e4b38eaf3f44c6c6ef8362f2f54fc00e09d6fc25640854c15dfcacaa8a2cecce5a3aba53ab705b", + "withdrawal_credentials": "0x18db94b4d338a5143e63408d8724b0cf3fae17a3f79be1072fb63c35d6042c41", + "amount": "6924566981437551529", + "signature": "0x60f38ee9e2b454d522b5ffa17604193fb8966710a7960732ca52cf53c3f520c889b79bf504cfb57c7601232d589baccea9d6e263e25c27741d3f6c62cbbb15d9afbcbf7f7da41ab0408e3969c2e2cdcf233438bf1774ace7709a4f091e9a83fd" + } + }, + { + "distributed_public_key": "0xeae0ec55eb233a9b5394cb3c7856b546d313c8a3b4c1c0e05447f4ba370eb36dbcfdec90b302dcdc3b9ef522e2a6f1ed", + "public_shares": [ + "0x0afec1f8e20faabedf6b162e717d3a748a58677a0c56348f8921a266b11d0f334c62fe52ba53af19779cb2948b6570ff", + "0xa0b773963c130ad797ddeafe4e3ad29b5125210f0ef1c314090f07c79a6f571c246f3e9ac0b7413ef110bd58b00ce73b" + ], + "deposit_data": { + "pubkey": "0xff706f7ff4b6f44090a32711f3208e4e4b89cb5165ce64002cbd9c2887aa113df2468928d5a23b9ca740f80c9382d9c6", + "withdrawal_credentials": "0x034ad2960c796503e1ce221725f50caf1fbfe831b10b7bf5b15c47a53dbf8e7d", + "amount": "118298131398851786", + "signature": "0xb44ed4bce964ed47f74aa594468ced323cb76f0d3fac476c9fb03fc9228fbae88fd580663a0454b68312207f0a3b584c62316492b49753b5d5027ce15a4f0a58250d8fb50e77f2bf4f0152e5d49435807f9d4b97be6fb77970466a5626fe3340" + } + } + ], + "signature_aggregate": "0x85650c30ec29a3703934bf50a28da102975deda77e758579ea3dfe4136abf752", + "lock_hash": "0x1c29a86a308f2b2ad45b5bec3d9df3531a7d47e97428ad707695ecb481e5627a" +} \ No newline at end of file diff --git a/cluster/version.go b/cluster/version.go index 38e42d172..40071cd28 100644 --- a/cluster/version.go +++ b/cluster/version.go @@ -18,11 +18,12 @@ package cluster import "testing" const ( - currentVersion = v1_4 + currentVersion = v1_5 dkgAlgo = "default" - v1_5 = "v1.5.0" // Draft - v1_4 = "v1.4.0" // Default + v1_6 = "v1.6.0" // Draft + v1_5 = "v1.5.0" // Default + v1_4 = "v1.4.0" v1_3 = "v1.3.0" v1_2 = "v1.2.0" v1_1 = "v1.1.0" @@ -32,6 +33,7 @@ const ( ) var supportedVersions = map[string]bool{ + v1_6: true, v1_5: true, v1_4: true, v1_3: true, @@ -50,30 +52,10 @@ func isAnyVersion(version string, versions ...string) bool { return false } -func isV1x0(version string) bool { - return version == v1_0 -} - -func isV1x1(version string) bool { - return version == v1_1 -} - -func isV1x2(version string) bool { - return version == v1_2 -} - func isV1x3(version string) bool { return version == v1_3 } -func isV1x4(version string) bool { - return version == v1_4 -} - -func isV1x5(version string) bool { - return version == v1_5 -} - // SupportedVersionsForT returns the supported definition versions for testing purposes only. func SupportedVersionsForT(*testing.T) []string { var resp []string diff --git a/testutil/random.go b/testutil/random.go index 0263ac8ff..267d27308 100644 --- a/testutil/random.go +++ b/testutil/random.go @@ -682,6 +682,13 @@ func RandomETHAddress() string { return fmt.Sprintf("%#x", RandomBytes32()[:20]) } +func RandomBytes96() []byte { + var resp [96]byte + _, _ = rand.Read(resp[:]) + + return resp[:] +} + func RandomBytes48() []byte { var resp [48]byte _, _ = rand.Read(resp[:])