ethergo/client/client_test.go
package client_test
import (
"context"
"github.com/brianvoe/gofakeit/v6"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/lmittmann/w3/module/eth"
"github.com/lmittmann/w3/w3types"
. "github.com/stretchr/testify/assert"
"github.com/synapsecns/sanguine/core/metrics"
"github.com/synapsecns/sanguine/core/metrics/instrumentation/httpcapture"
"github.com/synapsecns/sanguine/ethergo/client"
"github.com/synapsecns/sanguine/ethergo/mocks"
"github.com/synapsecns/sanguine/ethergo/parser/rpc"
_ "go.opentelemetry.io/otel/sdk/trace/tracetest"
"io"
"math/big"
"net/http"
"net/http/httptest"
)
// TestEVM is an EVM that can be used for testing.
type TestEVM interface {
client.EVM
GetMetrics() metrics.Handler
}
func (c *ClientSuite) TestParseCalls() {
calls := make([]w3types.Caller, 4)
chainID := new(uint64)
calls[0] = eth.ChainID().Returns(chainID)
maxHeight := new(big.Int)
calls[1] = eth.BlockNumber().Returns(maxHeight)
var mockHash common.Hash
calls[2] = eth.StorageAt(mocks.MockAddress(), mocks.NewMockHash(c.T()), big.NewInt(1)).Returns(&mockHash)
filter := ethereum.FilterQuery{
Addresses: []common.Address{mocks.MockAddress()},
}
calls[3] = eth.Logs(filter).Returns(nil)
res := client.ParseCalls(calls)
Equal(c.T(), res.Value.AsStringSlice(), []string{"eth_chainId", "eth_blockNumber", "eth_getStorageAt", "eth_getLogs"})
}
// checkRequest is a helper method for checking requests.
func (c *ClientSuite) checkRequest(makeReq func(client TestEVM)) {
ctx, cancel := context.WithCancel(c.GetTestContext())
defer cancel()
doneChan := make(chan bool)
mockTracer := metrics.NewTestTracer(ctx, c.T())
var rpcRequest rpc.Requests
var body []byte
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
body, err = io.ReadAll(r.Body)
c.Require().NoError(err, "failed to read request body")
rpcRequest, err = rpc.ParseRPCPayload(body)
c.Require().NoError(err, "failed to parse rpc payload")
go func() {
doneChan <- true
}()
}))
defer server.Close()
evmClient, err := client.DialBackend(ctx, server.URL, mockTracer, client.Capture(true))
c.Require().NoError(err)
castClient, ok := evmClient.(TestEVM)
c.Require().True(ok, "failed to cast client to TestEVM")
makeReq(castClient)
<-doneChan
spans := mockTracer.GetSpansByName(rpcRequest.Method())
requestSpans := mockTracer.GetSpansByName(httpcapture.RequestSpanName)
// make sure we got at most 1 span
c.Require().Equal(len(spans), 1, "expected 1 span, got %d", len(spans))
span := spans[0]
c.Require().Equal(len(requestSpans), 1, "expected 1 request span, got %d", len(spans))
requestSpan := requestSpans[0]
// make sure the span has an exception
c.Require().True(metrics.SpanHasException(span), "expected exception event, got none")
Equal(c.T(), metrics.SpanAttributeByName(span, "endpoint").AsString(), server.URL)
Equal(c.T(), metrics.SpanEventByName(requestSpan, httpcapture.RequestEventName).AsString(), string(body))
}
// Test parsing.
//
//nolint:maintidx
func (c *ClientSuite) TestParseRPCPayload() {
/*
CHECK BLOCKS
*/
// check latest block, should not be confirmable since
// rpcs might be on different latest heights
c.checkRequest(func(client TestEVM) {
_, _ = client.BlockByNumber(c.GetTestContext(), nil)
})
// check non-latest block, should be confirmable
c.checkRequest(func(client TestEVM) {
_, _ = client.BlockByNumber(c.GetTestContext(), new(big.Int).SetUint64(gofakeit.Uint64()))
})
/*
CHECK HEADERS
*/
// check non-latest block, should be confirmable
c.checkRequest(func(client TestEVM) {
_, _ = client.HeaderByNumber(c.GetTestContext(), new(big.Int).SetUint64(gofakeit.Uint64()))
})
// eth_blockNumber should not be confirmable, can differ based on rpc
c.checkRequest(func(client TestEVM) {
_, _ = client.BlockNumber(c.GetTestContext())
})
/*
CHECK Transaction Methods
*/
c.checkRequest(func(client TestEVM) {
_, _, _ = client.TransactionByHash(c.GetTestContext(), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())))
})
c.checkRequest(func(client TestEVM) {
_, _ = client.TransactionCount(c.GetTestContext(), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())))
})
c.checkRequest(func(client TestEVM) {
_, _ = client.PendingTransactionCount(c.GetTestContext())
})
c.checkRequest(func(client TestEVM) {
_, _ = client.TransactionInBlock(c.GetTestContext(), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())), 1)
})
c.checkRequest(func(client TestEVM) {
_, _ = client.TransactionReceipt(c.GetTestContext(), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())))
})
/*
Sync Methods
*/
c.checkRequest(func(client TestEVM) {
_, _ = client.SyncProgress(c.GetTestContext())
})
c.checkRequest(func(client TestEVM) {
_, _ = client.NetworkID(c.GetTestContext())
})
/*
Accessor Methods
*/
// latest block, should not be confirmable
c.checkRequest(func(client TestEVM) {
_, _ = client.BalanceAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), nil)
})
// pending
c.checkRequest(func(client TestEVM) {
_, _ = client.PendingBalanceAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())))
})
// non-latest block
c.checkRequest(func(client TestEVM) {
_, _ = client.BalanceAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), new(big.Int).SetUint64(gofakeit.Uint64()))
})
// latest block, should not be confirmable
c.checkRequest(func(client TestEVM) {
_, _ = client.StorageAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())), nil)
})
c.checkRequest(func(client TestEVM) {
_, _ = client.PendingStorageAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())))
})
// non-latest block
c.checkRequest(func(client TestEVM) {
_, _ = client.StorageAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64())), new(big.Int).SetUint64(gofakeit.Uint64()))
})
c.checkRequest(func(client TestEVM) {
_, _ = client.CodeAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), nil)
})
c.checkRequest(func(client TestEVM) {
_, _ = client.PendingCodeAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())))
})
// non-latest block
c.checkRequest(func(client TestEVM) {
_, _ = client.CodeAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), new(big.Int).SetUint64(gofakeit.Uint64()))
})
// storage:
c.checkRequest(func(client TestEVM) {
_, _ = client.NonceAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), nil)
})
c.checkRequest(func(client TestEVM) {
_, _ = client.PendingNonceAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())))
})
// non-latest block
c.checkRequest(func(client TestEVM) {
_, _ = client.NonceAt(c.GetTestContext(), common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64())), new(big.Int).SetUint64(gofakeit.Uint64()))
})
c.checkRequest(func(client TestEVM) {
_, _ = client.FilterLogs(c.GetTestContext(), ethereum.FilterQuery{
FromBlock: nil,
ToBlock: nil,
Addresses: []common.Address{common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64()))},
Topics: [][]common.Hash{{common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64()))}},
})
})
// set only block hash
c.checkRequest(func(client TestEVM) {
bhash := common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64()))
_, _ = client.FilterLogs(c.GetTestContext(), ethereum.FilterQuery{
BlockHash: &bhash,
Addresses: []common.Address{common.BigToAddress(new(big.Int).SetUint64(gofakeit.Uint64()))},
Topics: [][]common.Hash{{common.BigToHash(new(big.Int).SetUint64(gofakeit.Uint64()))}},
})
})
/*
Call Contract
*/
c.checkRequest(func(client TestEVM) {
_, _ = client.CallContract(c.GetTestContext(), ethereum.CallMsg{}, nil)
})
c.checkRequest(func(client TestEVM) {
_, _ = client.CallContract(c.GetTestContext(), ethereum.CallMsg{}, big.NewInt(1))
})
c.checkRequest(func(client TestEVM) {
_, _ = client.PendingCallContract(c.GetTestContext(), ethereum.CallMsg{})
})
/*
Gas Pricing
*/
c.checkRequest(func(client TestEVM) {
_, _ = client.SuggestGasPrice(c.GetTestContext())
})
c.checkRequest(func(client TestEVM) {
_, _ = client.SuggestGasTipCap(c.GetTestContext())
})
c.checkRequest(func(client TestEVM) {
_, _ = client.EstimateGas(c.GetTestContext(), ethereum.CallMsg{})
})
// note: single batch requests will fail checkRequest since it is a helper function that expects more than one call, this could be checked manually
// if so desired
c.checkRequest(func(client TestEVM) {
_ = client.BatchWithContext(c.GetTestContext(), eth.ChainID().Returns(nil), eth.ChainID().Returns(nil))
})
}