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