Skip to content

Commit

Permalink
2/2 chore(cli): improve tooling around deposits (#2392)
Browse files Browse the repository at this point in the history
  • Loading branch information
calbera authored Jan 23, 2025
1 parent 890fd41 commit 419cd66
Show file tree
Hide file tree
Showing 7 changed files with 200 additions and 88 deletions.
38 changes: 26 additions & 12 deletions cli/commands/deposit/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,33 @@ import (
)

const (
createAddr0 = iota
createAmt1 = iota
createRoot2 = iota
createArgsCount = iota
createAddr0 = iota
createAmt1 = iota
createRoot2 = iota

overrideNodeKey = "override-node-key"
valPrivateKey = "validator-private-key"
minArgsCreateDeposit = 2
maxArgsCreateDeposit = 3

overrideNodeKey = "override-node-key"
valPrivateKey = "validator-private-key"
useGenesisValidatorRoot = "genesis-validator-root"

useGenesisValidatorRootShorthand = "g"

defaultGenesisValidatorRoot = ""
)

// NewCreateValidator creates a new command to create a validator deposit.
//
//nolint:lll // reads better if long description is one line
//nolint:lll // reads better if long description is one line.
func NewCreateValidator(
chainSpec chain.Spec,
) *cobra.Command {
cmd := &cobra.Command{
Use: "create-validator",
Use: "create-validator [withdrawal-address] [amount] ?[beacond/genesis.json]",
Short: "Creates a validator deposit",
Long: `Creates a validator deposit with the necessary credentials. The arguments are expected in the order of withdrawal address, deposit amount, and genesis validator root. If the broadcast flag is set to true, a private key must be provided to sign the transaction.`,
Args: cobra.ExactArgs(createArgsCount),
Long: `Creates a validator deposit with the necessary credentials. The arguments are expected in the order of withdrawal address, deposit amount, and optionally the beacond genesis file. If the genesis validator root flag is NOT set, the beacond genesis file MUST be provided as the last argument. If the broadcast flag is set to true, a private key must be provided to sign the transaction.`,
Args: cobra.RangeArgs(minArgsCreateDeposit, maxArgsCreateDeposit),
RunE: createValidatorCmd(chainSpec),
}

Expand All @@ -73,6 +80,12 @@ func NewCreateValidator(
"", // no default private key
"validator private key. This is required if the override-node-key flag is set.",
)
cmd.Flags().StringP(
useGenesisValidatorRoot,
useGenesisValidatorRootShorthand,
defaultGenesisValidatorRoot,
"Use the provided genesis validator root. If this is not set, the beacond genesis file must be provided manually as the last argument.",
)

return cmd
}
Expand Down Expand Up @@ -101,8 +114,9 @@ func createValidatorCmd(
return err
}

genValRootStr := args[createRoot2]
genesisValidatorRoot, err := parser.ConvertGenesisValidatorRoot(genValRootStr)
genesisValidatorRoot, err := getGenesisValidatorRoot(
cmd, chainSpec, args, maxArgsCreateDeposit,
)
if err != nil {
return err
}
Expand Down
55 changes: 55 additions & 0 deletions cli/commands/deposit/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// SPDX-License-Identifier: BUSL-1.1
//
// Copyright (C) 2025, Berachain Foundation. All rights reserved.
// Use of this software is governed by the Business Source License included
// in the LICENSE file of this repository and at www.mariadb.com/bsl11.
//
// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY
// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER
// VERSIONS OF THE LICENSED WORK.
//
// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF
// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF
// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE).
//
// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
// TITLE.

package deposit

import (
"github.com/berachain/beacon-kit/chain"
"github.com/berachain/beacon-kit/cli/utils/genesis"
"github.com/berachain/beacon-kit/cli/utils/parser"
"github.com/berachain/beacon-kit/errors"
"github.com/berachain/beacon-kit/primitives/common"
"github.com/spf13/cobra"
)

// Get the genesis validator root. If the genesis validator root flag is not set, the genesis
// validator root is computed from the genesis file at the last argument (idx: maxArgs - 1).
func getGenesisValidatorRoot(
cmd *cobra.Command, chainSpec chain.Spec, args []string, maxArgs int,
) (common.Root, error) {
var genesisValidatorRoot common.Root
genesisValidatorRootStr, err := cmd.Flags().GetString(useGenesisValidatorRoot)
if err != nil {
return common.Root{}, err
}

if genesisValidatorRootStr != defaultGenesisValidatorRoot {
genesisValidatorRoot, err = parser.ConvertGenesisValidatorRoot(genesisValidatorRootStr)
} else {
if len(args) != maxArgs {
return common.Root{}, errors.New(
"genesis validator root is required if not using the genesis file flag",
)
}
genesisValidatorRoot, err = genesis.ComputeValidatorsRootFromFile(args[maxArgs-1], chainSpec)
}

return genesisValidatorRoot, err
}
36 changes: 22 additions & 14 deletions cli/commands/deposit/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,36 +34,43 @@ import (
)

const (
validatePubKey0 = iota
validateCreds1 = iota
validateAmt2 = iota
validateSign3 = iota
validateRoot4 = iota
validateArgsCount = iota
validatePubKey0 = iota
validateCreds1 = iota
validateAmt2 = iota
validateSign3 = iota

minArgsValidateDeposit = 4
maxArgsValidateDeposit = 5
)

// NewValidateDeposit creates a new command for validating a deposit message.
//
//nolint:lll // reads better if long description is one line
func NewValidateDeposit(chainSpec chain.Spec) *cobra.Command {
cmd := &cobra.Command{
Use: "validate",
Use: "validate [pubkey] [withdrawal-credentials] [amount] [signature] ?[beacond/genesis.json]",
Short: "Validates a deposit message for creating a new validator",
Long: `Validates a deposit message for creating a new validator. The deposit message includes the public key, withdrawal credentials, and deposit amount. The args taken are in the order of the public key, withdrawal credentials, deposit amount, signature, and genesis validator root.`,
Args: cobra.ExactArgs(validateArgsCount),
Long: `Validates a deposit message (public key, withdrawal credentials, deposit amount) for creating a new validator. The args taken are in the order of the public key, withdrawal credentials, deposit amount, signature, and optionally the beacond genesis file. If the genesis validator root flag is NOT set, the beacond genesis file MUST be provided as the last argument.`,
Args: cobra.RangeArgs(minArgsValidateDeposit, maxArgsValidateDeposit),
RunE: validateDepositMessage(chainSpec),
}

cmd.Flags().StringP(
useGenesisValidatorRoot,
useGenesisValidatorRootShorthand,
defaultGenesisValidatorRoot,
"Use the provided genesis validator root. If this is not set, the beacond genesis file must be provided manually as the last argument.",
)

return cmd
}

// validateDepositMessage validates a deposit message for creating a new
// validator.
// validateDepositMessage validates a deposit message for creating a new validator.
func validateDepositMessage(chainSpec chain.Spec) func(
_ *cobra.Command,
args []string,
) error {
return func(_ *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
pubKeyStr := args[validatePubKey0]
pubkey, err := parser.ConvertPubkey(pubKeyStr)
if err != nil {
Expand All @@ -88,8 +95,9 @@ func validateDepositMessage(chainSpec chain.Spec) func(
return err
}

genValRootStr := args[validateRoot4]
genesisValidatorRoot, err := parser.ConvertGenesisValidatorRoot(genValRootStr)
genesisValidatorRoot, err := getGenesisValidatorRoot(
cmd, chainSpec, args, maxArgsValidateDeposit,
)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cli/commands/genesis/deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import (
// AddGenesisDepositCmd - returns the cobra command to
// add a premined deposit to the genesis file.
//
//nolint:lll // reads better if long description is one line
//nolint:lll // reads better if long description is one line.
func AddGenesisDepositCmd(cs chain.Spec) *cobra.Command {
cmd := &cobra.Command{
Use: "add-premined-deposit",
Expand Down
65 changes: 6 additions & 59 deletions cli/commands/genesis/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,80 +22,27 @@ package genesis

import (
"github.com/berachain/beacon-kit/chain"
"github.com/berachain/beacon-kit/consensus-types/types"
"github.com/berachain/beacon-kit/errors"
"github.com/berachain/beacon-kit/primitives/common"
"github.com/berachain/beacon-kit/primitives/encoding/json"
"github.com/berachain/beacon-kit/primitives/math"
"github.com/spf13/afero"
"github.com/berachain/beacon-kit/cli/utils/genesis"
"github.com/spf13/cobra"
)

// Beacon, AppState and Genesis are code duplications that
// collectively reproduce part of genesis file structure

type Beacon struct {
Deposits types.Deposits `json:"deposits"`
}

type AppState struct {
Beacon `json:"beacon"`
}

type Genesis struct {
AppState `json:"app_state"`
}

// TODO: move this logic to the `deposit create-validator/validate` commands as it is only
// required there.
// GetGenesisValidatorRootCmd returns a command that gets the genesis validator root from a given
// beacond genesis file.
func GetGenesisValidatorRootCmd(cs chain.Spec) *cobra.Command {
cmd := &cobra.Command{
Use: "validator-root [beacond/genesis.json]",
Short: "gets and returns the genesis validator root",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
// Read the genesis file.
genesisBz, err := afero.ReadFile(afero.NewOsFs(), args[0])
if err != nil {
return errors.Wrap(err, "failed to genesis json file")
}

var genesis Genesis
// Unmarshal JSON data into the Genesis struct
err = json.Unmarshal(genesisBz, &genesis)
genesisValidatorsRoot, err := genesis.ComputeValidatorsRootFromFile(args[0], cs)
if err != nil {
return errors.Wrap(err, "failed to unmarshal JSON")
return err
}

validatorHashTreeRoot := ValidatorsRoot(genesis.Deposits, cs)
cmd.Printf("%s\n", validatorHashTreeRoot)
cmd.Printf("%s\n", genesisValidatorsRoot)
return nil
},
}

return cmd
}

func ValidatorsRoot(deposits types.Deposits, cs chain.Spec) common.Root {
validators := make(types.Validators, len(deposits))
minEffectiveBalance := math.Gwei(cs.EjectionBalance() + cs.EffectiveBalanceIncrement())

for i, deposit := range deposits {
val := types.NewValidatorFromDeposit(
deposit.Pubkey,
deposit.Credentials,
deposit.Amount,
math.Gwei(cs.EffectiveBalanceIncrement()),
math.Gwei(cs.MaxEffectiveBalance()),
)

// mimic processGenesisActivation
if val.GetEffectiveBalance() >= minEffectiveBalance {
val.SetActivationEligibilityEpoch(0)
val.SetActivationEpoch(0)
}
validators[i] = val
}

return validators.HashTreeRoot()
}
88 changes: 88 additions & 0 deletions cli/utils/genesis/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: BUSL-1.1
//
// Copyright (C) 2025, Berachain Foundation. All rights reserved.
// Use of this software is governed by the Business Source License included
// in the LICENSE file of this repository and at www.mariadb.com/bsl11.
//
// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY
// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER
// VERSIONS OF THE LICENSED WORK.
//
// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF
// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF
// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE).
//
// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
// TITLE.

package genesis

import (
"github.com/berachain/beacon-kit/chain"
"github.com/berachain/beacon-kit/consensus-types/types"
"github.com/berachain/beacon-kit/errors"
"github.com/berachain/beacon-kit/primitives/common"
"github.com/berachain/beacon-kit/primitives/encoding/json"
"github.com/berachain/beacon-kit/primitives/math"
"github.com/spf13/afero"
)

// Beacon, AppState and Genesis are code duplications that
// collectively reproduce part of genesis file structure

type Beacon struct {
Deposits types.Deposits `json:"deposits"`
}

type AppState struct {
Beacon `json:"beacon"`
}

type Genesis struct {
AppState `json:"app_state"`
}

// ComputeValidatorsRootFromFile returns the validator root for a given genesis file and chain spec.
func ComputeValidatorsRootFromFile(genesisFile string, cs chain.Spec) (common.Root, error) {
genesisBz, err := afero.ReadFile(afero.NewOsFs(), genesisFile)
if err != nil {
return common.Root{}, errors.Wrap(err, "failed to genesis json file")
}

var appGenesis Genesis
err = json.Unmarshal(genesisBz, &appGenesis)
if err != nil {
return common.Root{}, errors.Wrap(err, "failed to unmarshal JSON")
}

return ComputeValidatorsRoot(appGenesis.Deposits, cs), nil
}

// ComputeValidatorsRoot returns the validator root for a given set of genesis deposits
// and a chain spec.
func ComputeValidatorsRoot(genesisDeposits types.Deposits, cs chain.Spec) common.Root {
validators := make(types.Validators, len(genesisDeposits))
minEffectiveBalance := math.Gwei(cs.EjectionBalance() + cs.EffectiveBalanceIncrement())

for i, deposit := range genesisDeposits {
val := types.NewValidatorFromDeposit(
deposit.Pubkey,
deposit.Credentials,
deposit.Amount,
math.Gwei(cs.EffectiveBalanceIncrement()),
math.Gwei(cs.MaxEffectiveBalance()),
)

// mimic processGenesisActivation
if val.GetEffectiveBalance() >= minEffectiveBalance {
val.SetActivationEligibilityEpoch(0)
val.SetActivationEpoch(0)
}
validators[i] = val
}

return validators.HashTreeRoot()
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import (
"testing"
"testing/quick"

"github.com/berachain/beacon-kit/cli/commands/genesis"
"github.com/berachain/beacon-kit/cli/utils/genesis"
"github.com/berachain/beacon-kit/config/spec"
"github.com/berachain/beacon-kit/consensus-types/types"
"github.com/berachain/beacon-kit/primitives/common"
Expand Down Expand Up @@ -96,7 +96,7 @@ func TestCompareGenesisCmdWithStateProcessor(t *testing.T) {
}
}
// genesis validators root from CLI
cliValRoot := genesis.ValidatorsRoot(deposits, cs)
cliValRoot := genesis.ComputeValidatorsRoot(deposits, cs)

// genesis validators root from StateProcessor
sp, st, _, _ := statetransition.SetupTestState(t, cs)
Expand Down

0 comments on commit 419cd66

Please sign in to comment.