op-geth/swarm/swarm.go

530 lines
15 KiB
Go
Raw Normal View History

// 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"
"context"
"crypto/ecdsa"
"fmt"
"io"
"math/big"
"net"
2018-06-20 12:06:27 +00:00
"path/filepath"
"strings"
"time"
"unicode"
"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/ethclient"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
2018-06-20 12:06:27 +00:00
"github.com/ethereum/go-ethereum/p2p/protocols"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/swarm/api"
httpapi "github.com/ethereum/go-ethereum/swarm/api/http"
"github.com/ethereum/go-ethereum/swarm/fuse"
2018-06-20 12:06:27 +00:00
"github.com/ethereum/go-ethereum/swarm/log"
"github.com/ethereum/go-ethereum/swarm/network"
2018-06-20 12:06:27 +00:00
"github.com/ethereum/go-ethereum/swarm/network/stream"
"github.com/ethereum/go-ethereum/swarm/pss"
"github.com/ethereum/go-ethereum/swarm/state"
"github.com/ethereum/go-ethereum/swarm/storage"
2018-06-20 12:06:27 +00:00
"github.com/ethereum/go-ethereum/swarm/storage/mock"
"github.com/ethereum/go-ethereum/swarm/storage/mru"
"github.com/ethereum/go-ethereum/swarm/tracing"
)
var (
startTime time.Time
updateGaugesPeriod = 5 * time.Second
startCounter = metrics.NewRegisteredCounter("stack,start", nil)
stopCounter = metrics.NewRegisteredCounter("stack,stop", nil)
uptimeGauge = metrics.NewRegisteredGauge("stack.uptime", nil)
2018-06-20 12:06:27 +00:00
requestsCacheGauge = metrics.NewRegisteredGauge("storage.cache.requests.size", nil)
)
// the swarm stack
type Swarm struct {
2018-06-20 12:06:27 +00:00
config *api.Config // swarm configuration
api *api.API // high level api layer (fs/manifest)
dns api.Resolver // DNS registrar
fileStore *storage.FileStore // distributed preimage archive, the local API to the storage with document level storage/retrieval support
streamer *stream.Registry
bzz *network.Bzz // the logistic manager
backend chequebook.Backend // simple blockchain Backend
privateKey *ecdsa.PrivateKey
corsString string
swapEnabled bool
lstore *storage.LocalStore // local store, needs to store for releasing resources after node stopped
sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit
2018-06-20 12:06:27 +00:00
ps *pss.Pss
tracerClose io.Closer
}
type SwarmAPI struct {
2018-06-20 12:06:27 +00:00
Api *api.API
Backend chequebook.Backend
}
func (self *Swarm) API() *SwarmAPI {
return &SwarmAPI{
Api: self.api,
Backend: self.backend,
}
}
// creates a new swarm service instance
// implements node.Service
2018-06-20 12:06:27 +00:00
// If mockStore is not nil, it will be used as the storage for chunk data.
// MockStore should be used only for testing.
func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err error) {
if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroAddr) {
return nil, fmt.Errorf("empty public key")
}
2018-06-20 12:06:27 +00:00
if bytes.Equal(common.FromHex(config.BzzKey), storage.ZeroAddr) {
return nil, fmt.Errorf("empty bzz key")
}
2018-06-20 12:06:27 +00:00
var backend chequebook.Backend
if config.SwapAPI != "" && config.SwapEnabled {
log.Info("connecting to SWAP API", "url", config.SwapAPI)
backend, err = ethclient.Dial(config.SwapAPI)
if err != nil {
return nil, fmt.Errorf("error connecting to SWAP API %s: %s", config.SwapAPI, err)
}
}
2018-06-20 12:06:27 +00:00
self = &Swarm{
config: config,
backend: backend,
privateKey: config.ShiftPrivateKey(),
}
log.Debug("Setting up Swarm service components")
2018-06-20 12:06:27 +00:00
config.HiveParams.Discovery = true
2018-06-20 12:06:27 +00:00
nodeID, err := discover.HexID(config.NodeID)
if err != nil {
return nil, err
}
addr := &network.BzzAddr{
OAddr: common.FromHex(config.BzzKey),
UAddr: []byte(discover.NewNode(nodeID, net.IP{127, 0, 0, 1}, 30303, 30303).String()),
}
bzzconfig := &network.BzzConfig{
NetworkID: config.NetworkID,
OverlayAddr: addr.OAddr,
UnderlayAddr: addr.UAddr,
HiveParams: config.HiveParams,
LightNode: config.LightNodeEnabled,
2018-06-20 12:06:27 +00:00
}
2018-06-20 12:06:27 +00:00
stateStore, err := state.NewDBStore(filepath.Join(config.Path, "state-store.db"))
if err != nil {
return
}
2018-06-20 12:06:27 +00:00
// set up high level api
var resolver *api.MultiResolver
if len(config.EnsAPIs) > 0 {
opts := []api.MultiResolverOption{}
for _, c := range config.EnsAPIs {
tld, endpoint, addr := parseEnsAPIAddress(c)
2018-06-20 12:06:27 +00:00
r, err := newEnsClient(endpoint, addr, config, self.privateKey)
if err != nil {
return nil, err
}
opts = append(opts, api.MultiResolverOptionWithResolver(r, tld))
2018-06-20 12:06:27 +00:00
}
2018-06-20 12:06:27 +00:00
resolver = api.NewMultiResolver(opts...)
self.dns = resolver
}
self.lstore, err = storage.NewLocalStore(config.LocalStoreParams, mockStore)
if err != nil {
return
}
db := storage.NewDBAPI(self.lstore)
to := network.NewKademlia(
common.FromHex(config.BzzKey),
network.NewKadParams(),
)
delivery := stream.NewDelivery(to, db)
self.streamer = stream.NewRegistry(addr, delivery, db, stateStore, &stream.RegistryOptions{
SkipCheck: config.DeliverySkipCheck,
DoSync: config.SyncEnabled,
DoRetrieve: true,
SyncUpdateDelay: config.SyncUpdateDelay,
})
// set up NetStore, the cloud storage local access layer
netStore := storage.NewNetStore(self.lstore, self.streamer.Retrieve)
// Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage
self.fileStore = storage.NewFileStore(netStore, self.config.FileStoreParams)
var resourceHandler *mru.Handler
swarm/storage/mru: Client-side MRU signatures (#784) * swarm/storage/mru: Add embedded publickey and remove ENS dep This commit breaks swarm, swarm/api... but tests in swarm/storage/mru pass * swarm: Refactor swarm, swarm/api to mru changes, make tests pass * swarm/storage/mru: Remove self from recv, remove test ens vldtr * swarm/storage/mru: Remove redundant test, expose ResourceHash mthd * swarm/storage/mru: Make HeaderGetter mandatory + godoc fixes * swarm/storage: Remove validator prefix for metadata chunk * swarm/storage/mru: Use Address instead of PublicKey * swarm/storage/mru: Change index from name to metadata chunk addr * swarm/storage/mru: Refactor swarm/api/... to MRU index changes * swarm/storage/mru: Refactor cleanup * swarm/storage/mru: Rebase cleanup * swarm: Use constructor for GenericSigner MRU in swarm.go * swarm/storage: Change to BMTHash for MRU hashing * swarm/storage: Reduce loglevel on chunk validator logs * swarm/storage/mru: Delint * swarm: MRU Rebase cleanup * swarm/storage/mru: client-side mru signatures Rebase to PR #668 and fix all conflicts * swarm/storage/mru: refactor and documentation * swarm/resource/mru: error-checking tests for parseUpdate/newUpdateChunk * swarm/storage/mru: Added resourcemetadata tests * swarm/storage/mru: Added tests for UpdateRequest * swarm/storage/mru: more test coverage for UpdateRequest and comments * swarm/storage/mru: Avoid fake chunks in parseUpdate() * swarm/storage/mru: Documented resource.go extensively moved some functions where they make most sense * swarm/storage/mru: increase test coverage for UpdateRequest and variable name changes throughout to increase consistency * swarm/storage/mru: moved default timestamp to NewCreateRequest- * swarm/storage/mru: lookup refactor * swarm/storage/mru: added comments and renamed raw flag to rawmru * swarm/storage/mru: fix receiver typo * swarm/storage/mru: refactored update chunk new/create * swarm/storage/mru: refactored signature digest to avoid malleability * swarm/storage/mru: optimize update data serialization * swarm/storage/mru: refactor and cleanup * swarm/storage/mru: add timestamp struct and serialization * swarm/storage/mru: fix lint error and mark some old code for deletion * swarm/storage/mru: remove unnecessary variable * swarm/storage/mru: Added more comments throughout * swarm/storage/mru: Refactored metadata chunk layout + extensive error... * swarm/storage/mru: refactor cli parser Changed resource info output to JSON * swarm/storage/mru: refactor serialization for extensibility refactored error messages to NewErrorf * swarm/storage/mru: Moved Signature to resource_sign. Check Sign errors in server tests * swarm/storage/mru: Remove isSafeName() checks * swarm/storage/mru: scrubbed off all references to "block" for time * swarm/storage/mru: removed superfluous isSynced() call. * swarm/storage/mru: remove isMultihash() and ToSafeName functions * swarm/storage/mru: various fixes and comments * swarm/storage/mru: decoupled cli for independent create/update * Made resource name optional * Removed unused LookupPrevious * swarm/storage/mru: Decoupled resource create / update & refactor * swarm/storage/mru: Fixed some comments as per issues raised in PR #743 * swarm/storage/mru: Cosmetic changes as per #743 comments * swarm/storage/mru: refct request encoder/decoder > marshal/unmarshal * swarm/storage/mru: Cosmetic changes as per review in #748 * swarm/storage/mru: removed timestamp proof placeholder * swarm/storage/mru: cosmetic/doc/fixes changes as per comments in #704 * swarm/storage/mru: removed unnecessary check in Handler.update * swarm/storage/mru: Implemented Marshaler/Unmarshaler iface in Request * swarm/storage/mru: Fixed linter error * swarm/storage/mru: removed redundant address in signature digest * swarm/storage/mru: fixed bug: LookupLatestVersionInPeriod not working * swarm/storage/mru: Unfold Request creation API for create or update+create set common time source for mru package * swarm/api/http: fix HandleGetResource error variable shadowed when requesting a resource that does not exist * swarm/storage/mru: Add simple check to detect duplicate updates * swarm/storage/mru: moved Multihash() to the right place. * cmd/swarm: remove unneeded clientaccountmanager.go * swarm/storage/mru: Changed some comments as per reviews in #784 * swarm/storage/mru: Made SignedResourceUpdate.GetDigest() public * swarm/storage/mru: cosmetic changes as per comments in #784 * cmd/swarm: Inverted --multihash flag default * swarm/storage/mru: removed Verify from SignedResourceUpdate.fromChunk * swarm/storage/mru: Moved validation code out of serializer Cosmetic / comment changes * swarm/storage/mru: Added unit tests for UpdateLookup * swarm/storage/mru: Increased coverage of metadata serialization * swarm/storage/mru: Increased test coverage of updateHeader serializers * swarm/storage/mru: Add resourceUpdate serializer test
2018-07-21 19:49:36 +00:00
rhparams := &mru.HandlerParams{}
resourceHandler = mru.NewHandler(rhparams)
2018-06-20 12:06:27 +00:00
resourceHandler.SetStore(netStore)
self.lstore.Validators = []storage.ChunkValidator{
storage.NewContentAddressValidator(storage.MakeHashFunc(storage.DefaultHash)),
resourceHandler,
2018-06-20 12:06:27 +00:00
}
log.Debug("Setup local storage")
2018-06-20 12:06:27 +00:00
self.bzz = network.NewBzz(bzzconfig, to, stateStore, stream.Spec, self.streamer.Run)
// Pss = postal service over swarm (devp2p over bzz)
self.ps, err = pss.NewPss(to, config.Pss)
if err != nil {
return nil, err
}
if pss.IsActiveHandshake {
pss.SetHandshakeController(self.ps, pss.NewHandshakeParams())
}
self.api = api.NewAPI(self.fileStore, self.dns, resourceHandler, self.privateKey)
self.sfs = fuse.NewSwarmFS(self.api)
log.Debug("Initialized FUSE filesystem")
return self, nil
}
// 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
}
2018-06-20 12:06:27 +00:00
// ensClient provides functionality for api.ResolveValidator
type ensClient struct {
*ens.ENS
*ethclient.Client
}
// 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.
2018-06-20 12:06:27 +00:00
func newEnsClient(endpoint string, addr common.Address, config *api.Config, privkey *ecdsa.PrivateKey) (*ensClient, error) {
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)
}
2018-06-20 12:06:27 +00:00
ethClient := ethclient.NewClient(client)
ensRoot := config.EnsRoot
if addr != (common.Address{}) {
ensRoot = addr
} 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)
}
}
2018-06-20 12:06:27 +00:00
transactOpts := bind.NewKeyedTransactor(privkey)
dns, err := ens.NewENS(transactOpts, ensRoot, ethClient)
if err != nil {
return nil, err
}
log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar %v @ address %v", endpoint, ensRoot.Hex()))
2018-06-20 12:06:27 +00:00
return &ensClient{
ENS: dns,
Client: ethClient,
}, 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())
}
}
/*
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
func (self *Swarm) Start(srv *p2p.Server) error {
startTime = time.Now()
2018-06-20 12:06:27 +00:00
self.tracerClose = tracing.Closer
2018-06-20 12:06:27 +00:00
// update uaddr to correct enode
newaddr := self.bzz.UpdateLocalAddr([]byte(srv.Self().String()))
log.Info("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr))
// set chequebook
2018-06-20 12:06:27 +00:00
if self.config.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)
}
log.Debug(fmt.Sprintf("-> cheque book for SWAP: %v", self.config.Swap.Chequebook()))
} else {
log.Debug(fmt.Sprintf("SWAP disabled: no cheque book set"))
}
log.Info("Starting bzz service")
2018-06-20 12:06:27 +00:00
err := self.bzz.Start(srv)
if err != nil {
log.Error("bzz failed", "err", err)
return err
}
log.Info("Swarm network started", "bzzaddr", fmt.Sprintf("%x", self.bzz.Hive.Overlay.BaseAddr()))
2018-06-20 12:06:27 +00:00
if self.ps != nil {
self.ps.Start(srv)
}
// start swarm http proxy server
if self.config.Port != "" {
addr := net.JoinHostPort(self.config.ListenAddr, self.config.Port)
server := httpapi.NewServer(self.api, self.config.Cors)
if self.config.Cors != "" {
log.Debug("Swarm HTTP proxy CORS headers", "allowedOrigins", self.config.Cors)
}
2018-06-20 12:06:27 +00:00
log.Debug("Starting Swarm HTTP proxy", "port", self.config.Port)
go func() {
err := server.ListenAndServe(addr)
if err != nil {
log.Error("Could not start Swarm HTTP proxy", "err", err.Error())
}
}()
}
self.periodicallyUpdateGauges()
startCounter.Inc(1)
2018-06-20 12:06:27 +00:00
self.streamer.Start(srv)
return nil
}
func (self *Swarm) periodicallyUpdateGauges() {
ticker := time.NewTicker(updateGaugesPeriod)
go func() {
for range ticker.C {
self.updateGauges()
}
}()
}
func (self *Swarm) updateGauges() {
uptimeGauge.Update(time.Since(startTime).Nanoseconds())
2018-06-20 12:06:27 +00:00
requestsCacheGauge.Update(int64(self.lstore.RequestsCacheLen()))
}
// implements the node.Service interface
// stops all component services.
func (self *Swarm) Stop() error {
if self.tracerClose != nil {
err := self.tracerClose.Close()
if err != nil {
return err
}
}
2018-06-20 12:06:27 +00:00
if self.ps != nil {
self.ps.Stop()
}
if ch := self.config.Swap.Chequebook(); ch != nil {
ch.Stop()
ch.Save()
}
if self.lstore != nil {
self.lstore.DbStore.Close()
}
self.sfs.Stop()
stopCounter.Inc(1)
2018-06-20 12:06:27 +00:00
self.streamer.Stop()
return self.bzz.Stop()
}
// implements the node.Service interface
2018-06-20 12:06:27 +00:00
func (self *Swarm) Protocols() (protos []p2p.Protocol) {
protos = append(protos, self.bzz.Protocols()...)
if self.ps != nil {
protos = append(protos, self.ps.Protocols()...)
}
return
}
func (self *Swarm) RegisterPssProtocol(spec *protocols.Spec, targetprotocol *p2p.Protocol, options *pss.ProtocolParams) (*pss.Protocol, error) {
if !pss.IsActiveProtocol {
return nil, fmt.Errorf("Pss protocols not available (built with !nopssprotocol tag)")
}
2018-06-20 12:06:27 +00:00
topic := pss.ProtocolTopic(spec)
return pss.RegisterProtocol(self.ps, &topic, spec, targetprotocol, options)
}
// implements node.Service
2018-06-20 12:06:27 +00:00
// APIs returns the RPC API descriptors the Swarm implementation offers
func (self *Swarm) APIs() []rpc.API {
2018-06-20 12:06:27 +00:00
apis := []rpc.API{
// public APIs
{
Namespace: "bzz",
2018-06-20 12:06:27 +00:00
Version: "3.0",
Service: &Info{self.config, chequebook.ContractParams},
Public: true,
},
// admin APIs
{
Namespace: "bzz",
2018-06-20 12:06:27 +00:00
Version: "3.0",
Service: api.NewControl(self.api, self.bzz.Hive),
Public: false,
},
{
Namespace: "chequebook",
Version: chequebook.Version,
Service: chequebook.NewApi(self.config.Swap.Chequebook),
Public: false,
},
{
Namespace: "swarmfs",
Version: fuse.Swarmfs_Version,
Service: self.sfs,
Public: false,
},
// 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,
},
// {Namespace, Version, api.NewAdmin(self), false},
}
2018-06-20 12:06:27 +00:00
apis = append(apis, self.bzz.APIs()...)
if self.ps != nil {
apis = append(apis, self.ps.APIs()...)
}
return apis
}
2018-06-20 12:06:27 +00:00
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
}
log.Info(fmt.Sprintf("new chequebook set (%v): saving config file, resetting all connections in the hive", self.config.Swap.Contract.Hex()))
return nil
}
// serialisable info about swarm
type Info struct {
*api.Config
*chequebook.Params
}
func (self *Info) Info() *Info {
return self
}