erigon-pulse/cmd/devnet/requests/request_generator.go

207 lines
5.1 KiB
Go
Raw Normal View History

package requests
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
"github.com/ledgerwatch/log/v3"
"github.com/valyala/fastjson"
)
type CallResult struct {
Target string
Took time.Duration
RequestID int
Method string
RequestBody string
Response []byte
Result *fastjson.Value
Err error
}
type CommonResponse struct {
Version string `json:"jsonrpc"`
RequestId int `json:"id"`
Error *EthError `json:"error"`
}
type EthError struct {
Code int `json:"code"`
Message string `json:"message"`
}
type RequestGenerator struct {
reqID int
client *http.Client
logger log.Logger
target string
}
type (
// RPCMethod is the type for rpc methods used
RPCMethod string
// SubMethod is the type for sub methods used in subscriptions
SubMethod string
// BlockNumber represents the block number type
BlockNumber string
)
var BlockNumbers = struct {
// Latest is the parameter for the latest block
Latest BlockNumber
// Earliest is the parameter for the earliest block
Earliest BlockNumber
// Pending is the parameter for the pending block
Pending BlockNumber
}{
Latest: "latest",
Earliest: "earliest",
Pending: "pending",
}
var Methods = struct {
// ETHGetTransactionCount represents the eth_getTransactionCount method
ETHGetTransactionCount RPCMethod
// ETHGetBalance represents the eth_getBalance method
ETHGetBalance RPCMethod
// ETHSendRawTransaction represents the eth_sendRawTransaction method
ETHSendRawTransaction RPCMethod
// ETHGetBlockByNumber represents the eth_getBlockByNumber method
ETHGetBlockByNumber RPCMethod
// ETHGetBlock represents the eth_getBlock method
ETHGetBlock RPCMethod
// ETHGetLogs represents the eth_getLogs method
ETHGetLogs RPCMethod
// ETHBlockNumber represents the eth_blockNumber method
ETHBlockNumber RPCMethod
// AdminNodeInfo represents the admin_nodeInfo method
AdminNodeInfo RPCMethod
// TxpoolContent represents the txpool_content method
TxpoolContent RPCMethod
// OTSGetBlockDetails represents the ots_getBlockDetails method
OTSGetBlockDetails RPCMethod
// ETHNewHeads represents the eth_newHeads sub method
ETHNewHeads SubMethod
}{
ETHGetTransactionCount: "eth_getTransactionCount",
ETHGetBalance: "eth_getBalance",
ETHSendRawTransaction: "eth_sendRawTransaction",
ETHGetBlockByNumber: "eth_getBlockByNumber",
ETHGetBlock: "eth_getBlock",
ETHGetLogs: "eth_getLogs",
ETHBlockNumber: "eth_blockNumber",
AdminNodeInfo: "admin_nodeInfo",
TxpoolContent: "txpool_content",
OTSGetBlockDetails: "ots_getBlockDetails",
ETHNewHeads: "eth_newHeads",
}
func (req *RequestGenerator) call(method RPCMethod, body string, response interface{}) CallResult {
start := time.Now()
err := post(req.client, req.target, string(method), body, response, req.logger)
req.reqID++
return CallResult{
RequestBody: body,
Target: req.target,
Took: time.Since(start),
RequestID: req.reqID,
Method: string(method),
Err: err,
}
}
func (req *RequestGenerator) PingErigonRpc() CallResult {
start := time.Now()
res := CallResult{
RequestID: req.reqID,
}
// return early if the http module has issue fetching the url
resp, err := http.Get(req.target) //nolint
if err != nil {
res.Took = time.Since(start)
res.Err = err
return res
}
// close the response body after reading its content at the end of the function
defer func(body io.ReadCloser) {
closeErr := body.Close()
if closeErr != nil {
req.logger.Warn("failed to close readCloser", "err", closeErr)
}
}(resp.Body)
// return a bad request if the status code is not 200
if resp.StatusCode != 200 {
res.Took = time.Since(start)
res.Err = ErrBadRequest
return res
}
body, err := io.ReadAll(resp.Body)
if err != nil {
res.Took = time.Since(start)
res.Err = err
return res
}
res.Response = body
res.Took = time.Since(start)
res.Err = err
return res
}
func NewRequestGenerator(target string, logger log.Logger) *RequestGenerator {
var client = &http.Client{
Timeout: time.Second * 600,
}
reqGen := RequestGenerator{
client: client,
reqID: 1,
logger: logger,
target: target,
}
return &reqGen
}
func post(client *http.Client, url, method, request string, response interface{}, logger log.Logger) error {
start := time.Now()
r, err := client.Post(url, "application/json", strings.NewReader(request)) // nolint:bodyclose
if err != nil {
return fmt.Errorf("client failed to make post request: %w", err)
}
defer func(Body io.ReadCloser) {
closeErr := Body.Close()
if closeErr != nil {
logger.Warn("body close", "err", closeErr)
}
}(r.Body)
if r.StatusCode != 200 {
return fmt.Errorf("status %s", r.Status)
}
b, err := io.ReadAll(r.Body)
if err != nil {
return fmt.Errorf("failed to readAll from body: %w", err)
}
err = json.Unmarshal(b, &response)
if err != nil {
return fmt.Errorf("failed to unmarshal response: %w", err)
}
if len(method) > 0 {
method = "#" + method
}
logger.Info(fmt.Sprintf("%s%s", url, method), "time", time.Since(start).Seconds())
return nil
}