Skip to content

Commit

Permalink
TOML config loading package
Browse files Browse the repository at this point in the history
  • Loading branch information
azdagron committed Aug 1, 2024
1 parent 9b40cfc commit df93e7f
Show file tree
Hide file tree
Showing 14 changed files with 785 additions and 2 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ require (
github.com/manifoldco/promptui v0.8.0
github.com/mattn/go-sqlite3 v1.14.22
github.com/mitchellh/go-homedir v1.1.0
github.com/pelletier/go-toml/v2 v2.2.2
github.com/shopspring/decimal v1.2.0
github.com/spf13/cobra v1.5.0
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/zeebo/errs v1.2.2
github.com/zeebo/errs/v2 v2.0.3
github.com/zksync-sdk/zksync-sdk-go v0.0.0-20211119083613-58613b4d3d77
Expand Down
10 changes: 9 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
Expand Down Expand Up @@ -506,13 +508,19 @@ github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863 h
github.com/stephenlacy/go-ethereum-hdwallet v0.0.0-20230913225845-a4fa94429863/go.mod h1:oPTjPNrRucLv9mU27iNPj6n0CWWcNFhoXFOLVGJwHCA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/supranational/blst v0.3.11 h1:LyU6FolezeWAhvQk0k6O/d49jqgO52MSDDfYgbeoEm4=
github.com/supranational/blst v0.3.11/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY=
Expand Down
34 changes: 34 additions & 0 deletions pkg/config/auditors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package config

import "storj.io/crypto-batch-payment/pkg/payer"

type Auditor interface {
payer.Auditor
Close()
}

type auditorWrapper struct {
payer.Auditor
closeFunc func()
}

func (w auditorWrapper) Close() {
if w.closeFunc != nil {
w.closeFunc()
}
}

type Auditors map[payer.Type]Auditor

func (as *Auditors) Add(t payer.Type, a Auditor) {
if *as == nil {
*as = make(map[payer.Type]Auditor)
}
(*as)[t] = a
}

func (as Auditors) Close() {
for _, a := range as {
a.Close()
}
}
29 changes: 29 additions & 0 deletions pkg/config/coinmarketcap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package config

import (
"time"

"github.com/zeebo/errs"

"storj.io/crypto-batch-payment/pkg/coinmarketcap"
)

type CoinMarketCap struct {
APIURL string `toml:"api_url"`
APIKeyPath Path `toml:"api_key_path"`
CacheExpiry Duration `toml:"cache_expiry"`
}

func (c CoinMarketCap) NewQuoter() (coinmarketcap.Quoter, error) {
apiKey, err := loadFirstLine(string(c.APIKeyPath))
if err != nil {
return nil, errs.New("failed to load CoinMarketCap key: %v\n", err)
}

quoter, err := coinmarketcap.NewCachingClient(c.APIURL, apiKey, time.Duration(c.CacheExpiry))
if err != nil {
return nil, errs.New("failed instantiate coinmarketcap client: %v\n", err)
}

return quoter, nil
}
136 changes: 136 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package config

import (
"bytes"
"context"
"fmt"
"os"
"time"

"github.com/pelletier/go-toml/v2"

"storj.io/crypto-batch-payment/pkg/coinmarketcap"
"storj.io/crypto-batch-payment/pkg/payer"
"storj.io/crypto-batch-payment/pkg/pipeline"
)

type Config struct {
Pipeline Pipeline `toml:"pipeline"`
CoinMarketCap CoinMarketCap `toml:"coinmarketcap"`
Eth *Eth `toml:"eth"`
ZkSync *ZkSync `toml:"zksync"`
ZkSyncEra *ZkSyncEra `toml:"zksync-era"`
}

func (c *Config) NewPayers(ctx context.Context) (_ Payers, err error) {
var payers Payers
defer func() {
if err != nil {
payers.Close()
}
}()

if c.Eth != nil {
p, err := c.Eth.NewPayer(ctx)
if err != nil {
return nil, fmt.Errorf("failed to init eth payer: %w", err)
}
payers.Add(payer.Eth, p)
}

if c.ZkSync != nil {
p, err := c.ZkSync.NewPayer(ctx)
if err != nil {
return nil, fmt.Errorf("failed to init zksync payer: %w", err)
}
payers.Add(payer.ZkSync, p)
}

if c.ZkSyncEra != nil {
p, err := c.ZkSyncEra.NewPayer(ctx)
if err != nil {
return nil, fmt.Errorf("failed to init zksync-era payer: %w", err)
}
payers.Add(payer.ZkSyncEra, p)
}

return payers, nil
}

func (c *Config) NewAuditors(ctx context.Context) (_ Auditors, err error) {
var auditors Auditors
defer func() {
if err != nil {
auditors.Close()
}
}()

if c.Eth != nil {
p, err := c.Eth.NewAuditor(ctx)
if err != nil {
return nil, fmt.Errorf("failed to init eth auditor: %w", err)
}
auditors.Add(payer.Eth, p)
}

if c.ZkSync != nil {
p, err := c.ZkSync.NewAuditor(ctx)
if err != nil {
return nil, fmt.Errorf("failed to init zksync auditor: %w", err)
}
auditors.Add(payer.ZkSync, p)
}

if c.ZkSyncEra != nil {
p, err := c.ZkSyncEra.NewAuditor(ctx)
if err != nil {
return nil, fmt.Errorf("failed to init zksync-era auditor: %w", err)
}
auditors.Add(payer.ZkSyncEra, p)
}

return auditors, nil
}

type Pipeline struct {
DepthLimit int `toml:"depth_limit"`
TxDelay Duration `toml:"tx_delay"`
}

func Load(path string) (Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return Config{}, fmt.Errorf("failed to read config: %w", err)
}
return Parse(data)
}

func Parse(data []byte) (Config, error) {
const (
defaultPipelineDepthLimit = pipeline.DefaultLimit
defaultPipelineTxDelay = Duration(pipeline.DefaultTxDelay)
defaultCoinMarketCapAPIURL = coinmarketcap.ProductionAPIURL
defaultCoinMarketCapKeyPath = "~/.coinmarketcap"
defaultCoinMarketCapCacheExpiry = time.Second * 5
)

config := Config{
Pipeline: Pipeline{
DepthLimit: defaultPipelineDepthLimit,
TxDelay: defaultPipelineTxDelay,
},
CoinMarketCap: CoinMarketCap{
APIURL: defaultCoinMarketCapAPIURL,
APIKeyPath: ToPath(defaultCoinMarketCapKeyPath),
CacheExpiry: Duration(defaultCoinMarketCapCacheExpiry),
},
}

d := toml.NewDecoder(bytes.NewReader(data))
d.DisallowUnknownFields()
if err := d.Decode(&config); err != nil {
return Config{}, fmt.Errorf("failed to unmarshal config: %w", err)
}

return config, nil
}
108 changes: 108 additions & 0 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package config_test

import (
"math/big"
"os/user"
"path/filepath"
"testing"
"time"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"storj.io/crypto-batch-payment/pkg/config"
)

func TestLoad_Defaults(t *testing.T) {
currentUser, err := user.Current()
require.NoError(t, err)

homePath := func(suffix string) config.Path {
return config.Path(filepath.Join(currentUser.HomeDir, suffix))
}

cfg, err := config.Load("./testdata/defaults.toml")
require.NoError(t, err)

assert.Equal(t, config.Config{
Pipeline: config.Pipeline{
DepthLimit: 16,
TxDelay: 0,
},
CoinMarketCap: config.CoinMarketCap{
APIURL: "https://pro-api.coinmarketcap.com",
APIKeyPath: homePath(".coinmarketcap"),
CacheExpiry: 5000000000,
},
Eth: &config.Eth{
NodeAddress: "https://someaddress.test",
SpenderKeyPath: homePath("some.key"),
ERC20ContractAddress: common.HexToAddress("0x1111111111111111111111111111111111111111"),
ChainID: 0,
Owner: nil,
MaxGas: nil,
GasTipCap: nil,
},
ZkSync: &config.ZkSync{
NodeAddress: "https://api.zksync.io",
SpenderKeyPath: homePath("some.key"),
ChainID: 0,
MaxFee: nil,
},
ZkSyncEra: &config.ZkSyncEra{
NodeAddress: "https://mainnet.era.zksync.io",
SpenderKeyPath: homePath("some.key"),
ERC20ContractAddress: common.HexToAddress("0x2222222222222222222222222222222222222222"),
ChainID: 0,
MaxFee: nil,
PaymasterAddress: nil,
PaymasterPayload: nil,
},
}, cfg)
}

func TestLoad_Overrides(t *testing.T) {
cfg, err := config.Load("./testdata/override.toml")
require.NoError(t, err)

assert.Equal(t, config.Config{
Pipeline: config.Pipeline{
DepthLimit: 24,
TxDelay: config.Duration(time.Minute),
},
CoinMarketCap: config.CoinMarketCap{
APIURL: "https://override.test",
APIKeyPath: "override",
CacheExpiry: 5000000000,
},
Eth: &config.Eth{
NodeAddress: "https://override.test",
SpenderKeyPath: "override",
ERC20ContractAddress: common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e"),
ChainID: 12345,
Owner: ptrOf(common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e")),
MaxGas: big.NewInt(80_000_000_000),
GasTipCap: big.NewInt(2_000_000_000),
},
ZkSync: &config.ZkSync{
NodeAddress: "https://override.test",
SpenderKeyPath: "override",
ChainID: 12345,
MaxFee: big.NewInt(1234),
},
ZkSyncEra: &config.ZkSyncEra{
NodeAddress: "https://override.test",
SpenderKeyPath: "override",
ERC20ContractAddress: common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e"),
ChainID: 12345,
MaxFee: big.NewInt(5678),
PaymasterAddress: ptrOf(common.HexToAddress("0xe66652d41EE7e81d3fcAe1dF7F9B9f9411ac835e")),
PaymasterPayload: []byte("\x01\x23"),
},
}, cfg)
}

func ptrOf[T any](t T) *T {
return &t
}
Loading

0 comments on commit df93e7f

Please sign in to comment.