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