Skip to content

Commit

Permalink
Implement json rpc proxy
Browse files Browse the repository at this point in the history
  • Loading branch information
schmir committed Apr 4, 2022
1 parent d68681e commit 51fc0fc
Show file tree
Hide file tree
Showing 6 changed files with 191 additions and 2 deletions.
2 changes: 1 addition & 1 deletion rolling-shutter/.golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ linters:
issues:
exclude:
- "typeUnparen: could simplify \\(func.* to func\\("
- "Error return value of `.*Mark.*FlagRequired` is not checked"
- "Error return value of `.*Mark.*Flag.*` is not checked"
- "Error return value of `viper.BindEnv` is not checked"
- 'shadow: declaration of "err" shadows declaration at line'
- "Expect WriteFile permissions to be"
Expand Down
81 changes: 81 additions & 0 deletions rolling-shutter/cmd/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package proxy

import (
"context"
"os"
"os/signal"
"syscall"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/shutter-network/shutter/shuttermint/cmd/shversion"
"github.com/shutter-network/shutter/shuttermint/proxy"
)

var cfgFile string

func Cmd() *cobra.Command {
cmd := &cobra.Command{
Use: "proxy",
Short: "Run a json rpc proxy",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return proxyMain()
},
}
cmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
cmd.MarkPersistentFlagRequired("config")
cmd.MarkPersistentFlagFilename("config")
return cmd
}

func readConfig() (proxy.Config, error) {
config := proxy.Config{}
viper.AddConfigPath("$HOME/.config/shutter")
viper.SetConfigName("proxy")
viper.SetConfigType("toml")
viper.SetConfigFile(cfgFile)
var err error
err = viper.ReadInConfig()
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Config file not found
if cfgFile != "" {
return config, err
}
} else if err != nil {
return config, err // Config file was found but another error was produced
}
err = config.Unmarshal(viper.GetViper())
return config, err
}

func proxyMain() error {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})

config, err := readConfig()
if err != nil {
return err
}

log.Info().Msgf("Starting shutter proxy version %s", shversion.Version())

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
termChan := make(chan os.Signal, 1)
signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-termChan
log.Info().Str("signal", sig.String()).Msg("Received signal, shutting down")
cancel()
}()

err = proxy.Run(ctx, config)
if err == context.Canceled {
log.Info().Msg("Bye.")
return nil
}
return err
}
2 changes: 2 additions & 0 deletions rolling-shutter/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/shutter-network/shutter/shuttermint/cmd/cryptocmd"
"github.com/shutter-network/shutter/shuttermint/cmd/keyper"
"github.com/shutter-network/shutter/shuttermint/cmd/mocknode"
"github.com/shutter-network/shutter/shuttermint/cmd/proxy"
"github.com/shutter-network/shutter/shuttermint/cmd/shversion"
"github.com/shutter-network/shutter/shuttermint/cmd/snapshot"
"github.com/shutter-network/shutter/shuttermint/medley"
Expand Down Expand Up @@ -67,5 +68,6 @@ func Cmd() *cobra.Command {
cmd.AddCommand(mocknode.Cmd())
cmd.AddCommand(snapshot.Cmd())
cmd.AddCommand(cryptocmd.Cmd())
cmd.AddCommand(proxy.Cmd())
return cmd
}
2 changes: 1 addition & 1 deletion rolling-shutter/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/multiformats/go-multiaddr v0.3.3
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.12.1
github.com/rs/zerolog v1.26.1
github.com/shutter-network/shutter/shlib v0.1.9
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
Expand Down Expand Up @@ -180,7 +181,6 @@ require (
github.com/rjeczalik/notify v0.9.2 // indirect
github.com/rogpeppe/go-internal v1.8.1 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/rs/zerolog v1.26.1 // indirect
github.com/sasha-s/go-deadlock v0.2.1-0.20190427202633-1595213edefa // indirect
github.com/shirou/gopsutil v3.21.11+incompatible // indirect
github.com/spacemonkeygo/spacelog v0.0.0-20180420211403-2296661a0572 // indirect
Expand Down
8 changes: 8 additions & 0 deletions rolling-shutter/medley/decodehooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/ed25519"
"encoding/hex"
"fmt"
"net/url"
"reflect"

"github.com/ethereum/go-ethereum/common"
Expand Down Expand Up @@ -46,6 +47,13 @@ func P2PKeyHook(f reflect.Type, t reflect.Type, data interface{}) (interface{},
return privkey, nil
}

func StringToURL(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() != reflect.String || t != reflect.TypeOf(&url.URL{}) {
return data, nil
}
return url.Parse(data.(string))
}

func StringToEd25519PrivateKey(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) {
if f.Kind() != reflect.String || t != reflect.TypeOf(ed25519.PrivateKey{}) {
return data, nil
Expand Down
98 changes: 98 additions & 0 deletions rolling-shutter/proxy/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Package proxy contains a jsonrpc proxy implementation.
package proxy

import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/http/httputil"
"net/url"
"time"

"github.com/go-chi/chi/v5"
"github.com/mitchellh/mapstructure"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"

"github.com/shutter-network/shutter/shuttermint/medley"
)

type RPCRequest struct {
Version string `json:"jsonrpc"`
Method string `json:"method,omitempty"`
Params interface{} `json:"params,omitempty"`
ID interface{} `json:"id,omitempty"`
}

type Config struct {
CollatorURL, SequencerURL *url.URL
HTTPListenAddress string
}

func (config *Config) Unmarshal(v *viper.Viper) error {
err := v.Unmarshal(config, viper.DecodeHook(
mapstructure.ComposeDecodeHookFunc(
medley.StringToURL,
)))
return err
}

type JSONRPCProxy struct {
collator, sequencer *httputil.ReverseProxy
}

func (p *JSONRPCProxy) SelectReverseProxy(method string) *httputil.ReverseProxy {
switch method {
case "eth_sendTransaction":
return p.collator
case "eth_sendRawTransaction":
return p.collator
default:
return p.sequencer
}
}

func (p *JSONRPCProxy) HandleRequest(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
rpcreq := RPCRequest{}
err = json.Unmarshal(body, &rpcreq)
if err != nil {
http.Error(w, "Bad request", http.StatusBadRequest)
return
}
log.Info().Str("method", rpcreq.Method).Msg("dispatching")

// make the body available again before letting reverse proxy handle the rest
r.Body = io.NopCloser(bytes.NewBuffer(body))
p.SelectReverseProxy(rpcreq.Method).ServeHTTP(w, r)
}

func Run(ctx context.Context, config Config) error {
p := JSONRPCProxy{
collator: httputil.NewSingleHostReverseProxy(config.CollatorURL),
sequencer: httputil.NewSingleHostReverseProxy(config.SequencerURL),
}
router := chi.NewRouter()
router.Post("/*", p.HandleRequest)

httpServer := &http.Server{
Addr: config.HTTPListenAddress,
Handler: router,
}
errorgroup, errorctx := errgroup.WithContext(ctx)
errorgroup.Go(httpServer.ListenAndServe)
errorgroup.Go(func() error {
<-errorctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
return httpServer.Shutdown(shutdownCtx)
})
return errorgroup.Wait()
}

0 comments on commit 51fc0fc

Please sign in to comment.