-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
imp(cli): Add client, cmd and version packages (#23)
- Loading branch information
1 parent
86889fb
commit 71d1b9b
Showing
23 changed files
with
1,270 additions
and
161 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
// Copyright Tharsis Labs Ltd.(Evmos) | ||
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) | ||
|
||
package block | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"strconv" | ||
|
||
"github.com/cosmos/cosmos-sdk/server" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func Cmd() *cobra.Command { | ||
var height string | ||
cmd := &cobra.Command{ | ||
Use: "block", | ||
Short: "Get a specific block persisted in the db. If height is not specified, defaults to the latest.", | ||
Long: "Get a specific block persisted in the db. If height is not specified, defaults to the latest.\nThis command works only if no other process is using the db. Before using it, make sure to stop your node.\nIf you're using a custom home directory, specify it with the '--home' flag", | ||
PreRunE: func(cmd *cobra.Command, _ []string) error { | ||
// Bind flags to the Context's Viper so the app construction can set | ||
// options accordingly. | ||
serverCtx := server.GetServerContextFromCmd(cmd) | ||
return serverCtx.Viper.BindPFlags(cmd.Flags()) | ||
}, | ||
RunE: func(cmd *cobra.Command, _ []string) error { | ||
serverCtx := server.GetServerContextFromCmd(cmd) | ||
cfg := serverCtx.Config | ||
home := cfg.RootDir | ||
|
||
store, err := newStore(home, server.GetAppDBBackend(serverCtx.Viper)) | ||
if err != nil { | ||
return fmt.Errorf("error while openning db: %w", err) | ||
} | ||
|
||
state, err := store.state() | ||
if err != nil { | ||
return fmt.Errorf("error while getting blockstore state: %w", err) | ||
} | ||
|
||
var reqHeight int64 | ||
if height != "latest" { | ||
reqHeight, err = strconv.ParseInt(height, 10, 64) | ||
if err != nil { | ||
return errors.New("invalid height, please provide an integer") | ||
} | ||
if reqHeight > state.Height { | ||
return fmt.Errorf("invalid height, the latest height found in the db is %d, and you asked for %d", state.Height, reqHeight) | ||
} | ||
} else { | ||
reqHeight = state.Height | ||
} | ||
|
||
block, err := store.block(reqHeight) | ||
if err != nil { | ||
return fmt.Errorf("error while getting block with height %d: %w", reqHeight, err) | ||
} | ||
|
||
bz, err := json.Marshal(block) | ||
if err != nil { | ||
return fmt.Errorf("error while parsing block to JSON: %w", err) | ||
} | ||
|
||
cmd.Println(string(bz)) | ||
return nil | ||
}, | ||
} | ||
|
||
cmd.Flags().StringVar(&height, "height", "latest", "Block height to retrieve") | ||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
// Copyright Tharsis Labs Ltd.(Evmos) | ||
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) | ||
|
||
package block | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"path/filepath" | ||
|
||
dbm "github.com/cometbft/cometbft-db" | ||
tmstore "github.com/cometbft/cometbft/proto/tendermint/store" | ||
tmproto "github.com/cometbft/cometbft/proto/tendermint/types" | ||
"github.com/cometbft/cometbft/types" | ||
"github.com/cosmos/gogoproto/proto" | ||
) | ||
|
||
var storeKey = []byte("blockStore") | ||
|
||
// store is the block store struct | ||
type store struct { | ||
dbm.DB | ||
} | ||
|
||
// newStore opens the 'blockstore' db | ||
// and returns it. | ||
func newStore(rootDir string, backendType dbm.BackendType) (*store, error) { | ||
dataDir := filepath.Join(rootDir, "data") | ||
db, err := dbm.NewDB("blockstore", backendType, dataDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &store{db}, nil | ||
} | ||
|
||
// state returns the BlockStoreState as loaded from disk. | ||
func (s *store) state() (*tmstore.BlockStoreState, error) { | ||
bytes, err := s.Get(storeKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(bytes) == 0 { | ||
return nil, errors.New("could not find a BlockStoreState persisted in db") | ||
} | ||
|
||
var bss tmstore.BlockStoreState | ||
if err := proto.Unmarshal(bytes, &bss); err != nil { | ||
return nil, fmt.Errorf("could not unmarshal bytes: %X", bytes) | ||
} | ||
|
||
// Backwards compatibility with persisted data from before Base existed. | ||
if bss.Height > 0 && bss.Base == 0 { | ||
bss.Base = 1 | ||
} | ||
|
||
return &bss, nil | ||
} | ||
|
||
// block returns the Block for the given height. | ||
func (s *store) block(height int64) (*types.Block, error) { | ||
bm, err := s.meta(height) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting block metadata: %v", err) | ||
} | ||
|
||
pbb := new(tmproto.Block) | ||
buf := []byte{} | ||
for i := uint32(0); i < bm.BlockID.PartSetHeader.Total; i++ { | ||
part, err := s.part(height, i) | ||
// If the part is missing (e.g. since it has been deleted after we | ||
// loaded the block meta) we consider the whole block to be missing. | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting block part: %v", err) | ||
} | ||
buf = append(buf, part.Bytes...) | ||
} | ||
if err := proto.Unmarshal(buf, pbb); err != nil { | ||
// NOTE: The existence of meta should imply the existence of the | ||
// block. So, make sure meta is only saved after blocks are saved. | ||
return nil, fmt.Errorf("error reading block: %v", err) | ||
} | ||
|
||
return types.BlockFromProto(pbb) | ||
} | ||
|
||
// meta returns the BlockMeta for the given height. | ||
// If no block is found for the given height, it returns nil. | ||
func (s *store) meta(height int64) (*types.BlockMeta, error) { | ||
bz, err := s.Get(metaKey(height)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if len(bz) == 0 { | ||
return nil, fmt.Errorf("could not find the block metadata for height %d", height) | ||
} | ||
|
||
pbbm := new(tmproto.BlockMeta) | ||
if err = proto.Unmarshal(bz, pbbm); err != nil { | ||
return nil, fmt.Errorf("unmarshal to tmproto.BlockMeta: %w", err) | ||
} | ||
|
||
return types.BlockMetaFromProto(pbbm) | ||
} | ||
|
||
// part returns the part of the block for the given height and part index. | ||
// If no block part is found for the given height and index, it returns nil. | ||
func (s *store) part(height int64, index uint32) (*types.Part, error) { | ||
bz, err := s.Get(partKey(height, index)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if len(bz) == 0 { | ||
return nil, fmt.Errorf("could not find block part with index %d for block at height %d", index, height) | ||
} | ||
|
||
pbpart := new(tmproto.Part) | ||
if err := proto.Unmarshal(bz, pbpart); err != nil { | ||
return nil, fmt.Errorf("unmarshal to tmproto.Part failed: %w", err) | ||
} | ||
|
||
return types.PartFromProto(pbpart) | ||
} | ||
|
||
// metaKey is a helper function that takes the block height | ||
// as input parameter and returns the corresponding block metadata store key | ||
func metaKey(height int64) []byte { | ||
return []byte(fmt.Sprintf("H:%v", height)) | ||
} | ||
|
||
// partKey is a helper function that takes the block height | ||
// and the part index as input parameters and returns the corresponding block part store key | ||
func partKey(height int64, partIndex uint32) []byte { | ||
return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// Copyright Tharsis Labs Ltd.(Evmos) | ||
// SPDX-License-Identifier:ENCL-1.0(https://github.com/evmos/evmos/blob/main/LICENSE) | ||
|
||
package client | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/cometbft/cometbft/libs/cli" | ||
"github.com/cosmos/cosmos-sdk/client/flags" | ||
"github.com/evmos/os/types" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
// InitConfig adds the chain-id, encoding and output flags to the persistent flag set. | ||
func InitConfig(cmd *cobra.Command) error { | ||
home, err := cmd.PersistentFlags().GetString(cli.HomeFlag) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
configFile := filepath.Join(home, "config", "config.toml") | ||
_, err = os.Stat(configFile) | ||
if err != nil && !os.IsNotExist(err) { | ||
// Immediately return if the error isn't related to the file not existing. | ||
// See issue https://github.com/evmos/ethermint/issues/539 | ||
return err | ||
} | ||
if err == nil { | ||
viper.SetConfigFile(configFile) | ||
|
||
if err := viper.ReadInConfig(); err != nil { | ||
return err | ||
} | ||
} | ||
|
||
if err := viper.BindPFlag(flags.FlagChainID, cmd.PersistentFlags().Lookup(flags.FlagChainID)); err != nil { | ||
return err | ||
} | ||
|
||
if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { | ||
return err | ||
} | ||
|
||
return viper.BindPFlag(cli.OutputFlag, cmd.PersistentFlags().Lookup(cli.OutputFlag)) | ||
} | ||
|
||
// ValidateChainID wraps a cobra command with a RunE function with base 10 integer chain-id verification. | ||
func ValidateChainID(baseCmd *cobra.Command) *cobra.Command { | ||
// Copy base run command to be used after chain verification | ||
baseRunE := baseCmd.RunE | ||
|
||
// Function to replace command's RunE function | ||
validateFn := func(cmd *cobra.Command, args []string) error { | ||
chainID, _ := cmd.Flags().GetString(flags.FlagChainID) | ||
|
||
if !types.IsValidChainID(chainID) { | ||
return fmt.Errorf("invalid chain-id format: %s", chainID) | ||
} | ||
|
||
return baseRunE(cmd, args) | ||
} | ||
|
||
baseCmd.RunE = validateFn | ||
return baseCmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package client | ||
|
||
import ( | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/cosmos/cosmos-sdk/client/flags" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
func TestInitConfigNonNotExistError(t *testing.T) { | ||
tempDir := t.TempDir() | ||
subDir := filepath.Join(tempDir, "nonPerms") | ||
if err := os.Mkdir(subDir, 0o600); err != nil { | ||
t.Fatalf("Failed to create sub directory: %v", err) | ||
} | ||
cmd := &cobra.Command{} | ||
cmd.PersistentFlags().String(flags.FlagHome, "", "") | ||
if err := cmd.PersistentFlags().Set(flags.FlagHome, subDir); err != nil { | ||
t.Fatalf("Could not set home flag [%T] %v", err, err) | ||
} | ||
|
||
if err := InitConfig(cmd); !os.IsPermission(err) { | ||
t.Fatalf("Failed to catch permissions error, got: [%T] %v", err, err) | ||
} | ||
} |
Oops, something went wrong.