Skip to content

Commit

Permalink
Generalize interfaces, add a base class and minimize data (#4)
Browse files Browse the repository at this point in the history
* add a second revision

* revise router

* lint
  • Loading branch information
keefertaylor authored Dec 12, 2023
1 parent 13ff006 commit 01b95af
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 98 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,14 @@ go get github.com/tessellated-io/router

Several errors are also exported for convenience from `errors.go`.

The most useful functionality for `Router` is to add a `replace` for a private go Module in your `go.mod`, in order to route to your own infrastructure. By design, `Router` is statically configured, but in dynamic systems, a replacement module could dynamically refresh routes and supported chains.
We provide two default routing strategies out of the box:
- `static`: Allows configuration of a router with preconfigured chains
- `file`: Allows configuration of a router with routes in a file.
A file router assumes a config such as:

```yaml
- chain-id: my-chain
grpc: tcp://1.2.3.4:9090
```
The most useful functionality for `Router` is to add a `replace` for a private go Module in your `go.mod`, in order to route to your own infrastructure.
37 changes: 37 additions & 0 deletions base/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package base

import "github.com/tessellated-io/router/types"

// baseRouter encapsulates basic functionality for all routers. You probably shouldn't use this directly.
type BaseRouter struct {
// Map of chain-id to chain.
chains map[string]types.Chain
}

// NewRouter returns a new BaseRouter

func NewRouter() (BaseRouter, error) {
chainMap := make(map[string]types.Chain)
return BaseRouter{
chains: chainMap,
}, nil
}

// Router Interacted

func (br *BaseRouter) GrpcEndpoint(chainID string) (string, error) {
chain := br.chains[chainID]
if chain == nil {
return "", types.ErrNoChainWithID
}

return chain.GrpcEndpoint()
}

// Helper methods

func (br *BaseRouter) AddChains(chains []types.Chain) {
for _, chain := range chains {
br.chains[chain.ChainID()] = chain
}
}
67 changes: 67 additions & 0 deletions file/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package file

import (
"fmt"
"os"

"github.com/tessellated-io/pickaxe/config"
"github.com/tessellated-io/pickaxe/log"
"gopkg.in/yaml.v2"
)

type Config struct {
Networks []NetworkConfig `yaml:"networks"`
}

type NetworkConfig struct {
ChainID string `yaml:"chain-id"`
GrpcEndpoint string `yaml:"grpc"`
}

// Parse a file into a UserConfig
func parseConfig(filename string) (*Config, error) {
data, err := os.ReadFile(filename)
if err != nil {
return nil, err
}

config := &Config{}
err = yaml.Unmarshal(data, config)
if err != nil {
return nil, err
}

return config, nil
}

// Write an example config file.
func InitializeConfigFile(filename, configurationDirectory string, logger *log.Logger) error {
networks := []NetworkConfig{
{
ChainID: "cosmoshub-4",
GrpcEndpoint: "tcp://cosmos.rpc.directory",
},
{
ChainID: "osmosis-1",
GrpcEndpoint: "tcp://osmosis.rpc.directory",
},
}

routerConfig := Config{
Networks: networks,
}

// Create folder if needed
err := config.CreateDirectoryIfNeeded(configurationDirectory, logger)
if err != nil {
return err
}

routerConfigFile := fmt.Sprintf("%s/%s", configurationDirectory, filename)
header := "This is a file to route chain IDs to RPC endpoints\n"
err = config.WriteYamlWithComments(routerConfig, header, routerConfigFile, logger)
if err != nil {
return err
}
return nil
}
53 changes: 53 additions & 0 deletions file/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package file

import (
"github.com/tessellated-io/pickaxe/arrays"
"github.com/tessellated-io/router/base"
"github.com/tessellated-io/router/types"
)

// fileRouter is a router that maps to networks in a config file.
type fileRouter struct {
base.BaseRouter

configFile string
}

// Type assertion
var _ types.Router = (*fileRouter)(nil)

// NewRouter creates a new file router.
func NewRouter(configFile string) (types.Router, error) {
fileRouter := &fileRouter{
configFile: configFile,
}

err := fileRouter.loadConfigFile()
if err != nil {
return nil, err
}

return fileRouter, nil
}

// Router interface

func (fr *fileRouter) Refresh() error {
return fr.loadConfigFile()
}

// Private methods

func (fr *fileRouter) loadConfigFile() error {
parsed, err := parseConfig(fr.configFile)
if err != nil {
return err
}

chains := arrays.Map(parsed.Networks, func(networkConfig NetworkConfig) types.Chain {
return types.NewChain(networkConfig.ChainID, &networkConfig.GrpcEndpoint)
})

fr.BaseRouter.AddChains(chains)
return nil
}
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
module github.com/tessellated-io/router

go 1.20

require (
github.com/tessellated-io/pickaxe v1.0.14
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/cometbft/cometbft v0.37.2 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.29.1 // indirect
golang.org/x/sys v0.10.0 // indirect
)
36 changes: 36 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
github.com/cometbft/cometbft v0.37.2 h1:XB0yyHGT0lwmJlFmM4+rsRnczPlHoAKFX6K8Zgc2/Jc=
github.com/cometbft/cometbft v0.37.2/go.mod h1:Y2MMMN//O5K4YKd8ze4r9jmk4Y7h0ajqILXbH5JQFVs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tessellated-io/pickaxe v1.0.14 h1:se9GbcTCdJHFo6Ej0MN2I2nTW9ruuauqjeU4tme2xDc=
github.com/tessellated-io/pickaxe v1.0.14/go.mod h1:wO7zNwLt2PbwH5C9j+p/KM+4I6K4zXY7rrq1567CPJE=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
79 changes: 0 additions & 79 deletions router/router.go

This file was deleted.

35 changes: 35 additions & 0 deletions static/router.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package static

import (
"github.com/tessellated-io/router/base"
"github.com/tessellated-io/router/types"
)

// staticRouter uses a preconfigured set of routes.
type staticRouter struct {
base.BaseRouter
}

// Type assertion
var _ types.Router = (*staticRouter)(nil)

// NewRouter makes a new router with the given chains.
func NewRouter(chains []types.Chain) (types.Router, error) {
baseRouter, err := base.NewRouter()
if err != nil {
return nil, err
}

baseRouter.AddChains(chains)

return &staticRouter{
BaseRouter: baseRouter,
}, nil
}

// Router Interface

func (sr *staticRouter) Refresh() error {
// Intentional no-op
return nil
}
23 changes: 6 additions & 17 deletions router/chain.go → types/chain.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,31 @@
package router
package types

// Chain defines an abstraction around a Chain that Validator tooling or other blockchain applications may use.
type Chain interface {
HumanReadableName() string
ChainID() string
Bech32Prefix() string

GrpcEndpoint() (string, error)
}

// private implementation
type chain struct {
chainID string
humanReadableName string
bech32Prefix string

chainID string
grpcEndpoint *string
}

// Ensure chain is a Chain
var _ Chain = (*chain)(nil)

// Create a new Chain
func NewChain(chainID, humanReadableName, bech32Prefix string, grpcEndpoint *string) (Chain, error) {
func NewChain(chainID string, grpcEndpoint *string) Chain {
return &chain{
chainID: chainID,
humanReadableName: humanReadableName,
bech32Prefix: bech32Prefix,

chainID: chainID,
grpcEndpoint: grpcEndpoint,
}, nil
}
}

// Chain Interface

func (c *chain) HumanReadableName() string { return c.humanReadableName }
func (c *chain) ChainID() string { return c.chainID }
func (c *chain) Bech32Prefix() string { return c.bech32Prefix }
func (c *chain) ChainID() string { return c.chainID }

func (c *chain) GrpcEndpoint() (string, error) {
if c.grpcEndpoint == nil {
Expand Down
Loading

0 comments on commit 01b95af

Please sign in to comment.