swarm.go 13.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package swarm

import (
	"bytes"
21
	"context"
22 23
	"crypto/ecdsa"
	"fmt"
24
	"math/big"
25
	"net"
26
	"strings"
27
	"time"
28
	"unicode"
29 30 31 32 33 34

	"github.com/ethereum/go-ethereum/accounts/abi/bind"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/contracts/chequebook"
	"github.com/ethereum/go-ethereum/contracts/ens"
	"github.com/ethereum/go-ethereum/crypto"
35
	"github.com/ethereum/go-ethereum/ethclient"
36
	"github.com/ethereum/go-ethereum/log"
37 38 39
	"github.com/ethereum/go-ethereum/node"
	"github.com/ethereum/go-ethereum/p2p"
	"github.com/ethereum/go-ethereum/p2p/discover"
40
	"github.com/ethereum/go-ethereum/params"
41 42 43
	"github.com/ethereum/go-ethereum/rpc"
	"github.com/ethereum/go-ethereum/swarm/api"
	httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
44
	"github.com/ethereum/go-ethereum/swarm/fuse"
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
	"github.com/ethereum/go-ethereum/swarm/network"
	"github.com/ethereum/go-ethereum/swarm/storage"
)

// the swarm stack
type Swarm struct {
	config      *api.Config            // swarm configuration
	api         *api.Api               // high level api layer (fs/manifest)
	dns         api.Resolver           // DNS registrar
	dbAccess    *network.DbAccess      // access to local chunk db iterator and storage counter
	storage     storage.ChunkStore     // internal access to storage, common interface to cloud storage backends
	dpa         *storage.DPA           // distributed preimage archive, the local API to the storage with document level storage/retrieval support
	depo        network.StorageHandler // remote request handler, interface between bzz protocol and the storage
	cloud       storage.CloudStore     // procurement, cloud storage backend (can multi-cloud)
	hive        *network.Hive          // the logistic manager
	backend     chequebook.Backend     // simple blockchain Backend
	privateKey  *ecdsa.PrivateKey
62
	corsString  string
63
	swapEnabled bool
64
	lstore      *storage.LocalStore // local store, needs to store for releasing resources after node stopped
65
	sfs         *fuse.SwarmFS       // need this to cleanup all the active mounts on node exit
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
}

type SwarmAPI struct {
	Api     *api.Api
	Backend chequebook.Backend
	PrvKey  *ecdsa.PrivateKey
}

func (self *Swarm) API() *SwarmAPI {
	return &SwarmAPI{
		Api:     self.api,
		Backend: self.backend,
		PrvKey:  self.privateKey,
	}
}

// creates a new swarm service instance
// implements node.Service
84
func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.Config) (self *Swarm, err error) {
85 86 87 88 89 90 91 92 93
	if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) {
		return nil, fmt.Errorf("empty public key")
	}
	if bytes.Equal(common.FromHex(config.BzzKey), storage.ZeroKey) {
		return nil, fmt.Errorf("empty bzz key")
	}

	self = &Swarm{
		config:      config,
94
		swapEnabled: config.SwapEnabled,
95 96
		backend:     backend,
		privateKey:  config.Swap.PrivateKey(),
97
		corsString:  config.Cors,
98
	}
99
	log.Debug(fmt.Sprintf("Setting up Swarm service components"))
100 101

	hash := storage.MakeHashFunc(config.ChunkerParams.Hash)
102
	self.lstore, err = storage.NewLocalStore(hash, config.StoreParams)
103 104 105 106 107
	if err != nil {
		return
	}

	// setup local store
108
	log.Debug(fmt.Sprintf("Set up local storage"))
109

110
	self.dbAccess = network.NewDbAccess(self.lstore)
111
	log.Debug(fmt.Sprintf("Set up local db access (iterator/counter)"))
112 113 114 115 116

	// set up the kademlia hive
	self.hive = network.NewHive(
		common.HexToHash(self.config.BzzKey), // key to hive (kademlia base address)
		config.HiveParams,                    // configuration parameters
117 118
		config.SwapEnabled,                   // SWAP enabled
		config.SyncEnabled,                   // syncronisation enabled
119
	)
120
	log.Debug(fmt.Sprintf("Set up swarm network with Kademlia hive"))
121 122

	// setup cloud storage backend
E
Egon Elbre 已提交
123
	self.cloud = network.NewForwarder(self.hive)
124
	log.Debug(fmt.Sprintf("-> set swarm forwarder as cloud storage backend"))
125

E
Egon Elbre 已提交
126 127
	// setup cloud storage internal access layer
	self.storage = storage.NewNetStore(hash, self.lstore, self.cloud, config.StoreParams)
128
	log.Debug(fmt.Sprintf("-> swarm net store shared access layer to Swarm Chunk Store"))
129 130

	// set up Depo (storage handler = cloud storage access layer for incoming remote requests)
131
	self.depo = network.NewDepo(hash, self.lstore, self.storage)
132
	log.Debug(fmt.Sprintf("-> REmote Access to CHunks"))
133 134

	// set up DPA, the cloud storage local access layer
135
	dpaChunkStore := storage.NewDpaChunkStore(self.lstore, self.storage)
136
	log.Debug(fmt.Sprintf("-> Local Access to Swarm"))
137 138
	// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
	self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams)
139
	log.Debug(fmt.Sprintf("-> Content Store API"))
140

141 142 143 144 145
	if !config.EnsDisabled {
		if len(config.EnsAPIs) == 0 {
			// ENS is enabled and has no specific configuration,
			// use defaults
			self.dns, err = newEnsClient(node.DefaultIPCEndpoint("geth"), config.EnsRoot, config)
146 147 148
			if err != nil {
				return nil, err
			}
149 150 151 152 153 154 155 156 157 158 159
		} else {
			opts := []api.MultiResolverOption{}
			for _, c := range config.EnsAPIs {
				tld, endpoint, addr := parseEnsAPIAddress(c)
				r, err := newEnsClient(endpoint, addr, config)
				if err != nil {
					return nil, err
				}
				opts = append(opts, api.MultiResolverOptionWithResolver(r, tld))
			}
			self.dns = api.NewMultiResolver(opts...)
160
		}
161 162 163 164
	}

	self.api = api.NewApi(self.dpa, self.dns)
	// Manifests for Smart Hosting
165
	log.Debug(fmt.Sprintf("-> Web3 virtual server API"))
166

167
	self.sfs = fuse.NewSwarmFS(self.api)
168 169
	log.Debug("-> Initializing Fuse file system")

170 171 172
	return self, nil
}

173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
// parseEnsAPIAddress parses string according to format
// [tld:][contract-addr@]url and returns ENSClientConfig structure
// with endpoint, contract address and TLD.
func parseEnsAPIAddress(s string) (tld, endpoint string, addr common.Address) {
	isAllLetterString := func(s string) bool {
		for _, r := range s {
			if !unicode.IsLetter(r) {
				return false
			}
		}
		return true
	}
	endpoint = s
	if i := strings.Index(endpoint, ":"); i > 0 {
		if isAllLetterString(endpoint[:i]) && len(endpoint) > i+2 && endpoint[i+1:i+3] != "//" {
			tld = endpoint[:i]
			endpoint = endpoint[i+1:]
		}
	}
	if i := strings.Index(endpoint, "@"); i > 0 {
		addr = common.HexToAddress(endpoint[:i])
		endpoint = endpoint[i+1:]
	}
	return
197 198 199 200 201
}

// newEnsClient creates a new ENS client for that is a consumer of
// a ENS API on a specific endpoint. It is used as a helper function
// for creating multiple resolvers in NewSwarm function.
202
func newEnsClient(endpoint string, addr common.Address, config *api.Config) (*ens.ENS, error) {
203 204 205 206 207 208 209 210
	log.Info("connecting to ENS API", "url", endpoint)
	client, err := rpc.Dial(endpoint)
	if err != nil {
		return nil, fmt.Errorf("error connecting to ENS API %s: %s", endpoint, err)
	}
	ensClient := ethclient.NewClient(client)

	ensRoot := config.EnsRoot
211 212
	if addr != (common.Address{}) {
		ensRoot = addr
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
	} else {
		a, err := detectEnsAddr(client)
		if err == nil {
			ensRoot = a
		} else {
			log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", ensRoot), "err", err)
		}
	}
	transactOpts := bind.NewKeyedTransactor(config.Swap.PrivateKey())
	dns, err := ens.NewENS(transactOpts, ensRoot, ensClient)
	if err != nil {
		return nil, err
	}
	log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar %v @ address %v", endpoint, ensRoot.Hex()))
	return dns, err
}

// detectEnsAddr determines the ENS contract address by getting both the
// version and genesis hash using the client and matching them to either
// mainnet or testnet addresses
func detectEnsAddr(client *rpc.Client) (common.Address, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	var version string
	if err := client.CallContext(ctx, &version, "net_version"); err != nil {
		return common.Address{}, err
	}

	block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0))
	if err != nil {
		return common.Address{}, err
	}

	switch {

	case version == "1" && block.Hash() == params.MainnetGenesisHash:
		log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress)
		return ens.MainNetAddress, nil

	case version == "3" && block.Hash() == params.TestnetGenesisHash:
		log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress)
		return ens.TestNetAddress, nil

	default:
		return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash())
	}
}

262 263 264 265 266 267 268 269 270 271 272
/*
Start is called when the stack is started
* starts the network kademlia hive peer management
* (starts netStore level 0 api)
* starts DPA level 1 api (chunking -> store/retrieve requests)
* (starts level 2 api)
* starts http proxy server
* registers url scheme handlers for bzz, etc
* TODO: start subservices like sword, swear, swarmdns
*/
// implements the node.Service interface
273
func (self *Swarm) Start(srv *p2p.Server) error {
274 275 276 277 278
	connectPeer := func(url string) error {
		node, err := discover.ParseNode(url)
		if err != nil {
			return fmt.Errorf("invalid node URL: %v", err)
		}
279
		srv.AddPeer(node)
280 281 282 283 284 285 286 287 288
		return nil
	}
	// set chequebook
	if self.swapEnabled {
		ctx := context.Background() // The initial setup has no deadline.
		err := self.SetChequebook(ctx)
		if err != nil {
			return fmt.Errorf("Unable to set chequebook for SWAP: %v", err)
		}
289
		log.Debug(fmt.Sprintf("-> cheque book for SWAP: %v", self.config.Swap.Chequebook()))
290
	} else {
291
		log.Debug(fmt.Sprintf("SWAP disabled: no cheque book set"))
292 293
	}

294
	log.Warn(fmt.Sprintf("Starting Swarm service"))
295
	self.hive.Start(
296 297
		discover.PubkeyID(&srv.PrivateKey.PublicKey),
		func() string { return srv.ListenAddr },
298 299
		connectPeer,
	)
300
	log.Info(fmt.Sprintf("Swarm network started on bzz address: %v", self.hive.Addr()))
301 302

	self.dpa.Start()
303
	log.Debug(fmt.Sprintf("Swarm DPA started"))
304 305 306

	// start swarm http proxy server
	if self.config.Port != "" {
307
		addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port)
308 309 310 311
		go httpapi.StartHttpServer(self.api, &httpapi.ServerConfig{
			Addr:       addr,
			CorsString: self.corsString,
		})
312
		log.Info(fmt.Sprintf("Swarm http proxy started on %v", addr))
313

314 315 316
		if self.corsString != "" {
			log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.corsString))
		}
317 318
	}

319 320 321 322 323 324 325
	return nil
}

// implements the node.Service interface
// stops all component services.
func (self *Swarm) Stop() error {
	self.dpa.Stop()
H
holisticode 已提交
326
	err := self.hive.Stop()
327 328 329 330
	if ch := self.config.Swap.Chequebook(); ch != nil {
		ch.Stop()
		ch.Save()
	}
331 332 333 334

	if self.lstore != nil {
		self.lstore.DbStore.Close()
	}
335
	self.sfs.Stop()
H
holisticode 已提交
336
	return err
337 338 339 340
}

// implements the node.Service interface
func (self *Swarm) Protocols() []p2p.Protocol {
341
	proto, err := network.Bzz(self.depo, self.backend, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams, self.config.NetworkId)
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
	if err != nil {
		return nil
	}
	return []p2p.Protocol{proto}
}

// implements node.Service
// Apis returns the RPC Api descriptors the Swarm implementation offers
func (self *Swarm) APIs() []rpc.API {
	return []rpc.API{
		// public APIs
		{
			Namespace: "bzz",
			Version:   "0.1",
			Service:   &Info{self.config, chequebook.ContractParams},
			Public:    true,
		},
		// admin APIs
		{
			Namespace: "bzz",
			Version:   "0.1",
			Service:   api.NewControl(self.api, self.hive),
			Public:    false,
		},
		{
			Namespace: "chequebook",
			Version:   chequebook.Version,
			Service:   chequebook.NewApi(self.config.Swap.Chequebook),
			Public:    false,
		},
372 373
		{
			Namespace: "swarmfs",
374
			Version:   fuse.Swarmfs_Version,
375 376 377
			Service:   self.sfs,
			Public:    false,
		},
378 379 380 381 382 383 384 385 386 387 388 389 390 391
		// storage APIs
		// DEPRECATED: Use the HTTP API instead
		{
			Namespace: "bzz",
			Version:   "0.1",
			Service:   api.NewStorage(self.api),
			Public:    true,
		},
		{
			Namespace: "bzz",
			Version:   "0.1",
			Service:   api.NewFileSystem(self.api),
			Public:    false,
		},
392 393 394 395 396 397 398 399 400 401 402 403 404 405
		// {Namespace, Version, api.NewAdmin(self), false},
	}
}

func (self *Swarm) Api() *api.Api {
	return self.api
}

// SetChequebook ensures that the local checquebook is set up on chain.
func (self *Swarm) SetChequebook(ctx context.Context) error {
	err := self.config.Swap.SetChequebook(ctx, self.backend, self.config.Path)
	if err != nil {
		return err
	}
406
	log.Info(fmt.Sprintf("new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract.Hex()))
407 408 409 410 411 412 413 414 415 416 417 418
	self.hive.DropAll()
	return nil
}

// Local swarm without netStore
func NewLocalSwarm(datadir, port string) (self *Swarm, err error) {

	prvKey, err := crypto.GenerateKey()
	if err != nil {
		return
	}

H
holisticode 已提交
419 420 421
	config := api.NewDefaultConfig()
	config.Path = datadir
	config.Init(prvKey)
422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
	config.Port = port

	dpa, err := storage.NewLocalDPA(datadir)
	if err != nil {
		return
	}

	self = &Swarm{
		api:    api.NewApi(dpa, nil),
		config: config,
	}

	return
}

// serialisable info about swarm
type Info struct {
	*api.Config
	*chequebook.Params
}

func (self *Info) Info() *Info {
	return self
}