synapsecns/sanguine

View on GitHub
ethergo/client/client_test.go

Summary

Maintainability
B
6 hrs
Test Coverage
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))
    })
}