erigon-pulse/cmd/devnet/requests/request_generator.go
Mark Holt bcc2a4a2f8
Cleaned up error handling in network and node start-up (#7811)
The check in catches errors in the node start-up code and makes sure
that the network is stopped if any node fails to start cleanly, and
that5 it returns an error - so that any calling code can take
appropriate action.
2023-06-28 18:21:15 +01:00

221 lines
5.8 KiB
Go

package requests
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"time"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/p2p"
"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 interface {
PingErigonRpc() CallResult
GetBalance(address libcommon.Address, blockNum BlockNumber) (uint64, error)
AdminNodeInfo() (p2p.NodeInfo, error)
GetBlockByNumberDetails(blockNum string, withTxs bool) (map[string]interface{}, error)
GetBlockByNumber(blockNum uint64, withTxs bool) (EthBlockByNumber, error)
BlockNumber() (uint64, error)
GetTransactionCount(address libcommon.Address, blockNum BlockNumber) (EthGetTransactionCount, error)
SendTransaction(signedTx types.Transaction) (*libcommon.Hash, error)
GetAndCompareLogs(fromBlock uint64, toBlock uint64, expected Log) error
TxpoolContent() (int, int, int, error)
}
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 {
return &requestGenerator{
client: &http.Client{
Timeout: time.Second * 10,
},
reqID: 1,
logger: logger,
target: target,
}
}
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
}