Skip to content

Commit

Permalink
cmd: --deposit-amounts flag (#2820)
Browse files Browse the repository at this point in the history
Added `--deposit-amounts` flag to cmd interface for `create cluster` and `dkg` commands in according with the spec.

category: feature
ticket: none
  • Loading branch information
pinebit authored Jan 29, 2024
1 parent 0e79a55 commit f929f59
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 1 deletion.
15 changes: 14 additions & 1 deletion cmd/createcluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type clusterConfig struct {
Network string
NumDVs int

DepositAmounts []int // Amounts specified in ETH (integers).

SplitKeys bool
SplitKeysDir string

Expand All @@ -86,7 +88,7 @@ func newCreateClusterCmd(runFunc func(context.Context, io.Writer, clusterConfig)
cmd := &cobra.Command{
Use: "cluster",
Short: "Create private keys and configuration files needed to run a distributed validator cluster locally",
Long: "Creates a local charon cluster configuration including validator keys, charon p2p keys, cluster-lock.json and a deposit-data.json. " +
Long: "Creates a local charon cluster configuration including validator keys, charon p2p keys, cluster-lock.json and deposit-data.json file(s). " +
"See flags for supported features.",
RunE: func(cmd *cobra.Command, args []string) error {
return runFunc(cmd.Context(), cmd.OutOrStdout(), conf)
Expand Down Expand Up @@ -119,6 +121,7 @@ func bindClusterFlags(flags *pflag.FlagSet, config *clusterConfig) {
flags.StringVar(&config.testnetConfig.GenesisForkVersionHex, "testnet-fork-version", "", "Genesis fork version of the custom test network (in hex).")
flags.Uint64Var(&config.testnetConfig.ChainID, "testnet-chain-id", 0, "Chain ID of the custom test network.")
flags.Int64Var(&config.testnetConfig.GenesisTimestamp, "testnet-genesis-timestamp", 0, "Genesis timestamp of the custom test network.")
flags.IntSliceVar(&config.DepositAmounts, "deposit-amounts", nil, "List of partial deposit amounts (integers) in ETH. Values must sum up to exactly 32ETH.")
}

func bindInsecureFlags(flags *pflag.FlagSet, insecureKeys *bool) {
Expand Down Expand Up @@ -321,6 +324,16 @@ func validateCreateConfig(ctx context.Context, conf clusterConfig) error {
return errors.New("number of --keymanager-addresses do not match --keymanager-auth-tokens. Please fix configuration flags")
}

if len(conf.DepositAmounts) > 0 {
amounts := deposit.EthsToGweis(conf.DepositAmounts)

if err := deposit.VerifyDepositAmounts(amounts); err != nil {
return err
}

log.Warn(ctx, "Partial deposits feature is under development. The --deposit-amounts flag has no effect yet.", nil)
}

for _, addr := range conf.KeymanagerAddrs {
keymanagerURL, err := url.ParseRequestURI(addr)
if err != nil {
Expand Down
20 changes: 20 additions & 0 deletions cmd/createcluster_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ func TestCreateCluster(t *testing.T) {
Network: eth2util.Goerli.Name,
},
},
{
Name: "two partial deposits",
Config: clusterConfig{
NumNodes: 4,
Threshold: 3,
NumDVs: 1,
Network: eth2util.Goerli.Name,
DepositAmounts: []int{31, 1},
},
},
{
Name: "four partial deposits",
Config: clusterConfig{
NumNodes: 4,
Threshold: 3,
NumDVs: 1,
Network: eth2util.Goerli.Name,
DepositAmounts: []int{8, 8, 8, 8},
},
},
{
Name: "splitkeys",
Config: clusterConfig{
Expand Down
22 changes: 22 additions & 0 deletions cmd/testdata/TestCreateCluster_four_partial_deposits_files.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
"node0",
"node1",
"node2",
"node3",
"node0/charon-enr-private-key",
"node0/cluster-lock.json",
"node0/deposit-data.json",
"node0/validator_keys",
"node1/charon-enr-private-key",
"node1/cluster-lock.json",
"node1/deposit-data.json",
"node1/validator_keys",
"node2/charon-enr-private-key",
"node2/cluster-lock.json",
"node2/deposit-data.json",
"node2/validator_keys",
"node3/charon-enr-private-key",
"node3/cluster-lock.json",
"node3/deposit-data.json",
"node3/validator_keys"
]
11 changes: 11 additions & 0 deletions cmd/testdata/TestCreateCluster_four_partial_deposits_output.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Created charon cluster:
--split-existing-keys=false

charon/
├─ node[0-3]/ Directory for each node
│ ├─ charon-enr-private-key Charon networking private key for node authentication
│ ├─ cluster-lock.json Cluster lock defines the cluster lock file which is signed by all nodes
│ ├─ deposit-data.json Deposit data file is used to activate a Distributed Validator on DV Launchpad
│ ├─ validator_keys Validator keystores and password
│ │ ├─ keystore-*.json Validator private share key for duty signing
│ │ ├─ keystore-*.txt Keystore password files for keystore-*.json
22 changes: 22 additions & 0 deletions cmd/testdata/TestCreateCluster_two_partial_deposits_files.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[
"node0",
"node1",
"node2",
"node3",
"node0/charon-enr-private-key",
"node0/cluster-lock.json",
"node0/deposit-data.json",
"node0/validator_keys",
"node1/charon-enr-private-key",
"node1/cluster-lock.json",
"node1/deposit-data.json",
"node1/validator_keys",
"node2/charon-enr-private-key",
"node2/cluster-lock.json",
"node2/deposit-data.json",
"node2/validator_keys",
"node3/charon-enr-private-key",
"node3/cluster-lock.json",
"node3/deposit-data.json",
"node3/validator_keys"
]
11 changes: 11 additions & 0 deletions cmd/testdata/TestCreateCluster_two_partial_deposits_output.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Created charon cluster:
--split-existing-keys=false

charon/
├─ node[0-3]/ Directory for each node
│ ├─ charon-enr-private-key Charon networking private key for node authentication
│ ├─ cluster-lock.json Cluster lock defines the cluster lock file which is signed by all nodes
│ ├─ deposit-data.json Deposit data file is used to activate a Distributed Validator on DV Launchpad
│ ├─ validator_keys Validator keystores and password
│ │ ├─ keystore-*.json Validator private share key for duty signing
│ │ ├─ keystore-*.txt Keystore password files for keystore-*.json
45 changes: 45 additions & 0 deletions eth2util/deposit/deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import (
"github.com/obolnetwork/charon/tbls"
)

const (
minSingleDepositAmountGwei = 1000000000 // 1ETH
maxTotalDepositAmountGwei = 32000000000 // 32ETH
oneEthInGwei = 1000000000 // 1ETH in Gwei
)

var (
// Minimum allowed deposit amount (1ETH).
MinValidatorAmount = eth2p0.Gwei(1000000000)
Expand Down Expand Up @@ -197,3 +203,42 @@ type depositDataJSON struct {
NetworkName string `json:"network_name"`
DepositCliVersion string `json:"deposit_cli_version"`
}

// VerifyDepositAmounts verifies various conditions about partial deposits rules.
func VerifyDepositAmounts(amounts []eth2p0.Gwei) error {
if len(amounts) == 0 {
// If no partial amounts specified, the implementation shall default to 32ETH.
return nil
}

var sum eth2p0.Gwei
for _, amount := range amounts {
if amount < minSingleDepositAmountGwei {
return errors.New("each partial deposit amount must be greater than 1ETH", z.U64("amount", uint64(amount)))
}

sum += amount
}

if sum > eth2p0.Gwei(maxTotalDepositAmountGwei) {
return errors.New("sum of partial deposit amounts must sum up to 32ETH", z.U64("sum", uint64(sum)))
}

return nil
}

// EthsToGweis converts amounts from []int (ETH) to []eth2p0.Gwei.
// For verification, please see VerifyDepositAmounts().
func EthsToGweis(ethAmounts []int) []eth2p0.Gwei {
if ethAmounts == nil {
return nil
}

var gweiAmounts []eth2p0.Gwei
for _, ethAmount := range ethAmounts {
gwei := eth2p0.Gwei(oneEthInGwei * ethAmount)
gweiAmounts = append(gweiAmounts, gwei)
}

return gweiAmounts
}
58 changes: 58 additions & 0 deletions eth2util/deposit/deposit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,61 @@ func GetKeys(t *testing.T, privKey string) (tbls.PrivateKey, eth2p0.BLSPubKey) {

return sk, pubkey
}

func TestVerifyDepositAmounts(t *testing.T) {
t.Run("empty slice", func(t *testing.T) {
err := deposit.VerifyDepositAmounts(nil)

require.NoError(t, err)
})

t.Run("valid amounts", func(t *testing.T) {
amounts := []eth2p0.Gwei{
eth2p0.Gwei(16000000000),
eth2p0.Gwei(16000000000),
}

err := deposit.VerifyDepositAmounts(amounts)

require.NoError(t, err)
})

t.Run("each amount is greater than 1ETH", func(t *testing.T) {
amounts := []eth2p0.Gwei{
eth2p0.Gwei(500000000), // 0.5ETH
eth2p0.Gwei(31500000000), // 31.5ETH
}

err := deposit.VerifyDepositAmounts(amounts)

require.ErrorContains(t, err, "each partial deposit amount must be greater than 1ETH")
})

t.Run("total sum is 32ETH", func(t *testing.T) {
amounts := []eth2p0.Gwei{
eth2p0.Gwei(1000000000),
eth2p0.Gwei(32000000000),
}

err := deposit.VerifyDepositAmounts(amounts)

require.ErrorContains(t, err, "sum of partial deposit amounts must sum up to 32ETH")
})
}

func TestEthsToGweis(t *testing.T) {
t.Run("nil slice", func(t *testing.T) {
slice := deposit.EthsToGweis(nil)

require.Nil(t, slice)
})

t.Run("values", func(t *testing.T) {
slice := deposit.EthsToGweis([]int{1, 5})

require.Equal(t, []eth2p0.Gwei{
eth2p0.Gwei(1000000000),
eth2p0.Gwei(5000000000),
}, slice)
})
}

0 comments on commit f929f59

Please sign in to comment.