Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: receipt indexing to include contract address if that is creation #38

Merged
merged 2 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading