erigon-pulse/cmd/devnet/requests/request_generator.go
Mark Holt 415cf86250
refactor to allow switchable consensus and multiple communicating nodes (#7646)
This branch is intended to allow the devnet to be used for testing
multiple consents types beyond the default clique. It is initially being
used to test Bor consensus for polygon.

It also has the following refactoring:

### 1.  Network configuration

The two node arg building functions miningNodeArgs and nonMiningNodeArgs
have been replaced with a configuration struct which is used to
configure:

```go
network := &node.Network{
		DataDir: dataDir,
		Chain:   networkname.DevChainName,
		//Chain:              networkname.BorDevnetChainName,
		Logger:             logger,
		BasePrivateApiAddr: "localhost:9090",
		BaseRPCAddr:        "localhost:8545",
		Nodes: []node.NetworkNode{
			&node.Miner{},
			&node.NonMiner{},
		},
	}
```
and start multiple nodes

```go
network.Start()
```
Network start will create a network of nodes ensuring that all nodes are
configured with non clashing network ports set via command line
arguments on start-up.

### 2. Request Routing

The `RequestRouter` has been updated to take a 'target' rather than
using a static dispatcher which routes to a single node on the network.
Each node in the network has its own request generator so command and
services have more flexibility in request routing and
`ExecuteAllMethods` currently takes the `node.Network` as an argument
and can pick which node (node 0 for the moment) to send requests to.
2023-06-04 20:53:05 +01:00

207 lines
5.1 KiB
Go

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
}