Rewrite constructor to functional opts

This commit is contained in:
Łukasz Magiera 2019-07-04 17:50:48 +02:00
parent f08263662f
commit de604065fb
4 changed files with 260 additions and 140 deletions

View File

@ -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
}

View File

@ -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()
}

View File

@ -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
}

106
node/options.go Normal file
View File

@ -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()
}