package integration_tests import ( "bytes" "encoding/json" "fmt" "io" "net/http" "os" "testing" "time" "github.com/BurntSushi/toml" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" "golang.org/x/exp/slog" "github.com/ethereum/go-ethereum/log" "github.com/ethereum-optimism/optimism/proxyd" ) type ProxydHTTPClient struct { url string headers http.Header } func NewProxydClient(url string) *ProxydHTTPClient { return NewProxydClientWithHeaders(url, make(http.Header)) } func NewProxydClientWithHeaders(url string, headers http.Header) *ProxydHTTPClient { clonedHeaders := headers.Clone() clonedHeaders.Set("Content-Type", "application/json") return &ProxydHTTPClient{ url: url, headers: clonedHeaders, } } func (p *ProxydHTTPClient) SendRPC(method string, params []interface{}) ([]byte, int, error) { rpcReq := NewRPCReq("999", method, params) body, err := json.Marshal(rpcReq) if err != nil { panic(err) } return p.SendRequest(body) } func (p *ProxydHTTPClient) SendBatchRPC(reqs ...*proxyd.RPCReq) ([]byte, int, error) { body, err := json.Marshal(reqs) if err != nil { panic(err) } return p.SendRequest(body) } func (p *ProxydHTTPClient) SendRequest(body []byte) ([]byte, int, error) { req, err := http.NewRequest("POST", p.url, bytes.NewReader(body)) if err != nil { panic(err) } req.Header = p.headers res, err := http.DefaultClient.Do(req) if err != nil { return nil, -1, err } defer res.Body.Close() code := res.StatusCode resBody, err := io.ReadAll(res.Body) if err != nil { panic(err) } return resBody, code, nil } func RequireEqualJSON(t *testing.T, expected []byte, actual []byte) { expJSON := canonicalizeJSON(t, expected) actJSON := canonicalizeJSON(t, actual) require.Equal(t, string(expJSON), string(actJSON)) } func canonicalizeJSON(t *testing.T, in []byte) []byte { var any interface{} if in[0] == '[' { any = make([]interface{}, 0) } else { any = make(map[string]interface{}) } err := json.Unmarshal(in, &any) require.NoError(t, err) out, err := json.Marshal(any) require.NoError(t, err) return out } func ReadConfig(name string) *proxyd.Config { config := new(proxyd.Config) _, err := toml.DecodeFile(fmt.Sprintf("testdata/%s.toml", name), config) if err != nil { panic(err) } return config } func NewRPCReq(id string, method string, params []interface{}) *proxyd.RPCReq { jsonParams, err := json.Marshal(params) if err != nil { panic(err) } return &proxyd.RPCReq{ JSONRPC: proxyd.JSONRPCVersion, Method: method, Params: jsonParams, ID: []byte(id), } } type ProxydWSClient struct { conn *websocket.Conn msgCB ProxydWSClientOnMessage closeCB ProxydWSClientOnClose } type WSMessage struct { Type int Body []byte } type ( ProxydWSClientOnMessage func(msgType int, data []byte) ProxydWSClientOnClose func(err error) ) func NewProxydWSClient( url string, msgCB ProxydWSClientOnMessage, closeCB ProxydWSClientOnClose, ) (*ProxydWSClient, error) { conn, _, err := websocket.DefaultDialer.Dial(url, nil) // nolint:bodyclose if err != nil { return nil, err } c := &ProxydWSClient{ conn: conn, msgCB: msgCB, closeCB: closeCB, } go c.readPump() return c, nil } func (h *ProxydWSClient) readPump() { for { mType, msg, err := h.conn.ReadMessage() if err != nil { if h.closeCB != nil { h.closeCB(err) } return } if h.msgCB != nil { h.msgCB(mType, msg) } } } func (h *ProxydWSClient) HardClose() { h.conn.Close() } func (h *ProxydWSClient) SoftClose() error { return h.WriteMessage(websocket.CloseMessage, nil) } func (h *ProxydWSClient) WriteMessage(msgType int, msg []byte) error { return h.conn.WriteMessage(msgType, msg) } func (h *ProxydWSClient) WriteControlMessage(msgType int, msg []byte) error { return h.conn.WriteControl(msgType, msg, time.Now().Add(time.Minute)) } func InitLogger() { log.SetDefault(log.NewLogger(slog.NewJSONHandler( os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug}))) }