Skip to content

Commit

Permalink
fix: receipt indexing to include contract address if that is creation (
Browse files Browse the repository at this point in the history
…#38)

* fix receipt indexing to include contract address if that is creation

* add base fee to block header
  • Loading branch information
beer-1 authored Jul 16, 2024
1 parent 387ff14 commit 219db7d
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 119 deletions.
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1334,7 +1334,7 @@ func (app *MinitiaApp) setupIndexer(indexerDB dbm.DB, appOpts servertypes.AppOpt
}

// add evm indexer
evmIndexer, err := evmindexer.NewEVMIndexer(indexerDB, appCodec, app.Logger(), app.txConfig, app.EVMKeeper)
evmIndexer, err := evmindexer.NewEVMIndexer(indexerDB, appCodec, app.Logger(), app.txConfig, app.EVMKeeper, app.OPChildKeeper)
if err != nil {
return err
}
Expand Down
28 changes: 25 additions & 3 deletions indexer/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,21 +61,42 @@ func (e *EVMIndexerImpl) ListenFinalizeBlock(ctx context.Context, req abci.Reque
}

ethTxs = append(ethTxs, ethTx)
ethLogs := e.extractLogsFromEvents(txResults.Events)
receipts = append(receipts, &coretypes.Receipt{

// extract logs and contract address from tx results
ethLogs, contractAddr, err := extractLogsAndContractAddr(txResults.Data, ethTx.To() == nil)
if err != nil {
e.logger.Error("failed to extract logs and contract address", "err", err)
return err
}

receipt := coretypes.Receipt{
PostState: nil,
Status: txStatus,
CumulativeGasUsed: usedGas,
Bloom: coretypes.Bloom(coretypes.LogsBloom(ethLogs)),
Logs: ethLogs,
TransactionIndex: txIndex,
})
}

// fill in contract address if it's a contract creation
if contractAddr != nil {
receipt.ContractAddress = *contractAddr
}

receipts = append(receipts, &receipt)
}

chainId := types.ConvertCosmosChainIDToEthereumChainID(sdkCtx.ChainID())
blockGasMeter := sdkCtx.BlockGasMeter()
blockHeight := sdkCtx.BlockHeight()

// compute base fee from the opChild gas prices
baseFee, err := e.baseFee(ctx)
if err != nil {
e.logger.Error("failed to get base fee", "err", err)
return err
}

hasher := trie.NewStackTrie(nil)
blockHeader := coretypes.Header{
TxHash: coretypes.DeriveSha(coretypes.Transactions(ethTxs), hasher),
Expand All @@ -85,6 +106,7 @@ func (e *EVMIndexerImpl) ListenFinalizeBlock(ctx context.Context, req abci.Reque
GasUsed: blockGasMeter.GasConsumedToLimit(),
Number: big.NewInt(blockHeight),
Time: uint64(sdkCtx.BlockTime().Unix()),
BaseFee: baseFee.ToInt(),

// empty values
Root: coretypes.EmptyRootHash,
Expand Down
21 changes: 13 additions & 8 deletions indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/ethereum/go-ethereum/common"
coretypes "github.com/ethereum/go-ethereum/core/types"

opchildkeeper "github.com/initia-labs/OPinit/x/opchild/keeper"
"github.com/initia-labs/kvindexer/store"
rpctypes "github.com/initia-labs/minievm/jsonrpc/types"
evmkeeper "github.com/initia-labs/minievm/x/evm/keeper"
Expand Down Expand Up @@ -50,8 +51,9 @@ type EVMIndexerImpl struct {
txConfig client.TxConfig
appCodec codec.Codec

evmKeeper *evmkeeper.Keeper
store *store.CacheStore
store *store.CacheStore
evmKeeper *evmkeeper.Keeper
opChildKeeper *opchildkeeper.Keeper

schema collections.Schema
TxMap collections.Map[[]byte, rpctypes.RPCTransaction]
Expand All @@ -71,6 +73,7 @@ func NewEVMIndexer(
logger log.Logger,
txConfig client.TxConfig,
evmKeeper *evmkeeper.Keeper,
opChildKeeper *opchildkeeper.Keeper,
) (EVMIndexer, error) {
// TODO make cache size configurable
store := store.NewCacheStore(dbadapter.Store{DB: db}, 100)
Expand All @@ -81,12 +84,14 @@ func NewEVMIndexer(
)

indexer := &EVMIndexerImpl{
db: db,
store: store,
logger: logger,
txConfig: txConfig,
appCodec: appCodec,
evmKeeper: evmKeeper,
db: db,
store: store,
logger: logger,
txConfig: txConfig,
appCodec: appCodec,

evmKeeper: evmKeeper,
opChildKeeper: opChildKeeper,

TxMap: collections.NewMap(sb, prefixTx, "tx", collections.BytesKey, CollJsonVal[rpctypes.RPCTransaction]()),
TxReceiptMap: collections.NewMap(sb, prefixTxReceipt, "tx_receipt", collections.BytesKey, CollJsonVal[coretypes.Receipt]()),
Expand Down
132 changes: 86 additions & 46 deletions indexer/utils.go
Original file line number Diff line number Diff line change
@@ -1,67 +1,64 @@
package indexer

import (
"context"
"encoding/json"
"fmt"
"path/filepath"

"github.com/spf13/cast"

abci "github.com/cometbft/cometbft/abci/types"

collcodec "cosmossdk.io/collections/codec"
dbm "github.com/cosmos/cosmos-db"
"github.com/cosmos/cosmos-sdk/server"
servertypes "github.com/cosmos/cosmos-sdk/server/types"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/gogoproto/proto"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
coretypes "github.com/ethereum/go-ethereum/core/types"

"github.com/initia-labs/minievm/x/evm/types"
)

// helper function to make config creation independent of root dir
func rootify(path, root string) string {
if filepath.IsAbs(path) {
return path
func extractLogsAndContractAddr(data []byte, isContractCreation bool) ([]*coretypes.Log, *common.Address, error) {
var ethLogs []*coretypes.Log
var contractAddr *common.Address

if isContractCreation {
var resp types.MsgCreateResponse
if err := unpackData(data, &resp); err != nil {
return nil, nil, err
}

ethLogs = types.Logs(resp.Logs).ToEthLogs()
contractAddr_ := common.HexToAddress(resp.ContractAddr)
contractAddr = &contractAddr_
} else {
var resp types.MsgCallResponse
if err := unpackData(data, &resp); err != nil {
return nil, nil, err
}

ethLogs = types.Logs(resp.Logs).ToEthLogs()
}
return filepath.Join(root, path)

return ethLogs, contractAddr, nil
}

// getDBConfig returns the database configuration for the EVM indexer
func getDBConfig(appOpts servertypes.AppOptions) (string, dbm.BackendType) {
rootDir := cast.ToString(appOpts.Get("home"))
dbDir := cast.ToString(appOpts.Get("db_dir"))
dbBackend := server.GetAppDBBackend(appOpts)
// unpackData extracts msg response from the data
func unpackData(data []byte, resp proto.Message) error {
var txMsgData sdk.TxMsgData
if err := proto.Unmarshal(data, &txMsgData); err != nil {
return err
}

return rootify(dbDir, rootDir), dbBackend
}
msgResp := txMsgData.MsgResponses[0]
expectedTypeUrl := sdk.MsgTypeURL(resp)
if msgResp.TypeUrl != expectedTypeUrl {
return fmt.Errorf("unexpected type URL; got: %s, expected: %s", msgResp.TypeUrl, expectedTypeUrl)
}

// extractLogsFromEvents extracts logs from the events
func (e *EVMIndexerImpl) extractLogsFromEvents(events []abci.Event) []*coretypes.Log {
var ethLogs []*coretypes.Log
for _, event := range events {
if event.Type == types.EventTypeEVM {
logs := make(types.Logs, 0, len(event.Attributes))

for _, attr := range event.Attributes {
if attr.Key == types.AttributeKeyLog {
var log types.Log
err := json.Unmarshal([]byte(attr.Value), &log)
if err != nil {
e.logger.Error("failed to unmarshal log", "err", err)
continue
}

logs = append(logs, log)
}
}

ethLogs = logs.ToEthLogs()
break
}
// Unpack the response
if err := proto.Unmarshal(msgResp.Value, resp); err != nil {
return err
}

return ethLogs
return nil
}

// CollJsonVal is used for protobuf values of the newest google.golang.org/protobuf API.
Expand Down Expand Up @@ -100,3 +97,46 @@ func (c collJsonVal[T]) Stringify(value T) string {
func (c collJsonVal[T]) ValueType() string {
return "jsonvalue"
}

// calculate BaseFee
func (e *EVMIndexerImpl) feeDenom(ctx context.Context) (string, error) {
params, err := e.evmKeeper.Params.Get(ctx)
if err != nil {
return "", err
}

return params.FeeDenom, nil
}

func (e *EVMIndexerImpl) feeDenomWithDecimals(ctx context.Context) (string, uint8, error) {
feeDenom, err := e.feeDenom(ctx)
if err != nil {
return "", 0, err
}

decimals, err := e.evmKeeper.ERC20Keeper().GetDecimals(ctx, feeDenom)
if err != nil {
return "", 0, err
}

return feeDenom, decimals, nil
}

func (e *EVMIndexerImpl) baseFee(ctx context.Context) (*hexutil.Big, error) {
params, err := e.opChildKeeper.GetParams(ctx)
if err != nil {
return nil, err
}

feeDenom, decimals, err := e.feeDenomWithDecimals(ctx)
if err != nil {
return nil, err
}

// multiply by 1e9 to prevent decimal drops
gasPrice := params.MinGasPrices.AmountOf(feeDenom).
MulTruncate(math.LegacyNewDec(1e9)).
TruncateInt().BigInt()

return (*hexutil.Big)(types.ToEthersUint(decimals+9, gasPrice)), nil
}
37 changes: 37 additions & 0 deletions indexer/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package indexer

import (
"testing"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/gogoproto/proto"
"github.com/stretchr/testify/require"

"github.com/initia-labs/minievm/x/evm/types"
)

func Test_UnpackData(t *testing.T) {
resp := types.MsgCreateResponse{
Result: "ret",
ContractAddr: types.StdAddress.Hex(),
Logs: []types.Log{
{
Address: types.StdAddress.Hex(),
Topics: []string{"topic"},
Data: "data",
},
},
}

anyResp, err := codectypes.NewAnyWithValue(&resp)
require.NoError(t, err)

data, err := proto.Marshal(&sdk.TxMsgData{MsgResponses: []*codectypes.Any{anyResp}})
require.NoError(t, err)

var respOut types.MsgCreateResponse
err = unpackData(data, &respOut)
require.NoError(t, err)
require.Equal(t, resp, respOut)
}
7 changes: 1 addition & 6 deletions jsonrpc/backend/gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,12 +129,7 @@ func (b *JSONRPCBackend) GasPrice() (*hexutil.Big, error) {
return nil, err
}

feeDenom, err := b.feeDenom()
if err != nil {
return nil, err
}

decimals, err := b.app.EVMKeeper.ERC20Keeper().GetDecimals(queryCtx, feeDenom)
feeDenom, decimals, err := b.feeDenomWithDecimals()
if err != nil {
return nil, err
}
Expand Down
6 changes: 3 additions & 3 deletions jsonrpc/backend/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,17 +255,17 @@ func (b *JSONRPCBackend) PendingTransactions() ([]*rpctypes.RPCTransaction, erro
func marshalReceipt(receipt *coretypes.Receipt, tx *rpctypes.RPCTransaction) map[string]interface{} {
fields := map[string]interface{}{
"blockHash": tx.BlockHash,
"blockNumber": hexutil.Big(*tx.BlockNumber),
"blockNumber": hexutil.Uint64(tx.BlockNumber.ToInt().Uint64()),
"transactionHash": tx.Hash,
"transactionIndex": hexutil.Uint64(*tx.TransactionIndex),
"transactionIndex": *tx.TransactionIndex,
"from": tx.From,
"to": tx.To,
"gasUsed": hexutil.Uint64(receipt.GasUsed),
"cumulativeGasUsed": hexutil.Uint64(receipt.CumulativeGasUsed),
"contractAddress": nil,
"logs": receipt.Logs,
"logsBloom": receipt.Bloom,
"type": hexutil.Uint(coretypes.LegacyTxType),
"type": hexutil.Uint(tx.Type),
"effectiveGasPrice": (*hexutil.Big)(receipt.EffectiveGasPrice),
}

Expand Down
7 changes: 2 additions & 5 deletions proto/minievm/evm/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,8 @@ message QueryCallRequest {
// hex encoded call input
string input = 3;
// Value is the amount of fee denom token to transfer to the contract.
string value = 4 [
(gogoproto.customtype) = "cosmossdk.io/math.Int",
(gogoproto.nullable) = false,
(amino.dont_omitempty) = true
];
string value = 4
[(gogoproto.customtype) = "cosmossdk.io/math.Int", (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
// whether to trace the call
// `nil` means no trace
TraceOptions trace_options = 5;
Expand Down
12 changes: 10 additions & 2 deletions proto/minievm/evm/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ message MsgCreateResponse {

// hex encoded address
string contract_addr = 2;

// logs are the contract logs generated by the contract execution.
repeated Log logs = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}

// MsgCreate2 is a message to create a contract with the CREATE2 opcode.
Expand Down Expand Up @@ -81,6 +84,9 @@ message MsgCreate2Response {

// hex encoded address
string contract_addr = 2;

// logs are the contract logs generated by the contract execution.
repeated Log logs = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}

// MsgCall is a message to call an Ethereum contract.
Expand Down Expand Up @@ -109,8 +115,10 @@ message MsgCall {

// MsgCallResponse defines the Msg/Call response type.
message MsgCallResponse {
string result = 1;
repeated Log logs = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
string result = 1;

// logs are the contract logs generated by the contract execution.
repeated Log logs = 2 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}

// MsgUpdateParams defines a Msg for updating the x/evm module parameters.
Expand Down
2 changes: 1 addition & 1 deletion proto/minievm/evm/v1/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ message Params {
(gogoproto.moretags) = "yaml:\"allowed_custom_erc20s\"",
(amino.dont_omitempty) = true
];

// fee_denom defines the fee denom for the evm transactions
string fee_denom = 5 [(gogoproto.moretags) = "yaml:\"fee_denom\""];
}
Expand Down
Loading

0 comments on commit 219db7d

Please sign in to comment.