Skip to content

Commit

Permalink
imp(cli): Add client, cmd and version packages (#23)
Browse files Browse the repository at this point in the history
  • Loading branch information
MalteHerrmann authored Aug 6, 2024
1 parent 86889fb commit 71d1b9b
Show file tree
Hide file tree
Showing 23 changed files with 1,270 additions and 161 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This changelog was created using the `clu` binary

### Improvements

- (cli) [#23](https://github.com/evmos/os/pull/23) Add client, cmd and version packages.
- (server) [#22](https://github.com/evmos/os/pull/22) Add server implementation.
- (rpc) [#21](https://github.com/evmos/os/pull/21) Add RPC and indexer types.
- (types) [#20](https://github.com/evmos/os/pull/20) Add crypto and encoding packages.
Expand Down
73 changes: 73 additions & 0 deletions client/block/block.go
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
}
137 changes: 137 additions & 0 deletions client/block/store.go
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))
}
69 changes: 69 additions & 0 deletions client/config.go
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
}
27 changes: 27 additions & 0 deletions client/config_test.go
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)
}
}
Loading

0 comments on commit 71d1b9b

Please sign in to comment.