-
Notifications
You must be signed in to change notification settings - Fork 79
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dkg: generate deposit data #491
Conversation
testutil/eth2/deposit_data.go
Outdated
@@ -0,0 +1,79 @@ | |||
package eth2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@xenowits why is this in testutil? isn't it used in the normal DKG process?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked him to put it here for now, next to keystore
, we need to move them both and some other "eth2" related utils somewhere else but I'm not sure where. Waiting for a pattern to emerge.
@xenowits please rename package to deposits
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@corverroos don't you think we should avoid the eth2 term as it has become unofficial? Perhaps instead of using eth2 we could use ECL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as discussed, we are sticking with eth2 for now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
commented
testutil/eth2/deposit_data.go
Outdated
@@ -0,0 +1,79 @@ | |||
package eth2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I asked him to put it here for now, next to keystore
, we need to move them both and some other "eth2" related utils somewhere else but I'm not sure where. Waiting for a pattern to emerge.
@xenowits please rename package to deposits
go.mod
Outdated
@@ -22,6 +22,7 @@ require ( | |||
github.com/multiformats/go-multiaddr v0.5.0 | |||
github.com/prometheus/client_golang v1.12.1 | |||
github.com/prysmaticlabs/go-bitfield v0.0.0-20210809151128-385d8c5e3fb7 | |||
github.com/prysmaticlabs/prysm/v2 v2.0.1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would avoid importing prysm, it is a HUGE dependency. And we do not need it actually.
testutil/eth2/deposit_data.go
Outdated
} | ||
|
||
domainType := [4]byte{1, 2, 3, 4} | ||
domain, err := signing.ComputeDomain(domainType, nil, nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already have this logic, see validatorapi.GetDomain()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might beed to dig into how eth2client
does it and copy inner logic since genesis and forkversion is nil in this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is forkversion nil in this case?
Mainnet started with genesis_fork_version of 0x00000000, but I think some testnets started with a non-zero genesis fork version, if that's where the nil is coming from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is how prysm does it, probably in die spec somewhere. Will confirm
testutil/eth2/deposit_data.go
Outdated
} | ||
|
||
// GenerateDepositData generates a deposit data object by populating the required fields. | ||
func GenerateDepositData(def cluster.Definition, pubkey string) (DepositData, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pass in only what you need. You only use withdrawal address and forkversion from the definition, rather just pass them in directly.
testutil/eth2/deposit_data.go
Outdated
case "00000064": | ||
return "gnosis" | ||
default: | ||
return "mainnet" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure defaulting to mainnet is correct. Rather default to "unknown"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm not sure either but sure will default to unknown
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No no! Jim McDonald and others pushed to change defaults to mainnet instead of testnet, to reduce the chance of an invalid deposit on mainnet. An unknown/non-mainnet default could blow up lots of money. Sending a mainnet deposit data to testnet blows up monopoly money. (I once blew up 120 testnet ether when Jim made this change. Before then ethdo pulled fork_version from connected beacon node, now it defaults to mainnet and you have to explicitly tell it to do something else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok sure, mainnet default
testutil/eth2/deposit_data.go
Outdated
|
||
domainType := [4]byte{1, 2, 3, 4} | ||
domain, err := signing.ComputeDomain(domainType, nil, nil) | ||
depositMessageRoot, err := signing.ComputeSigningRoot(depositMessage, domain) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we also alreadyhave this, see validatorapi.prepSigningData
. You can just copy it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please, don't copy/paste code if it is possible
testutil/eth2/deposit_data.go
Outdated
// GenerateDepositData generates a deposit data object by populating the required fields. | ||
func GenerateDepositData(def cluster.Definition, pubkey string) (DepositData, error) { | ||
amount := uint64(32000000000) // 32 Ether in Gwei | ||
depositMessage := DepositMessage{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggest making DepositMessage unexported, and adding a HashTreeRoot()
function to it. See prysm, cluster.Lock
and cluster.Defintion
how to implement HashTreeRoot
testutil/eth2/deposit_data.go
Outdated
return DepositData{}, errors.Wrap(err, "compute domain") | ||
} | ||
|
||
depositDataRoot := "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here depositDataRoot needs to be calcualted by a method you need to add to DepositData
called HashTreeRoot
, see prsym which fields and what order to hash them.
testutil/eth2/deposit_data.go
Outdated
} | ||
|
||
// GenerateDepositData generates a deposit data object by populating the required fields. | ||
func GenerateDepositData(def cluster.Definition, pubkey string) (DepositData, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
always use proper types, use eth2p0.PublicKey not string
testutil/eth2/deposit_data.go
Outdated
deposit_data := DepositData{ | ||
PubKey: pubkey, | ||
Amount: amount, | ||
WithdrawalCredentials: def.WithdrawalAddress, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that withdrawal credentials isn't an address, is looks different to me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct, they need to be slightly converted
See the spec here https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#eth1_address_withdrawal_prefix
And an example here https://discord.com/channels/930779837218562078/932001790763864094/970934579563204688
We should only support type 0x01 for creation, because they are the only type that exits will be supported for (split-keys shouldn't care/know what the withdrawal creds are)
testutil/eth2/deposit_data.go
Outdated
} | ||
|
||
depositDataRoot := "" | ||
signature := "" // sign the depositMsgRoot with the bls key |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
signing in a distributed validator is not trivial, so we need to restructure this logic, you cannot sign "inside a function".
Suggest splitting into two functions:
deposits.GetMessageRoot(ctx, eth2Cl, pubkey, withdrawalPubKey) eth2p0.Root, error
- We will then threshold sign this sig root, and aggregate it to get a the signature, and only then make deposit data.
deposits.GetDepositData(pubkey, withdrawalPubKey, forkversion, messageRoot, messageSig) DepositData, error
testutil/eth2/deposit_message.go
Outdated
idx := hh.Index() | ||
|
||
// Field 0 'PubKey` | ||
hh.PutBytes([]byte(d.PubKey)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is going to fail, since the type of pubkey is a string, so it is really unclear what format should be. Rather make it []byte
then we know it is a standard public key bytes format
testutil/eth2/deposit_message.go
Outdated
return b, nil | ||
} | ||
|
||
func (d DepositMessage) HashTreeRootWith(hh *ssz.Hasher) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please add tests for this, try to find reference data in prysm. Ie. given a message with values X,Y,Z, we know the hash should be something
testutil/eth2/deposit_message.go
Outdated
hh.PutUint64(d.Amount) | ||
|
||
// Field 2 'WithdrawalCredentials' | ||
hh.PutBytes([]byte(d.WithdrawalCredentials)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, this should be bytes, also please reference the format spec.
…owits/add-deposit-data
…owits/add-deposit-data
eth2util/deposit/deposit_message.go
Outdated
"github.com/obolnetwork/charon/app/z" | ||
) | ||
|
||
const ETH1_ADDRESS_WITHDRAWAL_PREFIX = "01" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do not export types/interfaces/variables/constants by default, always start unexported
eth2util/deposit/deposit_message.go
Outdated
|
||
withdrawalCreds := make([]byte, 0, 32) | ||
|
||
prefix, err := hex.DecodeString(ETH1_ADDRESS_WITHDRAWAL_PREFIX) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rather make the constant a []byte, then no need to convert
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok. made it a single byte: byte(1)
eth2util/deposit/deposit_message.go
Outdated
return WithdrawalCredentials{}, errors.New("invalid withdrawal address", z.Str("address", addr)) | ||
} | ||
|
||
withdrawalCreds := make([]byte, 0, 32) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
introduces variables just before they are used
canonical slice instantiation: var creds []byte
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
avoid micro optimisations (adding length to slice). It draws attention ... to nothing in this case.
Only do micro optimisations if really required, like in some hot path.
eth2util/deposit/deposit_message.go
Outdated
// Append the single byte ETH1_ADDRESS_WITHDRAWAL_PREFIX as prefix. | ||
withdrawalCreds = append(withdrawalCreds, prefix...) | ||
|
||
zeroes, err := hex.DecodeString("0000000000000000000000") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, make this a global var elevenZeros = []byte{0x00,0x00, ...}
eth2util/deposit/deposit_message.go
Outdated
withdrawalCreds = append(withdrawalCreds, zeroes...) | ||
|
||
// Finally, append 20 bytes of ethereum address. | ||
withdrawalCreds = append(withdrawalCreds, addr...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you are appending a hex string to a byte slice...
Codecov Report
@@ Coverage Diff @@
## main #491 +/- ##
==========================================
+ Coverage 55.87% 56.44% +0.57%
==========================================
Files 84 87 +3
Lines 7578 7979 +401
==========================================
+ Hits 4234 4504 +270
- Misses 2770 2874 +104
- Partials 574 601 +27
Continue to review full report at Codecov.
|
eth2util/deposit/deposit_data.go
Outdated
var version eth2p0.Version | ||
copy(version[:], forkVersion) | ||
|
||
depositData := DepositData{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inline this: return DepositData{...}, nil
eth2util/deposit/deposit_data.go
Outdated
} | ||
|
||
// GenerateDepositData generates a deposit data object by populating the required fields. | ||
func GenerateDepositData(addr string, forkVersion string, pubkey eth2p0.BLSPubKey) (DepositData, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: NewDepositData
Also add signature as parameter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another idea is to skip the DepositData
struct and go stright to raw json bytes:
// MarshalDepositData returns the serialised json deposit data bytes to be written to disk.
func MarshalDepositData(pubkey eth2p0.BLSPubKey, withdrawalAddr string, forkVersion string, signature []byte) []byte, error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the idea to skip the structs obviously seems very simple. But, i think that'd be oversimplifying it. We're not doing anything complex here so we can keep the types for the time being. (Pro tip: makes tree hashing easier)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we can get rid of the unmarshal functions though since we only need to marshal and save to disk
eth2util/deposit/deposit_message.go
Outdated
|
||
// WithdrawalCredentials is the 0x01 withdrawal credentials. See spec: | ||
// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/validator.md#withdrawal-credentials | ||
type WithdrawalCredentials [32]byte |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
type not required, can just use [32]byte
directly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wouldn't oppose a type, and maybe an assertion that the first byte is 01
, along with maybe some 0x prepend handling.
Overkill though?
eth2util/deposit/deposit_message.go
Outdated
// Append 11 bytes of 0. | ||
withdrawalCreds = append(withdrawalCreds, elevenZeroes...) | ||
|
||
addrBytes, err := hex.DecodeString(addr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might have "0x" prefix, trim it first: addr = strings.TrimPrefix(addr, "0x")
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this appending or prepending the zeros? Because it should be at the start of the string.
E.g. 0x01 + 00000000000 + %s
return string(credentials[12:]), nil | ||
} | ||
|
||
func (d depositMessage) HashTreeRoot() ([32]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
colocate type and its methods
eth2util/deposit/deposit_data.go
Outdated
} | ||
|
||
// ddFmt is the json formatter for DepositData. | ||
type ddFmt struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: ddJSON
eth2util/deposit/deposit_data.go
Outdated
|
||
// ddFmt is the json formatter for DepositData. | ||
type ddFmt struct { | ||
PubKey []byte `json:"pubkey"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[]byte
is encoded as base64, so this needs to be string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
all hex fields should be string
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other option is to create your own wrapper type called type hex []byte
Then implement json.Marshaller and json.Unmarshaller to does hex conversion.
Then just wrap the byte:
json.Marshal(ddJson{
Pubkey: hex(pkBytes),
Signature: hex(sig),
})
eth2util/deposit/deposit_data.go
Outdated
if err != nil { | ||
return DepositData{}, err | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO(): verify the signature
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
verify sig using: pubkey == crypto.SigToPub(depositMessageRoot[:], sig)
} | ||
|
||
// MarshalDepositData returns the json serialised deposit data bytes to be written to disk. | ||
func MarshalDepositData(pubkey eth2p0.BLSPubKey, msgRoot eth2p0.Root, sig eth2p0.BLSSignature, withdrawalAddr, forkVersion string) ([]byte, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rather remove msgRoot, since you can calculate it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm thinking along the lines of if we have a sig
(that's calculated on msgRoot
), we already have a msgRoot
. So, we could have that as an input parameter.
eth2util/deposit/deposit_data.go
Outdated
hh.PutUint64(uint64(d.Amount)) | ||
|
||
// Field 2 'Eth1WithdrawalAddress' | ||
hh.PutBytes([]byte(d.Eth1WithdrawalAddress)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should probably be credentials, not address
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct, also changed the ordering of fields according to spec: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#depositdata
eth2util/deposit/deposit_data.go
Outdated
|
||
// construct DepositData and then calculate the hash. | ||
var version eth2p0.Version | ||
copy(version[:], forkVersion) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
forkVersion is hex string, you need to convert to bytes first
eth2util/deposit/deposit_data.go
Outdated
const depositAmt = 32000000000 | ||
|
||
// DepositData contains all the information required for activating validators on the Ethereum Network. | ||
type DepositData struct { //nolint:revive |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unexport this
DepositData
andDepositMessage
.HashTreeRoot
methods on above types.DepositData
category: feature
ticket: #481
feature_set: alpha