Skip to content

Commit

Permalink
Exclude Synthetic txs from *ExcludePanicTx endpoints (#2058)
Browse files Browse the repository at this point in the history
* fix

* fix

* fix

* fix

* add SeiEndpointsTest to evm_test.sh

* cleanup prints

* cleanup + add integration test
  • Loading branch information
jewei1997 authored Jan 31, 2025
1 parent e078ec6 commit 6788468
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 28 deletions.
84 changes: 84 additions & 0 deletions contracts/test/SeiEndpointsTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const { expect } = require("chai");
const { ethers } = require('hardhat');
const { deployWasm, ABI, WASM, executeWasm, deployErc20PointerForCw20, getAdmin, setupSigners } = require("./lib")

describe("Sei Endpoints Tester", function () {
let accounts;
let admin;
let cw20Address;
let pointer;

before(async function () {
accounts = await setupSigners(await hre.ethers.getSigners());
admin = await getAdmin();

cw20Address = await deployWasm(WASM.CW20, accounts[0].seiAddress, "cw20", {
name: "Test",
symbol: "TEST",
decimals: 6,
initial_balances: [
{ address: admin.seiAddress, amount: "1000000" },
{ address: accounts[0].seiAddress, amount: "2000000" },
{ address: accounts[1].seiAddress, amount: "3000000" }
],
mint: {
"minter": admin.seiAddress, "cap": "99900000000"
}
});
// deploy a pointer
const pointerAddr = await deployErc20PointerForCw20(hre.ethers.provider, cw20Address);
const contract = new hre.ethers.Contract(pointerAddr, ABI.ERC20, hre.ethers.provider);
pointer = contract.connect(accounts[0].signer);
});

it("Should emit a synthetic event upon transfer", async function () {
const res = await executeWasm(cw20Address, { transfer: { recipient: accounts[1].seiAddress, amount: "1" } });
const blockNumber = parseInt(res["height"], 10);
// look for synthetic event on evm sei_ endpoints
const filter = {
fromBlock: '0x' + blockNumber.toString(16),
toBlock: '0x' + blockNumber.toString(16),
address: pointer.address,
topics: [ethers.id("Transfer(address,address,uint256)")]
};
const seilogs = await ethers.provider.send('sei_getLogs', [filter]);
expect(seilogs.length).to.equal(1);
});

it("sei_getBlockByNumberExcludeTraceFail should not have the synthetic tx", async function () {
// create a synthetic tx
const res = await executeWasm(cw20Address, { transfer: { recipient: accounts[1].seiAddress, amount: "1" } });
const blockNumber = parseInt(res["height"], 10);

// Query sei_getBlockByNumber - should have synthetic tx
const seiBlock = await ethers.provider.send('sei_getBlockByNumber', ['0x' + blockNumber.toString(16), false]);
expect(seiBlock.transactions.length).to.equal(1);

// Query sei_getBlockByNumberExcludeTraceFail - should not have synthetic tx
const seiBlockExcludeTrace = await ethers.provider.send('sei_getBlockByNumberExcludeTraceFail', ['0x' + blockNumber.toString(16), false]);
expect(seiBlockExcludeTrace.transactions.length).to.equal(0);
});

it("sei_traceBlockByNumberExcludeTraceFail should not have synthetic tx", async function () {
// create a synthetic tx
const res = await executeWasm(cw20Address, { transfer: { recipient: accounts[1].seiAddress, amount: "1" } });
const blockNumber = parseInt(res["height"], 10);
const seiBlockExcludeTrace = await ethers.provider.send('sei_traceBlockByNumberExcludeTraceFail', ['0x' + blockNumber.toString(16), {"tracer": "callTracer"}]);
expect(seiBlockExcludeTrace.length).to.equal(0);
});

it("sei_traceBlockByHashExcludeTraceFail should not have synthetic tx", async function () {
// create a synthetic tx
const res = await executeWasm(cw20Address, { transfer: { recipient: accounts[1].seiAddress, amount: "1" } });
const blockNumber = parseInt(res["height"], 10);
// get the block hash
const block = await ethers.provider.send('eth_getBlockByNumber', ['0x' + blockNumber.toString(16), false]);
const blockHash = block.hash;
// check sei_getBlockByHash
const seiBlock = await ethers.provider.send('sei_getBlockByHash', [blockHash, false]);
expect(seiBlock.transactions.length).to.equal(1);
// check sei_traceBlockByHashExcludeTraceFail
const seiBlockExcludeTrace = await ethers.provider.send('sei_traceBlockByHashExcludeTraceFail', [blockHash, {"tracer": "callTracer"}]);
expect(seiBlockExcludeTrace.length).to.equal(0);
});
})
34 changes: 17 additions & 17 deletions evmrpc/block.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,13 @@ func NewSeiBlockAPI(
}

func (a *SeiBlockAPI) GetBlockByNumberExcludeTraceFail(ctx context.Context, number rpc.BlockNumber, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByNumber(ctx, number, fullTx, a.isPanicTx)
// exclude synthetic txs
return a.getBlockByNumber(ctx, number, fullTx, false, a.isPanicTx)
}

func (a *SeiBlockAPI) GetBlockByHashExcludeTraceFail(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
// exclude synthetic txs
return a.getBlockByHash(ctx, blockHash, fullTx, false, a.isPanicTx)
}

func (a *BlockAPI) GetBlockTransactionCountByNumber(ctx context.Context, number rpc.BlockNumber) (result *hexutil.Uint, returnErr error) {
Expand Down Expand Up @@ -108,18 +114,11 @@ func (a *BlockAPI) GetBlockTransactionCountByHash(ctx context.Context, blockHash
}

func (a *BlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByHash(ctx, blockHash, fullTx, nil)
// used for both: eth_ and sei_ namespaces
return a.getBlockByHash(ctx, blockHash, fullTx, a.includeShellReceipts, nil)
}

func (a *SeiBlockAPI) GetBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByHash(ctx, blockHash, fullTx, nil)
}

func (a *SeiBlockAPI) GetBlockByHashExcludeTraceFail(ctx context.Context, blockHash common.Hash, fullTx bool) (result map[string]interface{}, returnErr error) {
return a.getBlockByHash(ctx, blockHash, fullTx, a.isPanicTx)
}

func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, isPanicTx func(ctx context.Context, hash common.Hash) (bool, error)) (result map[string]interface{}, returnErr error) {
func (a *BlockAPI) getBlockByHash(ctx context.Context, blockHash common.Hash, fullTx bool, includeSyntheticTxs bool, isPanicTx func(ctx context.Context, hash common.Hash) (bool, error)) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
defer recordMetrics(fmt.Sprintf("%s_getBlockByHash", a.namespace), a.connectionType, startTime, returnErr == nil)
block, err := blockByHashWithRetry(ctx, a.tmClient, blockHash[:], 1)
Expand Down Expand Up @@ -162,13 +161,14 @@ func (a *BlockAPI) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber,
"baseFeePerGas": (*hexutil.Big)(big.NewInt(0)),
}, nil
}
return a.getBlockByNumber(ctx, number, fullTx, nil)
return a.getBlockByNumber(ctx, number, fullTx, a.includeShellReceipts, nil)
}

func (a *BlockAPI) getBlockByNumber(
ctx context.Context,
number rpc.BlockNumber,
fullTx bool,
includeSyntheticTxs bool,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
) (result map[string]interface{}, returnErr error) {
startTime := time.Now()
Expand All @@ -186,7 +186,7 @@ func (a *BlockAPI) getBlockByNumber(
return nil, err
}
blockBloom := a.keeper.GetBlockBloom(a.ctxProvider(block.Block.Height))
return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, a.includeShellReceipts, isPanicTx)
return EncodeTmBlock(a.ctxProvider(block.Block.Height), block, blockRes, blockBloom, a.keeper, a.txConfig.TxDecoder(), fullTx, includeSyntheticTxs, isPanicTx)
}

func (a *BlockAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc.BlockNumberOrHash) (result []map[string]interface{}, returnErr error) {
Expand Down Expand Up @@ -267,7 +267,7 @@ func EncodeTmBlock(
txDecoder sdk.TxDecoder,
fullTx bool,
includeSyntheticTxs bool,
isPanicTx func(ctx context.Context, hash common.Hash) (bool, error),
isPanicOrSynthetic func(ctx context.Context, hash common.Hash) (bool, error),
) (map[string]interface{}, error) {
number := big.NewInt(block.Block.Height)
blockhash := common.HexToHash(block.BlockID.Hash.String())
Expand Down Expand Up @@ -296,12 +296,12 @@ func EncodeTmBlock(
}
ethtx, _ := m.AsTransaction()
hash := ethtx.Hash()
if isPanicTx != nil {
isPanic, err := isPanicTx(ctx.Context(), hash)
if isPanicOrSynthetic != nil {
isPanicOrSynthetic, err := isPanicOrSynthetic(ctx.Context(), hash)
if err != nil {
return nil, fmt.Errorf("failed to check if tx is panic tx: %w", err)
}
if isPanic {
if isPanicOrSynthetic {
continue
}
}
Expand Down
2 changes: 1 addition & 1 deletion evmrpc/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestGetSeiBlockByHash(t *testing.T) {

func TestGetSeiBlockByNumberExcludeTraceFail(t *testing.T) {
resObj := sendSeiRequestGood(t, "getBlockByNumberExcludeTraceFail", "0x67", true)
// first tx is not a panic tx, second tx is a panic tx
// first tx is not a panic tx, second tx is a panic tx, third tx is a synthetic tx
expectedNumTxs := 1
require.Equal(t, expectedNumTxs, len(resObj["result"].(map[string]interface{})["transactions"].([]interface{})))
}
Expand Down
12 changes: 6 additions & 6 deletions evmrpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func NewEVMHTTPServer(
ctxProvider func(int64) sdk.Context,
txConfig client.TxConfig,
homeDir string,
isPanicTxFunc func(ctx context.Context, hash common.Hash) (bool, error), // optional - for testing
isPanicOrSyntheticTxFunc func(ctx context.Context, hash common.Hash) (bool, error), // used in *ExcludeTraceFail endpoints
) (EVMServer, error) {
httpServer := NewHTTPServer(logger, rpc.HTTPTimeouts{
ReadTimeout: config.ReadTimeout,
Expand All @@ -50,12 +50,12 @@ func NewEVMHTTPServer(

txAPI := NewTransactionAPI(tmClient, k, ctxProvider, txConfig, homeDir, ConnectionTypeHTTP)
debugAPI := NewDebugAPI(tmClient, k, ctxProvider, txConfig.TxDecoder(), simulateConfig, ConnectionTypeHTTP)
if isPanicTxFunc == nil {
isPanicTxFunc = func(ctx context.Context, hash common.Hash) (bool, error) {
return debugAPI.isPanicTx(ctx, hash)
if isPanicOrSyntheticTxFunc == nil {
isPanicOrSyntheticTxFunc = func(ctx context.Context, hash common.Hash) (bool, error) {
return debugAPI.isPanicOrSyntheticTx(ctx, hash)
}
}
seiTxAPI := NewSeiTransactionAPI(tmClient, k, ctxProvider, txConfig, homeDir, ConnectionTypeHTTP, isPanicTxFunc)
seiTxAPI := NewSeiTransactionAPI(tmClient, k, ctxProvider, txConfig, homeDir, ConnectionTypeHTTP, isPanicOrSyntheticTxFunc)
seiDebugAPI := NewSeiDebugAPI(tmClient, k, ctxProvider, txConfig.TxDecoder(), simulateConfig, ConnectionTypeHTTP)

apis := []rpc.API{
Expand All @@ -69,7 +69,7 @@ func NewEVMHTTPServer(
},
{
Namespace: "sei",
Service: NewSeiBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeHTTP, isPanicTxFunc),
Service: NewSeiBlockAPI(tmClient, k, ctxProvider, txConfig, ConnectionTypeHTTP, isPanicOrSyntheticTxFunc),
},
{
Namespace: "eth",
Expand Down
25 changes: 24 additions & 1 deletion evmrpc/setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ var TestEvmTxHash = "0xf02362077ac075a397344172496b28e913ce5294879d811bb0269b3be

var TestNonPanicTxHash = "0x566f1c956c74b089643a1e6f880ac65745de0e5cd8cfc3c7482d20a486576219"
var TestPanicTxHash = "0x0ea197de8403de9c2e8cf9ec724e43734e9dbd3a8294a09d031acd67914b73e4"
var TestSyntheticTxHash = "0x3ca69dcca32f435b642fcb13e50a1c6934b04c6f901deffa42cec6eb58c40f20"
var TestBlockHash = "0x0000000000000000000000000000000000000000000000000000000000000001"

var EncodingConfig = app.MakeEncodingConfig()
Expand All @@ -86,6 +87,7 @@ var multiTxBlockSynthTx *ethtypes.Transaction
var DebugTraceTx sdk.Tx
var DebugTracePanicTx sdk.Tx
var DebugTraceNonPanicTx sdk.Tx
var DebugTraceSyntheticTx sdk.Tx
var TxNonEvm sdk.Tx
var TxNonEvmWithSyntheticLog sdk.Tx
var UnconfirmedTx sdk.Tx
Expand Down Expand Up @@ -236,6 +238,10 @@ func (c *MockClient) mockBlock(height int64) *coretypes.ResultBlock {
bz, _ := Encoder(DebugTracePanicTx)
return bz
}(),
func() []byte {
bz, _ := Encoder(DebugTraceSyntheticTx)
return bz
}(),
}
}
return res
Expand Down Expand Up @@ -662,6 +668,15 @@ func generateTxData() {
Data: []byte("abc"),
ChainID: chainId,
})
debugTraceSyntheticTxBuilder, _ := buildTx(ethtypes.DynamicFeeTx{
Nonce: 0,
GasFeeCap: big.NewInt(10),
Gas: 25000,
To: &to,
Value: big.NewInt(1000),
Data: []byte("synthetic"),
ChainID: chainId,
})
Tx1 = txBuilder1.GetTx()
MultiTxBlockTx1 = txBuilder1_5.GetTx()
MultiTxBlockTx2 = txBuilder2.GetTx()
Expand All @@ -671,6 +686,7 @@ func generateTxData() {
DebugTraceTx = debugTraceTxBuilder.GetTx()
DebugTracePanicTx = debugTracePanicTxBuilder.GetTx()
DebugTraceNonPanicTx = debugTraceNonPanicTxBuilder.GetTx()
DebugTraceSyntheticTx = debugTraceSyntheticTxBuilder.GetTx()
TxNonEvm = app.TestTx{}
TxNonEvmWithSyntheticLog = app.TestTx{}
bloomTx1 := ethtypes.CreateBloom(ethtypes.Receipts{&ethtypes.Receipt{Logs: []*ethtypes.Log{{
Expand Down Expand Up @@ -876,6 +892,12 @@ func setupLogs() {
}},
EffectiveGasPrice: 0,
})
EVMKeeper.MockReceipt(CtxMock, common.HexToHash(TestSyntheticTxHash), &types.Receipt{
TxType: evmrpc.ShellEVMTxType,
BlockNumber: MockHeight103,
TransactionIndex: 2,
TxHashHex: TestSyntheticTxHash,
})
CtxDebugTrace := Ctx.WithBlockHeight(MockHeight101)
EVMKeeper.MockReceipt(CtxDebugTrace, common.HexToHash(DebugTraceHashHex), &types.Receipt{
BlockNumber: MockHeight101,
Expand Down Expand Up @@ -1114,5 +1136,6 @@ func TestEcho(t *testing.T) {
}

func isPanicTxFunc(ctx context.Context, hash common.Hash) (bool, error) {
return hash == common.HexToHash(TestPanicTxHash), nil
result := hash == common.HexToHash(TestPanicTxHash) || hash == common.HexToHash(TestSyntheticTxHash)
return result, nil
}
11 changes: 10 additions & 1 deletion evmrpc/tracers.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ func (api *SeiDebugAPI) TraceBlockByHashExcludeTraceFail(ctx context.Context, ha
return finalTraces, nil
}

func (api *DebugAPI) isPanicTx(ctx context.Context, hash common.Hash) (isPanic bool, err error) {
// isPanicOrSyntheticTx returns true if the tx is a panic tx or if it is a synthetic tx. Used in the *ExcludeTraceFail endpoints.
func (api *DebugAPI) isPanicOrSyntheticTx(ctx context.Context, hash common.Hash) (isPanic bool, err error) {
sdkctx := api.ctxProvider(LatestCtxHeight)
receipt, err := api.keeper.GetReceipt(sdkctx, hash)
if err != nil {
Expand All @@ -131,17 +132,25 @@ func (api *DebugAPI) isPanicTx(ctx context.Context, hash common.Hash) (isPanic b
return false, err
}

found := false
result := false
for _, trace := range tracersResult {
if trace.TxHash == hash {
found = true
result = len(trace.Error) > 0
}
// for each tx, add to cache to avoid re-tracing
if len(trace.Error) > 0 {
api.isPanicCache.Add(trace.TxHash, true)
} else {
api.isPanicCache.Add(trace.TxHash, false)
}
}

if !found { // likely a synthetic tx
return true, nil
}

return result, nil
}

Expand Down
4 changes: 2 additions & 2 deletions evmrpc/tracers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ func TestTraceBlockByNumberExcludeTraceFail(t *testing.T) {
blockNumber := fmt.Sprintf("%#x", MockHeight103)
seiResObj := sendRequestGoodWithNamespace(t, "sei", "traceBlockByNumberExcludeTraceFail", blockNumber, args)
result := seiResObj["result"].([]interface{})
// sei_traceBlockByNumberExcludeTraceFail returns 1 trace, and removes the panic tx
// sei_traceBlockByNumberExcludeTraceFail returns 1 trace, and removes the panic tx and synthetic tx
require.Equal(t, 1, len(result))
ethResObj := sendRequestGoodWithNamespace(t, "debug", "traceBlockByNumber", blockNumber, args)
// eth_traceBlockByNumber returns 2 traces, including the panic tx
// eth_traceBlockByNumber returns 2 traces, including the panic tx, but excludes the synthetic tx
require.Equal(t, 2, len(ethResObj["result"].([]interface{})))
}

Expand Down
1 change: 1 addition & 0 deletions integration_test/evm_module/scripts/evm_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ cd contracts
npm ci
npx hardhat test --network seilocal test/EVMCompatabilityTest.js
npx hardhat test --network seilocal test/EVMPrecompileTest.js
npx hardhat test --network seilocal test/SeiEndpointsTest.js
npx hardhat test --network seilocal test/AssociateTest.js

0 comments on commit 6788468

Please sign in to comment.