diff --git a/chain/gen/gen.go b/chain/gen/gen.go index b38f2c0df..e1452bb91 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -75,7 +75,7 @@ func (m mybs) Get(c cid.Cid) (block.Block, error) { func NewGenerator() (*ChainGen, error) { mr := repo.NewMemory(nil) - lr, err := mr.Lock() + lr, err := mr.Lock(repo.RepoStorageMiner) if err != nil { return nil, xerrors.Errorf("taking mem-repo lock failed: %w", err) } diff --git a/cmd/lotus-storage-miner/init.go b/cmd/lotus-storage-miner/init.go index 54d488a1b..5943d6dac 100644 --- a/cmd/lotus-storage-miner/init.go +++ b/cmd/lotus-storage-miner/init.go @@ -101,7 +101,7 @@ var initCmd = &cli.Command{ log.Info("Initializing repo") - if err := r.Init(); err != nil { + if err := r.Init(repo.RepoStorageMiner); err != nil { return err } @@ -122,7 +122,7 @@ var initCmd = &cli.Command{ } func storageMinerInit(ctx context.Context, cctx *cli.Context, api api.FullNode, r repo.Repo) error { - lr, err := r.Lock() + lr, err := r.Lock(repo.RepoStorageMiner) if err != nil { return err } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index c9b736c72..4921f3930 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -4,9 +4,10 @@ package main import ( "context" - "github.com/filecoin-project/lotus/peermgr" "io/ioutil" + "github.com/filecoin-project/lotus/peermgr" + "github.com/multiformats/go-multiaddr" "golang.org/x/xerrors" "gopkg.in/urfave/cli.v2" @@ -53,7 +54,7 @@ var DaemonCmd = &cli.Command{ return err } - if err := r.Init(); err != nil && err != repo.ErrRepoExists { + if err := r.Init(repo.RepoFullNode); err != nil && err != repo.ErrRepoExists { return err } diff --git a/node/builder.go b/node/builder.go index c316139ee..f704bef19 100644 --- a/node/builder.go +++ b/node/builder.go @@ -15,6 +15,7 @@ import ( pubsub "github.com/libp2p/go-libp2p-pubsub" record "github.com/libp2p/go-libp2p-record" "go.uber.org/fx" + "golang.org/x/xerrors" "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain" @@ -100,11 +101,6 @@ const ( _nInvokes // keep this last ) -const ( - nodeFull = iota - nodeStorageMiner -) - type Settings struct { // modules is a map of constructors for DI // @@ -117,14 +113,12 @@ type Settings struct { // type, and must be applied in correct order invokes []fx.Option - nodeType int + nodeType repo.RepoType Online bool // Online option applied Config bool // Config option applied } -var defConf = config.Default() - func defaults() []Option { return []Option{ Override(new(helpers.MetricsCtx), context.Background), @@ -161,10 +155,14 @@ func libp2p() Option { Override(new(*pubsub.PubSub), lp2p.GossipSub()), Override(PstoreAddSelfKeysKey, lp2p.PstoreAddSelfKeys), - Override(StartListeningKey, lp2p.StartListening(defConf.Libp2p.ListenAddresses)), + Override(StartListeningKey, lp2p.StartListening(config.DefaultFullNode().Libp2p.ListenAddresses)), ) } +func isType(t repo.RepoType) func(s *Settings) bool { + return func(s *Settings) bool { return s.nodeType == t } +} + // Online sets up basic libp2p node func Online() Option { return Options( @@ -181,7 +179,7 @@ func Online() Option { // Full node - ApplyIf(func(s *Settings) bool { return s.nodeType == nodeFull }, + ApplyIf(isType(repo.RepoFullNode), // TODO: Fix offline mode Override(new(dtypes.BootstrapPeers), modules.BuiltinBootstrap), @@ -230,7 +228,7 @@ func Online() Option { ), // Storage miner - ApplyIf(func(s *Settings) bool { return s.nodeType == nodeStorageMiner }, + ApplyIf(func(s *Settings) bool { return s.nodeType == repo.RepoStorageMiner }, Override(new(*sectorbuilder.SectorBuilder), sectorbuilder.New), Override(new(*sector.Store), sector.NewStore), Override(new(*sectorblocks.SectorBlocks), sectorblocks.NewSectorBlocks), @@ -260,7 +258,7 @@ func StorageMiner(out *api.StorageMiner) Option { ), func(s *Settings) error { - s.nodeType = nodeStorageMiner + s.nodeType = repo.RepoStorageMiner return nil }, @@ -274,7 +272,7 @@ func StorageMiner(out *api.StorageMiner) Option { } // Config sets up constructors based on the provided Config -func Config(cfg *config.Root) Option { +func ConfigCommon(cfg *config.Common) Option { return Options( func(s *Settings) error { s.Config = true; return nil }, @@ -284,27 +282,64 @@ func Config(cfg *config.Root) Option { ApplyIf(func(s *Settings) bool { return len(cfg.Libp2p.BootstrapPeers) > 0 }, Override(new(dtypes.BootstrapPeers), modules.ConfigBootstrap(cfg.Libp2p.BootstrapPeers)), ), - - ApplyIf(func(s *Settings) bool { return s.nodeType == nodeFull }, - Override(HeadMetricsKey, metrics.SendHeadNotifs(cfg.Metrics.Nickname)), - ), ), ) } -func Repo(r repo.Repo) Option { - lr, err := r.Lock() +func ConfigFullNode(cfg *config.FullNode) Option { + //ApplyIf(func(s *Settings) bool { return s.nodeType == repo.RepoFullNode }), + return Options( + ConfigCommon(&cfg.Common), + Override(HeadMetricsKey, metrics.SendHeadNotifs(cfg.Metrics.Nickname)), + ) +} + +func repoFull(r repo.Repo) Option { + lr, err := r.Lock(repo.RepoFullNode) if err != nil { return Error(err) } - cfg, err := lr.Config() + c, err := lr.Config() + if err != nil { + return Error(err) + } + cfg, ok := c.(*config.FullNode) + if !ok { + return Error(xerrors.Errorf("invalid config from repo, got: %T", c)) + } + + return Options( + ConfigFullNode(cfg), + Override(new(repo.LockedRepo), modules.LockedRepo(lr)), // module handles closing + ) +} + +func repoMiner(r repo.Repo) Option { + lr, err := r.Lock(repo.RepoStorageMiner) + if err != nil { + return Error(err) + } + c, err := lr.Config() if err != nil { return Error(err) } + cfg, ok := c.(*config.StorageMiner) + if !ok { + return Error(xerrors.Errorf("invalid config from repo, got: %T", c)) + } + return Options( - Config(cfg), + ConfigCommon(&cfg.Common), Override(new(repo.LockedRepo), modules.LockedRepo(lr)), // module handles closing + ) +} + +func Repo(r repo.Repo) Option { + + return Options( + ApplyIf(isType(repo.RepoFullNode), repoFull(r)), + ApplyIf(isType(repo.RepoStorageMiner), repoMiner(r)), Override(new(dtypes.MetadataDS), modules.Datastore), Override(new(dtypes.ChainBlockstore), modules.ChainBlockstore), @@ -337,8 +372,9 @@ type StopFunc func(context.Context) error // New builds and starts new Filecoin node func New(ctx context.Context, opts ...Option) (StopFunc, error) { settings := Settings{ - modules: map[interface{}]fx.Option{}, - invokes: make([]fx.Option, _nInvokes), + modules: map[interface{}]fx.Option{}, + invokes: make([]fx.Option, _nInvokes), + nodeType: repo.RepoFullNode, } // apply module options in the right order diff --git a/node/config/def.go b/node/config/def.go index 3b1020840..93a876656 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -1,15 +1,27 @@ package config -import "time" +import ( + "encoding" + "time" +) -// Root is starting point of the config -type Root struct { +// Common is common config between full node and miner +type Common struct { API API Libp2p Libp2p +} +// FullNode is a full node config +type FullNode struct { + Common Metrics Metrics } +// StorageMiner is a storage miner config +type StorageMiner struct { + Common +} + // API contains configs for API endpoint type API struct { ListenAddress string @@ -26,9 +38,8 @@ type Metrics struct { Nickname string } -// Default returns the default config -func Default() *Root { - def := Root{ +func defCommon() Common { + return Common{ API: API{ ListenAddress: "/ip6/::1/tcp/1234/http", Timeout: Duration(30 * time.Second), @@ -40,10 +51,27 @@ func Default() *Root { }, }, } - return &def + } -// Duration is a wrapper type for time.Duration for decoding it from TOML +// Default returns the default config +func DefaultFullNode() *FullNode { + return &FullNode{ + Common: defCommon(), + } +} + +func DefaultStorageMiner() *StorageMiner { + return &StorageMiner{ + Common: defCommon(), + } +} + +var _ encoding.TextMarshaler = (*Duration)(nil) +var _ encoding.TextUnmarshaler = (*Duration)(nil) + +// Duration is a wrapper type for time.Duration +// for decoding and encoding from/to TOML type Duration time.Duration // UnmarshalText implements interface for TOML decoding @@ -55,3 +83,8 @@ func (dur *Duration) UnmarshalText(text []byte) error { *dur = Duration(d) return err } + +func (dur Duration) MarshalText() ([]byte, error) { + d := time.Duration(dur) + return []byte(d.String()), nil +} diff --git a/node/config/load.go b/node/config/load.go index 2f2d223b3..fd1aeb181 100644 --- a/node/config/load.go +++ b/node/config/load.go @@ -1,30 +1,32 @@ package config import ( + "bytes" "io" "os" "github.com/BurntSushi/toml" + "golang.org/x/xerrors" ) // FromFile loads config from a specified file overriding defaults specified in -// the source code. If file does not exist or is empty defaults are asummed. -func FromFile(path string) (*Root, error) { +// the def parameter. If file does not exist or is empty defaults are asummed. +func FromFile(path string, def interface{}) (interface{}, error) { file, err := os.Open(path) switch { case os.IsNotExist(err): - return Default(), nil + return def, nil case err != nil: return nil, err } defer file.Close() //nolint:errcheck // The file is RO - return FromReader(file) + return FromReader(file, def) } // FromReader loads config from a reader instance. -func FromReader(reader io.Reader) (*Root, error) { - cfg := Default() +func FromReader(reader io.Reader, def interface{}) (interface{}, error) { + cfg := def _, err := toml.DecodeReader(reader, cfg) if err != nil { return nil, err @@ -32,3 +34,16 @@ func FromReader(reader io.Reader) (*Root, error) { return cfg, nil } + +func ConfigComment(t interface{}) ([]byte, error) { + buf := new(bytes.Buffer) + _, _ = buf.WriteString("# Default config:\n") + e := toml.NewEncoder(buf) + if err := e.Encode(t); err != nil { + return nil, xerrors.Errorf("encoding config: %w", err) + } + b := buf.Bytes() + b = bytes.ReplaceAll(b, []byte("\n"), []byte("\n#")) + return b, nil + +} diff --git a/node/config/load_test.go b/node/config/load_test.go index 543a00103..9abe8a54b 100644 --- a/node/config/load_test.go +++ b/node/config/load_test.go @@ -14,16 +14,16 @@ func TestDecodeNothing(t *testing.T) { assert := assert.New(t) { - cfg, err := FromFile(os.DevNull) + cfg, err := FromFile(os.DevNull, DefaultFullNode()) assert.Nil(err, "error should be nil") - assert.Equal(Default(), cfg, + assert.Equal(DefaultFullNode(), cfg, "config from empty file should be the same as default") } { - cfg, err := FromFile("./does-not-exist.toml") + cfg, err := FromFile("./does-not-exist.toml", DefaultFullNode()) assert.Nil(err, "error should be nil") - assert.Equal(Default(), cfg, + assert.Equal(DefaultFullNode(), cfg, "config from not exisiting file should be the same as default") } } @@ -34,11 +34,11 @@ func TestParitalConfig(t *testing.T) { [API] Timeout = "10s" ` - expected := Default() + expected := DefaultFullNode() expected.API.Timeout = Duration(10 * time.Second) { - cfg, err := FromReader(bytes.NewReader([]byte(cfgString))) + cfg, err := FromReader(bytes.NewReader([]byte(cfgString)), DefaultFullNode()) assert.NoError(err, "error should be nil") assert.Equal(expected, cfg, "config from reader should contain changes") @@ -55,7 +55,7 @@ func TestParitalConfig(t *testing.T) { assert.NoError(err, "closing tmp file should not error") defer os.Remove(fname) //nolint:errcheck - cfg, err := FromFile(fname) + cfg, err := FromFile(fname, DefaultFullNode()) assert.Nil(err, "error should be nil") assert.Equal(expected, cfg, "config from reader should contain changes") diff --git a/node/node_test.go b/node/node_test.go index 06f1fc1c6..82ab996bd 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -34,7 +34,7 @@ import ( func testStorageNode(ctx context.Context, t *testing.T, waddr address.Address, act address.Address, tnd test.TestNode) test.TestStorageNode { r := repo.NewMemory(nil) - lr, err := r.Lock() + lr, err := r.Lock(repo.RepoStorageMiner) require.NoError(t, err) pk, _, err := crypto.GenerateEd25519Key(rand.Reader) diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index df496315c..0a81e6513 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -2,6 +2,7 @@ package repo import ( "encoding/json" + "fmt" "io" "io/ioutil" "os" @@ -33,13 +34,33 @@ const ( fsKeystore = "keystore" ) +type RepoType int + +const ( + _ = iota // Default is invalid + RepoFullNode RepoType = iota + RepoStorageMiner +) + +func defConfForType(t RepoType) interface{} { + switch t { + case RepoFullNode: + return config.DefaultFullNode() + case RepoStorageMiner: + return config.DefaultStorageMiner() + default: + panic(fmt.Sprintf("unknown RepoType(%d)", int(t))) + } +} + var log = logging.Logger("repo") var ErrRepoExists = xerrors.New("repo exists") // FsRepo is struct for repo, use NewFS to create type FsRepo struct { - path string + path string + repoType RepoType } var _ Repo = &FsRepo{} @@ -65,7 +86,7 @@ func (fsr *FsRepo) Exists() (bool, error) { return !notexist, err } -func (fsr *FsRepo) Init() error { +func (fsr *FsRepo) Init(t RepoType) error { exist, err := fsr.Exists() if err != nil { return err @@ -79,18 +100,36 @@ func (fsr *FsRepo) Init() error { if err != nil && !os.IsExist(err) { return err } - c, err := os.Create(filepath.Join(fsr.path, fsConfig)) - if err != nil { - return err - } - if err := c.Close(); err != nil { - return err + + if err := fsr.initConfig(t); err != nil { + return xerrors.Errorf("init config: %w", err) } return fsr.initKeystore() } +func (fsr *FsRepo) initConfig(t RepoType) error { + c, err := os.Create(filepath.Join(fsr.path, fsConfig)) + if err != nil { + return err + } + + comm, err := config.ConfigComment(defConfForType(t)) + if err != nil { + return xerrors.Errorf("comment: %w", err) + } + _, err = c.Write(comm) + if err != nil { + return xerrors.Errorf("write config: %w", err) + } + + if err := c.Close(); err != nil { + return xerrors.Errorf("close config: %w", err) + } + return nil +} + func (fsr *FsRepo) initKeystore() error { kstorePath := filepath.Join(fsr.path, fsKeystore) if _, err := os.Stat(kstorePath); err == nil { @@ -142,7 +181,7 @@ func (fsr *FsRepo) APIToken() ([]byte, error) { } // Lock acquires exclusive lock on this repo -func (fsr *FsRepo) Lock() (LockedRepo, error) { +func (fsr *FsRepo) Lock(repoType RepoType) (LockedRepo, error) { locked, err := fslock.Locked(fsr.path, fsLock) if err != nil { return nil, xerrors.Errorf("could not check lock status: %w", err) @@ -156,14 +195,16 @@ func (fsr *FsRepo) Lock() (LockedRepo, error) { return nil, xerrors.Errorf("could not lock the repo: %w", err) } return &fsLockedRepo{ - path: fsr.path, - closer: closer, + path: fsr.path, + repoType: repoType, + closer: closer, }, nil } type fsLockedRepo struct { - path string - closer io.Closer + path string + repoType RepoType + closer io.Closer ds datastore.Batching dsErr error @@ -219,11 +260,11 @@ func (fsr *fsLockedRepo) Datastore(ns string) (datastore.Batching, error) { return namespace.Wrap(fsr.ds, datastore.NewKey(ns)), nil } -func (fsr *fsLockedRepo) Config() (*config.Root, error) { +func (fsr *fsLockedRepo) Config() (interface{}, error) { if err := fsr.stillValid(); err != nil { return nil, err } - return config.FromFile(fsr.join(fsConfig)) + return config.FromFile(fsr.join(fsConfig), defConfForType(fsr.repoType)) } func (fsr *fsLockedRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error { diff --git a/node/repo/fsrepo_test.go b/node/repo/fsrepo_test.go index 938314a23..de55eb351 100644 --- a/node/repo/fsrepo_test.go +++ b/node/repo/fsrepo_test.go @@ -17,7 +17,7 @@ func genFsRepo(t *testing.T) (*FsRepo, func()) { t.Fatal(err) } - err = repo.Init() + err = repo.Init(RepoFullNode) if err != ErrRepoExists && err != nil { t.Fatal(err) } diff --git a/node/repo/interface.go b/node/repo/interface.go index 9fabc32d0..9a7a3e3eb 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -7,7 +7,6 @@ import ( "github.com/multiformats/go-multiaddr" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/config" ) var ( @@ -25,7 +24,7 @@ type Repo interface { APIToken() ([]byte, error) // Lock locks the repo for exclusive use. - Lock() (LockedRepo, error) + Lock(RepoType) (LockedRepo, error) } type LockedRepo interface { @@ -36,7 +35,7 @@ type LockedRepo interface { Datastore(namespace string) (datastore.Batching, error) // Returns config in this repo - Config() (*config.Root, error) + Config() (interface{}, error) // SetAPIEndpoint sets the endpoint of the current API // so it can be read by API clients diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 84c52ed4f..00f6de424 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -10,7 +10,6 @@ import ( "golang.org/x/xerrors" "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/node/config" ) type MemRepo struct { @@ -24,12 +23,13 @@ type MemRepo struct { token *byte datastore datastore.Datastore - configF func() *config.Root + configF func(t RepoType) interface{} keystore map[string]types.KeyInfo } type lockedMemRepo struct { mem *MemRepo + t RepoType sync.RWMutex token *byte @@ -44,7 +44,7 @@ var _ Repo = &MemRepo{} // MemRepoOptions contains options for memory repo type MemRepoOptions struct { Ds datastore.Datastore - ConfigF func() *config.Root + ConfigF func(RepoType) interface{} KeyStore map[string]types.KeyInfo } @@ -56,7 +56,7 @@ func NewMemory(opts *MemRepoOptions) *MemRepo { opts = &MemRepoOptions{} } if opts.ConfigF == nil { - opts.ConfigF = config.Default + opts.ConfigF = defConfForType } if opts.Ds == nil { opts.Ds = dssync.MutexWrap(datastore.NewMapDatastore()) @@ -92,7 +92,7 @@ func (mem *MemRepo) APIToken() ([]byte, error) { return mem.api.token, nil } -func (mem *MemRepo) Lock() (LockedRepo, error) { +func (mem *MemRepo) Lock(t RepoType) (LockedRepo, error) { select { case mem.repoLock <- struct{}{}: default: @@ -102,6 +102,7 @@ func (mem *MemRepo) Lock() (LockedRepo, error) { return &lockedMemRepo{ mem: mem, + t: t, token: mem.token, }, nil } @@ -143,11 +144,11 @@ func (lmem *lockedMemRepo) Datastore(ns string) (datastore.Batching, error) { return namespace.Wrap(lmem.mem.datastore, datastore.NewKey(ns)), nil } -func (lmem *lockedMemRepo) Config() (*config.Root, error) { +func (lmem *lockedMemRepo) Config() (interface{}, error) { if err := lmem.checkToken(); err != nil { return nil, err } - return lmem.mem.configF(), nil + return lmem.mem.configF(lmem.t), nil } func (lmem *lockedMemRepo) SetAPIEndpoint(ma multiaddr.Multiaddr) error { diff --git a/node/repo/repo_test.go b/node/repo/repo_test.go index 34185a2c9..d9cdc5dc5 100644 --- a/node/repo/repo_test.go +++ b/node/repo/repo_test.go @@ -18,12 +18,12 @@ func basicTest(t *testing.T, repo Repo) { } assert.Nil(t, apima, "with no api endpoint, return should be nil") - lrepo, err := repo.Lock() + lrepo, err := repo.Lock(RepoFullNode) assert.NoError(t, err, "should be able to lock once") assert.NotNil(t, lrepo, "locked repo shouldn't be nil") { - lrepo2, err := repo.Lock() + lrepo2, err := repo.Lock(RepoFullNode) if assert.Error(t, err) { assert.Equal(t, ErrRepoAlreadyLocked, err) } @@ -33,7 +33,7 @@ func basicTest(t *testing.T, repo Repo) { err = lrepo.Close() assert.NoError(t, err, "should be able to unlock") - lrepo, err = repo.Lock() + lrepo, err = repo.Lock(RepoFullNode) assert.NoError(t, err, "should be able to relock") assert.NotNil(t, lrepo, "locked repo shouldn't be nil") @@ -48,7 +48,7 @@ func basicTest(t *testing.T, repo Repo) { assert.Equal(t, ma, apima, "returned API multiaddr should be the same") cfg, err := lrepo.Config() - assert.Equal(t, config.Default(), cfg, "there should be a default config") + assert.Equal(t, config.DefaultFullNode(), cfg, "there should be a default config") assert.NoError(t, err, "config should not error") err = lrepo.Close() @@ -64,7 +64,7 @@ func basicTest(t *testing.T, repo Repo) { k1 := types.KeyInfo{Type: "foo"} k2 := types.KeyInfo{Type: "bar"} - lrepo, err = repo.Lock() + lrepo, err = repo.Lock(RepoFullNode) assert.NoError(t, err, "should be able to relock") assert.NotNil(t, lrepo, "locked repo shouldn't be nil")