Skip to content

How to interact with cosmos databases?

Published: at 03:22 PM

Create a small go file to interact with the cosmos database

Table of contents

Open Table of contents

Introduction

When it comes to debugging on the Cosmos blockchain, efficiency is key. Picture yourself with your Cosmos-based blockchain, troubleshooting your module state. You’ve prepared your debugger, and your coffee’s within reach, but where do you begin?

Debugging in the Cosmos ecosystem often involves inserting snippets into functions like BeginBlock, to avoid setting up a custom project will all the module declarations defined in app.go or fighting the database initializations.

That’s where a compact Go file designed specifically for interacting with the specific section of the database related to the module that you are interested in comes into play. With just a few lines of code, you can create a dedicated debugging tool without disrupting your main application.

In this blog post, we’ll create a Go file, integrating it seamlessly into the Cosmos application, and efficiently examining your state for issues.

Considerations

In this tutorial, we assumed that the database backend used is LevelDB, but the code can be adapted to any other backend.

We are going to use Evmos v16, so in our go.mod file we need to replace some dependencies to be able to compile the code, your file should look like this:

module github.com/hanchon/test

go 1.22.2

require (
        github.com/cometbft/cometbft-db v0.11.0
        github.com/cosmos/cosmos-sdk v0.47.8
        github.com/cosmos/iavl v0.21.0-alpha.1.0.20230904092046-df3db2d96583
        github.com/evmos/evmos/v16 v16.0.4
)

replace (
	github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0
	github.com/cosmos/cosmos-sdk => github.com/evmos/cosmos-sdk v0.47.5-evmos.2
	github.com/ethereum/go-ethereum => github.com/evmos/go-ethereum v1.10.26-evmos-rc2
	github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.9.1
	github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
)

Coding

Open the database

To open the database we need the application directory where the data folder is located and the database backend (in our case GoLevelDB).

import (
	opendb "github.com/evmos/evmos/v16/cmd/evmosd/opendb"
)

func main() {
	home, err := os.UserHomeDir()
	if err != nil {
		log.Fatal(err)
	}
	db, err := opendb.OpenDB(nil, home+"/.evmosd", dbm.GoLevelDBBackend)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()
}

Open the module database

Now that we have the LevelDB database object, we need to create the prefixed database for the module that we want to query

import (
    ...
	dbm "github.com/cometbft/cometbft-db"
	iavlnew "github.com/cosmos/iavl"
    evmtypes "github.com/evmos/evmos/v16/x/evm/types"
)

func OpenModuleDB(db dbm.DB, moduleStoreKey string, chainHeight int64) *iavlnew.MutableTree {
	prefixedDb := dbm.NewPrefixDB(db, []byte("s/k:"+moduleStoreKey+"/"))
	tree, err := iavlnew.NewMutableTreeWithOpts(
		prefixedDb,
		0,
		&iavlnew.Options{InitialVersion: 0},
		true,
	)
	if err != nil {
		log.Fatal(err)
	}

	_, err = tree.LoadVersion(chainHeight)
	if err != nil {
		log.Fatal(err)
	}

	return tree
}

func main(){
    ...
    chainHeight := int64(6)
	evmDB := OpenModuleDB(db, evmtypes.StoreKey, chainHeight)
}

NOTE: I am using chain height as 6 for this example because I am running a local node. You should use the latest chain height or a height that was not pruned.

Get all the EVM module entries by wallet

For this example and to avoid handling errors, I am going to make a quick function to panic in case of an error.

import (
    ...
    storeTypes "github.com/cosmos/cosmos-sdk/store/types"
)
func GetIterator(db *iavlnew.MutableTree, start []byte) dbm.Iterator {
	it, err := db.Iterator(start, storeTypes.PrefixEndBytes(start), true)
	if err != nil {
		log.Fatal(err)
	}
	return it
}

Create the key, you can usually find a way to create the keys using the keys.go file for the module (x/evm/types/key.go).

	acc, err := sdk.AccAddressFromHexUnsafe("2AB0e9e4eE70FFf1fB9D67031E44F6410170d00e")
	if err != nil {
		log.Fatal(err)
	}

	prefix := append(evmtypes.KeyPrefixStorage, acc.Bytes()...)

Now that we have the key, we can iterate over all the storage entries for that wallet

import (
    ...
	"github.com/ethereum/go-ethereum/common"
)
func main(){
    ...
	for ; it.Valid(); it.Next() {
		storageKey := common.BytesToHash(it.Key())
		storageValue := common.BytesToHash(it.Value())
		fmt.Println("storageKey", storageKey, "storageValue", storageValue)
	}
}

Putting all together

package main

import (
	"fmt"
	"log"
	"os"

	dbm "github.com/cometbft/cometbft-db"
	storeTypes "github.com/cosmos/cosmos-sdk/store/types"
	sdk "github.com/cosmos/cosmos-sdk/types"
	iavlnew "github.com/cosmos/iavl"
	"github.com/ethereum/go-ethereum/common"
	opendb "github.com/evmos/evmos/v16/cmd/evmosd/opendb"
	evmtypes "github.com/evmos/evmos/v16/x/evm/types"
)

func OpenModuleDB(db dbm.DB, moduleStoreKey string, chainHeight int64) *iavlnew.MutableTree {
	prefixedDb := dbm.NewPrefixDB(db, []byte("s/k:"+moduleStoreKey+"/"))
	tree, err := iavlnew.NewMutableTreeWithOpts(
		prefixedDb,
		0,
		&iavlnew.Options{InitialVersion: 0},
		true,
	)
	if err != nil {
		log.Fatal(err)
	}

	_, err = tree.LoadVersion(chainHeight)
	if err != nil {
		log.Fatal(err)
	}

	return tree
}

func GetIterator(db *iavlnew.MutableTree, start []byte) dbm.Iterator {
	it, err := db.Iterator(start, storeTypes.PrefixEndBytes(start), true)
	if err != nil {
		log.Fatal(err)
	}
	return it
}

func main() {
	home, err := os.UserHomeDir()
	if err != nil {
		log.Fatal(err)
	}
	db, err := opendb.OpenDB(nil, home+"/.evmosd", dbm.GoLevelDBBackend)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	chainHeight := int64(6)
	evmDB := OpenModuleDB(db, evmtypes.StoreKey, chainHeight)

	acc, err := sdk.AccAddressFromHexUnsafe("2AB0e9e4eE70FFf1fB9D67031E44F6410170d00e")
	if err != nil {
		log.Fatal(err)
	}

	prefix := append(evmtypes.KeyPrefixStorage, acc.Bytes()...)
	it := GetIterator(evmDB, prefix)

	for ; it.Valid(); it.Next() {
		storageKey := common.BytesToHash(it.Key())
		storageValue := common.BytesToHash(it.Value())
		fmt.Println("storageKey", storageKey, "storageValue", storageValue)
	}
}

Conclusion

With a small go file, you can read the chain state to gather chain analytics without adding any hacky code to the node.

Accessing directly to the database is useful when you need to make big queries that time out when trying to get that information using the node’s rpc.

I used this method to gather the information related to the XEN token while debugging the state bloat in the Evmos chain, I will publish a detailed explanation about how I did it in the future.