github.com/ethersphere/bee/v2@v2.2.0/pkg/node/bootstrap.go (about)

     1  // Copyright 2022 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package node
     6  
     7  import (
     8  	"context"
     9  	"crypto/ecdsa"
    10  	"encoding/hex"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"math/big"
    16  	"time"
    17  
    18  	"github.com/ethereum/go-ethereum/common"
    19  	"github.com/ethersphere/bee/v2/pkg/accounting"
    20  	"github.com/ethersphere/bee/v2/pkg/addressbook"
    21  	"github.com/ethersphere/bee/v2/pkg/crypto"
    22  	"github.com/ethersphere/bee/v2/pkg/feeds"
    23  	"github.com/ethersphere/bee/v2/pkg/feeds/factory"
    24  	"github.com/ethersphere/bee/v2/pkg/file"
    25  	"github.com/ethersphere/bee/v2/pkg/file/joiner"
    26  	"github.com/ethersphere/bee/v2/pkg/file/loadsave"
    27  	"github.com/ethersphere/bee/v2/pkg/hive"
    28  	"github.com/ethersphere/bee/v2/pkg/log"
    29  	"github.com/ethersphere/bee/v2/pkg/manifest"
    30  	"github.com/ethersphere/bee/v2/pkg/p2p/libp2p"
    31  	"github.com/ethersphere/bee/v2/pkg/postage"
    32  	"github.com/ethersphere/bee/v2/pkg/pricer"
    33  	"github.com/ethersphere/bee/v2/pkg/pricing"
    34  	"github.com/ethersphere/bee/v2/pkg/retrieval"
    35  	"github.com/ethersphere/bee/v2/pkg/settlement/pseudosettle"
    36  	"github.com/ethersphere/bee/v2/pkg/spinlock"
    37  	"github.com/ethersphere/bee/v2/pkg/storage"
    38  	storer "github.com/ethersphere/bee/v2/pkg/storer"
    39  	"github.com/ethersphere/bee/v2/pkg/swarm"
    40  	"github.com/ethersphere/bee/v2/pkg/topology"
    41  	"github.com/ethersphere/bee/v2/pkg/topology/kademlia"
    42  	"github.com/ethersphere/bee/v2/pkg/topology/lightnode"
    43  	"github.com/ethersphere/bee/v2/pkg/tracing"
    44  	"github.com/hashicorp/go-multierror"
    45  	ma "github.com/multiformats/go-multiaddr"
    46  )
    47  
    48  var (
    49  	// zeroed out while waiting to be  replacement for the new snapshot feed address
    50  	// must be different to avoid stale reads on the old contract
    51  	snapshotFeed    = swarm.MustParseHexAddress("0000000000000000000000000000000000000000000000000000000000000000")
    52  	errDataMismatch = errors.New("data length mismatch")
    53  )
    54  
    55  const (
    56  	getSnapshotRetries = 3
    57  	retryWait          = time.Second * 5
    58  	timeout            = time.Minute * 2
    59  )
    60  
    61  func bootstrapNode(
    62  	ctx context.Context,
    63  	addr string,
    64  	swarmAddress swarm.Address,
    65  	nonce []byte,
    66  	addressbook addressbook.Interface,
    67  	bootnodes []ma.Multiaddr,
    68  	lightNodes *lightnode.Container,
    69  	stateStore storage.StateStorer,
    70  	signer crypto.Signer,
    71  	networkID uint64,
    72  	logger log.Logger,
    73  	libp2pPrivateKey *ecdsa.PrivateKey,
    74  	o *Options,
    75  ) (snapshot *postage.ChainSnapshot, retErr error) {
    76  
    77  	tracer, tracerCloser, err := tracing.NewTracer(&tracing.Options{
    78  		Enabled:     o.TracingEnabled,
    79  		Endpoint:    o.TracingEndpoint,
    80  		ServiceName: o.TracingServiceName,
    81  	})
    82  	if err != nil {
    83  		return nil, fmt.Errorf("tracer: %w", err)
    84  	}
    85  
    86  	p2pCtx, p2pCancel := context.WithCancel(ctx)
    87  
    88  	b := &Bee{
    89  		ctxCancel:    p2pCancel,
    90  		tracerCloser: tracerCloser,
    91  	}
    92  
    93  	defer func() {
    94  		retErr = multierror.Append(new(multierror.Error), retErr, b.Shutdown()).ErrorOrNil()
    95  	}()
    96  
    97  	p2ps, err := libp2p.New(p2pCtx, signer, networkID, swarmAddress, addr, addressbook, stateStore, lightNodes, logger, tracer, libp2p.Options{
    98  		PrivateKey:     libp2pPrivateKey,
    99  		NATAddr:        o.NATAddr,
   100  		EnableWS:       o.EnableWS,
   101  		WelcomeMessage: o.WelcomeMessage,
   102  		FullNode:       false,
   103  		Nonce:          nonce,
   104  	})
   105  	if err != nil {
   106  		return nil, fmt.Errorf("p2p service: %w", err)
   107  	}
   108  	b.p2pService = p2ps
   109  	b.p2pHalter = p2ps
   110  
   111  	hive := hive.New(p2ps, addressbook, networkID, o.BootnodeMode, o.AllowPrivateCIDRs, logger)
   112  
   113  	if err = p2ps.AddProtocol(hive.Protocol()); err != nil {
   114  		return nil, fmt.Errorf("hive service: %w", err)
   115  	}
   116  	b.hiveCloser = hive
   117  
   118  	kad, err := kademlia.New(swarmAddress, addressbook, hive, p2ps, logger,
   119  		kademlia.Options{Bootnodes: bootnodes, BootnodeMode: o.BootnodeMode, StaticNodes: o.StaticNodes, DataDir: o.DataDir})
   120  	if err != nil {
   121  		return nil, fmt.Errorf("unable to create kademlia: %w", err)
   122  	}
   123  	b.topologyCloser = kad
   124  	b.topologyHalter = kad
   125  	hive.SetAddPeersHandler(kad.AddPeers)
   126  	p2ps.SetPickyNotifier(kad)
   127  
   128  	paymentThreshold, _ := new(big.Int).SetString(o.PaymentThreshold, 10)
   129  	lightPaymentThreshold := new(big.Int).Div(paymentThreshold, big.NewInt(lightFactor))
   130  
   131  	pricer := pricer.NewFixedPricer(swarmAddress, basePrice)
   132  
   133  	pricing := pricing.New(p2ps, logger, paymentThreshold, lightPaymentThreshold, big.NewInt(minPaymentThreshold))
   134  	if err = p2ps.AddProtocol(pricing.Protocol()); err != nil {
   135  		return nil, fmt.Errorf("pricing service: %w", err)
   136  	}
   137  
   138  	acc, err := accounting.NewAccounting(
   139  		paymentThreshold,
   140  		o.PaymentTolerance,
   141  		o.PaymentEarly,
   142  		logger,
   143  		stateStore,
   144  		pricing,
   145  		big.NewInt(refreshRate),
   146  		lightFactor,
   147  		p2ps,
   148  	)
   149  	if err != nil {
   150  		return nil, fmt.Errorf("accounting: %w", err)
   151  	}
   152  	b.accountingCloser = acc
   153  
   154  	// bootstrapper mode uses the light node refresh rate
   155  	enforcedRefreshRate := big.NewInt(lightRefreshRate)
   156  
   157  	pseudosettleService := pseudosettle.New(p2ps, logger, stateStore, acc, enforcedRefreshRate, enforcedRefreshRate, p2ps)
   158  	if err = p2ps.AddProtocol(pseudosettleService.Protocol()); err != nil {
   159  		return nil, fmt.Errorf("pseudosettle service: %w", err)
   160  	}
   161  
   162  	acc.SetRefreshFunc(pseudosettleService.Pay)
   163  
   164  	pricing.SetPaymentThresholdObserver(acc)
   165  
   166  	localStore, err := storer.New(ctx, "", &storer.Options{
   167  		CacheCapacity: 1_000_000,
   168  	})
   169  	if err != nil {
   170  		return nil, fmt.Errorf("local store creation: %w", err)
   171  	}
   172  	b.localstoreCloser = localStore
   173  
   174  	radiusF := func() (uint8, error) { return swarm.MaxBins, nil }
   175  
   176  	retrieve := retrieval.New(swarmAddress, radiusF, localStore, p2ps, kad, logger, acc, pricer, tracer, o.RetrievalCaching)
   177  	if err = p2ps.AddProtocol(retrieve.Protocol()); err != nil {
   178  		return nil, fmt.Errorf("retrieval service: %w", err)
   179  	}
   180  	b.retrievalCloser = retrieve
   181  
   182  	localStore.SetRetrievalService(retrieve)
   183  
   184  	if err := kad.Start(p2pCtx); err != nil {
   185  		return nil, err
   186  	}
   187  
   188  	if err := p2ps.Ready(); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	if err := waitPeers(kad); err != nil {
   193  		return nil, errors.New("timed out waiting for kademlia peers")
   194  	}
   195  
   196  	logger.Info("bootstrap: trying to fetch stamps snapshot")
   197  
   198  	var (
   199  		snapshotReference swarm.Address
   200  		reader            file.Joiner
   201  		l                 int64
   202  		eventsJSON        []byte
   203  	)
   204  
   205  	for i := 0; i < getSnapshotRetries; i++ {
   206  		if err != nil {
   207  			time.Sleep(retryWait)
   208  		}
   209  
   210  		ctx, cancel := context.WithTimeout(ctx, timeout)
   211  		defer cancel()
   212  
   213  		snapshotReference, err = getLatestSnapshot(ctx, localStore.Download(true), snapshotFeed)
   214  		if err != nil {
   215  			logger.Warning("bootstrap: fetching snapshot failed", "error", err)
   216  			continue
   217  		}
   218  		break
   219  	}
   220  	if err != nil {
   221  		return nil, err
   222  	}
   223  
   224  	for i := 0; i < getSnapshotRetries; i++ {
   225  		if err != nil {
   226  			time.Sleep(retryWait)
   227  		}
   228  
   229  		ctx, cancel := context.WithTimeout(ctx, timeout)
   230  		defer cancel()
   231  
   232  		reader, l, err = joiner.New(ctx, localStore.Download(true), localStore.Cache(), snapshotReference)
   233  		if err != nil {
   234  			logger.Warning("bootstrap: file joiner failed", "error", err)
   235  			continue
   236  		}
   237  
   238  		eventsJSON, err = io.ReadAll(reader)
   239  		if err != nil {
   240  			logger.Warning("bootstrap: reading failed", "error", err)
   241  			continue
   242  		}
   243  
   244  		if len(eventsJSON) != int(l) {
   245  			err = errDataMismatch
   246  			logger.Warning("bootstrap: count mismatch", "error", err)
   247  			continue
   248  		}
   249  		break
   250  	}
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	events := postage.ChainSnapshot{}
   256  	err = json.Unmarshal(eventsJSON, &events)
   257  	if err != nil {
   258  		return nil, err
   259  	}
   260  
   261  	return &events, nil
   262  }
   263  
   264  // wait till some peers are connected. returns true if all is ok
   265  func waitPeers(kad *kademlia.Kad) error {
   266  	const minPeersCount = 25
   267  	return spinlock.WaitWithInterval(time.Minute, time.Second, func() bool {
   268  		count := 0
   269  		_ = kad.EachConnectedPeer(func(_ swarm.Address, _ uint8) (bool, bool, error) {
   270  			count++
   271  			return false, false, nil
   272  		}, topology.Select{})
   273  		return count >= minPeersCount
   274  	})
   275  }
   276  
   277  func getLatestSnapshot(
   278  	ctx context.Context,
   279  	st storage.Getter,
   280  	address swarm.Address,
   281  ) (swarm.Address, error) {
   282  	ls := loadsave.NewReadonly(st)
   283  	feedFactory := factory.New(st)
   284  
   285  	m, err := manifest.NewDefaultManifestReference(
   286  		address,
   287  		ls,
   288  	)
   289  	if err != nil {
   290  		return swarm.ZeroAddress, fmt.Errorf("not a manifest: %w", err)
   291  	}
   292  
   293  	e, err := m.Lookup(ctx, "/")
   294  	if err != nil {
   295  		return swarm.ZeroAddress, fmt.Errorf("node lookup: %w", err)
   296  	}
   297  
   298  	var (
   299  		owner, topic []byte
   300  		t            = new(feeds.Type)
   301  	)
   302  	meta := e.Metadata()
   303  	if e := meta["swarm-feed-owner"]; e != "" {
   304  		owner, err = hex.DecodeString(e)
   305  		if err != nil {
   306  			return swarm.ZeroAddress, err
   307  		}
   308  	}
   309  	if e := meta["swarm-feed-topic"]; e != "" {
   310  		topic, err = hex.DecodeString(e)
   311  		if err != nil {
   312  			return swarm.ZeroAddress, err
   313  		}
   314  	}
   315  	if e := meta["swarm-feed-type"]; e != "" {
   316  		err := t.FromString(e)
   317  		if err != nil {
   318  			return swarm.ZeroAddress, err
   319  		}
   320  	}
   321  	if len(owner) == 0 || len(topic) == 0 {
   322  		return swarm.ZeroAddress, fmt.Errorf("node lookup: %s", "feed metadata absent")
   323  	}
   324  	f := feeds.New(topic, common.BytesToAddress(owner))
   325  
   326  	l, err := feedFactory.NewLookup(*t, f)
   327  	if err != nil {
   328  		return swarm.ZeroAddress, fmt.Errorf("feed lookup failed: %w", err)
   329  	}
   330  
   331  	u, _, _, err := l.At(ctx, time.Now().Unix(), 0)
   332  	if err != nil {
   333  		return swarm.ZeroAddress, err
   334  	}
   335  
   336  	_, ref, err := feeds.FromChunk(u)
   337  	if err != nil {
   338  		return swarm.ZeroAddress, err
   339  	}
   340  
   341  	return swarm.NewAddress(ref), nil
   342  }
   343  
   344  func batchStoreExists(s storage.StateStorer) (bool, error) {
   345  
   346  	hasOne := false
   347  	err := s.Iterate("batchstore_", func(key, value []byte) (stop bool, err error) {
   348  		hasOne = true
   349  		return true, err
   350  	})
   351  
   352  	return hasOne, err
   353  }