github.com/anthdm/go-ethereum@v1.8.4-0.20180412101906-60516c83b011/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 "math/big" 25 "net" 26 "strings" 27 "time" 28 "unicode" 29 30 "github.com/ethereum/go-ethereum/accounts/abi/bind" 31 "github.com/ethereum/go-ethereum/common" 32 "github.com/ethereum/go-ethereum/contracts/chequebook" 33 "github.com/ethereum/go-ethereum/contracts/ens" 34 "github.com/ethereum/go-ethereum/crypto" 35 "github.com/ethereum/go-ethereum/ethclient" 36 "github.com/ethereum/go-ethereum/log" 37 "github.com/ethereum/go-ethereum/metrics" 38 "github.com/ethereum/go-ethereum/node" 39 "github.com/ethereum/go-ethereum/p2p" 40 "github.com/ethereum/go-ethereum/p2p/discover" 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/network" 47 "github.com/ethereum/go-ethereum/swarm/storage" 48 ) 49 50 var ( 51 startTime time.Time 52 updateGaugesPeriod = 5 * time.Second 53 startCounter = metrics.NewRegisteredCounter("stack,start", nil) 54 stopCounter = metrics.NewRegisteredCounter("stack,stop", nil) 55 uptimeGauge = metrics.NewRegisteredGauge("stack.uptime", nil) 56 dbSizeGauge = metrics.NewRegisteredGauge("storage.db.chunks.size", nil) 57 cacheSizeGauge = metrics.NewRegisteredGauge("storage.db.cache.size", nil) 58 ) 59 60 // the swarm stack 61 type Swarm struct { 62 config *api.Config // swarm configuration 63 api *api.Api // high level api layer (fs/manifest) 64 dns api.Resolver // DNS registrar 65 dbAccess *network.DbAccess // access to local chunk db iterator and storage counter 66 storage storage.ChunkStore // internal access to storage, common interface to cloud storage backends 67 dpa *storage.DPA // distributed preimage archive, the local API to the storage with document level storage/retrieval support 68 depo network.StorageHandler // remote request handler, interface between bzz protocol and the storage 69 cloud storage.CloudStore // procurement, cloud storage backend (can multi-cloud) 70 hive *network.Hive // the logistic manager 71 backend chequebook.Backend // simple blockchain Backend 72 privateKey *ecdsa.PrivateKey 73 corsString string 74 swapEnabled bool 75 lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped 76 sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit 77 } 78 79 type SwarmAPI struct { 80 Api *api.Api 81 Backend chequebook.Backend 82 PrvKey *ecdsa.PrivateKey 83 } 84 85 func (self *Swarm) API() *SwarmAPI { 86 return &SwarmAPI{ 87 Api: self.api, 88 Backend: self.backend, 89 PrvKey: self.privateKey, 90 } 91 } 92 93 // creates a new swarm service instance 94 // implements node.Service 95 func NewSwarm(ctx *node.ServiceContext, backend chequebook.Backend, config *api.Config) (self *Swarm, err error) { 96 if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroKey) { 97 return nil, fmt.Errorf("empty public key") 98 } 99 if bytes.Equal(common.FromHex(config.BzzKey), storage.ZeroKey) { 100 return nil, fmt.Errorf("empty bzz key") 101 } 102 103 self = &Swarm{ 104 config: config, 105 swapEnabled: config.SwapEnabled, 106 backend: backend, 107 privateKey: config.Swap.PrivateKey(), 108 corsString: config.Cors, 109 } 110 log.Debug(fmt.Sprintf("Setting up Swarm service components")) 111 112 hash := storage.MakeHashFunc(config.ChunkerParams.Hash) 113 self.lstore, err = storage.NewLocalStore(hash, config.StoreParams) 114 if err != nil { 115 return 116 } 117 118 // setup local store 119 log.Debug(fmt.Sprintf("Set up local storage")) 120 121 self.dbAccess = network.NewDbAccess(self.lstore) 122 log.Debug(fmt.Sprintf("Set up local db access (iterator/counter)")) 123 124 // set up the kademlia hive 125 self.hive = network.NewHive( 126 common.HexToHash(self.config.BzzKey), // key to hive (kademlia base address) 127 config.HiveParams, // configuration parameters 128 config.SwapEnabled, // SWAP enabled 129 config.SyncEnabled, // syncronisation enabled 130 ) 131 log.Debug(fmt.Sprintf("Set up swarm network with Kademlia hive")) 132 133 // setup cloud storage backend 134 self.cloud = network.NewForwarder(self.hive) 135 log.Debug(fmt.Sprintf("-> set swarm forwarder as cloud storage backend")) 136 137 // setup cloud storage internal access layer 138 self.storage = storage.NewNetStore(hash, self.lstore, self.cloud, config.StoreParams) 139 log.Debug(fmt.Sprintf("-> swarm net store shared access layer to Swarm Chunk Store")) 140 141 // set up Depo (storage handler = cloud storage access layer for incoming remote requests) 142 self.depo = network.NewDepo(hash, self.lstore, self.storage) 143 log.Debug(fmt.Sprintf("-> REmote Access to CHunks")) 144 145 // set up DPA, the cloud storage local access layer 146 dpaChunkStore := storage.NewDpaChunkStore(self.lstore, self.storage) 147 log.Debug(fmt.Sprintf("-> Local Access to Swarm")) 148 // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage 149 self.dpa = storage.NewDPA(dpaChunkStore, self.config.ChunkerParams) 150 log.Debug(fmt.Sprintf("-> Content Store API")) 151 152 if len(config.EnsAPIs) > 0 { 153 opts := []api.MultiResolverOption{} 154 for _, c := range config.EnsAPIs { 155 tld, endpoint, addr := parseEnsAPIAddress(c) 156 r, err := newEnsClient(endpoint, addr, config) 157 if err != nil { 158 return nil, err 159 } 160 opts = append(opts, api.MultiResolverOptionWithResolver(r, tld)) 161 } 162 self.dns = api.NewMultiResolver(opts...) 163 } 164 165 self.api = api.NewApi(self.dpa, self.dns) 166 // Manifests for Smart Hosting 167 log.Debug(fmt.Sprintf("-> Web3 virtual server API")) 168 169 self.sfs = fuse.NewSwarmFS(self.api) 170 log.Debug("-> Initializing Fuse file system") 171 172 return self, nil 173 } 174 175 // parseEnsAPIAddress parses string according to format 176 // [tld:][contract-addr@]url and returns ENSClientConfig structure 177 // with endpoint, contract address and TLD. 178 func parseEnsAPIAddress(s string) (tld, endpoint string, addr common.Address) { 179 isAllLetterString := func(s string) bool { 180 for _, r := range s { 181 if !unicode.IsLetter(r) { 182 return false 183 } 184 } 185 return true 186 } 187 endpoint = s 188 if i := strings.Index(endpoint, ":"); i > 0 { 189 if isAllLetterString(endpoint[:i]) && len(endpoint) > i+2 && endpoint[i+1:i+3] != "//" { 190 tld = endpoint[:i] 191 endpoint = endpoint[i+1:] 192 } 193 } 194 if i := strings.Index(endpoint, "@"); i > 0 { 195 addr = common.HexToAddress(endpoint[:i]) 196 endpoint = endpoint[i+1:] 197 } 198 return 199 } 200 201 // newEnsClient creates a new ENS client for that is a consumer of 202 // a ENS API on a specific endpoint. It is used as a helper function 203 // for creating multiple resolvers in NewSwarm function. 204 func newEnsClient(endpoint string, addr common.Address, config *api.Config) (*ens.ENS, error) { 205 log.Info("connecting to ENS API", "url", endpoint) 206 client, err := rpc.Dial(endpoint) 207 if err != nil { 208 return nil, fmt.Errorf("error connecting to ENS API %s: %s", endpoint, err) 209 } 210 ensClient := ethclient.NewClient(client) 211 212 ensRoot := config.EnsRoot 213 if addr != (common.Address{}) { 214 ensRoot = addr 215 } else { 216 a, err := detectEnsAddr(client) 217 if err == nil { 218 ensRoot = a 219 } else { 220 log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", ensRoot), "err", err) 221 } 222 } 223 transactOpts := bind.NewKeyedTransactor(config.Swap.PrivateKey()) 224 dns, err := ens.NewENS(transactOpts, ensRoot, ensClient) 225 if err != nil { 226 return nil, err 227 } 228 log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar %v @ address %v", endpoint, ensRoot.Hex())) 229 return dns, err 230 } 231 232 // detectEnsAddr determines the ENS contract address by getting both the 233 // version and genesis hash using the client and matching them to either 234 // mainnet or testnet addresses 235 func detectEnsAddr(client *rpc.Client) (common.Address, error) { 236 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 237 defer cancel() 238 239 var version string 240 if err := client.CallContext(ctx, &version, "net_version"); err != nil { 241 return common.Address{}, err 242 } 243 244 block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0)) 245 if err != nil { 246 return common.Address{}, err 247 } 248 249 switch { 250 251 case version == "1" && block.Hash() == params.MainnetGenesisHash: 252 log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress) 253 return ens.MainNetAddress, nil 254 255 case version == "3" && block.Hash() == params.TestnetGenesisHash: 256 log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress) 257 return ens.TestNetAddress, nil 258 259 default: 260 return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash()) 261 } 262 } 263 264 /* 265 Start is called when the stack is started 266 * starts the network kademlia hive peer management 267 * (starts netStore level 0 api) 268 * starts DPA level 1 api (chunking -> store/retrieve requests) 269 * (starts level 2 api) 270 * starts http proxy server 271 * registers url scheme handlers for bzz, etc 272 * TODO: start subservices like sword, swear, swarmdns 273 */ 274 // implements the node.Service interface 275 func (self *Swarm) Start(srv *p2p.Server) error { 276 startTime = time.Now() 277 connectPeer := func(url string) error { 278 node, err := discover.ParseNode(url) 279 if err != nil { 280 return fmt.Errorf("invalid node URL: %v", err) 281 } 282 srv.AddPeer(node) 283 return nil 284 } 285 // set chequebook 286 if self.swapEnabled { 287 ctx := context.Background() // The initial setup has no deadline. 288 err := self.SetChequebook(ctx) 289 if err != nil { 290 return fmt.Errorf("Unable to set chequebook for SWAP: %v", err) 291 } 292 log.Debug(fmt.Sprintf("-> cheque book for SWAP: %v", self.config.Swap.Chequebook())) 293 } else { 294 log.Debug(fmt.Sprintf("SWAP disabled: no cheque book set")) 295 } 296 297 log.Warn(fmt.Sprintf("Starting Swarm service")) 298 self.hive.Start( 299 discover.PubkeyID(&srv.PrivateKey.PublicKey), 300 func() string { return srv.ListenAddr }, 301 connectPeer, 302 ) 303 log.Info(fmt.Sprintf("Swarm network started on bzz address: %v", self.hive.Addr())) 304 305 self.dpa.Start() 306 log.Debug(fmt.Sprintf("Swarm DPA started")) 307 308 // start swarm http proxy server 309 if self.config.Port != "" { 310 addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port) 311 go httpapi.StartHttpServer(self.api, &httpapi.ServerConfig{ 312 Addr: addr, 313 CorsString: self.corsString, 314 }) 315 log.Info(fmt.Sprintf("Swarm http proxy started on %v", addr)) 316 317 if self.corsString != "" { 318 log.Debug(fmt.Sprintf("Swarm http proxy started with corsdomain: %v", self.corsString)) 319 } 320 } 321 322 self.periodicallyUpdateGauges() 323 324 startCounter.Inc(1) 325 return nil 326 } 327 328 func (self *Swarm) periodicallyUpdateGauges() { 329 ticker := time.NewTicker(updateGaugesPeriod) 330 331 go func() { 332 for range ticker.C { 333 self.updateGauges() 334 } 335 }() 336 } 337 338 func (self *Swarm) updateGauges() { 339 dbSizeGauge.Update(int64(self.lstore.DbCounter())) 340 cacheSizeGauge.Update(int64(self.lstore.CacheCounter())) 341 uptimeGauge.Update(time.Since(startTime).Nanoseconds()) 342 } 343 344 // implements the node.Service interface 345 // stops all component services. 346 func (self *Swarm) Stop() error { 347 self.dpa.Stop() 348 err := self.hive.Stop() 349 if ch := self.config.Swap.Chequebook(); ch != nil { 350 ch.Stop() 351 ch.Save() 352 } 353 354 if self.lstore != nil { 355 self.lstore.DbStore.Close() 356 } 357 self.sfs.Stop() 358 stopCounter.Inc(1) 359 return err 360 } 361 362 // implements the node.Service interface 363 func (self *Swarm) Protocols() []p2p.Protocol { 364 proto, err := network.Bzz(self.depo, self.backend, self.hive, self.dbAccess, self.config.Swap, self.config.SyncParams, self.config.NetworkId) 365 if err != nil { 366 return nil 367 } 368 return []p2p.Protocol{proto} 369 } 370 371 // implements node.Service 372 // Apis returns the RPC Api descriptors the Swarm implementation offers 373 func (self *Swarm) APIs() []rpc.API { 374 return []rpc.API{ 375 // public APIs 376 { 377 Namespace: "bzz", 378 Version: "0.1", 379 Service: &Info{self.config, chequebook.ContractParams}, 380 Public: true, 381 }, 382 // admin APIs 383 { 384 Namespace: "bzz", 385 Version: "0.1", 386 Service: api.NewControl(self.api, self.hive), 387 Public: false, 388 }, 389 { 390 Namespace: "chequebook", 391 Version: chequebook.Version, 392 Service: chequebook.NewApi(self.config.Swap.Chequebook), 393 Public: false, 394 }, 395 { 396 Namespace: "swarmfs", 397 Version: fuse.Swarmfs_Version, 398 Service: self.sfs, 399 Public: false, 400 }, 401 // storage APIs 402 // DEPRECATED: Use the HTTP API instead 403 { 404 Namespace: "bzz", 405 Version: "0.1", 406 Service: api.NewStorage(self.api), 407 Public: true, 408 }, 409 { 410 Namespace: "bzz", 411 Version: "0.1", 412 Service: api.NewFileSystem(self.api), 413 Public: false, 414 }, 415 // {Namespace, Version, api.NewAdmin(self), false}, 416 } 417 } 418 419 func (self *Swarm) Api() *api.Api { 420 return self.api 421 } 422 423 // SetChequebook ensures that the local checquebook is set up on chain. 424 func (self *Swarm) SetChequebook(ctx context.Context) error { 425 err := self.config.Swap.SetChequebook(ctx, self.backend, self.config.Path) 426 if err != nil { 427 return err 428 } 429 log.Info(fmt.Sprintf("new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract.Hex())) 430 self.hive.DropAll() 431 return nil 432 } 433 434 // Local swarm without netStore 435 func NewLocalSwarm(datadir, port string) (self *Swarm, err error) { 436 437 prvKey, err := crypto.GenerateKey() 438 if err != nil { 439 return 440 } 441 442 config := api.NewDefaultConfig() 443 config.Path = datadir 444 config.Init(prvKey) 445 config.Port = port 446 447 dpa, err := storage.NewLocalDPA(datadir) 448 if err != nil { 449 return 450 } 451 452 self = &Swarm{ 453 api: api.NewApi(dpa, nil), 454 config: config, 455 } 456 457 return 458 } 459 460 // serialisable info about swarm 461 type Info struct { 462 *api.Config 463 *chequebook.Params 464 } 465 466 func (self *Info) Info() *Info { 467 return self 468 }