Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/curio/guidedsetup/guidedsetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ type MigrationData struct {
selectTemplates *promptui.SelectTemplates
MinerConfigPath string
DB *harmonydb.DB
HarmonyCfg config.HarmonyDB
HarmonyCfg harmonydb.Config
MinerID address.Address
full api.Chain
cctx *cli.Context
Expand Down
120 changes: 120 additions & 0 deletions deps/config/dynamic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package config

import (
"context"
"fmt"
"reflect"
"strings"
"sync"
"time"

"github.com/BurntSushi/toml"
"github.com/filecoin-project/curio/harmony/harmonydb"
logging "github.com/ipfs/go-log/v2"
)

var logger = logging.Logger("config-dynamic")
var DynamicMx sync.RWMutex

type Dynamic[T any] struct {
Value T
}

func NewDynamic[T any](value T) *Dynamic[T] {
return &Dynamic[T]{Value: value}
}

func (d *Dynamic[T]) Set(value T) {
DynamicMx.Lock()
defer DynamicMx.Unlock()
d.Value = value
}

func (d *Dynamic[T]) Get() T {
DynamicMx.RLock()
defer DynamicMx.RUnlock()
return d.Value
}

func (d *Dynamic[T]) UnmarshalText(text []byte) error {
DynamicMx.Lock()
defer DynamicMx.Unlock()
return toml.Unmarshal(text, d.Value)
}

type cfgRoot struct {
db *harmonydb.DB
layers []string
treeCopy *CurioConfig
}

func EnableChangeDetection(db *harmonydb.DB, obj *CurioConfig, layers []string) error {
r := &cfgRoot{db: db, treeCopy: obj, layers: layers}
err := r.copyWithOriginalDynamics(obj)
if err != nil {
return err
}
go r.changeMonitor()
return nil
}

// copyWithOriginalDynamics copies the original dynamics from the original object to the new object.
func (r *cfgRoot) copyWithOriginalDynamics(orig *CurioConfig) error {
typ := reflect.TypeOf(orig)
if typ.Kind() != reflect.Struct {
return fmt.Errorf("expected struct, got %s", typ.Kind())
}
result := reflect.New(typ)
// recursively walk the struct tree, and copy the dynamics from the original object to the new object.
var walker func(orig, result reflect.Value)
walker = func(orig, result reflect.Value) {
for i := 0; i < orig.NumField(); i++ {
field := orig.Field(i)
if field.Kind() == reflect.Struct {
walker(field, result.Field(i))
} else if field.Kind() == reflect.Ptr {
walker(field.Elem(), result.Field(i).Elem())
} else if field.Kind() == reflect.Interface {
walker(field.Elem(), result.Field(i).Elem())
} else {
result.Field(i).Set(field)
}
}
}
walker(reflect.ValueOf(orig), result)
r.treeCopy = result.Interface().(*CurioConfig)
return nil
}

func (r *cfgRoot) changeMonitor() {
lastTimestamp := time.Now().Add(-30 * time.Second) // plenty of time for start-up

for {
time.Sleep(30 * time.Second)
configCount := 0
err := r.db.QueryRow(context.Background(), `SELECT COUNT(*) FROM harmony_config WHERE timestamp > $1 AND title IN ($2)`, lastTimestamp, strings.Join(r.layers, ",")).Scan(&configCount)
if err != nil {
logger.Errorf("error selecting configs: %s", err)
continue
}
if configCount == 0 {
continue
}
lastTimestamp = time.Now()

// 1. get all configs
configs, err := GetConfigs(context.Background(), r.db, r.layers)
if err != nil {
logger.Errorf("error getting configs: %s", err)
continue
}

// 2. lock "dynamic" mutex
func() {
DynamicMx.Lock()
defer DynamicMx.Unlock()
ApplyLayers(context.Background(), r.treeCopy, configs)

Check failure on line 116 in deps/config/dynamic.go

View workflow job for this annotation

GitHub Actions / lint

Error return value is not checked (errcheck)
}()
DynamicMx.Lock()
}
}
75 changes: 75 additions & 0 deletions deps/config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package config

import (
"bytes"
"context"
"errors"
"fmt"
"io"
"math/big"
Expand All @@ -14,9 +16,11 @@ import (
"unicode"

"github.com/BurntSushi/toml"
"github.com/filecoin-project/curio/harmony/harmonydb"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/kelseyhightower/envconfig"
"github.com/samber/lo"
"golang.org/x/xerrors"
)

Expand Down Expand Up @@ -564,3 +568,74 @@ func FixTOML(newText string, cfg *CurioConfig) error {
}
return nil
}

func LoadConfigWithUpgrades(text string, curioConfigWithDefaults *CurioConfig) (toml.MetaData, error) {
// allow migration from old config format that was limited to 1 wallet setup.
newText := strings.Join(lo.Map(strings.Split(text, "\n"), func(line string, _ int) string {
if strings.EqualFold(line, "[addresses]") {
return "[[addresses]]"
}
return line
}), "\n")

err := FixTOML(newText, curioConfigWithDefaults)
if err != nil {
return toml.MetaData{}, err
}

return toml.Decode(newText, &curioConfigWithDefaults)
}

type ConfigText struct {
Title string
Config string
}

// GetConfigs returns the configs in the order of the layers
func GetConfigs(ctx context.Context, db *harmonydb.DB, layers []string) ([]ConfigText, error) {
inputMap := map[string]int{}
for i, layer := range layers {
inputMap[layer] = i
}

layers = append([]string{"base"}, layers...) // Always stack on top of "base" layer

var configs []ConfigText
err := db.Select(ctx, &configs, `SELECT title, config FROM harmony_config WHERE title IN ($1)`, strings.Join(layers, ","))
if err != nil {
return nil, err
}
result := make([]ConfigText, len(layers))
for _, config := range configs {
index, ok := inputMap[config.Title]
if !ok {
if config.Title == "base" {
return nil, errors.New(`curio defaults to a layer named 'base'.
Either use 'migrate' command or edit a base.toml and upload it with: curio config set base.toml`)

}
return nil, fmt.Errorf("missing layer %s", config.Title)
}
result[index] = config
}
return result, nil
}

func ApplyLayers(ctx context.Context, curioConfig *CurioConfig, layers []ConfigText) error {
have := []string{}
for _, layer := range layers {
meta, err := LoadConfigWithUpgrades(layer.Config, curioConfig)
if err != nil {
return fmt.Errorf("could not read layer, bad toml %s: %w", layer, err)
}
for _, k := range meta.Keys() {
have = append(have, strings.Join(k, " "))
}
logger.Debugf("Using layer %s, config %v", layer, curioConfig)
}
_ = have // FUTURE: verify that required fields are here.
// If config includes 3rd-party config, consider JSONSchema as a way that
// 3rd-parties can dynamically include config requirements and we can
// validate the config. Because of layering, we must validate @ startup.
return nil
}
26 changes: 3 additions & 23 deletions deps/config/old_lotus_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/ipfs/go-cid"

"github.com/filecoin-project/curio/harmony/harmonydb"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/builtin"
"github.com/filecoin-project/go-state-types/network"
Expand All @@ -14,27 +15,6 @@ import (
"github.com/filecoin-project/lotus/chain/types"
)

type HarmonyDB struct {
// HOSTS is a list of hostnames to nodes running YugabyteDB
// in a cluster. Only 1 is required
Hosts []string

// The Yugabyte server's username with full credentials to operate on Lotus' Database. Blank for default.
Username string

// The password for the related username. Blank for default.
Password string

// The database (logical partition) within Yugabyte. Blank for default.
Database string

// The port to find Yugabyte. Blank for default.
Port string

// Load Balance the connection over multiple nodes
LoadBalance bool
}

// StorageMiner is a miner config
type StorageMiner struct {
Common
Expand All @@ -49,7 +29,7 @@ type StorageMiner struct {
Addresses MinerAddressConfig
DAGStore DAGStoreConfig

HarmonyDB HarmonyDB
HarmonyDB harmonydb.Config
}

type DAGStoreConfig struct {
Expand Down Expand Up @@ -683,7 +663,7 @@ func DefaultStorageMiner() *StorageMiner {
MaxConcurrentUnseals: 5,
GCInterval: time.Minute,
},
HarmonyDB: HarmonyDB{
HarmonyDB: harmonydb.Config{
Hosts: []string{"127.0.0.1"},
Username: "yugabyte",
Password: "yugabyte",
Expand Down
64 changes: 19 additions & 45 deletions deps/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ var log = logging.Logger("curio/deps")
func MakeDB(cctx *cli.Context) (*harmonydb.DB, error) {
// #1 CLI opts
fromCLI := func() (*harmonydb.DB, error) {
dbConfig := config.HarmonyDB{
dbConfig := harmonydb.Config{
Username: cctx.String("db-user"),
Password: cctx.String("db-password"),
Hosts: strings.Split(cctx.String("db-host"), ","),
Expand Down Expand Up @@ -261,7 +261,7 @@ func (deps *Deps) PopulateRemainingDeps(ctx context.Context, cctx *cli.Context,
}

if deps.EthClient == nil {
deps.EthClient = lazy.MakeLazy[*ethclient.Client](func() (*ethclient.Client, error) {
deps.EthClient = lazy.MakeLazy(func() (*ethclient.Client, error) {
cfgApiInfo := deps.Cfg.Apis.ChainApiInfo
if v := os.Getenv("FULLNODE_API_INFO"); v != "" {
cfgApiInfo = []string{v}
Expand Down Expand Up @@ -419,20 +419,7 @@ func sealProofType(maddr dtypes.MinerAddress, fnapi api.Chain) (abi.RegisteredSe
}

func LoadConfigWithUpgrades(text string, curioConfigWithDefaults *config.CurioConfig) (toml.MetaData, error) {
// allow migration from old config format that was limited to 1 wallet setup.
newText := strings.Join(lo.Map(strings.Split(text, "\n"), func(line string, _ int) string {
if strings.EqualFold(line, "[addresses]") {
return "[[addresses]]"
}
return line
}), "\n")

err := config.FixTOML(newText, curioConfigWithDefaults)
if err != nil {
return toml.MetaData{}, err
}

return toml.Decode(newText, &curioConfigWithDefaults)
return config.LoadConfigWithUpgrades(text, curioConfigWithDefaults)
}

func GetConfig(ctx context.Context, layers []string, db *harmonydb.DB) (*config.CurioConfig, error) {
Expand All @@ -442,38 +429,25 @@ func GetConfig(ctx context.Context, layers []string, db *harmonydb.DB) (*config.
}

curioConfig := config.DefaultCurioConfig()
have := []string{}
layers = append([]string{"base"}, layers...) // Always stack on top of "base" layer
for _, layer := range layers {
text := ""
err := db.QueryRow(ctx, `SELECT config FROM harmony_config WHERE title=$1`, layer).Scan(&text)
if err != nil {
if strings.Contains(err.Error(), pgx.ErrNoRows.Error()) {
return nil, fmt.Errorf("missing layer '%s' ", layer)
}
if layer == "base" {
return nil, errors.New(`curio defaults to a layer named 'base'.
Either use 'migrate' command or edit a base.toml and upload it with: curio config set base.toml`)
}
return nil, fmt.Errorf("could not read layer '%s': %w", layer, err)
}

meta, err := LoadConfigWithUpgrades(text, curioConfig)
if err != nil {
return curioConfig, fmt.Errorf("could not read layer, bad toml %s: %w", layer, err)
}
for _, k := range meta.Keys() {
have = append(have, strings.Join(k, " "))
}
log.Debugw("Using layer", "layer", layer, "config", curioConfig)
err = ApplyLayers(ctx, db, curioConfig, layers)
if err != nil {
return nil, err
}
err = config.EnableChangeDetection(db, curioConfig, layers)
if err != nil {
return nil, err
}
_ = have // FUTURE: verify that required fields are here.
// If config includes 3rd-party config, consider JSONSchema as a way that
// 3rd-parties can dynamically include config requirements and we can
// validate the config. Because of layering, we must validate @ startup.
return curioConfig, nil
}

func ApplyLayers(ctx context.Context, db *harmonydb.DB, curioConfig *config.CurioConfig, layers []string) error {
configs, err := config.GetConfigs(ctx, db, layers)
if err != nil {
return err
}
return config.ApplyLayers(ctx, curioConfig, configs)
}

func updateBaseLayer(ctx context.Context, db *harmonydb.DB) error {
_, err := db.BeginTransaction(ctx, func(tx *harmonydb.Tx) (commit bool, err error) {
// Get existing base from DB
Expand Down Expand Up @@ -631,7 +605,7 @@ func GetAPI(ctx context.Context, cctx *cli.Context) (*harmonydb.DB, *config.Curi
return nil, nil, nil, nil, nil, err
}

ethClient := lazy.MakeLazy[*ethclient.Client](func() (*ethclient.Client, error) {
ethClient := lazy.MakeLazy(func() (*ethclient.Client, error) {
return GetEthClient(cctx, cfgApiInfo)
})

Expand Down
Loading
Loading