Skip to content

Commit

Permalink
Add serve command bootstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
zivkovicmilos committed Sep 14, 2023
1 parent b4b8cef commit addc280
Show file tree
Hide file tree
Showing 10 changed files with 278 additions and 19 deletions.
77 changes: 65 additions & 12 deletions cmd/root/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ package root
import (
"context"
"flag"
"fmt"
"os"

"github.com/gnolang/faucet"
"github.com/gnolang/faucet/config"
"github.com/gnolang/faucet/waiter"
"github.com/pelletier/go-toml"
"github.com/peterbourgon/ff/v3"
"github.com/peterbourgon/ff/v3/ffcli"
"github.com/peterbourgon/ff/v3/fftoml"
Expand All @@ -16,7 +21,7 @@ const (
)

// faucetCfg wraps the faucet
// startup command configuration
// root command configuration
type faucetCfg struct {
config.Config

Expand All @@ -27,12 +32,12 @@ type faucetCfg struct {
func New() *ffcli.Command {
cfg := &faucetCfg{}

fs := flag.NewFlagSet("start", flag.ExitOnError)
fs := flag.NewFlagSet("serve", flag.ExitOnError)
registerFlags(cfg, fs)

return &ffcli.Command{
Name: "start",
ShortUsage: "start [flags]",
Name: "serve",
ShortUsage: "serve [flags]",
LongHelp: "Starts the Gno faucet service",
FlagSet: fs,
Exec: cfg.exec,
Expand All @@ -42,6 +47,7 @@ func New() *ffcli.Command {
ff.WithEnvVarPrefix(envPrefix),

// Allow using TOML config files
ff.WithAllowMissingConfigFile(true),
ff.WithConfigFileFlag(configFlagName),
ff.WithConfigFileParser(fftoml.Parser),
},
Expand All @@ -61,35 +67,42 @@ func registerFlags(cfg *faucetCfg, fs *flag.FlagSet) {
fs.StringVar(
&cfg.ListenAddress,
"listen-address",
"0.0.0.0:8545",
config.DefaultListenAddress,
"the IP:PORT URL for the faucet server",
)

fs.StringVar(
&cfg.Remote,
"remote",
"http://127.0.0.1:26657",
config.DefaultRemote,
"the JSON-RPC URL of the Gno chain",
)

fs.StringVar(
&cfg.ChainID,
"chain-id",
config.DefaultChainID,
"dev",
"the chain ID associated with the remote Gno chain",
)

fs.StringVar(
&cfg.SendAmount,
"send-amount",
config.DefaultSendAmount,
"the static send amount (native currency)",
)

fs.StringVar(
&cfg.GasFee,
"gas-fee",
"1000000ugnot",
config.DefaultGasFee,
"the static gas fee for the transaction",
)

fs.StringVar(
&cfg.GasWanted,
"gas-wanted",
"100000",
config.DefaultGasWanted,
"the static gas wanted for the transaction",
)

Expand All @@ -101,9 +114,49 @@ func registerFlags(cfg *faucetCfg, fs *flag.FlagSet) {
)
}

// exec executes the faucet start command
// exec executes the faucet root command
func (c *faucetCfg) exec(context.Context, []string) error {
// TODO
// Read the CORS configuration, if any
if c.corsConfigPath != "" {
corsConfig, err := readCORSConfig(c.corsConfigPath)
if err != nil {
return fmt.Errorf("unable to read CORS config, %w", err)
}

c.CORSConfig = corsConfig
}

// Create a new faucet
f, err := faucet.NewFaucet()
if err != nil {
return fmt.Errorf("unable to create faucet, %w", err)
}

// Create a new waiter
w := waiter.New()

// Add the faucet service
w.Add(f.Serve)

// Wait for the faucet to exit
return w.Wait()
}

// readCORSConfig reads the CORS configuration
// from the specified path
func readCORSConfig(path string) (*config.CORS, error) {
// Read the config file
content, err := os.ReadFile(path)
if err != nil {
return nil, err
}

// Parse it
var corsConfig config.CORS
err = toml.Unmarshal(content, &corsConfig)
if err != nil {
return nil, err
}

return nil
return &corsConfig, nil
}
75 changes: 74 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
package config

import (
"errors"
"regexp"
)

const (
DefaultListenAddress = "0.0.0.0:8545"
DefaultRemote = "http://127.0.0.1:26657"
DefaultChainID = "dev"
DefaultSendAmount = "1000000ugnot"
DefaultGasFee = "1000000ugnot"
DefaultGasWanted = "100000"
)

var (
listenAddressRegex = regexp.MustCompile(`^\d{1,3}(\.\d{1,3}){3}:\d+$`)
remoteRegex = regexp.MustCompile(`^https?:\/\/(?:w{1,3}\.)?[^\s.]+(?:\.[a-z]+)*(?::\d+)(?![^<]*(?:<\/\w+>|\/?>))$`)
amountRegex = regexp.MustCompile(`^\d+ugnot$`)
numberRegex = regexp.MustCompile(`^\d+$`)
)

// Config defines the base-level Faucet configuration
type Config struct {
// The address at which the faucet will be served.
Expand All @@ -12,14 +33,66 @@ type Config struct {
// The chain ID associated with the remote Gno chain
ChainID string `toml:"chain_id"`

// The static send amount (native currency).
// Format should be: <AMOUNT>ugnot
SendAmount string `toml:"send_amount"`

// The static gas fee for the transaction.
// Format should be: <AMOUNT>ugnot
GasFee string `toml:"gas_fee"`

// The static gas wanted for the transaction.
// Format should be: <AMOUNT>ugnot
// Format should be: <AMOUNT>
GasWanted string `toml:"gas_wanted"`

// The associated CORS config, if any
CORSConfig *CORS `toml:"cors_config"`
}

// DefaultConfig returns the default faucet configuration
func DefaultConfig() *Config {
return &Config{
ListenAddress: DefaultListenAddress,
Remote: DefaultRemote,
ChainID: DefaultChainID,
SendAmount: DefaultSendAmount,
GasFee: DefaultGasFee,
GasWanted: DefaultGasWanted,
CORSConfig: DefaultCORSConfig(),
}
}

// ValidateConfig validates the faucet configuration
func ValidateConfig(config *Config) error {
// validate the listen address
if !listenAddressRegex.Match([]byte(config.ListenAddress)) {
return errors.New("invalid listen address")
}

// validate the remote address
if !remoteRegex.Match([]byte(config.Remote)) {
return errors.New("invalid remote address")
}

// validate the chain ID
if config.ChainID == "" {
return errors.New("invalid chain ID")
}

// validate the send amount
if !amountRegex.Match([]byte(config.SendAmount)) {
return errors.New("invalid send amount")
}

// validate the gas fee
if !amountRegex.Match([]byte(config.GasFee)) {
return errors.New("invalid gas fee")
}

// validate the gas wanted
if !numberRegex.Match([]byte(config.GasWanted)) {
return errors.New("invalid gas wanted")
}

return nil
}
13 changes: 13 additions & 0 deletions config/cors.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package config

import (
"net/http"
)

// CORS defines the Faucet CORS configuration
type CORS struct {
// A list of origins a cross-domain request can be executed from.
Expand All @@ -14,3 +18,12 @@ type CORS struct {
// A list of methods the client is allowed to use with cross-domain requests
AllowedMethods []string `toml:"cors_allowed_methods"`
}

// DefaultCORSConfig returns the default CORS configuration
func DefaultCORSConfig() *CORS {
return &CORS{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{http.MethodHead, http.MethodGet, http.MethodPost, http.MethodOptions},
AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "X-Server-Time"},
}
}
40 changes: 34 additions & 6 deletions faucet.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (

"github.com/gnolang/faucet/config"
"github.com/go-chi/chi/v5"
"github.com/rs/cors"
"golang.org/x/sync/errgroup"
)

// Faucet is a standard Gno
// native currency faucet
// Faucet is a standard Gno faucet
type Faucet struct {
estimator Estimator // gas pricing estimations
logger Logger // log feedback
Expand All @@ -22,15 +22,17 @@ type Faucet struct {

config *config.Config // faucet configuration
middlewares []Middleware // request middlewares
handlers []Handler // request handlers
}

// NewFaucet creates a new instance of the Gno faucet server
func NewFaucet(opts ...Option) *Faucet {
func NewFaucet(opts ...Option) (*Faucet, error) {
f := &Faucet{
estimator: nil, // TODO static estimator
logger: nil, // TODO nil logger
config: nil, // TODO default config
middlewares: nil, // TODO no middlewares
config: config.DefaultConfig(),
middlewares: nil,
handlers: nil, // TODO single default handler

mux: chi.NewMux(),
}
Expand All @@ -40,7 +42,33 @@ func NewFaucet(opts ...Option) *Faucet {
opt(f)
}

return f
// Validate the configuration
if err := config.ValidateConfig(f.config); err != nil {
return nil, fmt.Errorf("invalid configuration, %w", err)
}

// Set up the CORS middleware
if f.config.CORSConfig != nil {
corsMiddleware := cors.New(cors.Options{
AllowedOrigins: f.config.CORSConfig.AllowedOrigins,
AllowedMethods: f.config.CORSConfig.AllowedMethods,
AllowedHeaders: f.config.CORSConfig.AllowedHeaders,
})

f.mux.Use(corsMiddleware.Handler)
}

// Set up additional middlewares
for _, middleware := range f.middlewares {
f.mux.Use(middleware)
}

// Set up the request handlers
for _, handler := range f.handlers {
f.mux.HandleFunc(handler.Pattern, handler.HandlerFunc)
}

return f, nil
}

// Serve serves the Gno faucet [BLOCKING]
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.21
require (
github.com/go-chi/chi/v5 v5.0.10
github.com/peterbourgon/ff/v3 v3.4.0
github.com/rs/cors v1.10.0
golang.org/x/sync v0.3.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peterbourgon/ff/v3 v3.4.0 h1:QBvM/rizZM1cB0p0lGMdmR7HxZeI/ZrBWB4DqLkMUBc=
github.com/peterbourgon/ff/v3 v3.4.0/go.mod h1:zjJVUhx+twciwfDl0zBcFzl4dW8axCRyXE/eKY9RztQ=
github.com/rs/cors v1.10.0 h1:62NOS1h+r8p1mW6FM0FSB0exioXLhd/sh15KpjWBZ+8=
github.com/rs/cors v1.10.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
7 changes: 7 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package faucet

// transferFunds transfers funds to the given address
func (f *Faucet) transferFunds(address string) error {

return nil
}
6 changes: 6 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ func WithMiddlewares(middlewares []Middleware) Option {
f.middlewares = middlewares
}
}

func WithHandlers(handlers []Handler) Option {
return func(f *Faucet) {
f.handlers = handlers
}
}
5 changes: 5 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ type Logger interface {
}

type Middleware func(next http.Handler) http.Handler

type Handler struct {
Pattern string
HandlerFunc http.HandlerFunc
}
Loading

0 comments on commit addc280

Please sign in to comment.