Skip to content

Commit

Permalink
feat(cli): add operator unjail command (#1790)
Browse files Browse the repository at this point in the history
Adds a `omni operator unjail` command that must be called by the
operator-private-key.

issue: #1442
  • Loading branch information
corverroos authored Aug 29, 2024
1 parent aa1c0f8 commit 59ee4c4
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 27 deletions.
1 change: 1 addition & 0 deletions cli/cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func newOperatorCmds() *cobra.Command {
newInitCmd(),
newCreateValCmd(),
newCreateKeyCmd(),
newUnjailCmd(),
)

return cmd
Expand Down
35 changes: 23 additions & 12 deletions cli/cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,20 @@ import (
"github.com/spf13/cobra"
)

const (
flagPrivateKeyFile = "private-key-file"
flagConsPubKeyHex = "consensus-pubkey-hex"
flagSelfDelegation = "self-delegation"
flagConfig = "config-file"
flagOperator = "operator"
flagRPCURL = "rpc-url"
flagAddress = "address"
flagType = "type"
)

func bindRegConfig(cmd *cobra.Command, cfg *RegConfig) {
bindAVSAddress(cmd, &cfg.AVSAddr)

const flagConfig = "config-file"
cmd.Flags().StringVar(&cfg.ConfigFile, flagConfig, cfg.ConfigFile, "Path to the Eigen-Layer yaml configuration file")
_ = cmd.MarkFlagRequired(flagConfig)
}
Expand All @@ -35,45 +45,46 @@ func bindDevnetAVSAllowConfig(cmd *cobra.Command, cfg *devnetAllowConfig) {
bindRPCURL(cmd, &cfg.RPCURL)
bindAVSAddress(cmd, &cfg.AVSAddr)

const flagOperator = "operator"
cmd.Flags().StringVar(&cfg.OperatorAddr, flagOperator, cfg.OperatorAddr, "Operator address to allow")
_ = cmd.MarkFlagRequired(flagOperator)
}

func bindDevnetFundConfig(cmd *cobra.Command, d *devnetFundConfig) {
bindRPCURL(cmd, &d.RPCURL)

const flagAddress = "address"
cmd.Flags().StringVar(&d.Address, flagAddress, d.Address, "Address to fund")
_ = cmd.MarkFlagRequired(flagAddress)
}

func bindRPCURL(cmd *cobra.Command, rpcURL *string) {
const flagRPCURL = "rpc-url"
cmd.Flags().StringVar(rpcURL, flagRPCURL, *rpcURL, "URL of the eth-json RPC server")
_ = cmd.MarkFlagRequired(flagRPCURL)
}

func bindUnjailConfig(cmd *cobra.Command, cfg *unjailConfig) {
netconf.BindFlag(cmd.Flags(), &cfg.Network)
bindPrivateKeyFile(cmd, &cfg.PrivateKeyFile)
_ = cmd.MarkFlagRequired("network")
}

func bindCreateValConfig(cmd *cobra.Command, cfg *createValConfig) {
netconf.BindFlag(cmd.Flags(), &cfg.Network)
bindPrivateKeyFile(cmd, &cfg.PrivateKeyFile)

const (
flagPrivateKeyFile = "private-key-file"
flagConsPubKeyHex = "consensus-pubkey-hex"
flagSelfDelegation = "self-delegation"
)
cmd.Flags().StringVar(&cfg.PrivateKeyFile, flagPrivateKeyFile, cfg.PrivateKeyFile, "Path to the insecure operator private key file")
cmd.Flags().StringVar(&cfg.ConsensusPubKeyHex, flagConsPubKeyHex, cfg.ConsensusPubKeyHex, "Hex-encoded validator consensus public key")
cmd.Flags().Uint64Var(&cfg.SelfDelegation, flagSelfDelegation, cfg.SelfDelegation, "Self-delegation amount in OMNI (minimum 100 OMNI)")

_ = cmd.MarkFlagRequired(flagPrivateKeyFile)
_ = cmd.MarkFlagRequired(flagConsPubKeyHex)
_ = cmd.MarkFlagRequired(flagSelfDelegation)
_ = cmd.MarkFlagRequired("network")
}

func bindPrivateKeyFile(cmd *cobra.Command, privateKeyFile *string) {
cmd.Flags().StringVar(privateKeyFile, flagPrivateKeyFile, *privateKeyFile, "Path to the private key file")
_ = cmd.MarkFlagRequired(flagPrivateKeyFile)
}

func bindCreateKeyConfig(cmd *cobra.Command, cfg *createKeyConfig) {
const flagType = "type"
cmd.Flags().StringVar((*string)(&cfg.Type), flagType, string(cfg.Type), "Type of key to create")
cmd.Flags().StringVar(&cfg.PrivateKeyFile, "output-file", cfg.PrivateKeyFile, "Path to output private key file")
}
File renamed without changes.
3 changes: 0 additions & 3 deletions cli/cmd/key.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package cmd

import (
"encoding/hex"

"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/log"

Expand Down Expand Up @@ -35,7 +33,6 @@ func newCreateKeyCmd() *cobra.Command {
"type", cfg.Type,
"file", cfg.PrivateKeyFile,
"address", crypto.PubkeyToAddress(privKey.PublicKey).Hex(),
"pubkey", hex.EncodeToString(crypto.CompressPubkey(&privKey.PublicKey)),
)
log.Info(cmd.Context(), "🚧 Remember to backup this key 🚧")

Expand Down
139 changes: 137 additions & 2 deletions cli/cmd/staking.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import (

"github.com/omni-network/omni/contracts/bindings"
"github.com/omni-network/omni/halo/genutil/evm/predeploys"
"github.com/omni-network/omni/lib/cchain/provider"
"github.com/omni-network/omni/lib/errors"
"github.com/omni-network/omni/lib/ethclient"
"github.com/omni-network/omni/lib/ethclient/ethbackend"
"github.com/omni-network/omni/lib/evmchain"
"github.com/omni-network/omni/lib/log"
"github.com/omni-network/omni/lib/netconf"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
Expand Down Expand Up @@ -120,6 +122,20 @@ func createValidator(ctx context.Context, cfg createValConfig) error {
return err
}

cprov, err := provider.Dial(cfg.Network)
if err != nil {
return err
}

if _, ok, err = cprov.Validator(ctx, opAddr); err != nil {
return err
} else if ok {
return &CliError{
Msg: "Operator address already a validator: " + opAddr.Hex(),
Suggest: "Ensure correct operator address",
}
}

backend, err := ethbackend.NewBackend(chainMeta.Name, chainID, chainMeta.BlockPeriod, ethCl, opPrivKey)
if err != nil {
return err
Expand Down Expand Up @@ -165,13 +181,132 @@ func createValidator(ctx context.Context, cfg createValConfig) error {
return errors.Wrap(err, "create validator")
}

_, err = backend.WaitMined(ctx, tx)
rec, err := backend.WaitMined(ctx, tx)
if err != nil {
return errors.Wrap(err, "wait mined")
}

link := fmt.Sprintf("https://%s.omniscan.network/tx/%s", cfg.Network, tx.Hash().Hex())
log.Info(ctx, "🎉 Create-validator transaction created and included on-chain", "link", link)
log.Info(ctx, "🎉 Create-validator transaction sent and included on-chain", "link", link, "block", rec.BlockNumber.Uint64())

return nil
}

func newUnjailCmd() *cobra.Command {
var cfg unjailConfig

cmd := &cobra.Command{
Use: "unjail",
Short: "Unjail a validator",
Long: "Sign and broadcast a unjail transaction that unjails a jailed validator. " +
"This transaction must be sent by the operator address and costs 0.1 OMNI.",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if err := cfg.Verify(); err != nil {
return errors.Wrap(err, "verify flags")
}

err := unjailValidator(cmd.Context(), cfg)
if err != nil {
return errors.Wrap(err, "unjail")
}

return nil
},
}

bindUnjailConfig(cmd, &cfg)

return cmd
}

type unjailConfig struct {
Network netconf.ID
PrivateKeyFile string
}

func (c unjailConfig) Verify() error {
if c.PrivateKeyFile == "" {
return errors.New("required flag --private-key-file not set")
}

if err := c.Network.Verify(); err != nil {
return errors.Wrap(err, "verify --network flag")
}

return nil
}

func unjailValidator(ctx context.Context, cfg unjailConfig) error {
opPrivKey, err := crypto.LoadECDSA(cfg.PrivateKeyFile)
if err != nil {
return errors.Wrap(err, "load private key")
}
opAddr := crypto.PubkeyToAddress(opPrivKey.PublicKey)

chainID := cfg.Network.Static().OmniExecutionChainID
chainMeta, ok := evmchain.MetadataByID(chainID)
if !ok {
return errors.New("chain metadata not found")
}

ethCl, err := ethclient.Dial(chainMeta.Name, cfg.Network.Static().ExecutionRPC())
if err != nil {
return err
}

cprov, err := provider.Dial(cfg.Network)
if err != nil {
return err
}

if val, ok, err := cprov.Validator(ctx, opAddr); err != nil {
return err
} else if !ok {
return &CliError{
Msg: "Operator address not a validator: " + opAddr.Hex(),
Suggest: "Ensure operator address is a validator",
}
} else if !val.IsJailed() {
return &CliError{
Msg: "Validator not jailed: " + opAddr.Hex(),
Suggest: "Ensure validator is jailed before unjailing",
}
}

backend, err := ethbackend.NewBackend(chainMeta.Name, chainID, chainMeta.BlockPeriod, ethCl, opPrivKey)
if err != nil {
return err
}

contract, err := bindings.NewSlashing(common.HexToAddress(predeploys.Slashing), backend)
if err != nil {
return err
}

fee, err := contract.Fee(&bind.CallOpts{Context: ctx})
if err != nil {
return err
}

txOpts, err := backend.BindOpts(ctx, opAddr)
if err != nil {
return err
}

txOpts.Value = fee
tx, err := contract.Unjail(txOpts)
if err != nil {
return errors.Wrap(err, "unjail validator")
}

rec, err := backend.WaitMined(ctx, tx)
if err != nil {
return errors.Wrap(err, "wait mined")
}

link := fmt.Sprintf("https://%s.omniscan.network/tx/%s", cfg.Network, rec.TxHash.Hex())
log.Info(ctx, "🎉 Unjail transaction sent and included on-chain", "link", link, "block", rec.BlockNumber.Uint64())

return nil
}
10 changes: 10 additions & 0 deletions lib/cchain/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ethereum/go-ethereum/common"

utypes "cosmossdk.io/x/upgrade/types"
stypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

// ProviderCallback is the callback function signature that will be called with each approved attestation per
Expand Down Expand Up @@ -56,6 +57,15 @@ type Provider interface {
// Note the genesis validator set has ID 1.
ValidatorSet(ctx context.Context, valSetID uint64) ([]Validator, bool, error)

// Validator returns the staking module validator by operator address from latest height.
Validator(ctx context.Context, operator common.Address) (stypes.Validator, bool, error)

// Validators returns the current staking module validators from latest height.
Validators(ctx context.Context) ([]stypes.Validator, error)

// Rewards returns the staking module rewards for the given operator address from latest height.
Rewards(ctx context.Context, operator common.Address) (float64, bool, error)

// XBlock returns the portal module block for the given blockHeight/attestOffset (or latest) or false if none exist or an error.
XBlock(ctx context.Context, heightAndOffset uint64, latest bool) (xchain.Block, bool, error)

Expand Down
Loading

0 comments on commit 59ee4c4

Please sign in to comment.