Skip to content
8 changes: 7 additions & 1 deletion rpc/backend/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,10 +316,16 @@ func (b *Backend) parseDerivedTxFromAdditionalFields(
additional *rpctypes.TxResultAdditionalFields,
) *evmtypes.MsgEthereumTx {
recipient := additional.Recipient

// for transactions before v31 this value was mistakenly used for Gas field
gas := additional.GasUsed
if additional.GasLimit != nil {
gas = *additional.GasLimit
}
t := ethtypes.NewTx(&ethtypes.LegacyTx{
Nonce: additional.Nonce,
Data: additional.Data,
Gas: additional.GasUsed,
Gas: gas,
To: &recipient,
GasPrice: nil,
Value: additional.Value,
Expand Down
94 changes: 78 additions & 16 deletions rpc/backend/tracing.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
// and returns them as a JSON object.
func (b *Backend) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
// Get transaction by hash
transaction, _, err := b.GetTxByEthHash(hash)
transaction, additional, err := b.GetTxByEthHash(hash)
if err != nil {
b.logger.Debug("tx not found", "hash", hash)
return nil, err
Expand All @@ -40,7 +40,7 @@

// check tx index is not out of bound
if len(blk.Block.Txs) > math.MaxUint32 {
return nil, fmt.Errorf("tx count %d is overfloing", len(blk.Block.Txs))
return nil, fmt.Errorf("tx count %d is overflowing", len(blk.Block.Txs))
}
txsLen := uint32(len(blk.Block.Txs)) // #nosec G115 -- checked for int overflow already
if txsLen < transaction.TxIndex {
Expand All @@ -49,19 +49,56 @@
}

var predecessors []*evmtypes.MsgEthereumTx
for _, txBz := range blk.Block.Txs[:transaction.TxIndex] {
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
for i := 0; i < int(transaction.TxIndex); i++ {
transaction, txAdditional, err := b.GetTxByTxIndex(blk.Block.Height, uint(i))

Check failure on line 53 in rpc/backend/tracing.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

G115: integer overflow conversion int -> uint (gosec)
if err != nil {
b.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
b.logger.Debug("failed to get tx by index",
"height", blk.Block.Height,
"index", i,
"error", err.Error())
continue
}
for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {

if txAdditional != nil {
// Handle synthetic EVM transaction
ethMsg := b.parseDerivedTxFromAdditionalFields(txAdditional)
if ethMsg != nil {
predecessors = append(predecessors, ethMsg)
}
continue
}

// Fallback: decode as normal Cosmos tx
tx, err := b.clientCtx.TxConfig.TxDecoder()(blk.Block.Txs[i])
if err != nil {
b.logger.Debug("failed to decode transaction in block",
"height", blk.Block.Height,
"index", i,
"error", err.Error())
continue
}

index := int(transaction.MsgIndex)
for j := 0; j < index; j++ {
msg := tx.GetMsgs()[j]
// Check if it’s a normal Ethereum tx
if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
predecessors = append(predecessors, ethMsg)
continue
}
// Fetch additional data for predecessors
_, txAdditionalMsg, err := b.GetTxByEthHashAndMsgIndex(hash, j)
if err != nil {
b.logger.Debug("failed to get tx additional info", "error", err.Error())
continue
}

predecessors = append(predecessors, ethMsg)
if txAdditionalMsg != nil {
ethMsg := b.parseDerivedTxFromAdditionalFields(txAdditionalMsg)
if ethMsg != nil {
predecessors = append(predecessors, ethMsg)
}
}
}
}

Expand All @@ -73,18 +110,43 @@

// add predecessor messages in current cosmos tx
index := int(transaction.MsgIndex) // #nosec G115

for i := 0; i < index; i++ {
ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
if !ok {
msg := tx.GetMsgs()[i]
// Check if it’s a normal Ethereum tx
if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
predecessors = append(predecessors, ethMsg)
continue
}
predecessors = append(predecessors, ethMsg)
// Fetch additional data for predecessors
_, txAdditional, err := b.GetTxByEthHashAndMsgIndex(hash, i)
if err != nil {
b.logger.Debug("failed to get tx additional info", "error", err.Error())
continue
}

if txAdditional != nil {
ethMsg := b.parseDerivedTxFromAdditionalFields(txAdditional)
if ethMsg != nil {
predecessors = append(predecessors, ethMsg)
}
}
}
var ethMessage *evmtypes.MsgEthereumTx
var ok bool

ethMessage, ok := tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
return nil, fmt.Errorf("invalid transaction type %T", tx)
if additional == nil {
ethMessage, ok = tx.GetMsgs()[transaction.MsgIndex].(*evmtypes.MsgEthereumTx)
if !ok {
b.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx.GetMsgs()[transaction.MsgIndex]))
return nil, fmt.Errorf("invalid transaction type %T", tx.GetMsgs()[transaction.MsgIndex])
}
} else {
ethMessage = b.parseDerivedTxFromAdditionalFields(additional)
if ethMessage == nil {
b.logger.Error("failed to get derived eth msg from additional fields")
return nil, fmt.Errorf("failed to get derived eth msg from additional fields")
}
}

nc, ok := b.clientCtx.Client.(tmrpcclient.NetworkClient)
Expand Down
20 changes: 20 additions & 0 deletions rpc/backend/tx_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
if i > math.MaxInt32 {
return nil, errors.New("tx index overflow")
}
res.EthTxIndex = int32(i)

Check failure on line 75 in rpc/backend/tx_info.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

G115: integer overflow conversion int -> int32 (gosec)
break
}
}
Expand Down Expand Up @@ -224,7 +224,7 @@
}

var from common.Address
if additional != nil {

Check failure on line 227 in rpc/backend/tx_info.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

ifElseChain: rewrite if-else to switch statement (gocritic)
from = common.HexToAddress(ethMsg.From)
} else if ethMsg.Data != nil {
chainID, err := b.ChainID()
Expand Down Expand Up @@ -430,6 +430,26 @@
return txResult, txAdditional, nil
}

func (b *Backend) GetTxByEthHashAndMsgIndex(hash common.Hash, index int) (*types.TxResult, *rpctypes.TxResultAdditionalFields, error) {
if b.indexer != nil {
txRes, err := b.indexer.GetByTxHash(hash)
if err != nil {
return nil, nil, err
}
return txRes, nil, nil
}

// fallback to tendermint tx indexer
query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
txResult, txAdditional, err := b.queryTendermintTxIndexer(query, func(txs *rpctypes.ParsedTxs) *rpctypes.ParsedTx {
return txs.GetTxByMsgIndex(index)
})
if err != nil {
return nil, nil, errorsmod.Wrapf(err, "GetTxByEthHash %s", hash.Hex())
}
return txResult, txAdditional, nil
}

// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
func (b *Backend) GetTxByTxIndex(height int64, index uint) (*types.TxResult, *rpctypes.TxResultAdditionalFields, error) {
int32Index := int32(index) //#nosec G115 -- checked for int overflow already
Expand Down
10 changes: 10 additions & 0 deletions rpc/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
Recipient common.Address
Sender common.Address
Nonce uint64
GasLimit uint64
Data []byte
}

Expand Down Expand Up @@ -153,7 +154,7 @@

// Handle fallback GasUsed
if len(p.Txs) == 1 && p.Txs[0].Type != evmtypes.DerivedTxType {
p.Txs[0].GasUsed = uint64(result.GasUsed)

Check failure on line 157 in rpc/types/events.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

G115: integer overflow conversion int64 -> uint64 (gosec)
}

// Handle failure fallback
Expand Down Expand Up @@ -233,6 +234,7 @@
GasUsed: parsedTx.GasUsed,
Data: parsedTx.Data,
Nonce: parsedTx.Nonce,
GasLimit: &parsedTx.GasLimit,
}, nil
}
return &types.TxResult{
Expand Down Expand Up @@ -285,6 +287,7 @@
GasUsed: parsedTx.GasUsed,
Data: parsedTx.Data,
Nonce: parsedTx.Nonce,
GasLimit: &parsedTx.GasLimit,
}, nil
}
return &types.TxResult{
Expand Down Expand Up @@ -411,6 +414,13 @@
}
tx.Nonce = nonce

case evmtypes.AttributeKeyTxGasLimit:
gasLimit, err := strconv.ParseUint(value, 10, 64)
if err != nil {
return err
}
tx.GasLimit = gasLimit

case evmtypes.AttributeKeyTxData:
hexBytes, err := hexutil.Decode(value)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions rpc/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type TxResultAdditionalFields struct {
GasUsed uint64 `json:"gasUsed"`
Nonce uint64 `json:"nonce"`
Data []byte `json:"data"`
GasLimit *uint64 `json:"gasLimit"`
}

// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
Expand Down
1 change: 1 addition & 0 deletions x/vm/keeper/call_evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ func (k Keeper) DerivedEVMCallWithData(
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxData, hexutil.Encode(msg.Data())))
// adding nonce for more info in rpc methods in order to parse derived txs
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxNonce, strconv.FormatUint(nonce, 10)))
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyTxGasLimit, strconv.FormatUint(gasCap, 10)))
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEthereumTx,
Expand Down
58 changes: 50 additions & 8 deletions x/vm/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -487,10 +488,18 @@

for i, tx := range req.Predecessors {
ethTx := tx.AsTransaction()
msg, err := ethTx.AsMessage(signer, cfg.BaseFee)
if err != nil {
continue

var msg ethtypes.Message
// if tx is not unsigned, from field should be derived from signer, which can be done using AsMessage function
if !isUnsigned(ethTx) {
msg, err = ethTx.AsMessage(signer, cfg.BaseFee)
if err != nil {
continue
}
} else {
msg = unsignedTxAsMessage(common.BytesToAddress(req.Msg.GetFrom()), ethTx, cfg.BaseFee)
}

txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i) //nolint:gosec // G115 // won't exceed uint64
// reset gas meter for each transaction
Expand All @@ -515,7 +524,7 @@
_ = json.Unmarshal([]byte(req.TraceConfig.TracerJsonConfig), &tracerConfig)
}

result, _, err := k.traceTx(ctx, cfg, txConfig, signer, tx, req.TraceConfig, false, tracerConfig)
result, _, err := k.traceTx(ctx, cfg, txConfig, signer, common.BytesToAddress(req.Msg.GetFrom()), tx, req.TraceConfig, false, tracerConfig)
if err != nil {
// error will be returned with detail status from traceTx
return nil, err
Expand Down Expand Up @@ -582,7 +591,7 @@
ethTx := tx.AsTransaction()
txConfig.TxHash = ethTx.Hash()
txConfig.TxIndex = uint(i) //nolint:gosec // G115 // won't exceed uint64
traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, ethTx, req.TraceConfig, true, nil)
traceResult, logIndex, err := k.traceTx(ctx, cfg, txConfig, signer, common.BytesToAddress(tx.GetFrom()), ethTx, req.TraceConfig, true, nil)
if err != nil {
result.Error = err.Error()
} else {
Expand All @@ -608,6 +617,7 @@
cfg *statedb.EVMConfig,
txConfig statedb.TxConfig,
signer ethtypes.Signer,
from common.Address,
tx *ethtypes.Transaction,
traceConfig *types.TraceConfig,
commitMessage bool,
Expand All @@ -620,9 +630,15 @@
err error
timeout = defaultTraceTimeout
)
msg, err := tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
var msg ethtypes.Message
// if tx is not unsigned, from field should be derived from signer, which can be done using AsMessage function
if !isUnsigned(tx) {
msg, err = tx.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, 0, status.Error(codes.Internal, err.Error())
}
} else {
msg = unsignedTxAsMessage(from, tx, cfg.BaseFee)
}

if traceConfig == nil {
Expand Down Expand Up @@ -722,3 +738,29 @@

return &types.QueryConfigResponse{Config: config}, nil
}

func isUnsigned(ethTx *ethtypes.Transaction) bool {
r, v, s := ethTx.RawSignatureValues()

return (r == nil && v == nil && s == nil) || (r.Int64() == 0 && v.Int64() == 0 && s.Int64() == 0)
}

Check failure on line 746 in x/vm/keeper/grpc_query.go

View workflow job for this annotation

GitHub Actions / Run golangci-lint

File is not properly formatted (gofumpt)
func unsignedTxAsMessage(fromAddress common.Address, tx *ethtypes.Transaction, baseFee *big.Int) ethtypes.Message {
gasPrice := new(big.Int).Set(tx.GasPrice())
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
gasPrice = math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
}
return ethtypes.NewMessage(
fromAddress,
tx.To(),
tx.Nonce(),
tx.Value(),
tx.Gas(),
gasPrice,
tx.GasFeeCap(),
tx.GasTipCap(),
tx.Data(),
tx.AccessList(),
false,
)
}
1 change: 1 addition & 0 deletions x/vm/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const (
AttributeKeyTxType = "txType"
AttributeKeyTxLog = "txLog"
AttributeKeyTxNonce = "txNonce"
AttributeKeyTxGasLimit = "txGasLimit"
AttributeKeyTxData = "txData"

// tx failed in eth vm execution
Expand Down
Loading