github.com/divan/go-ethereum@v1.8.14-0.20180820134928-1de9ada4016d/swarm/swarm.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package swarm 18 19 import ( 20 "bytes" 21 "context" 22 "crypto/ecdsa" 23 "fmt" 24 "io" 25 "math/big" 26 "net" 27 "path/filepath" 28 "strings" 29 "time" 30 "unicode" 31 32 "github.com/ethereum/go-ethereum/accounts/abi/bind" 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/contracts/chequebook" 35 "github.com/ethereum/go-ethereum/contracts/ens" 36 "github.com/ethereum/go-ethereum/ethclient" 37 "github.com/ethereum/go-ethereum/metrics" 38 "github.com/ethereum/go-ethereum/p2p" 39 "github.com/ethereum/go-ethereum/p2p/discover" 40 "github.com/ethereum/go-ethereum/p2p/protocols" 41 "github.com/ethereum/go-ethereum/params" 42 "github.com/ethereum/go-ethereum/rpc" 43 "github.com/ethereum/go-ethereum/swarm/api" 44 httpapi "github.com/ethereum/go-ethereum/swarm/api/http" 45 "github.com/ethereum/go-ethereum/swarm/fuse" 46 "github.com/ethereum/go-ethereum/swarm/log" 47 "github.com/ethereum/go-ethereum/swarm/network" 48 "github.com/ethereum/go-ethereum/swarm/network/stream" 49 "github.com/ethereum/go-ethereum/swarm/pss" 50 "github.com/ethereum/go-ethereum/swarm/state" 51 "github.com/ethereum/go-ethereum/swarm/storage" 52 "github.com/ethereum/go-ethereum/swarm/storage/mock" 53 "github.com/ethereum/go-ethereum/swarm/storage/mru" 54 "github.com/ethereum/go-ethereum/swarm/tracing" 55 ) 56 57 var ( 58 startTime time.Time 59 updateGaugesPeriod = 5 * time.Second 60 startCounter = metrics.NewRegisteredCounter("stack,start", nil) 61 stopCounter = metrics.NewRegisteredCounter("stack,stop", nil) 62 uptimeGauge = metrics.NewRegisteredGauge("stack.uptime", nil) 63 requestsCacheGauge = metrics.NewRegisteredGauge("storage.cache.requests.size", nil) 64 ) 65 66 // the swarm stack 67 type Swarm struct { 68 config *api.Config // swarm configuration 69 api *api.API // high level api layer (fs/manifest) 70 dns api.Resolver // DNS registrar 71 fileStore *storage.FileStore // distributed preimage archive, the local API to the storage with document level storage/retrieval support 72 streamer *stream.Registry 73 bzz *network.Bzz // the logistic manager 74 backend chequebook.Backend // simple blockchain Backend 75 privateKey *ecdsa.PrivateKey 76 corsString string 77 swapEnabled bool 78 lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped 79 sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit 80 ps *pss.Pss 81 82 tracerClose io.Closer 83 } 84 85 type SwarmAPI struct { 86 Api *api.API 87 Backend chequebook.Backend 88 } 89 90 func (self *Swarm) API() *SwarmAPI { 91 return &SwarmAPI{ 92 Api: self.api, 93 Backend: self.backend, 94 } 95 } 96 97 // creates a new swarm service instance 98 // implements node.Service 99 // If mockStore is not nil, it will be used as the storage for chunk data. 100 // MockStore should be used only for testing. 101 func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err error) { 102 103 if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroAddr) { 104 return nil, fmt.Errorf("empty public key") 105 } 106 if bytes.Equal(common.FromHex(config.BzzKey), storage.ZeroAddr) { 107 return nil, fmt.Errorf("empty bzz key") 108 } 109 110 var backend chequebook.Backend 111 if config.SwapAPI != "" && config.SwapEnabled { 112 log.Info("connecting to SWAP API", "url", config.SwapAPI) 113 backend, err = ethclient.Dial(config.SwapAPI) 114 if err != nil { 115 return nil, fmt.Errorf("error connecting to SWAP API %s: %s", config.SwapAPI, err) 116 } 117 } 118 119 self = &Swarm{ 120 config: config, 121 backend: backend, 122 privateKey: config.ShiftPrivateKey(), 123 } 124 log.Debug(fmt.Sprintf("Setting up Swarm service components")) 125 126 config.HiveParams.Discovery = true 127 128 log.Debug(fmt.Sprintf("-> swarm net store shared access layer to Swarm Chunk Store")) 129 130 nodeID, err := discover.HexID(config.NodeID) 131 if err != nil { 132 return nil, err 133 } 134 addr := &network.BzzAddr{ 135 OAddr: common.FromHex(config.BzzKey), 136 UAddr: []byte(discover.NewNode(nodeID, net.IP{127, 0, 0, 1}, 30303, 30303).String()), 137 } 138 139 bzzconfig := &network.BzzConfig{ 140 NetworkID: config.NetworkID, 141 OverlayAddr: addr.OAddr, 142 UnderlayAddr: addr.UAddr, 143 HiveParams: config.HiveParams, 144 LightNode: config.LightNodeEnabled, 145 } 146 147 stateStore, err := state.NewDBStore(filepath.Join(config.Path, "state-store.db")) 148 if err != nil { 149 return 150 } 151 152 // set up high level api 153 var resolver *api.MultiResolver 154 if len(config.EnsAPIs) > 0 { 155 opts := []api.MultiResolverOption{} 156 for _, c := range config.EnsAPIs { 157 tld, endpoint, addr := parseEnsAPIAddress(c) 158 r, err := newEnsClient(endpoint, addr, config, self.privateKey) 159 if err != nil { 160 return nil, err 161 } 162 opts = append(opts, api.MultiResolverOptionWithResolver(r, tld)) 163 164 } 165 resolver = api.NewMultiResolver(opts...) 166 self.dns = resolver 167 } 168 169 self.lstore, err = storage.NewLocalStore(config.LocalStoreParams, mockStore) 170 if err != nil { 171 return 172 } 173 174 db := storage.NewDBAPI(self.lstore) 175 to := network.NewKademlia( 176 common.FromHex(config.BzzKey), 177 network.NewKadParams(), 178 ) 179 delivery := stream.NewDelivery(to, db) 180 181 self.streamer = stream.NewRegistry(addr, delivery, db, stateStore, &stream.RegistryOptions{ 182 SkipCheck: config.DeliverySkipCheck, 183 DoSync: config.SyncEnabled, 184 DoRetrieve: true, 185 SyncUpdateDelay: config.SyncUpdateDelay, 186 }) 187 188 // set up NetStore, the cloud storage local access layer 189 netStore := storage.NewNetStore(self.lstore, self.streamer.Retrieve) 190 // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage 191 self.fileStore = storage.NewFileStore(netStore, self.config.FileStoreParams) 192 193 var resourceHandler *mru.Handler 194 rhparams := &mru.HandlerParams{} 195 196 resourceHandler = mru.NewHandler(rhparams) 197 resourceHandler.SetStore(netStore) 198 199 self.lstore.Validators = []storage.ChunkValidator{ 200 storage.NewContentAddressValidator(storage.MakeHashFunc(storage.DefaultHash)), 201 resourceHandler, 202 } 203 204 // setup local store 205 log.Debug(fmt.Sprintf("Set up local storage")) 206 207 self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run) 208 209 // Pss = postal service over swarm (devp2p over bzz) 210 self.ps, err = pss.NewPss(to, config.Pss) 211 if err != nil { 212 return nil, err 213 } 214 if pss.IsActiveHandshake { 215 pss.SetHandshakeController(self.ps, pss.NewHandshakeParams()) 216 } 217 218 self.api = api.NewAPI(self.fileStore, self.dns, resourceHandler, self.privateKey) 219 // Manifests for Smart Hosting 220 log.Debug(fmt.Sprintf("-> Web3 virtual server API")) 221 222 self.sfs = fuse.NewSwarmFS(self.api) 223 log.Debug("-> Initializing Fuse file system") 224 225 return self, nil 226 } 227 228 // parseEnsAPIAddress parses string according to format 229 // [tld:][contract-addr@]url and returns ENSClientConfig structure 230 // with endpoint, contract address and TLD. 231 func parseEnsAPIAddress(s string) (tld, endpoint string, addr common.Address) { 232 isAllLetterString := func(s string) bool { 233 for _, r := range s { 234 if !unicode.IsLetter(r) { 235 return false 236 } 237 } 238 return true 239 } 240 endpoint = s 241 if i := strings.Index(endpoint, ":"); i > 0 { 242 if isAllLetterString(endpoint[:i]) && len(endpoint) > i+2 && endpoint[i+1:i+3] != "//" { 243 tld = endpoint[:i] 244 endpoint = endpoint[i+1:] 245 } 246 } 247 if i := strings.Index(endpoint, "@"); i > 0 { 248 addr = common.HexToAddress(endpoint[:i]) 249 endpoint = endpoint[i+1:] 250 } 251 return 252 } 253 254 // ensClient provides functionality for api.ResolveValidator 255 type ensClient struct { 256 *ens.ENS 257 *ethclient.Client 258 } 259 260 // newEnsClient creates a new ENS client for that is a consumer of 261 // a ENS API on a specific endpoint. It is used as a helper function 262 // for creating multiple resolvers in NewSwarm function. 263 func newEnsClient(endpoint string, addr common.Address, config *api.Config, privkey *ecdsa.PrivateKey) (*ensClient, error) { 264 log.Info("connecting to ENS API", "url", endpoint) 265 client, err := rpc.Dial(endpoint) 266 if err != nil { 267 return nil, fmt.Errorf("error connecting to ENS API %s: %s", endpoint, err) 268 } 269 ethClient := ethclient.NewClient(client) 270 271 ensRoot := config.EnsRoot 272 if addr != (common.Address{}) { 273 ensRoot = addr 274 } else { 275 a, err := detectEnsAddr(client) 276 if err == nil { 277 ensRoot = a 278 } else { 279 log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", ensRoot), "err", err) 280 } 281 } 282 transactOpts := bind.NewKeyedTransactor(privkey) 283 dns, err := ens.NewENS(transactOpts, ensRoot, ethClient) 284 if err != nil { 285 return nil, err 286 } 287 log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar %v @ address %v", endpoint, ensRoot.Hex())) 288 return &ensClient{ 289 ENS: dns, 290 Client: ethClient, 291 }, err 292 } 293 294 // detectEnsAddr determines the ENS contract address by getting both the 295 // version and genesis hash using the client and matching them to either 296 // mainnet or testnet addresses 297 func detectEnsAddr(client *rpc.Client) (common.Address, error) { 298 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 299 defer cancel() 300 301 var version string 302 if err := client.CallContext(ctx, &version, "net_version"); err != nil { 303 return common.Address{}, err 304 } 305 306 block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0)) 307 if err != nil { 308 return common.Address{}, err 309 } 310 311 switch { 312 313 case version == "1" && block.Hash() == params.MainnetGenesisHash: 314 log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress) 315 return ens.MainNetAddress, nil 316 317 case version == "3" && block.Hash() == params.TestnetGenesisHash: 318 log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress) 319 return ens.TestNetAddress, nil 320 321 default: 322 return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash()) 323 } 324 } 325 326 /* 327 Start is called when the stack is started 328 * starts the network kademlia hive peer management 329 * (starts netStore level 0 api) 330 * starts DPA level 1 api (chunking -> store/retrieve requests) 331 * (starts level 2 api) 332 * starts http proxy server 333 * registers url scheme handlers for bzz, etc 334 * TODO: start subservices like sword, swear, swarmdns 335 */ 336 // implements the node.Service interface 337 func (self *Swarm) Start(srv *p2p.Server) error { 338 startTime = time.Now() 339 340 self.tracerClose = tracing.Closer 341 342 // update uaddr to correct enode 343 newaddr := self.bzz.UpdateLocalAddr([]byte(srv.Self().String())) 344 log.Warn("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr)) 345 // set chequebook 346 if self.config.SwapEnabled { 347 ctx := context.Background() // The initial setup has no deadline. 348 err := self.SetChequebook(ctx) 349 if err != nil { 350 return fmt.Errorf("Unable to set chequebook for SWAP: %v", err) 351 } 352 log.Debug(fmt.Sprintf("-> cheque book for SWAP: %v", self.config.Swap.Chequebook())) 353 } else { 354 log.Debug(fmt.Sprintf("SWAP disabled: no cheque book set")) 355 } 356 357 log.Warn(fmt.Sprintf("Starting Swarm service")) 358 359 err := self.bzz.Start(srv) 360 if err != nil { 361 log.Error("bzz failed", "err", err) 362 return err 363 } 364 log.Info(fmt.Sprintf("Swarm network started on bzz address: %x", self.bzz.Hive.Overlay.BaseAddr())) 365 366 if self.ps != nil { 367 self.ps.Start(srv) 368 log.Info("Pss started") 369 } 370 371 // start swarm http proxy server 372 if self.config.Port != "" { 373 addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port) 374 server := httpapi.NewServer(self.api, self.config.Cors) 375 376 go server.ListenAndServe(addr) 377 } 378 379 log.Debug(fmt.Sprintf("Swarm http proxy started on port: %v", self.config.Port)) 380 381 if self.config.Cors != "" { 382 log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.config.Cors)) 383 } 384 385 self.periodicallyUpdateGauges() 386 387 startCounter.Inc(1) 388 self.streamer.Start(srv) 389 return nil 390 } 391 392 func (self *Swarm) periodicallyUpdateGauges() { 393 ticker := time.NewTicker(updateGaugesPeriod) 394 395 go func() { 396 for range ticker.C { 397 self.updateGauges() 398 } 399 }() 400 } 401 402 func (self *Swarm) updateGauges() { 403 uptimeGauge.Update(time.Since(startTime).Nanoseconds()) 404 requestsCacheGauge.Update(int64(self.lstore.RequestsCacheLen())) 405 } 406 407 // implements the node.Service interface 408 // stops all component services. 409 func (self *Swarm) Stop() error { 410 if self.tracerClose != nil { 411 err := self.tracerClose.Close() 412 if err != nil { 413 return err 414 } 415 } 416 417 if self.ps != nil { 418 self.ps.Stop() 419 } 420 if ch := self.config.Swap.Chequebook(); ch != nil { 421 ch.Stop() 422 ch.Save() 423 } 424 425 if self.lstore != nil { 426 self.lstore.DbStore.Close() 427 } 428 self.sfs.Stop() 429 stopCounter.Inc(1) 430 self.streamer.Stop() 431 return self.bzz.Stop() 432 } 433 434 // implements the node.Service interface 435 func (self *Swarm) Protocols() (protos []p2p.Protocol) { 436 protos = append(protos, self.bzz.Protocols()...) 437 438 if self.ps != nil { 439 protos = append(protos, self.ps.Protocols()...) 440 } 441 return 442 } 443 444 func (self *Swarm) RegisterPssProtocol(spec *protocols.Spec, targetprotocol *p2p.Protocol, options *pss.ProtocolParams) (*pss.Protocol, error) { 445 if !pss.IsActiveProtocol { 446 return nil, fmt.Errorf("Pss protocols not available (built with !nopssprotocol tag)") 447 } 448 topic := pss.ProtocolTopic(spec) 449 return pss.RegisterProtocol(self.ps, &topic, spec, targetprotocol, options) 450 } 451 452 // implements node.Service 453 // APIs returns the RPC API descriptors the Swarm implementation offers 454 func (self *Swarm) APIs() []rpc.API { 455 456 apis := []rpc.API{ 457 // public APIs 458 { 459 Namespace: "bzz", 460 Version: "3.0", 461 Service: &Info{self.config, chequebook.ContractParams}, 462 Public: true, 463 }, 464 // admin APIs 465 { 466 Namespace: "bzz", 467 Version: "3.0", 468 Service: api.NewControl(self.api, self.bzz.Hive), 469 Public: false, 470 }, 471 { 472 Namespace: "chequebook", 473 Version: chequebook.Version, 474 Service: chequebook.NewApi(self.config.Swap.Chequebook), 475 Public: false, 476 }, 477 { 478 Namespace: "swarmfs", 479 Version: fuse.Swarmfs_Version, 480 Service: self.sfs, 481 Public: false, 482 }, 483 // storage APIs 484 // DEPRECATED: Use the HTTP API instead 485 { 486 Namespace: "bzz", 487 Version: "0.1", 488 Service: api.NewStorage(self.api), 489 Public: true, 490 }, 491 { 492 Namespace: "bzz", 493 Version: "0.1", 494 Service: api.NewFileSystem(self.api), 495 Public: false, 496 }, 497 // {Namespace, Version, api.NewAdmin(self), false}, 498 } 499 500 apis = append(apis, self.bzz.APIs()...) 501 502 if self.ps != nil { 503 apis = append(apis, self.ps.APIs()...) 504 } 505 506 return apis 507 } 508 509 func (self *Swarm) Api() *api.API { 510 return self.api 511 } 512 513 // SetChequebook ensures that the local checquebook is set up on chain. 514 func (self *Swarm) SetChequebook(ctx context.Context) error { 515 err := self.config.Swap.SetChequebook(ctx, self.backend, self.config.Path) 516 if err != nil { 517 return err 518 } 519 log.Info(fmt.Sprintf("new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract.Hex())) 520 return nil 521 } 522 523 // serialisable info about swarm 524 type Info struct { 525 *api.Config 526 *chequebook.Params 527 } 528 529 func (self *Info) Info() *Info { 530 return self 531 }