forked from cerc-io/laconicd-deprecated
update fork
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// AddrLocker is a mutex structure used to avoid querying outdated account data
|
||||
type AddrLocker struct {
|
||||
mu sync.Mutex
|
||||
locks map[common.Address]*sync.Mutex
|
||||
}
|
||||
|
||||
// lock returns the lock of the given address.
|
||||
func (l *AddrLocker) lock(address common.Address) *sync.Mutex {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
if l.locks == nil {
|
||||
l.locks = make(map[common.Address]*sync.Mutex)
|
||||
}
|
||||
if _, ok := l.locks[address]; !ok {
|
||||
l.locks[address] = new(sync.Mutex)
|
||||
}
|
||||
return l.locks[address]
|
||||
}
|
||||
|
||||
// LockAddr locks an account's mutex. This is used to prevent another tx getting the
|
||||
// same nonce until the lock is released. The mutex prevents the (an identical nonce) from
|
||||
// being read again during the time that the first transaction is being signed.
|
||||
func (l *AddrLocker) LockAddr(address common.Address) {
|
||||
l.lock(address).Lock()
|
||||
}
|
||||
|
||||
// UnlockAddr unlocks the mutex of the given account.
|
||||
func (l *AddrLocker) UnlockAddr(address common.Address) {
|
||||
l.lock(address).Unlock()
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cast"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
|
||||
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
||||
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
)
|
||||
|
||||
// BlockNumber represents decoding hex string to block values
|
||||
type BlockNumber int64
|
||||
|
||||
const (
|
||||
EthPendingBlockNumber = BlockNumber(-2)
|
||||
EthLatestBlockNumber = BlockNumber(-1)
|
||||
EthEarliestBlockNumber = BlockNumber(0)
|
||||
)
|
||||
|
||||
const (
|
||||
BlockParamEarliest = "earliest"
|
||||
BlockParamLatest = "latest"
|
||||
BlockParamFinalized = "finalized"
|
||||
BlockParamPending = "pending"
|
||||
)
|
||||
|
||||
// NewBlockNumber creates a new BlockNumber instance.
|
||||
func NewBlockNumber(n *big.Int) BlockNumber {
|
||||
if !n.IsInt64() {
|
||||
// default to latest block if it overflows
|
||||
return EthLatestBlockNumber
|
||||
}
|
||||
|
||||
return BlockNumber(n.Int64())
|
||||
}
|
||||
|
||||
// ContextWithHeight wraps a context with the a gRPC block height header. If the provided height is
|
||||
// 0, it will return an empty context and the gRPC query will use the latest block height for querying.
|
||||
// Note that all metadata are processed and removed by tendermint layer, so it wont be accessible at gRPC server level.
|
||||
func ContextWithHeight(height int64) context.Context {
|
||||
if height == 0 {
|
||||
return context.Background()
|
||||
}
|
||||
|
||||
return metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, fmt.Sprintf("%d", height))
|
||||
}
|
||||
|
||||
// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports:
|
||||
// - "latest", "finalized", "earliest" or "pending" as string arguments
|
||||
// - the block number
|
||||
// Returned errors:
|
||||
// - an invalid block number error when the given argument isn't a known strings
|
||||
// - an out of range error when the given block number is either too little or too large
|
||||
func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
|
||||
input := strings.TrimSpace(string(data))
|
||||
if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' {
|
||||
input = input[1 : len(input)-1]
|
||||
}
|
||||
|
||||
switch input {
|
||||
case BlockParamEarliest:
|
||||
*bn = EthEarliestBlockNumber
|
||||
return nil
|
||||
case BlockParamLatest, BlockParamFinalized:
|
||||
*bn = EthLatestBlockNumber
|
||||
return nil
|
||||
case BlockParamPending:
|
||||
*bn = EthPendingBlockNumber
|
||||
return nil
|
||||
}
|
||||
|
||||
blckNum, err := hexutil.DecodeUint64(input)
|
||||
if errors.Is(err, hexutil.ErrMissingPrefix) {
|
||||
blckNum = cast.ToUint64(input)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if blckNum > math.MaxInt64 {
|
||||
return fmt.Errorf("block number larger than int64")
|
||||
}
|
||||
*bn = BlockNumber(blckNum)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Int64 converts block number to primitive type
|
||||
func (bn BlockNumber) Int64() int64 {
|
||||
if bn < 0 {
|
||||
return 0
|
||||
} else if bn == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return int64(bn)
|
||||
}
|
||||
|
||||
// TmHeight is a util function used for the Tendermint RPC client. It returns
|
||||
// nil if the block number is "latest". Otherwise, it returns the pointer of the
|
||||
// int64 value of the height.
|
||||
func (bn BlockNumber) TmHeight() *int64 {
|
||||
if bn < 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
height := bn.Int64()
|
||||
return &height
|
||||
}
|
||||
|
||||
// BlockNumberOrHash represents a block number or a block hash.
|
||||
type BlockNumberOrHash struct {
|
||||
BlockNumber *BlockNumber `json:"blockNumber,omitempty"`
|
||||
BlockHash *common.Hash `json:"blockHash,omitempty"`
|
||||
}
|
||||
|
||||
func (bnh *BlockNumberOrHash) UnmarshalJSON(data []byte) error {
|
||||
type erased BlockNumberOrHash
|
||||
e := erased{}
|
||||
err := json.Unmarshal(data, &e)
|
||||
if err == nil {
|
||||
return bnh.checkUnmarshal(BlockNumberOrHash(e))
|
||||
}
|
||||
var input string
|
||||
err = json.Unmarshal(data, &input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = bnh.decodeFromString(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bnh *BlockNumberOrHash) checkUnmarshal(e BlockNumberOrHash) error {
|
||||
if e.BlockNumber != nil && e.BlockHash != nil {
|
||||
return fmt.Errorf("cannot specify both BlockHash and BlockNumber, choose one or the other")
|
||||
}
|
||||
bnh.BlockNumber = e.BlockNumber
|
||||
bnh.BlockHash = e.BlockHash
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bnh *BlockNumberOrHash) decodeFromString(input string) error {
|
||||
switch input {
|
||||
case BlockParamEarliest:
|
||||
bn := EthEarliestBlockNumber
|
||||
bnh.BlockNumber = &bn
|
||||
case BlockParamLatest:
|
||||
bn := EthLatestBlockNumber
|
||||
bnh.BlockNumber = &bn
|
||||
case BlockParamPending:
|
||||
bn := EthPendingBlockNumber
|
||||
bnh.BlockNumber = &bn
|
||||
default:
|
||||
// check if the input is a block hash
|
||||
if len(input) == 66 {
|
||||
hash := common.Hash{}
|
||||
err := hash.UnmarshalText([]byte(input))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bnh.BlockHash = &hash
|
||||
break
|
||||
}
|
||||
// otherwise take the hex string has int64 value
|
||||
blockNumber, err := hexutil.DecodeUint64(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bnInt, err := ethermint.SafeInt64(blockNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bn := BlockNumber(bnInt)
|
||||
bnh.BlockNumber = &bn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnmarshalBlockNumberOrHash(t *testing.T) {
|
||||
bnh := new(BlockNumberOrHash)
|
||||
|
||||
testCases := []struct {
|
||||
msg string
|
||||
input []byte
|
||||
malleate func()
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
"JSON input with block hash",
|
||||
[]byte("{\"blockHash\": \"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\"}"),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockHash, common.HexToHash("0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739"))
|
||||
require.Nil(t, bnh.BlockNumber)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"JSON input with block number",
|
||||
[]byte("{\"blockNumber\": \"0x35\"}"),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, BlockNumber(0x35))
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"JSON input with block number latest",
|
||||
[]byte("{\"blockNumber\": \"latest\"}"),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, EthLatestBlockNumber)
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"JSON input with both block hash and block number",
|
||||
[]byte("{\"blockHash\": \"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\", \"blockNumber\": \"0x35\"}"),
|
||||
func() {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"String input with block hash",
|
||||
[]byte("\"0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739\""),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockHash, common.HexToHash("0x579917054e325746fda5c3ee431d73d26255bc4e10b51163862368629ae19739"))
|
||||
require.Nil(t, bnh.BlockNumber)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"String input with block number",
|
||||
[]byte("\"0x35\""),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, BlockNumber(0x35))
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"String input with block number latest",
|
||||
[]byte("\"latest\""),
|
||||
func() {
|
||||
require.Equal(t, *bnh.BlockNumber, EthLatestBlockNumber)
|
||||
require.Nil(t, bnh.BlockHash)
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"String input with block number overflow",
|
||||
[]byte("\"0xffffffffffffffffffffffffffffffffffffff\""),
|
||||
func() {
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
fmt.Sprintf("Case %s", tc.msg)
|
||||
// reset input
|
||||
bnh = new(BlockNumberOrHash)
|
||||
err := bnh.UnmarshalJSON(tc.input)
|
||||
tc.malleate()
|
||||
if tc.expPass {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
ethermint "github.com/cerc-io/laconicd/types"
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// EventFormat is the format version of the events.
|
||||
//
|
||||
// To fix the issue of tx exceeds block gas limit, we changed the event format in a breaking way.
|
||||
// But to avoid forcing clients to re-sync from scatch, we make json-rpc logic to be compatible with both formats.
|
||||
type EventFormat int
|
||||
|
||||
const (
|
||||
eventFormatUnknown EventFormat = iota
|
||||
|
||||
// Event Format 1 (the format used before PR #1062):
|
||||
// ```
|
||||
// ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed)
|
||||
// tx_log(txLog, txLog, ...)
|
||||
// ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed)
|
||||
// tx_log(txLog, txLog, ...)
|
||||
// ...
|
||||
// ```
|
||||
eventFormat1
|
||||
|
||||
// Event Format 2 (the format used after PR #1062):
|
||||
// ```
|
||||
// ethereum_tx(ethereumTxHash, txIndex)
|
||||
// ethereum_tx(ethereumTxHash, txIndex)
|
||||
// ...
|
||||
// ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed)
|
||||
// tx_log(txLog, txLog, ...)
|
||||
// ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed)
|
||||
// tx_log(txLog, txLog, ...)
|
||||
// ...
|
||||
// ```
|
||||
// If the transaction exceeds block gas limit, it only emits the first part.
|
||||
eventFormat2
|
||||
)
|
||||
|
||||
// ParsedTx is the tx infos parsed from events.
|
||||
type ParsedTx struct {
|
||||
MsgIndex int
|
||||
|
||||
// the following fields are parsed from events
|
||||
|
||||
Hash common.Hash
|
||||
// -1 means uninitialized
|
||||
EthTxIndex int32
|
||||
GasUsed uint64
|
||||
Failed bool
|
||||
}
|
||||
|
||||
// NewParsedTx initialize a ParsedTx
|
||||
func NewParsedTx(msgIndex int) ParsedTx {
|
||||
return ParsedTx{MsgIndex: msgIndex, EthTxIndex: -1}
|
||||
}
|
||||
|
||||
// ParsedTxs is the tx infos parsed from eth tx events.
|
||||
type ParsedTxs struct {
|
||||
// one item per message
|
||||
Txs []ParsedTx
|
||||
// map tx hash to msg index
|
||||
TxHashes map[common.Hash]int
|
||||
}
|
||||
|
||||
// ParseTxResult parse eth tx infos from cosmos-sdk events.
|
||||
// It supports two event formats, the formats are described in the comments of the format constants.
|
||||
func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error) {
|
||||
format := eventFormatUnknown
|
||||
// the index of current ethereum_tx event in format 1 or the second part of format 2
|
||||
eventIndex := -1
|
||||
|
||||
p := &ParsedTxs{
|
||||
TxHashes: make(map[common.Hash]int),
|
||||
}
|
||||
for _, event := range result.Events {
|
||||
if event.Type != evmtypes.EventTypeEthereumTx {
|
||||
continue
|
||||
}
|
||||
|
||||
if format == eventFormatUnknown {
|
||||
// discover the format version by inspect the first ethereum_tx event.
|
||||
if len(event.Attributes) > 2 {
|
||||
format = eventFormat1
|
||||
} else {
|
||||
format = eventFormat2
|
||||
}
|
||||
}
|
||||
|
||||
if len(event.Attributes) == 2 {
|
||||
// the first part of format 2
|
||||
if err := p.newTx(event.Attributes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// format 1 or second part of format 2
|
||||
eventIndex++
|
||||
if format == eventFormat1 {
|
||||
// append tx
|
||||
if err := p.newTx(event.Attributes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// the second part of format 2, update tx fields
|
||||
if err := p.updateTx(eventIndex, event.Attributes); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// some old versions miss some events, fill it with tx result
|
||||
if len(p.Txs) == 1 {
|
||||
p.Txs[0].GasUsed = uint64(result.GasUsed)
|
||||
}
|
||||
|
||||
// this could only happen if tx exceeds block gas limit
|
||||
if result.Code != 0 && tx != nil {
|
||||
for i := 0; i < len(p.Txs); i++ {
|
||||
p.Txs[i].Failed = true
|
||||
|
||||
// replace gasUsed with gasLimit because that's what's actually deducted.
|
||||
gasLimit := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx).GetGas()
|
||||
p.Txs[i].GasUsed = gasLimit
|
||||
}
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ParseTxIndexerResult parse tm tx result to a format compatible with the custom tx indexer.
|
||||
func ParseTxIndexerResult(txResult *tmrpctypes.ResultTx, tx sdk.Tx, getter func(*ParsedTxs) *ParsedTx) (*ethermint.TxResult, error) {
|
||||
txs, err := ParseTxResult(&txResult.TxResult, tx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse tx events: block %d, index %d, %v", txResult.Height, txResult.Index, err)
|
||||
}
|
||||
|
||||
parsedTx := getter(txs)
|
||||
if parsedTx == nil {
|
||||
return nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", txResult.Height, txResult.Index)
|
||||
}
|
||||
|
||||
return ðermint.TxResult{
|
||||
Height: txResult.Height,
|
||||
TxIndex: txResult.Index,
|
||||
MsgIndex: uint32(parsedTx.MsgIndex),
|
||||
EthTxIndex: parsedTx.EthTxIndex,
|
||||
Failed: parsedTx.Failed,
|
||||
GasUsed: parsedTx.GasUsed,
|
||||
CumulativeGasUsed: txs.AccumulativeGasUsed(parsedTx.MsgIndex),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newTx parse a new tx from events, called during parsing.
|
||||
func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error {
|
||||
msgIndex := len(p.Txs)
|
||||
tx := NewParsedTx(msgIndex)
|
||||
if err := fillTxAttributes(&tx, attrs); err != nil {
|
||||
return err
|
||||
}
|
||||
p.Txs = append(p.Txs, tx)
|
||||
p.TxHashes[tx.Hash] = msgIndex
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateTx updates an exiting tx from events, called during parsing.
|
||||
// In event format 2, we update the tx with the attributes of the second `ethereum_tx` event,
|
||||
// Due to bug https://github.com/cerc-io/laconicd/issues/1175, the first `ethereum_tx` event may emit incorrect tx hash,
|
||||
// so we prefer the second event and override the first one.
|
||||
func (p *ParsedTxs) updateTx(eventIndex int, attrs []abci.EventAttribute) error {
|
||||
tx := NewParsedTx(eventIndex)
|
||||
if err := fillTxAttributes(&tx, attrs); err != nil {
|
||||
return err
|
||||
}
|
||||
if tx.Hash != p.Txs[eventIndex].Hash {
|
||||
// if hash is different, index the new one too
|
||||
p.TxHashes[tx.Hash] = eventIndex
|
||||
}
|
||||
// override the tx because the second event is more trustworthy
|
||||
p.Txs[eventIndex] = tx
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTxByHash find ParsedTx by tx hash, returns nil if not exists.
|
||||
func (p *ParsedTxs) GetTxByHash(hash common.Hash) *ParsedTx {
|
||||
if idx, ok := p.TxHashes[hash]; ok {
|
||||
return &p.Txs[idx]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTxByMsgIndex returns ParsedTx by msg index
|
||||
func (p *ParsedTxs) GetTxByMsgIndex(i int) *ParsedTx {
|
||||
if i < 0 || i >= len(p.Txs) {
|
||||
return nil
|
||||
}
|
||||
return &p.Txs[i]
|
||||
}
|
||||
|
||||
// GetTxByTxIndex returns ParsedTx by tx index
|
||||
func (p *ParsedTxs) GetTxByTxIndex(txIndex int) *ParsedTx {
|
||||
if len(p.Txs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// assuming the `EthTxIndex` increase continuously,
|
||||
// convert TxIndex to MsgIndex by subtract the begin TxIndex.
|
||||
msgIndex := txIndex - int(p.Txs[0].EthTxIndex)
|
||||
// GetTxByMsgIndex will check the bound
|
||||
return p.GetTxByMsgIndex(msgIndex)
|
||||
}
|
||||
|
||||
// AccumulativeGasUsed calculates the accumulated gas used within the batch of txs
|
||||
func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) {
|
||||
for i := 0; i <= msgIndex; i++ {
|
||||
result += p.Txs[i].GasUsed
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// fillTxAttribute parse attributes by name, less efficient than hardcode the index, but more stable against event
|
||||
// format changes.
|
||||
func fillTxAttribute(tx *ParsedTx, key []byte, value []byte) error {
|
||||
switch string(key) {
|
||||
case evmtypes.AttributeKeyEthereumTxHash:
|
||||
tx.Hash = common.HexToHash(string(value))
|
||||
case evmtypes.AttributeKeyTxIndex:
|
||||
txIndex, err := strconv.ParseUint(string(value), 10, 31)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.EthTxIndex = int32(txIndex)
|
||||
case evmtypes.AttributeKeyTxGasUsed:
|
||||
gasUsed, err := strconv.ParseUint(string(value), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tx.GasUsed = gasUsed
|
||||
case evmtypes.AttributeKeyEthereumTxFailed:
|
||||
tx.Failed = len(value) > 0
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error {
|
||||
for _, attr := range attrs {
|
||||
if err := fillTxAttribute(tx, attr.Key, attr.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
func TestParseTxResult(t *testing.T) {
|
||||
address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2"
|
||||
txHash := common.BigToHash(big.NewInt(1))
|
||||
txHash2 := common.BigToHash(big.NewInt(2))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
response abci.ResponseDeliverTx
|
||||
expTxs []*ParsedTx // expected parse result, nil means expect error.
|
||||
}{
|
||||
{
|
||||
"format 1 events",
|
||||
abci.ResponseDeliverTx{
|
||||
GasUsed: 21000,
|
||||
Events: []abci.Event{
|
||||
{Type: "coin_received", Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("receiver"), Value: []byte("ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa")},
|
||||
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
|
||||
}},
|
||||
{Type: "coin_spent", Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("spender"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
||||
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("10")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
{Type: "message", Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
|
||||
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
||||
{Key: []byte("module"), Value: []byte("evm")},
|
||||
{Key: []byte("sender"), Value: []byte(address)},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("11")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
|
||||
},
|
||||
},
|
||||
[]*ParsedTx{
|
||||
{
|
||||
MsgIndex: 0,
|
||||
Hash: txHash,
|
||||
EthTxIndex: 10,
|
||||
GasUsed: 21000,
|
||||
Failed: false,
|
||||
},
|
||||
{
|
||||
MsgIndex: 1,
|
||||
Hash: txHash2,
|
||||
EthTxIndex: 11,
|
||||
GasUsed: 21000,
|
||||
Failed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"format 2 events",
|
||||
abci.ResponseDeliverTx{
|
||||
GasUsed: 21000,
|
||||
Events: []abci.Event{
|
||||
{Type: "coin_received", Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("receiver"), Value: []byte("ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa")},
|
||||
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
|
||||
}},
|
||||
{Type: "coin_spent", Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("spender"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
||||
{Key: []byte("amount"), Value: []byte("1252860basetcro")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("0")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("0")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
{Type: "message", Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("action"), Value: []byte("/ethermint.evm.v1.MsgEthereumTx")},
|
||||
{Key: []byte("key"), Value: []byte("ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl")},
|
||||
{Key: []byte("module"), Value: []byte("evm")},
|
||||
{Key: []byte("sender"), Value: []byte(address)},
|
||||
}},
|
||||
},
|
||||
},
|
||||
[]*ParsedTx{
|
||||
{
|
||||
MsgIndex: 0,
|
||||
Hash: txHash,
|
||||
EthTxIndex: 0,
|
||||
GasUsed: 21000,
|
||||
Failed: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"format 1 events, failed",
|
||||
abci.ResponseDeliverTx{
|
||||
GasUsed: 21000,
|
||||
Events: []abci.Event{
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("10")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("0x01")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"format 1 events, failed",
|
||||
abci.ResponseDeliverTx{
|
||||
GasUsed: 21000,
|
||||
Events: []abci.Event{
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("10")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash2.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("10")},
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("0x01")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
{Key: []byte("ethereumTxFailed"), Value: []byte("contract reverted")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeTxLog, Attributes: []abci.EventAttribute{}},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"format 2 events failed",
|
||||
abci.ResponseDeliverTx{
|
||||
GasUsed: 21000,
|
||||
Events: []abci.Event{
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("0x01")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("21000")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"format 2 events failed",
|
||||
abci.ResponseDeliverTx{
|
||||
GasUsed: 21000,
|
||||
Events: []abci.Event{
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("ethereumTxHash"), Value: []byte(txHash.Hex())},
|
||||
{Key: []byte("txIndex"), Value: []byte("10")},
|
||||
}},
|
||||
{Type: evmtypes.EventTypeEthereumTx, Attributes: []abci.EventAttribute{
|
||||
{Key: []byte("amount"), Value: []byte("1000")},
|
||||
{Key: []byte("txGasUsed"), Value: []byte("0x01")},
|
||||
{Key: []byte("txHash"), Value: []byte("14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57")},
|
||||
{Key: []byte("recipient"), Value: []byte("0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7")},
|
||||
}},
|
||||
},
|
||||
},
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
parsed, err := ParseTxResult(&tc.response, nil)
|
||||
if tc.expTxs == nil {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
for msgIndex, expTx := range tc.expTxs {
|
||||
require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex))
|
||||
require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash))
|
||||
require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex)))
|
||||
}
|
||||
// non-exists tx hash
|
||||
require.Nil(t, parsed.GetTxByHash(common.Hash{}))
|
||||
// out of range
|
||||
require.Nil(t, parsed.GetTxByMsgIndex(len(tc.expTxs)))
|
||||
require.Nil(t, parsed.GetTxByTxIndex(99999999))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/tx"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/proto/tendermint/crypto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
feemarkettypes "github.com/cerc-io/laconicd/x/feemarket/types"
|
||||
)
|
||||
|
||||
// QueryClient defines a gRPC Client used for:
|
||||
// - Transaction simulation
|
||||
// - EVM module queries
|
||||
// - Fee market module queries
|
||||
type QueryClient struct {
|
||||
tx.ServiceClient
|
||||
evmtypes.QueryClient
|
||||
FeeMarket feemarkettypes.QueryClient
|
||||
}
|
||||
|
||||
// NewQueryClient creates a new gRPC query client
|
||||
func NewQueryClient(clientCtx client.Context) *QueryClient {
|
||||
return &QueryClient{
|
||||
ServiceClient: tx.NewServiceClient(clientCtx),
|
||||
QueryClient: evmtypes.NewQueryClient(clientCtx),
|
||||
FeeMarket: feemarkettypes.NewQueryClient(clientCtx),
|
||||
}
|
||||
}
|
||||
|
||||
// GetProof performs an ABCI query with the given key and returns a merkle proof. The desired
|
||||
// tendermint height to perform the query should be set in the client context. The query will be
|
||||
// performed at one below this height (at the IAVL version) in order to obtain the correct merkle
|
||||
// proof. Proof queries at height less than or equal to 2 are not supported.
|
||||
// Issue: https://github.com/cosmos/cosmos-sdk/issues/6567
|
||||
func (QueryClient) GetProof(clientCtx client.Context, storeKey string, key []byte) ([]byte, *crypto.ProofOps, error) {
|
||||
height := clientCtx.Height
|
||||
// ABCI queries at height less than or equal to 2 are not supported.
|
||||
// Base app does not support queries for height less than or equal to 1.
|
||||
// Therefore, a query at height 2 would be equivalent to a query at height 3
|
||||
if height <= 2 {
|
||||
return nil, nil, fmt.Errorf("proof queries at height <= 2 are not supported")
|
||||
}
|
||||
|
||||
// Use the IAVL height if a valid tendermint height is passed in.
|
||||
height--
|
||||
|
||||
abciReq := abci.RequestQuery{
|
||||
Path: fmt.Sprintf("store/%s/key", storeKey),
|
||||
Data: key,
|
||||
Height: height,
|
||||
Prove: true,
|
||||
}
|
||||
|
||||
abciRes, err := clientCtx.QueryABCI(abciReq)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return abciRes.Value, abciRes.ProofOps, nil
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
)
|
||||
|
||||
// Copied the Account and StorageResult types since they are registered under an
|
||||
// internal pkg on geth.
|
||||
|
||||
// AccountResult struct for account proof
|
||||
type AccountResult struct {
|
||||
Address common.Address `json:"address"`
|
||||
AccountProof []string `json:"accountProof"`
|
||||
Balance *hexutil.Big `json:"balance"`
|
||||
CodeHash common.Hash `json:"codeHash"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
StorageHash common.Hash `json:"storageHash"`
|
||||
StorageProof []StorageResult `json:"storageProof"`
|
||||
}
|
||||
|
||||
// StorageResult defines the format for storage proof return
|
||||
type StorageResult struct {
|
||||
Key string `json:"key"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Proof []string `json:"proof"`
|
||||
}
|
||||
|
||||
// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
|
||||
type RPCTransaction struct {
|
||||
BlockHash *common.Hash `json:"blockHash"`
|
||||
BlockNumber *hexutil.Big `json:"blockNumber"`
|
||||
From common.Address `json:"from"`
|
||||
Gas hexutil.Uint64 `json:"gas"`
|
||||
GasPrice *hexutil.Big `json:"gasPrice"`
|
||||
GasFeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"`
|
||||
GasTipCap *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"`
|
||||
Hash common.Hash `json:"hash"`
|
||||
Input hexutil.Bytes `json:"input"`
|
||||
Nonce hexutil.Uint64 `json:"nonce"`
|
||||
To *common.Address `json:"to"`
|
||||
TransactionIndex *hexutil.Uint64 `json:"transactionIndex"`
|
||||
Value *hexutil.Big `json:"value"`
|
||||
Type hexutil.Uint64 `json:"type"`
|
||||
Accesses *ethtypes.AccessList `json:"accessList,omitempty"`
|
||||
ChainID *hexutil.Big `json:"chainId,omitempty"`
|
||||
V *hexutil.Big `json:"v"`
|
||||
R *hexutil.Big `json:"r"`
|
||||
S *hexutil.Big `json:"s"`
|
||||
}
|
||||
|
||||
// StateOverride is the collection of overridden accounts.
|
||||
type StateOverride map[common.Address]OverrideAccount
|
||||
|
||||
// OverrideAccount indicates the overriding fields of account during the execution of
|
||||
// a message call.
|
||||
// Note, state and stateDiff can't be specified at the same time. If state is
|
||||
// set, message execution will only use the data in the given state. Otherwise
|
||||
// if statDiff is set, all diff will be applied first and then execute the call
|
||||
// message.
|
||||
type OverrideAccount struct {
|
||||
Nonce *hexutil.Uint64 `json:"nonce"`
|
||||
Code *hexutil.Bytes `json:"code"`
|
||||
Balance **hexutil.Big `json:"balance"`
|
||||
State *map[common.Hash]common.Hash `json:"state"`
|
||||
StateDiff *map[common.Hash]common.Hash `json:"stateDiff"`
|
||||
}
|
||||
|
||||
type FeeHistoryResult struct {
|
||||
OldestBlock *hexutil.Big `json:"oldestBlock"`
|
||||
Reward [][]*hexutil.Big `json:"reward,omitempty"`
|
||||
BaseFee []*hexutil.Big `json:"baseFeePerGas,omitempty"`
|
||||
GasUsedRatio []float64 `json:"gasUsedRatio"`
|
||||
}
|
||||
|
||||
// SignTransactionResult represents a RLP encoded signed transaction.
|
||||
type SignTransactionResult struct {
|
||||
Raw hexutil.Bytes `json:"raw"`
|
||||
Tx *ethtypes.Transaction `json:"tx"`
|
||||
}
|
||||
|
||||
type OneFeeHistory struct {
|
||||
BaseFee *big.Int // base fee for each block
|
||||
Reward []*big.Int // each element of the array will have the tip provided to miners for the percentile given
|
||||
GasUsedRatio float64 // the ratio of gas used to the gas limit for each block
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtypes "github.com/tendermint/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
evmtypes "github.com/cerc-io/laconicd/x/evm/types"
|
||||
feemarkettypes "github.com/cerc-io/laconicd/x/feemarket/types"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
ethtypes "github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// ExceedBlockGasLimitError defines the error message when tx execution exceeds the block gas limit.
|
||||
// The tx fee is deducted in ante handler, so it shouldn't be ignored in JSON-RPC API.
|
||||
const ExceedBlockGasLimitError = "out of gas in location: block gas meter; gasWanted:"
|
||||
|
||||
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
|
||||
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
|
||||
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
|
||||
}
|
||||
|
||||
ethTxs := make([]*evmtypes.MsgEthereumTx, len(tx.GetMsgs()))
|
||||
for i, msg := range tx.GetMsgs() {
|
||||
ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid message type %T, expected %T", msg, &evmtypes.MsgEthereumTx{})
|
||||
}
|
||||
ethTx.Hash = ethTx.AsTransaction().Hash().Hex()
|
||||
ethTxs[i] = ethTx
|
||||
}
|
||||
return ethTxs, nil
|
||||
}
|
||||
|
||||
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
|
||||
// from a tendermint Header.
|
||||
func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int) *ethtypes.Header {
|
||||
txHash := ethtypes.EmptyRootHash
|
||||
if len(header.DataHash) == 0 {
|
||||
txHash = common.BytesToHash(header.DataHash)
|
||||
}
|
||||
|
||||
return ðtypes.Header{
|
||||
ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
||||
UncleHash: ethtypes.EmptyUncleHash,
|
||||
Coinbase: common.BytesToAddress(header.ProposerAddress),
|
||||
Root: common.BytesToHash(header.AppHash),
|
||||
TxHash: txHash,
|
||||
ReceiptHash: ethtypes.EmptyRootHash,
|
||||
Bloom: bloom,
|
||||
Difficulty: big.NewInt(0),
|
||||
Number: big.NewInt(header.Height),
|
||||
GasLimit: 0,
|
||||
GasUsed: 0,
|
||||
Time: uint64(header.Time.UTC().Unix()),
|
||||
Extra: []byte{},
|
||||
MixDigest: common.Hash{},
|
||||
Nonce: ethtypes.BlockNonce{},
|
||||
BaseFee: baseFee,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockMaxGasFromConsensusParams returns the gas limit for the current block from the chain consensus params.
|
||||
func BlockMaxGasFromConsensusParams(goCtx context.Context, clientCtx client.Context, blockHeight int64) (int64, error) {
|
||||
resConsParams, err := clientCtx.Client.ConsensusParams(goCtx, &blockHeight)
|
||||
if err != nil {
|
||||
return int64(^uint32(0)), err
|
||||
}
|
||||
|
||||
gasLimit := resConsParams.ConsensusParams.Block.MaxGas
|
||||
if gasLimit == -1 {
|
||||
// Sets gas limit to max uint32 to not error with javascript dev tooling
|
||||
// This -1 value indicating no block gas limit is set to max uint64 with geth hexutils
|
||||
// which errors certain javascript dev tooling which only supports up to 53 bits
|
||||
gasLimit = int64(^uint32(0))
|
||||
}
|
||||
|
||||
return gasLimit, nil
|
||||
}
|
||||
|
||||
// FormatBlock creates an ethereum block from a tendermint header and ethereum-formatted
|
||||
// transactions.
|
||||
func FormatBlock(
|
||||
header tmtypes.Header, size int, gasLimit int64,
|
||||
gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom,
|
||||
validatorAddr common.Address, baseFee *big.Int,
|
||||
) map[string]interface{} {
|
||||
var transactionsRoot common.Hash
|
||||
if len(transactions) == 0 {
|
||||
transactionsRoot = ethtypes.EmptyRootHash
|
||||
} else {
|
||||
transactionsRoot = common.BytesToHash(header.DataHash)
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"number": hexutil.Uint64(header.Height),
|
||||
"hash": hexutil.Bytes(header.Hash()),
|
||||
"parentHash": common.BytesToHash(header.LastBlockID.Hash.Bytes()),
|
||||
"nonce": ethtypes.BlockNonce{}, // PoW specific
|
||||
"sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint
|
||||
"logsBloom": bloom,
|
||||
"stateRoot": hexutil.Bytes(header.AppHash),
|
||||
"miner": validatorAddr,
|
||||
"mixHash": common.Hash{},
|
||||
"difficulty": (*hexutil.Big)(big.NewInt(0)),
|
||||
"extraData": "0x",
|
||||
"size": hexutil.Uint64(size),
|
||||
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
|
||||
"gasUsed": (*hexutil.Big)(gasUsed),
|
||||
"timestamp": hexutil.Uint64(header.Time.Unix()),
|
||||
"transactionsRoot": transactionsRoot,
|
||||
"receiptsRoot": ethtypes.EmptyRootHash,
|
||||
|
||||
"uncles": []common.Hash{},
|
||||
"transactions": transactions,
|
||||
"totalDifficulty": (*hexutil.Big)(big.NewInt(0)),
|
||||
}
|
||||
|
||||
if baseFee != nil {
|
||||
result["baseFeePerGas"] = (*hexutil.Big)(baseFee)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// NewTransactionFromMsg returns a transaction that will serialize to the RPC
|
||||
// representation, with the given location metadata set (if available).
|
||||
func NewTransactionFromMsg(
|
||||
msg *evmtypes.MsgEthereumTx,
|
||||
blockHash common.Hash,
|
||||
blockNumber, index uint64,
|
||||
baseFee *big.Int,
|
||||
) (*RPCTransaction, error) {
|
||||
tx := msg.AsTransaction()
|
||||
return NewRPCTransaction(tx, blockHash, blockNumber, index, baseFee)
|
||||
}
|
||||
|
||||
// NewTransactionFromData returns a transaction that will serialize to the RPC
|
||||
// representation, with the given location metadata set (if available).
|
||||
func NewRPCTransaction(
|
||||
tx *ethtypes.Transaction, blockHash common.Hash, blockNumber, index uint64, baseFee *big.Int,
|
||||
) (*RPCTransaction, error) {
|
||||
// Determine the signer. For replay-protected transactions, use the most permissive
|
||||
// signer, because we assume that signers are backwards-compatible with old
|
||||
// transactions. For non-protected transactions, the homestead signer signer is used
|
||||
// because the return value of ChainId is zero for those transactions.
|
||||
var signer ethtypes.Signer
|
||||
if tx.Protected() {
|
||||
signer = ethtypes.LatestSignerForChainID(tx.ChainId())
|
||||
} else {
|
||||
signer = ethtypes.HomesteadSigner{}
|
||||
}
|
||||
from, _ := ethtypes.Sender(signer, tx)
|
||||
v, r, s := tx.RawSignatureValues()
|
||||
result := &RPCTransaction{
|
||||
Type: hexutil.Uint64(tx.Type()),
|
||||
From: from,
|
||||
Gas: hexutil.Uint64(tx.Gas()),
|
||||
GasPrice: (*hexutil.Big)(tx.GasPrice()),
|
||||
Hash: tx.Hash(),
|
||||
Input: hexutil.Bytes(tx.Data()),
|
||||
Nonce: hexutil.Uint64(tx.Nonce()),
|
||||
To: tx.To(),
|
||||
Value: (*hexutil.Big)(tx.Value()),
|
||||
V: (*hexutil.Big)(v),
|
||||
R: (*hexutil.Big)(r),
|
||||
S: (*hexutil.Big)(s),
|
||||
}
|
||||
if blockHash != (common.Hash{}) {
|
||||
result.BlockHash = &blockHash
|
||||
result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
|
||||
result.TransactionIndex = (*hexutil.Uint64)(&index)
|
||||
}
|
||||
switch tx.Type() {
|
||||
case ethtypes.AccessListTxType:
|
||||
al := tx.AccessList()
|
||||
result.Accesses = &al
|
||||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||||
case ethtypes.DynamicFeeTxType:
|
||||
al := tx.AccessList()
|
||||
result.Accesses = &al
|
||||
result.ChainID = (*hexutil.Big)(tx.ChainId())
|
||||
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
|
||||
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
|
||||
// if the transaction has been mined, compute the effective gas price
|
||||
if baseFee != nil && blockHash != (common.Hash{}) {
|
||||
// price = min(tip, gasFeeCap - baseFee) + baseFee
|
||||
price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
|
||||
result.GasPrice = (*hexutil.Big)(price)
|
||||
} else {
|
||||
result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// BaseFeeFromEvents parses the feemarket basefee from cosmos events
|
||||
func BaseFeeFromEvents(events []abci.Event) *big.Int {
|
||||
for _, event := range events {
|
||||
if event.Type != feemarkettypes.EventTypeFeeMarket {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, attr := range event.Attributes {
|
||||
if bytes.Equal(attr.Key, []byte(feemarkettypes.AttributeKeyBaseFee)) {
|
||||
result, success := new(big.Int).SetString(string(attr.Value), 10)
|
||||
if success {
|
||||
return result
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckTxFee is an internal function used to check whether the fee of
|
||||
// the given transaction is _reasonable_(under the cap).
|
||||
func CheckTxFee(gasPrice *big.Int, gas uint64, cap float64) error {
|
||||
// Short circuit if there is no cap for transaction fee at all.
|
||||
if cap == 0 {
|
||||
return nil
|
||||
}
|
||||
totalfee := new(big.Float).SetInt(new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(gas)))
|
||||
// 1 photon in 10^18 aphoton
|
||||
oneToken := new(big.Float).SetInt(big.NewInt(params.Ether))
|
||||
// quo = rounded(x/y)
|
||||
feeEth := new(big.Float).Quo(totalfee, oneToken)
|
||||
// no need to check error from parsing
|
||||
feeFloat, _ := feeEth.Float64()
|
||||
if feeFloat > cap {
|
||||
return fmt.Errorf("tx fee (%.2f ether) exceeds the configured cap (%.2f ether)", feeFloat, cap)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TxExceedBlockGasLimit returns true if the tx exceeds block gas limit.
|
||||
func TxExceedBlockGasLimit(res *abci.ResponseDeliverTx) bool {
|
||||
return strings.Contains(res.Log, ExceedBlockGasLimitError)
|
||||
}
|
||||
|
||||
// TxSuccessOrExceedsBlockGasLimit returnsrue if the transaction was successful
|
||||
// or if it failed with an ExceedBlockGasLimit error
|
||||
func TxSuccessOrExceedsBlockGasLimit(res *abci.ResponseDeliverTx) bool {
|
||||
return res.Code == 0 || TxExceedBlockGasLimit(res)
|
||||
}
|
||||
Reference in New Issue
Block a user