core.go 14.5 KB
Newer Older
1 2 3 4
/*
Package core implements the IpfsNode object and related methods.

Packages underneath core/ provide a (relatively) stable, low-level API
5 6 7 8
to carry out most IPFS-related tasks.  For more details on the other
interfaces and how core/... fits into the bigger IPFS picture, see:

  $ godoc github.com/ipfs/go-ipfs
9
*/
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
10 11
package core

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
12
import (
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
13
	"errors"
Juan Batiz-Benet's avatar
go fmt  
Juan Batiz-Benet committed
14
	"fmt"
15
	"io"
Jeromy's avatar
Jeromy committed
16
	"net"
17
	"time"
18

19 20 21
	b58 "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-base58"
	ds "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-datastore"
	ma "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
22
	goprocess "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
Jeromy's avatar
Jeromy committed
23
	mamask "github.com/ipfs/go-ipfs/Godeps/_workspace/src/github.com/whyrusleeping/multiaddr-filter"
24 25
	context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
	diag "github.com/ipfs/go-ipfs/diagnostics"
Jeromy's avatar
Jeromy committed
26
	metrics "github.com/ipfs/go-ipfs/metrics"
27
	ic "github.com/ipfs/go-ipfs/p2p/crypto"
28
	discovery "github.com/ipfs/go-ipfs/p2p/discovery"
29 30 31 32 33 34
	p2phost "github.com/ipfs/go-ipfs/p2p/host"
	p2pbhost "github.com/ipfs/go-ipfs/p2p/host/basic"
	rhost "github.com/ipfs/go-ipfs/p2p/host/routed"
	swarm "github.com/ipfs/go-ipfs/p2p/net/swarm"
	addrutil "github.com/ipfs/go-ipfs/p2p/net/swarm/addr"
	peer "github.com/ipfs/go-ipfs/p2p/peer"
Jeromy's avatar
Jeromy committed
35
	ping "github.com/ipfs/go-ipfs/p2p/protocol/ping"
Jeromy's avatar
Jeromy committed
36
	logging "github.com/ipfs/go-ipfs/vendor/go-log-v1.0.0"
37

38 39
	routing "github.com/ipfs/go-ipfs/routing"
	dht "github.com/ipfs/go-ipfs/routing/dht"
40
	nilrouting "github.com/ipfs/go-ipfs/routing/none"
41
	offroute "github.com/ipfs/go-ipfs/routing/offline"
42

43 44 45 46 47 48
	bstore "github.com/ipfs/go-ipfs/blocks/blockstore"
	bserv "github.com/ipfs/go-ipfs/blockservice"
	exchange "github.com/ipfs/go-ipfs/exchange"
	bitswap "github.com/ipfs/go-ipfs/exchange/bitswap"
	bsnet "github.com/ipfs/go-ipfs/exchange/bitswap/network"
	rp "github.com/ipfs/go-ipfs/exchange/reprovide"
49

50 51 52 53
	mount "github.com/ipfs/go-ipfs/fuse/mount"
	ipnsfs "github.com/ipfs/go-ipfs/ipnsfs"
	merkledag "github.com/ipfs/go-ipfs/merkledag"
	namesys "github.com/ipfs/go-ipfs/namesys"
Jeromy's avatar
Jeromy committed
54
	ipnsrp "github.com/ipfs/go-ipfs/namesys/republisher"
55 56 57 58
	path "github.com/ipfs/go-ipfs/path"
	pin "github.com/ipfs/go-ipfs/pin"
	repo "github.com/ipfs/go-ipfs/repo"
	config "github.com/ipfs/go-ipfs/repo/config"
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
59 60
)

Jeromy's avatar
Jeromy committed
61
const IpnsValidatorTag = "ipns"
62
const kSizeBlockstoreWriteCache = 100
63
const kReprovideFrequency = time.Hour * 12
64
const discoveryConnTimeout = time.Second * 30
Jeromy's avatar
Jeromy committed
65

Jeromy's avatar
Jeromy committed
66
var log = logging.Logger("core")
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
67

68 69 70 71 72 73 74 75 76
type mode int

const (
	// zero value is not a valid mode, must be explicitly set
	invalidMode mode = iota
	offlineMode
	onlineMode
)

Juan Batiz-Benet's avatar
go lint  
Juan Batiz-Benet committed
77
// IpfsNode is IPFS Core module. It represents an IPFS instance.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
78 79
type IpfsNode struct {

80
	// Self
81
	Identity peer.ID // the local node's identity
82

83
	Repo repo.Repo
84 85

	// Local node
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
86 87 88
	Pinning    pin.Pinner // the pinning manager
	Mounts     Mounts     // current mount state, if any.
	PrivateKey ic.PrivKey // the local node's private Key
89 90

	// Services
91 92 93 94 95
	Peerstore  peer.Peerstore       // storage for other Peer instances
	Blockstore bstore.Blockstore    // the block store (lower level)
	Blocks     *bserv.BlockService  // the block service, get/add blocks.
	DAG        merkledag.DAGService // the merkle dag service, get/add objects.
	Resolver   *path.Resolver       // the path resolution system
Jeromy's avatar
Jeromy committed
96
	Reporter   metrics.Reporter
97
	Discovery  discovery.Service
98 99

	// Online
100 101 102 103 104 105
	PeerHost     p2phost.Host        // the network host (server+client)
	Bootstrapper io.Closer           // the periodic bootstrapper
	Routing      routing.IpfsRouting // the routing system. recommend ipfs-dht
	Exchange     exchange.Interface  // the block exchange + strategy (bitswap)
	Namesys      namesys.NameSystem  // the name system, resolves paths to hashes
	Diagnostics  *diag.Diagnostics   // the diagnostics service
Jeromy's avatar
Jeromy committed
106 107
	Ping         *ping.PingService
	Reprovider   *rp.Reprovider // the value reprovider system
Jeromy's avatar
Jeromy committed
108
	IpnsRepub    *ipnsrp.Republisher
109

110 111
	IpnsFs *ipnsfs.Filesystem

112 113
	proc goprocess.Process
	ctx  context.Context
114

115
	mode mode
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
116 117
}

118 119 120 121 122 123 124 125
// Mounts defines what the node's mount state is. This should
// perhaps be moved to the daemon or mount. It's here because
// it needs to be accessible across daemon requests.
type Mounts struct {
	Ipfs mount.Mount
	Ipns mount.Mount
}

Jeromy's avatar
Jeromy committed
126
func (n *IpfsNode) startOnlineServices(ctx context.Context, routingOption RoutingOption, hostOption HostOption, do DiscoveryOption) error {
127 128

	if n.PeerHost != nil { // already online.
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
129
		return errors.New("node already online")
130 131 132
	}

	// load private key
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
133
	if err := n.LoadPrivateKey(); err != nil {
134 135 136
		return err
	}

Jeromy's avatar
Jeromy committed
137 138 139
	// Set reporter
	n.Reporter = metrics.NewBandwidthCounter()

Jeromy's avatar
Jeromy committed
140
	// get undialable addrs from config
141 142 143 144
	cfg, err := n.Repo.Config()
	if err != nil {
		return err
	}
Jeromy's avatar
Jeromy committed
145
	var addrfilter []*net.IPNet
146
	for _, s := range cfg.Swarm.AddrFilters {
Jeromy's avatar
Jeromy committed
147 148 149 150 151 152 153 154
		f, err := mamask.NewMask(s)
		if err != nil {
			return fmt.Errorf("incorrectly formatter address filter in config: %s", s)
		}
		addrfilter = append(addrfilter, f)
	}

	peerhost, err := hostOption(ctx, n.Identity, n.Peerstore, n.Reporter, addrfilter)
155
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
156
		return err
157 158
	}

159
	if err := n.startOnlineServicesWithHost(ctx, peerhost, routingOption); err != nil {
160
		return err
161 162 163
	}

	// Ok, now we're ready to listen.
164
	if err := startListening(ctx, n.PeerHost, cfg); err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
165
		return err
166
	}
167

168
	n.Reprovider = rp.NewReprovider(n.Routing, n.Blockstore)
169
	go n.Reprovider.ProvideEvery(ctx, kReprovideFrequency)
170

171
	// setup local discovery
Jeromy's avatar
Jeromy committed
172 173 174
	if do != nil {
		service, err := do(n.PeerHost)
		if err != nil {
Jeromy's avatar
Jeromy committed
175 176 177 178
			log.Error("mdns error: ", err)
		} else {
			service.RegisterNotifee(n)
			n.Discovery = service
Jeromy's avatar
Jeromy committed
179
		}
180 181
	}

182
	return n.Bootstrap(DefaultBootstrapConfig)
183 184
}

Jeromy's avatar
Jeromy committed
185 186 187 188 189 190 191 192 193 194 195 196
func setupDiscoveryOption(d config.Discovery) DiscoveryOption {
	if d.MDNS.Enabled {
		return func(h p2phost.Host) (discovery.Service, error) {
			if d.MDNS.Interval == 0 {
				d.MDNS.Interval = 5
			}
			return discovery.NewMdnsService(h, time.Duration(d.MDNS.Interval)*time.Second)
		}
	}
	return nil
}

197 198
func (n *IpfsNode) HandlePeerFound(p peer.PeerInfo) {
	log.Warning("trying peer info: ", p)
199
	ctx, cancel := context.WithTimeout(n.Context(), discoveryConnTimeout)
rht's avatar
rht committed
200
	defer cancel()
201
	if err := n.PeerHost.Connect(ctx, p); err != nil {
202 203 204 205
		log.Warning("Failed to connect to peer found by discovery: ", err)
	}
}

206 207
// startOnlineServicesWithHost  is the set of services which need to be
// initialized with the host and _before_ we start listening.
208
func (n *IpfsNode) startOnlineServicesWithHost(ctx context.Context, host p2phost.Host, routingOption RoutingOption) error {
209
	// setup diagnostics service
210
	n.Diagnostics = diag.NewDiagnostics(n.Identity, host)
Jeromy's avatar
Jeromy committed
211
	n.Ping = ping.NewPingService(host)
212 213

	// setup routing service
214
	r, err := routingOption(ctx, host, n.Repo.Datastore())
Jeromy's avatar
Jeromy committed
215
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
216
		return err
217
	}
Jeromy's avatar
Jeromy committed
218
	n.Routing = r
219

220 221 222
	// Wrap standard peer host with routing system to allow unknown peer lookups
	n.PeerHost = rhost.Wrap(host, n.Routing)

223 224
	// setup exchange service
	const alwaysSendToPeer = true // use YesManStrategy
225
	bitswapNetwork := bsnet.NewFromIpfsHost(n.PeerHost, n.Routing)
226 227 228 229
	n.Exchange = bitswap.New(ctx, n.Identity, bitswapNetwork, n.Blockstore, alwaysSendToPeer)

	// setup name system
	n.Namesys = namesys.NewNameSystem(n.Routing)
230

Jeromy's avatar
Jeromy committed
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
	// setup ipns republishing
	n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore)
	n.IpnsRepub.AddName(n.Identity)

	cfg, err := n.Repo.Config()
	if err != nil {
		return err
	}
	if cfg.Ipns.RepublishPeriod != "" {
		d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod)
		if err != nil {
			return fmt.Errorf("failure to parse config setting IPNS.RepublishPeriod: %s", err)
		}

		if d < time.Minute || d > (time.Hour*24) {
			return fmt.Errorf("config setting IPNS.RepublishPeriod is not between 1min and 1day: %s", d)
		}

		n.IpnsRepub.Interval = d
	}

	n.Process().Go(n.IpnsRepub.Run)

254 255 256
	return nil
}

257 258 259 260 261 262 263 264 265 266 267 268
// Process returns the Process object
func (n *IpfsNode) Process() goprocess.Process {
	return n.proc
}

// Close calls Close() on the Process object
func (n *IpfsNode) Close() error {
	return n.proc.Close()
}

// Context returns the IpfsNode context
func (n *IpfsNode) Context() context.Context {
269 270 271
	if n.ctx == nil {
		n.ctx = context.TODO()
	}
272 273 274
	return n.ctx
}

275 276
// teardown closes owned children. If any errors occur, this function returns
// the first error.
Brian Tiger Chow's avatar
Brian Tiger Chow committed
277
func (n *IpfsNode) teardown() error {
278
	log.Debug("core is shutting down...")
279 280
	// owned objects are closed in this teardown to ensure that they're closed
	// regardless of which constructor was used to add them to the node.
Jeromy's avatar
Jeromy committed
281 282 283
	closers := []io.Closer{
		n.Repo,
	}
284

285 286 287 288
	if n.Exchange != nil {
		closers = append(closers, n.Exchange)
	}

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
289 290 291 292 293 294 295
	if n.Mounts.Ipfs != nil {
		closers = append(closers, mount.Closer(n.Mounts.Ipfs))
	}
	if n.Mounts.Ipns != nil {
		closers = append(closers, mount.Closer(n.Mounts.Ipns))
	}

Jeromy's avatar
Jeromy committed
296 297
	// Filesystem needs to be closed before network, dht, and blockservice
	// so it can use them as its shutting down
298
	if n.IpnsFs != nil {
Jeromy's avatar
Jeromy committed
299 300 301
		closers = append(closers, n.IpnsFs)
	}

Jeromy's avatar
Jeromy committed
302 303 304 305
	if n.Blocks != nil {
		closers = append(closers, n.Blocks)
	}

Jeromy's avatar
Jeromy committed
306 307
	if n.Bootstrapper != nil {
		closers = append(closers, n.Bootstrapper)
308 309
	}

310
	if dht, ok := n.Routing.(*dht.IpfsDHT); ok {
311
		closers = append(closers, dht.Process())
Jeromy's avatar
Jeromy committed
312 313 314 315
	}

	if n.PeerHost != nil {
		closers = append(closers, n.PeerHost)
316
	}
317

318
	var errs []error
319
	for _, closer := range closers {
320 321
		if err := closer.Close(); err != nil {
			errs = append(errs, err)
322 323 324 325
		}
	}
	if len(errs) > 0 {
		return errs[0]
Brian Tiger Chow's avatar
Brian Tiger Chow committed
326 327
	}
	return nil
Brian Tiger Chow's avatar
Brian Tiger Chow committed
328 329
}

Brian Tiger Chow's avatar
Brian Tiger Chow committed
330
func (n *IpfsNode) OnlineMode() bool {
331 332 333 334 335 336
	switch n.mode {
	case onlineMode:
		return true
	default:
		return false
	}
Brian Tiger Chow's avatar
Brian Tiger Chow committed
337 338
}

339
func (n *IpfsNode) Bootstrap(cfg BootstrapConfig) error {
340 341

	// TODO what should return value be when in offlineMode?
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
342 343 344 345
	if n.Routing == nil {
		return nil
	}

346 347 348 349 350 351 352 353
	if n.Bootstrapper != nil {
		n.Bootstrapper.Close() // stop previous bootstrap process.
	}

	// if the caller did not specify a bootstrap peer function, get the
	// freshest bootstrap peers from config. this responds to live changes.
	if cfg.BootstrapPeers == nil {
		cfg.BootstrapPeers = func() []peer.PeerInfo {
354
			ps, err := n.loadBootstrapPeers()
355
			if err != nil {
356
				log.Warning("failed to parse bootstrap peers from config")
357 358 359 360 361 362 363 364 365
				return nil
			}
			return ps
		}
	}

	var err error
	n.Bootstrapper, err = Bootstrap(n, cfg)
	return err
366 367
}

368 369
func (n *IpfsNode) loadID() error {
	if n.Identity != "" {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
370
		return errors.New("identity already loaded")
371 372
	}

373 374 375 376 377 378
	cfg, err := n.Repo.Config()
	if err != nil {
		return err
	}

	cid := cfg.Identity.PeerID
379
	if cid == "" {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
380
		return errors.New("Identity was not set in config (was ipfs init run?)")
381 382
	}
	if len(cid) == 0 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
383
		return errors.New("No peer ID in config! (was ipfs init run?)")
384 385
	}

386 387 388
	n.Identity = peer.ID(b58.Decode(cid))
	return nil
}
389

Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
390
func (n *IpfsNode) LoadPrivateKey() error {
391
	if n.Identity == "" || n.Peerstore == nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
392
		return errors.New("loaded private key out of order.")
393 394
	}

395
	if n.PrivateKey != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
396
		return errors.New("private key already loaded")
397 398
	}

399 400 401 402 403 404
	cfg, err := n.Repo.Config()
	if err != nil {
		return err
	}

	sk, err := loadPrivateKey(&cfg.Identity, n.Identity)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
405
	if err != nil {
406
		return err
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
407
	}
408

409 410
	n.PrivateKey = sk
	n.Peerstore.AddPrivKey(n.Identity, n.PrivateKey)
Jeromy's avatar
Jeromy committed
411 412 413 414
	n.Peerstore.AddPubKey(n.Identity, sk.GetPublic())
	return nil
}

415
func (n *IpfsNode) loadBootstrapPeers() ([]peer.PeerInfo, error) {
416 417 418 419 420 421
	cfg, err := n.Repo.Config()
	if err != nil {
		return nil, err
	}

	parsed, err := cfg.BootstrapPeers()
422 423 424 425 426 427
	if err != nil {
		return nil, err
	}
	return toPeerInfos(parsed), nil
}

Jeromy's avatar
Jeromy committed
428 429 430
// SetupOfflineRouting loads the local nodes private key and
// uses it to instantiate a routing system in offline mode.
// This is primarily used for offline ipns modifications.
Jeromy's avatar
Jeromy committed
431
func (n *IpfsNode) SetupOfflineRouting() error {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
432
	err := n.LoadPrivateKey()
Jeromy's avatar
Jeromy committed
433 434 435 436 437
	if err != nil {
		return err
	}

	n.Routing = offroute.NewOfflineRouter(n.Repo.Datastore(), n.PrivateKey)
438 439 440

	n.Namesys = namesys.NewNameSystem(n.Routing)

441
	return nil
442 443 444 445
}

func loadPrivateKey(cfg *config.Identity, id peer.ID) (ic.PrivKey, error) {
	sk, err := cfg.DecodePrivateKey("passphrase todo!")
446 447 448
	if err != nil {
		return nil, err
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
449

450 451 452 453
	id2, err := peer.IDFromPrivateKey(sk)
	if err != nil {
		return nil, err
	}
454

455 456
	if id2 != id {
		return nil, fmt.Errorf("private key in config does not match id: %s != %s", id, id2)
457 458
	}

459
	return sk, nil
460
}
461

462
func listenAddresses(cfg *config.Config) ([]ma.Multiaddr, error) {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
463 464 465
	var listen []ma.Multiaddr
	for _, addr := range cfg.Addresses.Swarm {
		maddr, err := ma.NewMultiaddr(addr)
466
		if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
467
			return nil, fmt.Errorf("Failure to parse config.Addresses.Swarm: %s", cfg.Addresses.Swarm)
468
		}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
469
		listen = append(listen, maddr)
470 471 472 473
	}

	return listen, nil
}
474

Jeromy's avatar
Jeromy committed
475
type HostOption func(ctx context.Context, id peer.ID, ps peer.Peerstore, bwr metrics.Reporter, fs []*net.IPNet) (p2phost.Host, error)
Jeromy's avatar
Jeromy committed
476 477 478

var DefaultHostOption HostOption = constructPeerHost

479
// isolates the complex initialization steps
Jeromy's avatar
Jeromy committed
480
func constructPeerHost(ctx context.Context, id peer.ID, ps peer.Peerstore, bwr metrics.Reporter, fs []*net.IPNet) (p2phost.Host, error) {
481 482

	// no addresses to begin with. we'll start later.
Jeromy's avatar
Jeromy committed
483
	network, err := swarm.NewNetwork(ctx, nil, id, ps, bwr)
484
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
485
		return nil, err
486
	}
487

488 489 490 491
	for _, f := range fs {
		network.Swarm().Filters.AddDialFilter(f)
	}

Jeromy's avatar
Jeromy committed
492 493
	host := p2pbhost.New(network, p2pbhost.NATPortMap, bwr)

494 495 496 497 498 499 500
	return host, nil
}

// startListening on the network addresses
func startListening(ctx context.Context, host p2phost.Host, cfg *config.Config) error {
	listenAddrs, err := listenAddresses(cfg)
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
501
		return err
502 503
	}

504
	// make sure we error out if our config does not have addresses we can use
505
	log.Debugf("Config.Addresses.Swarm:%s", listenAddrs)
506
	filteredAddrs := addrutil.FilterUsableAddrs(listenAddrs)
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
507
	log.Debugf("Config.Addresses.Swarm:%s (filtered)", filteredAddrs)
508
	if len(filteredAddrs) < 1 {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
509
		return fmt.Errorf("addresses in config not usable: %s", listenAddrs)
510 511
	}

512 513 514
	// Actually start listening:
	if err := host.Network().Listen(filteredAddrs...); err != nil {
		return err
515 516
	}

517
	// list out our addresses
518
	addrs, err := host.Network().InterfaceListenAddresses()
519
	if err != nil {
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
520
		return err
521
	}
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
522
	log.Infof("Swarm listening at: %s", addrs)
523
	return nil
524
}
525

526 527
func constructDHTRouting(ctx context.Context, host p2phost.Host, dstore ds.ThreadSafeDatastore) (routing.IpfsRouting, error) {
	dhtRouting := dht.NewDHT(ctx, host, dstore)
528
	dhtRouting.Validator[IpnsValidatorTag] = namesys.IpnsRecordValidator
529
	dhtRouting.Selector[IpnsValidatorTag] = namesys.IpnsSelectorFunc
530 531
	return dhtRouting, nil
}
Jeromy's avatar
Jeromy committed
532

533
type RoutingOption func(context.Context, p2phost.Host, ds.ThreadSafeDatastore) (routing.IpfsRouting, error)
Jeromy's avatar
Jeromy committed
534

Jeromy's avatar
Jeromy committed
535 536
type DiscoveryOption func(p2phost.Host) (discovery.Service, error)

537
var DHTOption RoutingOption = constructDHTRouting
538
var NilRouterOption RoutingOption = nilrouting.ConstructNilRouting