From de604065fb03aa8aa9578604bc2fffac8fc7eb82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 4 Jul 2019 17:50:48 +0200 Subject: [PATCH] Rewrite constructor to functional opts --- daemon/cmd.go | 8 +- node/builder.go | 251 +++++++++++++++++++------------------ node/modules/lp2p/addrs.go | 35 +++--- node/options.go | 106 ++++++++++++++++ 4 files changed, 260 insertions(+), 140 deletions(-) create mode 100644 node/options.go diff --git a/daemon/cmd.go b/daemon/cmd.go index 4e46e39ca..4c003160c 100644 --- a/daemon/cmd.go +++ b/daemon/cmd.go @@ -2,6 +2,7 @@ package daemon import ( "context" + "github.com/filecoin-project/go-lotus/node/config" "gopkg.in/urfave/cli.v2" @@ -15,7 +16,12 @@ var Cmd = &cli.Command{ Action: func(cctx *cli.Context) error { ctx := context.Background() - api, err := node.New(ctx) + cfg, err := config.FromFile("./config.toml") + if err != nil { + return err + } + + api, err := node.New(ctx, node.Online(), node.Config(cfg)) if err != nil { return err } diff --git a/node/builder.go b/node/builder.go index 1711e296b..8c2dfe406 100644 --- a/node/builder.go +++ b/node/builder.go @@ -2,10 +2,15 @@ package node import ( "context" - "reflect" + "errors" + "github.com/filecoin-project/go-lotus/node/config" + "github.com/ipfs/go-datastore" + "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/peerstore" + "github.com/libp2p/go-libp2p-core/routing" + record "github.com/libp2p/go-libp2p-record" "time" - "github.com/ipfs/go-datastore" ci "github.com/libp2p/go-libp2p-core/crypto" "github.com/libp2p/go-libp2p-core/peer" "github.com/libp2p/go-libp2p-peerstore/pstoremem" @@ -13,58 +18,130 @@ import ( "github.com/filecoin-project/go-lotus/api" "github.com/filecoin-project/go-lotus/build" - "github.com/filecoin-project/go-lotus/node/config" "github.com/filecoin-project/go-lotus/node/modules" "github.com/filecoin-project/go-lotus/node/modules/helpers" "github.com/filecoin-project/go-lotus/node/modules/lp2p" ) -// New builds and starts new Filecoin node -func New(ctx context.Context) (api.API, error) { - var resAPI api.Struct +// special is a type used to give keys to modules which +// can't really be identified by the returned type +type special struct{ id int } - online := true +var ( + DefaultTransportsKey = special{0} // Libp2p option + PNetKey = special{1} // Option + multiret + DiscoveryHandlerKey = special{2} // Private type + AddrsFactoryKey = special{3} // Libp2p option + SmuxTransportKey = special{4} // Libp2p option + RelayKey = special{5} // Libp2p option + SecurityKey = special{6} // Libp2p option + BaseRoutingKey = special{7} // fx groups + multiret + NatPortMapKey = special{8} // Libp2p option + ConnectionManagerKey = special{9} // Libp2p option + +) + +type invoke int + +const ( + PstoreAddSelfKeysKey = invoke(iota) + StartListeningKey + + _nInvokes // keep this last +) + +type settings struct { + modules map[interface{}]fx.Option + + // invokes are separate from modules as they can't be referenced by return + // type, and must be applied in correct order + invokes []fx.Option + + online bool // Online option applied + config bool // Config option applied +} + +var defConf = config.Default() + +var defaults = []Option{ + Override(new(helpers.MetricsCtx), context.Background), + + randomIdentity(), + + Override(new(datastore.Batching), datastore.NewMapDatastore), + Override(new(record.Validator), modules.RecordValidator), +} + +func Online() Option { + return Options( + func(s *settings) error { s.online = true; return nil }, + applyIf(func(s *settings) bool { return s.config }, + Error(errors.New("the Online option must be set before Config option")), + ), + + Override(new(peerstore.Peerstore), pstoremem.NewPeerstore), + + Override(DefaultTransportsKey, lp2p.DefaultTransports), + Override(PNetKey, lp2p.PNet), + + Override(new(lp2p.RawHost), lp2p.Host), + Override(new(host.Host), lp2p.RoutedHost), + Override(new(lp2p.BaseIpfsRouting), lp2p.DHTRouting(false)), + + Override(DiscoveryHandlerKey, lp2p.DiscoveryHandler), + Override(AddrsFactoryKey, lp2p.AddrsFactory(nil, nil)), + Override(SmuxTransportKey, lp2p.SmuxTransport(true)), + Override(RelayKey, lp2p.Relay(true, false)), + Override(SecurityKey, lp2p.Security(true, false)), + + Override(BaseRoutingKey, lp2p.BaseRouting), + Override(new(routing.Routing), lp2p.Routing), + + Override(NatPortMapKey, lp2p.NatPortMap), + Override(ConnectionManagerKey, lp2p.ConnectionManager(50, 200, 20*time.Second)), + + Override(PstoreAddSelfKeysKey, lp2p.PstoreAddSelfKeys), + Override(StartListeningKey, lp2p.StartListening(defConf.Libp2p.ListenAddresses)), + ) +} + +func Config(cfg *config.Root) Option { + return Options( + func(s *settings) error { s.config = true; return nil }, + + applyIf(func(s *settings) bool { return s.online }, + Override(StartListeningKey, lp2p.StartListening(cfg.Libp2p.ListenAddresses)), + ), + ) +} + +// New builds and starts new Filecoin node +func New(ctx context.Context, opts ...Option) (api.API, error) { + var resAPI api.Struct + settings := settings{ + modules: map[interface{}]fx.Option{}, + invokes: make([]fx.Option, _nInvokes), + } + + if err := Options(Options(defaults...), Options(opts...))(&settings); err != nil { + return nil, err + } + + ctors := make([]fx.Option, 0, len(settings.modules)) + for _, opt := range settings.modules { + ctors = append(ctors, opt) + } + + // fill holes in invokes + for i, opt := range settings.invokes { + if opt == nil { + settings.invokes[i] = fx.Options() + } + } app := fx.New( - fx.Provide(as(ctx, new(helpers.MetricsCtx))), - fx.Provide(func() (*config.Root, error) { - return config.FromFile("./config.toml") - }), - - //fx.Provide(modules.RandomPeerID), - randomIdentity(), - memrepo(), - - fx.Provide(modules.RecordValidator), - - ifOpt(online, - fx.Provide( - pstoremem.NewPeerstore, - - lp2p.DefaultTransports, - lp2p.PNet, - lp2p.Host, - lp2p.RoutedHost, - lp2p.DHTRouting(false), - - lp2p.DiscoveryHandler, - lp2p.AddrsFactory(nil, nil), - lp2p.SmuxTransport(true), - lp2p.Relay(true, false), - lp2p.Security(true, false), - - lp2p.BaseRouting, - lp2p.Routing, - - lp2p.NatPortMap, - lp2p.ConnectionManager(50, 200, 20*time.Second), - ), - - fx.Invoke( - lp2p.PstoreAddSelfKeys, - lp2p.StartListening, - ), - ), + fx.Options(ctors...), + fx.Options(settings.invokes...), fx.Invoke(versionAPI(&resAPI.Internal.Version)), fx.Invoke(idAPI(&resAPI.Internal.ID)), @@ -79,36 +156,19 @@ func New(ctx context.Context) (api.API, error) { // In-memory / testing -func memrepo() fx.Option { - return fx.Provide( - func() datastore.Batching { - return datastore.NewMapDatastore() - }, - ) -} - -func randomIdentity() fx.Option { +func randomIdentity() Option { sk, pk, err := ci.GenerateKeyPair(ci.RSA, 512) if err != nil { - return fx.Error(err) + return Error(err) } - return fx.Options( - fx.Provide(as(sk, new(ci.PrivKey))), - fx.Provide(as(pk, new(ci.PubKey))), - fx.Provide(peer.IDFromPublicKey), + return Options( + Override(new(ci.PrivKey), sk), + Override(new(ci.PubKey), pk), + Override(new(peer.ID), peer.IDFromPublicKey), ) } -// UTILS - -func ifOpt(cond bool, options ...fx.Option) fx.Option { - if cond { - return fx.Options(options...) - } - return fx.Options() -} - // API IMPL // TODO: figure out a better way, this isn't usable in long term @@ -129,56 +189,3 @@ func versionAPI(set *func(context.Context) (api.Version, error)) func() { } } } - -// from go-ipfs -// as casts input constructor to a given interface (if a value is given, it -// wraps it into a constructor). -// -// Note: this method may look like a hack, and in fact it is one. -// This is here only because https://github.com/uber-go/fx/issues/673 wasn't -// released yet -// -// Note 2: when making changes here, make sure this method stays at -// 100% coverage. This makes it less likely it will be terribly broken -func as(in interface{}, as interface{}) interface{} { - outType := reflect.TypeOf(as) - - if outType.Kind() != reflect.Ptr { - panic("outType is not a pointer") - } - - if reflect.TypeOf(in).Kind() != reflect.Func { - ctype := reflect.FuncOf(nil, []reflect.Type{outType.Elem()}, false) - - return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { - out := reflect.New(outType.Elem()) - out.Elem().Set(reflect.ValueOf(in)) - - return []reflect.Value{out.Elem()} - }).Interface() - } - - inType := reflect.TypeOf(in) - - ins := make([]reflect.Type, inType.NumIn()) - outs := make([]reflect.Type, inType.NumOut()) - - for i := range ins { - ins[i] = inType.In(i) - } - outs[0] = outType.Elem() - for i := range outs[1:] { - outs[i+1] = inType.Out(i + 1) - } - - ctype := reflect.FuncOf(ins, outs, false) - - return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { - outs := reflect.ValueOf(in).Call(args) - out := reflect.New(outType.Elem()) - out.Elem().Set(outs[0]) - outs[0] = out.Elem() - - return outs - }).Interface() -} diff --git a/node/modules/lp2p/addrs.go b/node/modules/lp2p/addrs.go index efbe4a03b..95971449d 100644 --- a/node/modules/lp2p/addrs.go +++ b/node/modules/lp2p/addrs.go @@ -3,9 +3,8 @@ package lp2p import ( "fmt" - "github.com/filecoin-project/go-lotus/node/config" "github.com/libp2p/go-libp2p" - host "github.com/libp2p/go-libp2p-core/host" + "github.com/libp2p/go-libp2p-core/host" p2pbhost "github.com/libp2p/go-libp2p/p2p/host/basic" mafilter "github.com/libp2p/go-maddr-filter" ma "github.com/multiformats/go-multiaddr" @@ -95,22 +94,24 @@ func listenAddresses(addresses []string) ([]ma.Multiaddr, error) { return listen, nil } -func StartListening(host host.Host, cfg *config.Root) error { - listenAddrs, err := listenAddresses(cfg.Libp2p.ListenAddresses) - if err != nil { - return err - } +func StartListening(addresses []string) func(host host.Host) error { + return func(host host.Host) error { + listenAddrs, err := listenAddresses(addresses) + if err != nil { + return err + } - // Actually start listening: - if err := host.Network().Listen(listenAddrs...); err != nil { - return err - } + // Actually start listening: + if err := host.Network().Listen(listenAddrs...); err != nil { + return err + } - // list out our addresses - addrs, err := host.Network().InterfaceListenAddresses() - if err != nil { - return err + // list out our addresses + addrs, err := host.Network().InterfaceListenAddresses() + if err != nil { + return err + } + log.Infof("Swarm listening at: %s", addrs) + return nil } - log.Infof("Swarm listening at: %s", addrs) - return nil } diff --git a/node/options.go b/node/options.go new file mode 100644 index 000000000..6260aaadf --- /dev/null +++ b/node/options.go @@ -0,0 +1,106 @@ +package node + +import ( + "go.uber.org/fx" + "reflect" +) + +type Option func(*settings) error + +func Override(typ, constructor interface{}) Option { + return func(s *settings) error { + if i, ok := typ.(invoke); ok { + s.invokes[i] = fx.Invoke(constructor) + return nil + } + + if c, ok := typ.(special); ok { + s.modules[c] = fx.Provide(constructor) + return nil + } + ctor := as(constructor, typ) + rt := reflect.TypeOf(typ).Elem() + + s.modules[rt] = fx.Provide(ctor) + return nil + } +} + +func Options(opts ...Option) Option { + return func(s *settings) error { + for _, opt := range opts { + if err := opt(s); err != nil { + return err + } + } + return nil + } +} + +func Error(err error) Option { + return func(_ *settings) error { + return err + } +} + +func applyIf(check func(s *settings) bool, opts ...Option) Option { + return func(s *settings) error { + if check(s) { + return Options(opts...)(s) + } + return nil + } +} + +// from go-ipfs +// as casts input constructor to a given interface (if a value is given, it +// wraps it into a constructor). +// +// Note: this method may look like a hack, and in fact it is one. +// This is here only because https://github.com/uber-go/fx/issues/673 wasn't +// released yet +// +// Note 2: when making changes here, make sure this method stays at +// 100% coverage. This makes it less likely it will be terribly broken +func as(in interface{}, as interface{}) interface{} { + outType := reflect.TypeOf(as) + + if outType.Kind() != reflect.Ptr { + panic("outType is not a pointer") + } + + if reflect.TypeOf(in).Kind() != reflect.Func { + ctype := reflect.FuncOf(nil, []reflect.Type{outType.Elem()}, false) + + return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { + out := reflect.New(outType.Elem()) + out.Elem().Set(reflect.ValueOf(in)) + + return []reflect.Value{out.Elem()} + }).Interface() + } + + inType := reflect.TypeOf(in) + + ins := make([]reflect.Type, inType.NumIn()) + outs := make([]reflect.Type, inType.NumOut()) + + for i := range ins { + ins[i] = inType.In(i) + } + outs[0] = outType.Elem() + for i := range outs[1:] { + outs[i+1] = inType.Out(i + 1) + } + + ctype := reflect.FuncOf(ins, outs, false) + + return reflect.MakeFunc(ctype, func(args []reflect.Value) (results []reflect.Value) { + outs := reflect.ValueOf(in).Call(args) + out := reflect.New(outType.Elem()) + out.Elem().Set(outs[0]) + outs[0] = out.Elem() + + return outs + }).Interface() +}