diff --git a/indexer/abci.go b/indexer/abci.go index 311d4d7..9c46099 100644 --- a/indexer/abci.go +++ b/indexer/abci.go @@ -5,6 +5,7 @@ import ( "math/big" abci "github.com/cometbft/cometbft/abci/types" + comettypes "github.com/cometbft/cometbft/types" "cosmossdk.io/collections" storetypes "cosmossdk.io/store/types" @@ -64,6 +65,17 @@ func (e *EVMIndexerImpl) ListenFinalizeBlock(ctx context.Context, req abci.Reque txStatus = coretypes.ReceiptStatusFailed } + // index tx hash + cosmosTxHash := comettypes.Tx(txBytes).Hash() + if err := e.TxHashToCosmosTxHash.Set(ctx, ethTx.Hash().Bytes(), cosmosTxHash); err != nil { + e.logger.Error("failed to store tx hash to cosmos tx hash", "err", err) + return err + } + if err := e.CosmosTxHashToTxHash.Set(ctx, cosmosTxHash, ethTx.Hash().Bytes()); err != nil { + e.logger.Error("failed to store cosmos tx hash to tx hash", "err", err) + return err + } + gasUsed := uint64(txResult.GasUsed) cumulativeGasUsed += gasUsed diff --git a/indexer/indexer.go b/indexer/indexer.go index 7486a23..8bcaa22 100644 --- a/indexer/indexer.go +++ b/indexer/indexer.go @@ -39,6 +39,10 @@ type EVMIndexer interface { BlockHeaderByHash(ctx context.Context, hash common.Hash) (*coretypes.Header, error) BlockHeaderByNumber(ctx context.Context, number uint64) (*coretypes.Header, error) + // cosmos tx hash + CosmosTxHashByTxHash(ctx context.Context, hash common.Hash) ([]byte, error) + TxHashByCosmosTxHash(ctx context.Context, hash []byte) (common.Hash, error) + // event subscription Subscribe() (chan *coretypes.Header, chan []*coretypes.Log, chan *rpctypes.RPCTransaction) MempoolWrapper(mempool mempool.Mempool) mempool.Mempool @@ -61,6 +65,8 @@ type EVMIndexerImpl struct { BlockHeaderMap collections.Map[uint64, coretypes.Header] BlockAndIndexToTxHashMap collections.Map[collections.Pair[uint64, uint64], []byte] BlockHashToNumberMap collections.Map[[]byte, uint64] + TxHashToCosmosTxHash collections.Map[[]byte, []byte] + CosmosTxHashToTxHash collections.Map[[]byte, []byte] blockChan chan *coretypes.Header logsChan chan []*coretypes.Log @@ -98,6 +104,8 @@ func NewEVMIndexer( BlockHeaderMap: collections.NewMap(sb, prefixBlockHeader, "block_header", collections.Uint64Key, CollJsonVal[coretypes.Header]()), BlockAndIndexToTxHashMap: collections.NewMap(sb, prefixBlockAndIndexToTxHash, "block_and_index_to_tx_hash", collections.PairKeyCodec(collections.Uint64Key, collections.Uint64Key), collections.BytesValue), BlockHashToNumberMap: collections.NewMap(sb, prefixBlockHashToNumber, "block_hash_to_number", collections.BytesKey, collections.Uint64Value), + TxHashToCosmosTxHash: collections.NewMap(sb, prefixTxHashToCosmosTxHash, "tx_hash_to_cosmos_tx_hash", collections.BytesKey, collections.BytesValue), + CosmosTxHashToTxHash: collections.NewMap(sb, prefixCosmosTxHashToTxHash, "cosmos_tx_hash_to_tx_hash", collections.BytesKey, collections.BytesValue), blockChan: nil, logsChan: nil, diff --git a/indexer/keys.go b/indexer/keys.go index dc01022..3e72618 100644 --- a/indexer/keys.go +++ b/indexer/keys.go @@ -13,4 +13,8 @@ var ( prefixTx = collections.Prefix([]byte{0, 0, 2, 1}) prefixTxReceipt = collections.Prefix([]byte{0, 0, 2, 2}) prefixBlockAndIndexToTxHash = collections.Prefix([]byte{0, 0, 2, 3}) + + // cosmos indexes + prefixTxHashToCosmosTxHash = collections.Prefix([]byte{0, 0, 3, 1}) + prefixCosmosTxHashToTxHash = collections.Prefix([]byte{0, 0, 3, 2}) ) diff --git a/indexer/reader.go b/indexer/reader.go index 4b1fb53..20b6e04 100644 --- a/indexer/reader.go +++ b/indexer/reader.go @@ -73,3 +73,18 @@ func (e *EVMIndexerImpl) TxReceiptByHash(ctx context.Context, hash common.Hash) func (e *EVMIndexerImpl) BlockHashToNumber(ctx context.Context, hash common.Hash) (uint64, error) { return e.BlockHashToNumberMap.Get(ctx, hash.Bytes()) } + +// CosmosTxHashByTxHash implements EVMIndexer. +func (e *EVMIndexerImpl) CosmosTxHashByTxHash(ctx context.Context, hash common.Hash) ([]byte, error) { + return e.TxHashToCosmosTxHash.Get(ctx, hash.Bytes()) +} + +// TxHashByCosmosTxHash implements EVMIndexer. +func (e *EVMIndexerImpl) TxHashByCosmosTxHash(ctx context.Context, hash []byte) (common.Hash, error) { + bz, err := e.CosmosTxHashToTxHash.Get(ctx, hash) + if err != nil { + return common.Hash{}, err + } + + return common.BytesToHash(bz), nil +} diff --git a/jsonrpc/backend/cosmos.go b/jsonrpc/backend/cosmos.go new file mode 100644 index 0000000..2669bdc --- /dev/null +++ b/jsonrpc/backend/cosmos.go @@ -0,0 +1,39 @@ +package backend + +import ( + "errors" + + "cosmossdk.io/collections" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// CosmosTxHashByTxHash returns the Cosmos transaction hash by the Ethereum transaction hash. +func (b *JSONRPCBackend) CosmosTxHashByTxHash(hash common.Hash) (hexutil.Bytes, error) { + queryCtx, err := b.getQueryCtx() + if err != nil { + return nil, err + } + + cosmosTxHash, err := b.app.EVMIndexer().CosmosTxHashByTxHash(queryCtx, hash) + if err != nil && errors.Is(err, collections.ErrNotFound) { + return nil, nil + } + + return cosmosTxHash, nil +} + +// TxHashByCosmosTxHash returns the Ethereum transaction hash by the Cosmos transaction hash. +func (b *JSONRPCBackend) TxHashByCosmosTxHash(hash []byte) (common.Hash, error) { + queryCtx, err := b.getQueryCtx() + if err != nil { + return common.Hash{}, err + } + + txHash, err := b.app.EVMIndexer().TxHashByCosmosTxHash(queryCtx, hash) + if err != nil && errors.Is(err, collections.ErrNotFound) { + return common.Hash{}, nil + } + + return txHash, nil +} diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go index a6b7388..a846fa2 100644 --- a/jsonrpc/jsonrpc.go +++ b/jsonrpc/jsonrpc.go @@ -19,6 +19,7 @@ import ( "github.com/initia-labs/minievm/app" "github.com/initia-labs/minievm/jsonrpc/backend" "github.com/initia-labs/minievm/jsonrpc/config" + cosmosns "github.com/initia-labs/minievm/jsonrpc/namespaces/cosmos" ethns "github.com/initia-labs/minievm/jsonrpc/namespaces/eth" "github.com/initia-labs/minievm/jsonrpc/namespaces/eth/filters" netns "github.com/initia-labs/minievm/jsonrpc/namespaces/net" @@ -29,14 +30,14 @@ import ( // RPC namespaces and API version const ( // TODO: implement commented apis in the namespaces for full Ethereum compatibility - EthNamespace = "eth" - NetNamespace = "net" - TxPoolNamespace = "txpool" - Web3Namespace = "web3" - // TODO: support more namespaces + EthNamespace = "eth" + NetNamespace = "net" + TxPoolNamespace = "txpool" + Web3Namespace = "web3" PersonalNamespace = "personal" DebugNamespace = "debug" MinerNamespace = "miner" + CosmosNamespace = "cosmos" apiVersion = "1.0" ) @@ -85,6 +86,12 @@ func StartJSONRPC( Service: txpoolns.NewTxPoolAPI(svrCtx.Logger, bkd), Public: true, }, + { + Namespace: CosmosNamespace, + Version: apiVersion, + Service: cosmosns.NewCosmosAPI(svrCtx.Logger, bkd), + Public: true, + }, } for _, api := range apis { diff --git a/jsonrpc/namespaces/cosmos/api.go b/jsonrpc/namespaces/cosmos/api.go new file mode 100644 index 0000000..ad187a1 --- /dev/null +++ b/jsonrpc/namespaces/cosmos/api.go @@ -0,0 +1,37 @@ +package cosmos + +import ( + "context" + + "cosmossdk.io/log" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/initia-labs/minievm/jsonrpc/backend" +) + +// CosmosAPI is the cosmos namespace for the Ethereum JSON-RPC APIs. +type CosmosAPI struct { + ctx context.Context + logger log.Logger + backend *backend.JSONRPCBackend +} + +// NewCosmosAPI creates an instance of the public ETH Web3 API. +func NewCosmosAPI(logger log.Logger, backend *backend.JSONRPCBackend) *CosmosAPI { + api := &CosmosAPI{ + ctx: context.TODO(), + logger: logger.With("client", "json-rpc"), + backend: backend, + } + + return api +} + +func (api *CosmosAPI) CosmosTxHashByTxHash(hash common.Hash) (hexutil.Bytes, error) { + return api.backend.CosmosTxHashByTxHash(hash) +} + +func (api *CosmosAPI) TxHashByCosmosTxHash(hash hexutil.Bytes) (common.Hash, error) { + return api.backend.TxHashByCosmosTxHash(hash) +}