go-eth-proxy

Transparent proxy server for eth-cache
Info | Log | Files | Refs

commit 320e5e15501e03a3855bd9e1ef78b8f384e754bb
parent 081d6d437cf328bd7ea8e95fb0cd2a8d33f1dce9
Author: lash <dev@holbrook.no>
Date:   Wed,  3 Jul 2024 18:45:45 +0100

Factor out geth specifics

Diffstat:
MMakefile | 12+++++++++++-
Acmd/geth_proxy/main.go | 43+++++++++++++++++++++++++++++++++++++++++++
Dcmd/main.go | 44--------------------------------------------
Dproxy/rpc.go | 186-------------------------------------------------------------------------------
Dproxy/service.go | 32--------------------------------
Dproxy/service_test.go | 49-------------------------------------------------
Arpc/geth/geth.go | 22++++++++++++++++++++++
Arpc/geth/service.go | 32++++++++++++++++++++++++++++++++
Arpc/geth/service_test.go | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Arpc/rpc.go | 179+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mstore/store.go | 1+
11 files changed, 337 insertions(+), 312 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,5 +1,15 @@ -all: +all: direct + +direct: go build -v -o eth-proxy ./cmd/ +geth: + go build -v -o eth-proxy ./cmd/geth_proxy/ + run: all ./eth-proxy + +clean: + rm -vf eth-proxy + +.PHONY: clean diff --git a/cmd/geth_proxy/main.go b/cmd/geth_proxy/main.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "log" + "os" + "net/http" + "strings" + + "defalsify.org/go-eth-proxy/rpc/geth" + "defalsify.org/go-eth-proxy/store/lmdb" + +) + + +func main() { + dbpath := flag.String("cachepath", ".", "Path to lmdb data") + host := flag.String("host", "0.0.0.0", "Remote host") + port := flag.String("port", "8545", "Remote path") + flag.Parse() + + db, err := lmdb.NewStore(*dbpath) + if err != nil { + log.Printf("%s", err) + os.Exit(1) + } + defer db.Close() + + h, err := geth.NewGethBackend(db) //svc, flag.Arg(0)) + if err != nil { + log.Printf("%s", err) + os.Exit(1) + } + srv := &http.Server{ + Handler: h, + Addr: strings.Join([]string{*host, *port}, ":"), + } + err = srv.ListenAndServe() + if err != nil { + log.Printf("%s", err) + os.Exit(1) + } +} diff --git a/cmd/main.go b/cmd/main.go @@ -1,44 +0,0 @@ -package main - -import ( - "flag" - "log" - "os" - "net/http" - "strings" - - "defalsify.org/go-eth-proxy/proxy" - "defalsify.org/go-eth-proxy/store/lmdb" - -) - - -func main() { - dbpath := flag.String("cachepath", ".", "Path to lmdb data") - host := flag.String("host", "0.0.0.0", "Remote host") - port := flag.String("port", "8545", "Remote path") - flag.Parse() - - db, err := lmdb.NewStore(*dbpath) - if err != nil { - log.Printf("%s", err) - os.Exit(1) - } - defer db.Close() - - svc := proxy.NewProxyService(db) - h, err := proxy.NewProxyServer(svc, flag.Arg(0)) - if err != nil { - log.Printf("%s", err) - os.Exit(1) - } - srv := &http.Server{ - Handler: h, - Addr: strings.Join([]string{*host, *port}, ":"), - } - err = srv.ListenAndServe() - if err != nil { - log.Printf("%s", err) - os.Exit(1) - } -} diff --git a/proxy/rpc.go b/proxy/rpc.go @@ -1,186 +0,0 @@ -package proxy - -import ( - "bytes" - "encoding/json" - "io" - "log" - "net/http" - "net/url" -// "strconv" - - "github.com/ethereum/go-ethereum/rpc" -) - -type jsonRpcMsg struct { - Method string - -} - -type jsonRpcError struct { - Code int -} - -type jsonRpcResponse struct { - Error jsonRpcError -} - -type ProxyServer struct { - *rpc.Server - uri *url.URL -} - -type proxyWriter struct { - header map[string][]string - status int - data *bytes.Buffer - afterHeader bool -} - - -func (p *proxyWriter) Header() http.Header { - return p.header -} - -func (p *proxyWriter) Write(b []byte) (int, error) { - log.Printf("proxyserver %s", b) - return p.data.Write(b) -} - -func (p *proxyWriter) WriteHeader(status int) { - p.status = status - -} - -func (p *proxyWriter) Copy(w http.ResponseWriter) (int, error) { - c := 0 - l := p.data.Len() - b := p.data.Bytes() - for ;c < l; { - r, err := w.Write(b[c:]) - if err != nil { - return 0, err - } - c += r - } - return c, nil -} - -func newProxyWriter() *proxyWriter { - b := make([]byte, 0, 1024) - p := &proxyWriter{ - header: make(map[string][]string), - data: bytes.NewBuffer(b), - } - return p -} - - - -func NewProxyServer(svc *ProxyService, remoteURI string) (*ProxyServer, error) { - var uri *url.URL - var err error - - if remoteURI != "" { - uri, err = url.Parse(remoteURI) - if err != nil { - return nil, err - } - } - srv := &ProxyServer{ - Server: rpc.NewServer(), - uri: uri, - } - err = srv.Server.RegisterName("eth", svc) - if err != nil { - return nil, err - } - return srv, nil -} - -func (s *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { - //var rrr io.Reader - msg := jsonRpcMsg{} - b := make([]byte, r.ContentLength) - c, err := io.ReadFull(r.Body, b) - if (err != nil) { - log.Printf("%s", err) - r.Body.Close() - w.WriteHeader(http.StatusInternalServerError) - return - } - r.Body.Close() - err = json.Unmarshal(b, &msg) - if (err != nil) { - log.Printf("%s", err) - r.Body.Close() - w.WriteHeader(http.StatusInternalServerError) - return - } - - rr := bytes.NewReader(b) - r.Body = io.NopCloser(rr) - - for _, k := range([]string{ - "eth_getTransactionByHash", - }) { - rw := newProxyWriter() - if msg.Method == k { - s.Server.ServeHTTP(rw, r) - rsp := jsonRpcResponse{} - err = json.Unmarshal(b, &rsp) - if (err != nil) { - log.Printf("%s", err) - r.Body.Close() - w.WriteHeader(http.StatusInternalServerError) - return - } - //hd := rw.Header() - //statusHd := hd["status"] - //log.Printf("got status %s from proxy", statusHd) - //if len(statusHd) > 0 && statusHd[0][:1] == "2" { - if rsp.Error.Code == 0 { -// statusCode, err := strconv.Atoi(hd["Status"][0]) -// if err != nil { -// r.Body.Close() -// w.WriteHeader(http.StatusInternalServerError) -// return -// } -// rw.WriteHeader(statusCode) - rw.WriteHeader(http.StatusOK) - rw.Copy(w) - return - } - - log.Printf("not found in proxy: %s", k) - rr.Seek(0, io.SeekStart) - } - } - - if s.uri == nil { - log.Printf("missing remote side for unproxied method: %s", msg.Method) - w.WriteHeader(http.StatusBadGateway) - return - } - - client_req := &http.Request{} - client_req.Method = "POST" - client_req.URL = s.uri - client_req.Body = r.Body - client_req.ContentLength = int64(c) - client := &http.Client{} - res, err := client.Do(client_req) - if err != nil { - log.Printf("%s", err) - r.Body.Close() - w.WriteHeader(http.StatusBadGateway) - return - } - if res.StatusCode != http.StatusOK { - v, _ := io.ReadAll(res.Body) - log.Printf("%s", v) - } - w.WriteHeader(res.StatusCode) - rrr := io.TeeReader(res.Body, w) - io.ReadAll(rrr) -} diff --git a/proxy/service.go b/proxy/service.go @@ -1,32 +0,0 @@ -package proxy - -import ( - "context" - "log" - - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/common" - - "defalsify.org/go-eth-proxy/store" -) - -type ProxyService struct { - store store.Store -} - -func NewProxyService(store store.Store) (*ProxyService) { - return &ProxyService{ - store: store, - } -} - -func (p *ProxyService) GetTransactionByHash(ctx context.Context, hsh string) (*types.Transaction, error) { - log.Printf("get tx hash %s", hsh) - b := common.FromHex(hsh) - tx, err := p.store.GetTransaction(b) - if err != nil { - return nil, err - } - - return tx, nil -} diff --git a/proxy/service_test.go b/proxy/service_test.go @@ -1,49 +0,0 @@ -package proxy - -import ( - "log" - "os" - "testing" - - "github.com/ethereum/go-ethereum/rpc" - "github.com/ethereum/go-ethereum/core/types" - - "defalsify.org/go-eth-proxy/store/lmdb" -) - -func TestProxyServerStart(t *testing.T) { - var err error - var tx types.Transaction - tx_test := "0x60891c813816bb378ee8af428c5eb53b0479c980307d265e4abe39b4efd02e1d" - //tx_test := "0xffee674f0467b59ce6dd455e9256f95f41118a5bf15a3cedf3d20b5fcbb787a1" - - dbpath, dbenv := os.LookupEnv("TEST_LMDB_DIR") - if !dbenv { - dbpath = "." - } - log.Printf("dbpath %s", dbpath) - - db, err := lmdb.NewStore(dbpath) - if err != nil { - t.Error(err) - } - defer db.Close() - svc := NewProxyService(db) - - srv := rpc.NewServer() - err = srv.RegisterName("eth", svc) - if err != nil { - t.Fatal(err) - } - client := rpc.DialInProc(srv) - mods, err := client.SupportedModules() - if err != nil { - t.Fatal(err) - } - t.Logf("mods %s", mods) - err = client.Call(&tx, "eth_getTransactionByHash", tx_test) - if err != nil { - t.Fatal(err) - } - t.Logf("tx %v", tx_test) -} diff --git a/rpc/geth/geth.go b/rpc/geth/geth.go @@ -0,0 +1,22 @@ +package geth + +import ( + "net/http" + + gethrpc "github.com/ethereum/go-ethereum/rpc" + + "defalsify.org/go-eth-proxy/store" +) + +func NewGethBackend(db store.Store) (http.Handler, error) { + var err error + + svc := NewProxyService(db) + srv := gethrpc.NewServer() + err = srv.RegisterName("eth", svc) + if err != nil { + return nil, err + } + return srv, nil + +} diff --git a/rpc/geth/service.go b/rpc/geth/service.go @@ -0,0 +1,32 @@ +package geth + +import ( + "context" + "log" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/common" + + "defalsify.org/go-eth-proxy/store" +) + +type ProxyService struct { + store store.Store +} + +func NewProxyService(store store.Store) (*ProxyService) { + return &ProxyService{ + store: store, + } +} + +func (p *ProxyService) GetTransactionByHash(ctx context.Context, hsh string) (*types.Transaction, error) { + log.Printf("get tx hash %s", hsh) + b := common.FromHex(hsh) + tx, err := p.store.GetTransaction(b) + if err != nil { + return nil, err + } + + return tx, nil +} diff --git a/rpc/geth/service_test.go b/rpc/geth/service_test.go @@ -0,0 +1,49 @@ +package geth + +import ( + "log" + "os" + "testing" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/ethereum/go-ethereum/core/types" + + "defalsify.org/go-eth-proxy/store/lmdb" +) + +func TestProxyServerStart(t *testing.T) { + var err error + var tx types.Transaction + tx_test := "0x60891c813816bb378ee8af428c5eb53b0479c980307d265e4abe39b4efd02e1d" + //tx_test := "0xffee674f0467b59ce6dd455e9256f95f41118a5bf15a3cedf3d20b5fcbb787a1" + + dbpath, dbenv := os.LookupEnv("TEST_LMDB_DIR") + if !dbenv { + dbpath = "." + } + log.Printf("dbpath %s", dbpath) + + db, err := lmdb.NewStore(dbpath) + if err != nil { + t.Error(err) + } + defer db.Close() + svc := NewProxyService(db) + + srv := rpc.NewServer() + err = srv.RegisterName("eth", svc) + if err != nil { + t.Fatal(err) + } + client := rpc.DialInProc(srv) + mods, err := client.SupportedModules() + if err != nil { + t.Fatal(err) + } + t.Logf("mods %s", mods) + err = client.Call(&tx, "eth_getTransactionByHash", tx_test) + if err != nil { + t.Fatal(err) + } + t.Logf("tx %v", tx_test) +} diff --git a/rpc/rpc.go b/rpc/rpc.go @@ -0,0 +1,179 @@ +package rpc + + +import ( + "bytes" + "encoding/json" + "io" + "log" + "net/http" + "net/url" +// "strconv" +) + +type jsonRpcMsg struct { + Method string + +} + +type jsonRpcError struct { + Code int +} + +type jsonRpcResponse struct { + Error jsonRpcError +} + +type ProxyServer struct { + Server http.Handler + uri *url.URL +} + +type proxyWriter struct { + header map[string][]string + status int + data *bytes.Buffer + afterHeader bool +} + + +func (p *proxyWriter) Header() http.Header { + return p.header +} + +func (p *proxyWriter) Write(b []byte) (int, error) { + log.Printf("proxyserver %s", b) + return p.data.Write(b) +} + +func (p *proxyWriter) WriteHeader(status int) { + p.status = status + +} + +func (p *proxyWriter) Copy(w http.ResponseWriter) (int, error) { + c := 0 + l := p.data.Len() + b := p.data.Bytes() + for ;c < l; { + r, err := w.Write(b[c:]) + if err != nil { + return 0, err + } + c += r + } + return c, nil +} + +func newProxyWriter() *proxyWriter { + b := make([]byte, 0, 1024) + p := &proxyWriter{ + header: make(map[string][]string), + data: bytes.NewBuffer(b), + } + return p +} + +func NewProxyServer(backend http.Handler, remoteURI string) (*ProxyServer, error) { + var uri *url.URL + var err error + + if remoteURI != "" { + uri, err = url.Parse(remoteURI) + if err != nil { + return nil, err + } + } + srv := &ProxyServer{ + Server: backend, + uri: uri, + } + return srv, nil +} + +func (s *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { + //var rrr io.Reader + msg := jsonRpcMsg{} + b := make([]byte, r.ContentLength) + c, err := io.ReadFull(r.Body, b) + if (err != nil) { + log.Printf("%s", err) + r.Body.Close() + w.WriteHeader(http.StatusInternalServerError) + return + } + r.Body.Close() + err = json.Unmarshal(b, &msg) + if (err != nil) { + log.Printf("%s", err) + r.Body.Close() + w.WriteHeader(http.StatusInternalServerError) + return + } + + rr := bytes.NewReader(b) + r.Body = io.NopCloser(rr) + + for _, k := range([]string{ + "eth_getTransactionByHash", + }) { + rw := newProxyWriter() + if msg.Method == k { + s.Server.ServeHTTP(rw, r) + rsp := jsonRpcResponse{} + err = json.Unmarshal(b, &rsp) + if (err != nil) { + log.Printf("%s", err) + r.Body.Close() + w.WriteHeader(http.StatusInternalServerError) + return + } + //hd := rw.Header() + //statusHd := hd["status"] + //log.Printf("got status %s from proxy", statusHd) + //if len(statusHd) > 0 && statusHd[0][:1] == "2" { + if rsp.Error.Code == 0 { +// statusCode, err := strconv.Atoi(hd["Status"][0]) +// if err != nil { +// r.Body.Close() +// w.WriteHeader(http.StatusInternalServerError) +// return +// } +// rw.WriteHeader(statusCode) + rw.WriteHeader(http.StatusOK) + rw.Copy(w) + return + } + + log.Printf("not found in proxy: %s", k) + rr.Seek(0, io.SeekStart) + } + } + + if s.uri == nil { + log.Printf("missing remote side for unproxied method: %s", msg.Method) + w.WriteHeader(http.StatusBadGateway) + return + } + + client_req := &http.Request{} + client_req.Method = "POST" + client_req.URL = s.uri + client_req.Body = r.Body + client_req.ContentLength = int64(c) + client := &http.Client{} + res, err := client.Do(client_req) + if err != nil { + log.Printf("%s", err) + r.Body.Close() + w.WriteHeader(http.StatusBadGateway) + return + } + if res.StatusCode != http.StatusOK { + v, _ := io.ReadAll(res.Body) + log.Printf("%s", v) + } + w.WriteHeader(res.StatusCode) + rrr := io.TeeReader(res.Body, w) + io.ReadAll(rrr) +} diff --git a/store/store.go b/store/store.go @@ -8,3 +8,4 @@ type Store interface { GetTransaction(b []byte) (*types.Transaction, error) Close() } +