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

[WIP] RPC simulation for high gas wanted txs #2069

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion contracts/test/EVMCompatabilityTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,10 @@ describe("EVM Test", function () {
testToken = await TestToken.deploy("TestToken", "TTK");

const EVMCompatibilityTester = await ethers.getContractFactory("EVMCompatibilityTester");
console.log("DEBUG: deploying evmTester")
evmTester = await EVMCompatibilityTester.deploy({ gasPrice: ethers.parseUnits('100', 'gwei') });
// evmTester = await EVMCompatibilityTester.deploy();
console.log("DEBUG: deployed evmTester")

await Promise.all([evmTester.waitForDeployment(), testToken.waitForDeployment()])

Expand Down Expand Up @@ -1221,7 +1224,7 @@ describe("EVM throughput", function(){

it("send 100 transactions from one account", async function(){
const wallet = generateWallet()
const toAddress =await wallet.getAddress()
const toAddress = await wallet.getAddress()
const sender = accounts[0].signer
const address = accounts[0].evmAddress;
const txCount = 100;
Expand Down
99 changes: 99 additions & 0 deletions evmrpc/send.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"context"
"errors"
"fmt"
"time"

"github.com/cosmos/cosmos-sdk/client"
Expand All @@ -12,7 +13,9 @@
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/lib/ethapi"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/sei-protocol/sei-chain/precompiles/wasmd"
"github.com/sei-protocol/sei-chain/x/evm/keeper"
"github.com/sei-protocol/sei-chain/x/evm/types"
"github.com/sei-protocol/sei-chain/x/evm/types/ethtx"
Expand All @@ -32,9 +35,21 @@

type SendConfig struct {
slow bool
// the threshold for the gas wanted to be considered "high" and sendRawTransaction will simulate the tx
// to check if the gas wanted is reasonable
simulationGasThreshold uint64
// the minimum percentage that the estimate gas used needs to be of the gas wanted for the tx to be sent
// For example, if the gasWantedVsGasUsedPercentDiff is .8 then the estimate gas used must be at least 80% of the gas wanted
gasWantedVsGasUsedPercentDiff float64
}

func NewSendAPI(tmClient rpcclient.Client, txConfig client.TxConfig, sendConfig *SendConfig, k *keeper.Keeper, ctxProvider func(int64) sdk.Context, homeDir string, simulateConfig *SimulateConfig, connectionType ConnectionType) *SendAPI {
if sendConfig.simulationGasThreshold == 0 {
panic("simulationGasThreshold must be greater than 0")
}
if sendConfig.gasWantedVsGasUsedPercentDiff > 1 || sendConfig.gasWantedVsGasUsedPercentDiff < 0 {
panic("minHGWPercentDiff must be between 0 and 1")
}
return &SendAPI{
tmClient: tmClient,
txConfig: txConfig,
Expand Down Expand Up @@ -72,7 +87,21 @@
return hash, encodeErr
}

if tx.Gas() > s.sendConfig.simulationGasThreshold {
fmt.Println("DEBUG: tx.Gas() = ", tx.Gas(), "gas threshold = ", s.sendConfig.simulationGasThreshold)
estimate, err := s.simulateTx(ctx, tx)
if err != nil {
return hash, err
}
fmt.Println("DEBUG: estimate = ", estimate)
percentDiff := float64(estimate) / float64(tx.Gas())

Check notice

Code scanning / CodeQL

Floating point arithmetic Note

Floating point arithmetic operations are not associative and a possible source of non-determinism
if percentDiff < s.sendConfig.gasWantedVsGasUsedPercentDiff {
return hash, fmt.Errorf("Estimated gas used (%d) differs too much from gas limit (%d). Please try again with a more accurate gas limit.", estimate, tx.Gas())

Check failure on line 99 in evmrpc/send.go

View workflow job for this annotation

GitHub Actions / lint

ST1005: error strings should not end with punctuation or newlines (stylecheck)
}
}

if s.sendConfig.slow {
fmt.Println("slow")
res, broadcastError := s.tmClient.BroadcastTxCommit(ctx, txbz)
if broadcastError != nil {
err = broadcastError
Expand All @@ -82,6 +111,7 @@
err = sdkerrors.ABCIError(sdkerrors.RootCodespace, res.CheckTx.Code, "")
}
} else {
fmt.Println("fast")
res, broadcastError := s.tmClient.BroadcastTx(ctx, txbz)
if broadcastError != nil {
err = broadcastError
Expand All @@ -94,6 +124,75 @@
return
}

func (s *SendAPI) simulateTx(ctx context.Context, tx *ethtypes.Transaction) (estimate uint64, err error) {
defer func() {
fmt.Println("DEBUG: estimate = ", estimate, "err = ", err)
}()
var from common.Address
if tx.Type() == ethtypes.DynamicFeeTxType {
signer := ethtypes.NewLondonSigner(s.keeper.ChainID(s.ctxProvider(LatestCtxHeight)))
from, err = signer.Sender(tx)
if err != nil {
err = fmt.Errorf("failed to get sender for dynamic fee tx: %w", err)
return
}
} else if tx.Protected() {
signer := ethtypes.NewEIP155Signer(s.keeper.ChainID(s.ctxProvider(LatestCtxHeight)))
from, err = signer.Sender(tx)
if err != nil {
err = fmt.Errorf("failed to get sender for protected tx: %w", err)
return
}
} else {
signer := ethtypes.HomesteadSigner{}
from, err = signer.Sender(tx)
if err != nil {
err = fmt.Errorf("failed to get sender for homestead tx: %w", err)
return
}
}
input_ := (hexutil.Bytes)(tx.Data())
gas_ := hexutil.Uint64(tx.Gas())
nonce_ := hexutil.Uint64(tx.Nonce())
al := tx.AccessList()
bNrOrHash := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
ctx = context.WithValue(ctx, CtxIsWasmdPrecompileCallKey, wasmd.IsWasmdCall(tx.To()))
fmt.Println("DEBUG: gas price = ", tx.GasPrice())
fmt.Println("DEBUG: gas fee cap = ", tx.GasFeeCap())
fmt.Println("DEBUG: gas tip cap = ", tx.GasTipCap())
gp := tx.GasPrice()
maxFeePerGas := tx.GasFeeCap()
maxPriorityFeePerGas := tx.GasTipCap()
if gp != nil {
maxFeePerGas = nil
maxPriorityFeePerGas = nil
} else {
gp = nil
}
fmt.Println("DEBUG: gp = ", gp)
fmt.Println("DEBUG: maxFeePerGas = ", maxFeePerGas)
fmt.Println("DEBUG: maxPriorityFeePerGas = ", maxPriorityFeePerGas)
txArgs := ethapi.TransactionArgs{
From: &from,
To: tx.To(),
Gas: &gas_,
GasPrice: (*hexutil.Big)(gp),
MaxFeePerGas: (*hexutil.Big)(maxFeePerGas),
MaxPriorityFeePerGas: (*hexutil.Big)(maxPriorityFeePerGas),
Value: (*hexutil.Big)(tx.Value()),
Nonce: &nonce_,
Input: &input_,
AccessList: &al,
ChainID: (*hexutil.Big)(tx.ChainId()),
}
estimate_, err := ethapi.DoEstimateGas(ctx, s.backend, txArgs, bNrOrHash, nil, s.backend.RPCGasCap())
if err != nil {
err = fmt.Errorf("failed to estimate gas: %w", err)
return
}
return uint64(estimate_), nil
}

func (s *SendAPI) SignTransaction(_ context.Context, args apitypes.SendTxArgs, _ *string) (result *ethapi.SignTransactionResult, returnErr error) {
startTime := time.Now()
defer recordMetrics("eth_signTransaction", s.connectionType, startTime, returnErr == nil)
Expand Down
53 changes: 39 additions & 14 deletions evmrpc/send_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package evmrpc_test

import (
"encoding/hex"
"fmt"
"math/big"
"testing"

Expand Down Expand Up @@ -35,20 +36,7 @@ func TestSendRawTransaction(t *testing.T) {
Data: []byte("abc"),
ChainID: EVMKeeper.ChainID(Ctx),
}
mnemonic := "fish mention unlock february marble dove vintage sand hub ordinary fade found inject room embark supply fabric improve spike stem give current similar glimpse"
derivedPriv, _ := hd.Secp256k1.Derive()(mnemonic, "", "")
privKey := hd.Secp256k1.Generate()(derivedPriv)
testPrivHex := hex.EncodeToString(privKey.Bytes())
key, _ := crypto.HexToECDSA(testPrivHex)
ethCfg := types.DefaultChainConfig().EthereumConfig(EVMKeeper.ChainID(Ctx))
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(Ctx.BlockHeight()), uint64(Ctx.BlockTime().Unix()))
tx := ethtypes.NewTx(&txData)
tx, err := ethtypes.SignTx(tx, signer, key)
require.Nil(t, err)
bz, err := tx.MarshalBinary()
require.Nil(t, err)
payload := "0x" + hex.EncodeToString(bz)

tx, payload := signTxData(t, txData)
resObj := sendRequestGood(t, "sendRawTransaction", payload)
result := resObj["result"].(string)
require.Equal(t, tx.Hash().Hex(), result)
Expand All @@ -63,3 +51,40 @@ func TestSendRawTransaction(t *testing.T) {
errMap = resObj["error"].(map[string]interface{})
require.Equal(t, ": invalid sequence", errMap["message"].(string))
}

func TestSendRawTransactionHighGasWanted(t *testing.T) {
to := common.HexToAddress("010203")
txData := ethtypes.DynamicFeeTx{
Nonce: 1,
GasFeeCap: big.NewInt(10),
Gas: 1200000,
To: &to,
Value: big.NewInt(1000),
Data: []byte("abc"),
ChainID: EVMKeeper.ChainID(Ctx),
}
_, payload := signTxData(t, txData)
resObj := sendRequestGood(t, "sendRawTransaction", payload)
fmt.Println(resObj)
require.NotNil(t, resObj["error"])
// expect an error
errMap := resObj["error"].(map[string]interface{})
require.Equal(t, "Estimated gas used (1200000) differs too much from gas limit (1000000). Please try again with a more accurate gas limit.", errMap["message"].(string))
}

func signTxData(t *testing.T, txData ethtypes.DynamicFeeTx) (*ethtypes.Transaction, string) {
mnemonic := "fish mention unlock february marble dove vintage sand hub ordinary fade found inject room embark supply fabric improve spike stem give current similar glimpse"
derivedPriv, _ := hd.Secp256k1.Derive()(mnemonic, "", "")
privKey := hd.Secp256k1.Generate()(derivedPriv)
testPrivHex := hex.EncodeToString(privKey.Bytes())
key, _ := crypto.HexToECDSA(testPrivHex)
ethCfg := types.DefaultChainConfig().EthereumConfig(EVMKeeper.ChainID(Ctx))
signer := ethtypes.MakeSigner(ethCfg, big.NewInt(Ctx.BlockHeight()), uint64(Ctx.BlockTime().Unix()))
tx := ethtypes.NewTx(&txData)
tx, err := ethtypes.SignTx(tx, signer, key)
require.Nil(t, err)
bz, err := tx.MarshalBinary()
require.Nil(t, err)
payload := "0x" + hex.EncodeToString(bz)
return tx, payload
}
6 changes: 4 additions & 2 deletions evmrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ func NewEVMHTTPServer(
return nil, err
}
simulateConfig := &SimulateConfig{GasCap: config.SimulationGasLimit, EVMTimeout: config.SimulationEVMTimeout}
sendAPI := NewSendAPI(tmClient, txConfig, &SendConfig{slow: config.Slow}, k, ctxProvider, homeDir, simulateConfig, ConnectionTypeHTTP)
sendConfig := &SendConfig{slow: config.Slow, simulationGasThreshold: 1000000, gasWantedVsGasUsedPercentDiff: 0.8}
sendAPI := NewSendAPI(tmClient, txConfig, sendConfig, k, ctxProvider, homeDir, simulateConfig, ConnectionTypeHTTP)
ctx := ctxProvider(LatestCtxHeight)

txAPI := NewTransactionAPI(tmClient, k, ctxProvider, txConfig, homeDir, ConnectionTypeHTTP)
Expand Down Expand Up @@ -171,6 +172,7 @@ func NewEVMWebSocketServer(
return nil, err
}
simulateConfig := &SimulateConfig{GasCap: config.SimulationGasLimit, EVMTimeout: config.SimulationEVMTimeout}
sendConfig := &SendConfig{slow: config.Slow, simulationGasThreshold: 1000000, gasWantedVsGasUsedPercentDiff: 0.8}
apis := []rpc.API{
{
Namespace: "echo",
Expand All @@ -194,7 +196,7 @@ func NewEVMWebSocketServer(
},
{
Namespace: "eth",
Service: NewSendAPI(tmClient, txConfig, &SendConfig{slow: config.Slow}, k, ctxProvider, homeDir, simulateConfig, ConnectionTypeWS),
Service: NewSendAPI(tmClient, txConfig, sendConfig, k, ctxProvider, homeDir, simulateConfig, ConnectionTypeWS),
},
{
Namespace: "eth",
Expand Down
Loading