builder.go 7.92 KB
Newer Older
Jeromy's avatar
Jeromy committed
1 2 3
package core

import (
Jeromy's avatar
Jeromy committed
4
	"context"
5 6
	"crypto/rand"
	"encoding/base64"
Jeromy's avatar
Jeromy committed
7
	"errors"
8 9 10
	"os"
	"syscall"
	"time"
Jeromy's avatar
Jeromy committed
11

12
	filestore "github.com/ipfs/go-ipfs/filestore"
13
	namesys "github.com/ipfs/go-ipfs/namesys"
14
	pin "github.com/ipfs/go-ipfs/pin"
15
	repo "github.com/ipfs/go-ipfs/repo"
16
	cidv0v1 "github.com/ipfs/go-ipfs/thirdparty/cidv0v1"
Jakub Sztandera's avatar
Jakub Sztandera committed
17
	"github.com/ipfs/go-ipfs/thirdparty/verifbs"
Steven Allen's avatar
Steven Allen committed
18

Steven Allen's avatar
Steven Allen committed
19
	ci "gx/ipfs/QmNiJiXwWE3kRhZrC5ej3kSjWHm337pYfhjLGSCDNKJP2s/go-libp2p-crypto"
Steven Allen's avatar
Steven Allen committed
20 21 22 23 24
	peer "gx/ipfs/QmPJxxDsX2UbchSHobbYuvz7qnyJTFKvaKMzE2rZWJ4x5B/go-libp2p-peer"
	resolver "gx/ipfs/QmQ3YSqfxunT5QBg6KBVskKyRE26q6hjSMyhpxchpm7jEN/go-path/resolver"
	pstore "gx/ipfs/QmQFFp4ntkd4C14sP3FaH9WJyBuetuGUVo6dShNHvnoEvC/go-libp2p-peerstore"
	pstoremem "gx/ipfs/QmQFFp4ntkd4C14sP3FaH9WJyBuetuGUVo6dShNHvnoEvC/go-libp2p-peerstore/pstoremem"
	offroute "gx/ipfs/QmRJvdmKJoDcQEhhTt5NYXJPQFnJYPo1kfapxtjZLfDDqH/go-ipfs-routing/offline"
25
	bstore "gx/ipfs/QmS2aqUZLJp8kF1ihE5rvDGE5LvmKDPnx32w9Z1BW9xLV5/go-ipfs-blockstore"
26
	goprocessctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context"
Steven Allen's avatar
Steven Allen committed
27 28 29 30
	libp2p "gx/ipfs/QmSgtf5vHyugoxcwMbyNy6bZ9qPDDTJSYEED2GkWjLwitZ/go-libp2p"
	cfg "gx/ipfs/QmTbcMKv6GU3fxhnNcbzYChdox9Fdd7VpucM3PQ7UWjX3D/go-ipfs-config"
	dag "gx/ipfs/QmUtsx89yiCY6F8mbpP6ecXckiSzCBH7EvkKZuZEHBcr1m/go-merkledag"
	ipns "gx/ipfs/QmVpC4PPSaoqZzWYEnQURnsQagimcWEzNKZouZyd7sNJdZ/go-ipns"
31
	offline "gx/ipfs/QmYZwey1thDTynSrvd6qQkX24UpTka6TFhQ2v569UpoqxD/go-ipfs-exchange-offline"
Steven Allen's avatar
Steven Allen committed
32
	bserv "gx/ipfs/QmbgbNxC1PMyS2gbx7nf2jKNG7bZAfYJJebdK4ptBBWCz1/go-blockservice"
Steven Allen's avatar
Steven Allen committed
33
	metrics "gx/ipfs/QmekzFM3hPZjTjUFGTABdQkEnQ3PTiMstY198PwSFr5w1Q/go-metrics-interface"
Steven Allen's avatar
Steven Allen committed
34
	uio "gx/ipfs/QmetDvVkKzbr8PYuBV6S48q5DU9EUQktYjo9KdkA3zbQgK/go-unixfs/io"
Steven Allen's avatar
Steven Allen committed
35
	record "gx/ipfs/QmexPd3srWxHC76gW2p5j5tQvwpPuCoW7b9vFhJ8BRPyh9/go-libp2p-record"
36 37 38
	ds "gx/ipfs/Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM/go-datastore"
	retry "gx/ipfs/Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM/go-datastore/retrystore"
	dsync "gx/ipfs/Qmf4xQhNomPNhrtZc67qSnfJSjxjXs9LWvknJtSXwimPrM/go-datastore/sync"
Steven Allen's avatar
Steven Allen committed
39
	p2phost "gx/ipfs/QmfRHxh8bt4jWLKRhNvR5fn7mFACrQBFLqV4wyoymEExKV/go-libp2p-host"
Łukasz Magiera's avatar
Łukasz Magiera committed
40
)
Jeromy's avatar
Jeromy committed
41

42 43 44
type BuildCfg struct {
	// If online is set, the node will have networking enabled
	Online bool
Jeromy's avatar
Jeromy committed
45

Jeromy's avatar
Jeromy committed
46 47 48
	// ExtraOpts is a map of extra options used to configure the ipfs nodes creation
	ExtraOpts map[string]bool

49
	// If permanent then node should run more expensive processes
50
	// that will improve performance in long run
51
	Permanent bool
52

Steven Allen's avatar
Steven Allen committed
53 54 55 56
	// DisableEncryptedConnections disables connection encryption *entirely*.
	// DO NOT SET THIS UNLESS YOU'RE TESTING.
	DisableEncryptedConnections bool

57 58 59 60 61 62
	// If NilRepo is set, a repo backed by a nil datastore will be constructed
	NilRepo bool

	Routing RoutingOption
	Host    HostOption
	Repo    repo.Repo
Jeromy's avatar
Jeromy committed
63 64
}

Jeromy's avatar
Jeromy committed
65 66 67 68 69 70 71 72
func (cfg *BuildCfg) getOpt(key string) bool {
	if cfg.ExtraOpts == nil {
		return false
	}

	return cfg.ExtraOpts[key]
}

73 74 75
func (cfg *BuildCfg) fillDefaults() error {
	if cfg.Repo != nil && cfg.NilRepo {
		return errors.New("cannot set a repo and specify nilrepo at the same time")
Jeromy's avatar
Jeromy committed
76
	}
77 78 79 80

	if cfg.Repo == nil {
		var d ds.Datastore
		d = ds.NewMapDatastore()
81

82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
		if cfg.NilRepo {
			d = ds.NewNullDatastore()
		}
		r, err := defaultRepo(dsync.MutexWrap(d))
		if err != nil {
			return err
		}
		cfg.Repo = r
	}

	if cfg.Routing == nil {
		cfg.Routing = DHTOption
	}

	if cfg.Host == nil {
		cfg.Host = DefaultHostOption
	}

	return nil
Jeromy's avatar
Jeromy committed
101 102
}

103
func defaultRepo(dstore repo.Datastore) (repo.Repo, error) {
104 105 106 107 108 109
	c := cfg.Config{}
	priv, pub, err := ci.GenerateKeyPairWithReader(ci.RSA, 1024, rand.Reader)
	if err != nil {
		return nil, err
	}

Jeromy's avatar
Jeromy committed
110
	pid, err := peer.IDFromPublicKey(pub)
111 112 113 114 115 116 117 118 119 120 121
	if err != nil {
		return nil, err
	}

	privkeyb, err := priv.Bytes()
	if err != nil {
		return nil, err
	}

	c.Bootstrap = cfg.DefaultBootstrapAddresses
	c.Addresses.Swarm = []string{"/ip4/0.0.0.0/tcp/4001"}
Jeromy's avatar
Jeromy committed
122
	c.Identity.PeerID = pid.Pretty()
123 124
	c.Identity.PrivKey = base64.StdEncoding.EncodeToString(privkeyb)

Jeromy's avatar
Jeromy committed
125
	return &repo.Mock{
Jeromy's avatar
Jeromy committed
126
		D: dstore,
127 128
		C: c,
	}, nil
Jeromy's avatar
Jeromy committed
129 130
}

131
// NewNode constructs and returns an IpfsNode using the given cfg.
132 133 134 135
func NewNode(ctx context.Context, cfg *BuildCfg) (*IpfsNode, error) {
	if cfg == nil {
		cfg = new(BuildCfg)
	}
Jeromy's avatar
Jeromy committed
136

137 138 139 140
	err := cfg.fillDefaults()
	if err != nil {
		return nil, err
	}
Steven Allen's avatar
Steven Allen committed
141

Jakub Sztandera's avatar
Jakub Sztandera committed
142
	ctx = metrics.CtxScope(ctx, "ipfs")
Jeromy's avatar
Jeromy committed
143

144 145 146 147
	n := &IpfsNode{
		mode:      offlineMode,
		Repo:      cfg.Repo,
		ctx:       ctx,
Steven Allen's avatar
Steven Allen committed
148
		Peerstore: pstoremem.NewPeerstore(),
149
	}
150 151 152 153 154 155

	n.RecordValidator = record.NamespacedValidator{
		"pk":   record.PublicKeyValidator{},
		"ipns": ipns.Validator{KeyBook: n.Peerstore},
	}

156 157 158
	if cfg.Online {
		n.mode = onlineMode
	}
Jeromy's avatar
Jeromy committed
159

160 161
	// TODO: this is a weird circular-ish dependency, rework it
	n.proc = goprocessctx.WithContextAndTeardown(ctx, n.teardown)
Jeromy's avatar
Jeromy committed
162

163 164 165 166
	if err := setupNode(ctx, n, cfg); err != nil {
		n.Close()
		return nil, err
	}
Jeromy's avatar
Jeromy committed
167

168
	return n, nil
Jeromy's avatar
Jeromy committed
169 170
}

171 172 173 174 175 176 177 178 179
func isTooManyFDError(err error) bool {
	perr, ok := err.(*os.PathError)
	if ok && perr.Err == syscall.EMFILE {
		return true
	}

	return false
}

180
func setupNode(ctx context.Context, n *IpfsNode, cfg *BuildCfg) error {
181
	// setup local identity
182 183
	if err := n.loadID(); err != nil {
		return err
Jeromy's avatar
Jeromy committed
184
	}
185

186 187 188 189 190
	// load the private key (if present)
	if err := n.loadPrivateKey(); err != nil {
		return err
	}

191 192 193 194 195 196 197
	rds := &retry.Datastore{
		Batching:    n.Repo.Datastore(),
		Delay:       time.Millisecond * 200,
		Retries:     6,
		TempErrFunc: isTooManyFDError,
	}

Jakub Sztandera's avatar
Jakub Sztandera committed
198
	// hash security
199
	bs := bstore.NewBlockstore(rds)
Łukasz Magiera's avatar
Łukasz Magiera committed
200
	bs = &verifbs.VerifBS{Blockstore: bs}
201

202
	opts := bstore.DefaultCacheOpts()
203 204 205 206 207
	conf, err := n.Repo.Config()
	if err != nil {
		return err
	}

Jeromy's avatar
Jeromy committed
208 209 210
	// TEMP: setting global sharding switch here
	uio.UseHAMTSharding = conf.Experimental.ShardingEnabled

211
	opts.HasBloomFilterSize = conf.Datastore.BloomFilterSize
212
	if !cfg.Permanent {
213 214 215
		opts.HasBloomFilterSize = 0
	}

216 217 218 219 220
	if !cfg.NilRepo {
		bs, err = bstore.CachedBlockstore(ctx, bs, opts)
		if err != nil {
			return err
		}
221 222
	}

223
	bs = bstore.NewIdStore(bs)
224

225
	bs = cidv0v1.NewBlockstore(bs)
226

227
	n.BaseBlocks = bs
228
	n.GCLocker = bstore.NewGCLocker()
229
	n.Blockstore = bstore.NewGCBlockstore(bs, n.GCLocker)
230

231
	if conf.Experimental.FilestoreEnabled || conf.Experimental.UrlstoreEnabled {
232
		// hash security
233
		n.Filestore = filestore.NewFilestore(bs, n.Repo.FileManager())
234
		n.Blockstore = bstore.NewGCBlockstore(n.Filestore, n.GCLocker)
Łukasz Magiera's avatar
Łukasz Magiera committed
235
		n.Blockstore = &verifbs.VerifBSGC{GCBlockstore: n.Blockstore}
236
	}
237

238 239 240 241 242 243
	rcfg, err := n.Repo.Config()
	if err != nil {
		return err
	}

	if rcfg.Datastore.HashOnRead {
244
		bs.HashOnRead(true)
245 246
	}

Steven Allen's avatar
Steven Allen committed
247 248 249 250 251 252 253 254 255 256
	hostOption := cfg.Host
	if cfg.DisableEncryptedConnections {
		innerHostOption := hostOption
		hostOption = func(ctx context.Context, id peer.ID, ps pstore.Peerstore, options ...libp2p.Option) (p2phost.Host, error) {
			return innerHostOption(ctx, id, ps, append(options, libp2p.NoSecurity)...)
		}
		log.Warningf(`Your IPFS node has been configured to run WITHOUT ENCRYPTED CONNECTIONS.
		You will not be able to connect to any nodes configured to use encrypted connections`)
	}

257
	if cfg.Online {
258
		do := setupDiscoveryOption(rcfg.Discovery)
Steven Allen's avatar
Steven Allen committed
259
		if err := n.startOnlineServices(ctx, cfg.Routing, hostOption, do, cfg.getOpt("pubsub"), cfg.getOpt("ipnsps"), cfg.getOpt("mplex")); err != nil {
260
			return err
261
		}
262 263
	} else {
		n.Exchange = offline.Exchange(n.Blockstore)
264 265
		n.Routing = offroute.NewOfflineRouter(n.Repo.Datastore(), n.RecordValidator)
		n.Namesys = namesys.NewNameSystem(n.Routing, n.Repo.Datastore(), 0)
Jeromy's avatar
Jeromy committed
266
	}
267 268 269

	n.Blocks = bserv.New(n.Blockstore, n.Exchange)
	n.DAG = dag.NewDAGService(n.Blocks)
270 271 272

	internalDag := dag.NewDAGService(bserv.New(n.Blockstore, offline.Exchange(n.Blockstore)))
	n.Pinning, err = pin.LoadPinner(n.Repo.Datastore(), n.DAG, internalDag)
273
	if err != nil {
Łukasz Magiera's avatar
Łukasz Magiera committed
274
		// TODO: we should move towards only running 'NewPinner' explicitly on
275 276 277
		// node init instead of implicitly here as a result of the pinner keys
		// not being found in the datastore.
		// this is kinda sketchy and could cause data loss
278
		n.Pinning = pin.NewPinner(n.Repo.Datastore(), n.DAG, internalDag)
279
	}
280
	n.Resolver = resolver.NewBasicResolver(n.DAG)
281

Łukasz Magiera's avatar
Łukasz Magiera committed
282 283 284 285 286 287
	if cfg.Online {
		if err := n.startLateOnlineServices(ctx); err != nil {
			return err
		}
	}

288
	return n.loadFilesRoot()
Jeromy's avatar
Jeromy committed
289
}