github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/network/p2p/blob/blob_service.go (about)

     1  package blob
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"time"
     8  
     9  	"github.com/hashicorp/go-multierror"
    10  	"github.com/ipfs/boxo/bitswap"
    11  	bsmsg "github.com/ipfs/boxo/bitswap/message"
    12  	bsnet "github.com/ipfs/boxo/bitswap/network"
    13  	"github.com/ipfs/boxo/blockservice"
    14  	"github.com/ipfs/boxo/blockstore"
    15  	"github.com/ipfs/boxo/provider"
    16  	blocks "github.com/ipfs/go-block-format"
    17  	"github.com/ipfs/go-cid"
    18  	"github.com/ipfs/go-datastore"
    19  	"github.com/libp2p/go-libp2p/core/host"
    20  	"github.com/libp2p/go-libp2p/core/peer"
    21  	"github.com/libp2p/go-libp2p/core/protocol"
    22  	"github.com/libp2p/go-libp2p/core/routing"
    23  	"github.com/rs/zerolog"
    24  	"golang.org/x/time/rate"
    25  
    26  	"github.com/onflow/flow-go/model/flow"
    27  	"github.com/onflow/flow-go/module"
    28  	"github.com/onflow/flow-go/module/blobs"
    29  	"github.com/onflow/flow-go/module/component"
    30  	"github.com/onflow/flow-go/module/irrecoverable"
    31  	"github.com/onflow/flow-go/module/metrics"
    32  	"github.com/onflow/flow-go/network"
    33  	p2plogging "github.com/onflow/flow-go/network/p2p/logging"
    34  	"github.com/onflow/flow-go/utils/logging"
    35  
    36  	ipld "github.com/ipfs/go-ipld-format"
    37  )
    38  
    39  const (
    40  	// DefaultReprovideInterval is the default interval at which DHT provider entries are refreshed
    41  	DefaultReprovideInterval = 12 * time.Hour
    42  )
    43  
    44  type blobService struct {
    45  	prefix string
    46  	component.Component
    47  	blockService blockservice.BlockService
    48  	blockStore   blockstore.Blockstore
    49  	reprovider   provider.System
    50  	config       *BlobServiceConfig
    51  }
    52  
    53  var _ network.BlobService = (*blobService)(nil)
    54  var _ component.Component = (*blobService)(nil)
    55  
    56  type BlobServiceConfig struct {
    57  	ReprovideInterval time.Duration    // the interval at which the DHT provider entries are refreshed
    58  	BitswapOptions    []bitswap.Option // options to pass to the Bitswap service
    59  }
    60  
    61  // WithReprovideInterval sets the interval at which DHT provider entries are refreshed
    62  func WithReprovideInterval(d time.Duration) network.BlobServiceOption {
    63  	return func(bs network.BlobService) {
    64  		bs.(*blobService).config.ReprovideInterval = d
    65  	}
    66  }
    67  
    68  // WithBitswapOptions sets additional options for Bitswap exchange
    69  func WithBitswapOptions(opts ...bitswap.Option) network.BlobServiceOption {
    70  	return func(bs network.BlobService) {
    71  		bs.(*blobService).config.BitswapOptions = opts
    72  	}
    73  }
    74  
    75  // WithParentBlobService configures the blob service to use the parent's blockstore
    76  func WithParentBlobService(parent network.BlobService) network.BlobServiceOption {
    77  	return func(bs network.BlobService) {
    78  		bs.(*blobService).blockStore = parent.(*blobService).blockStore
    79  	}
    80  }
    81  
    82  // WithHashOnRead sets whether the blobstore will rehash the blob data on read
    83  // When set, calls to GetBlob will fail with an error if the hash of the data in storage does not
    84  // match its CID
    85  func WithHashOnRead(enabled bool) network.BlobServiceOption {
    86  	return func(bs network.BlobService) {
    87  		bs.(*blobService).blockStore.HashOnRead(enabled)
    88  	}
    89  }
    90  
    91  // WithRateLimit sets a rate limit on reads from the underlying datastore that allows up
    92  // to r bytes per second and permits bursts of at most b bytes. Note that b should be
    93  // set to at least the max blob size, otherwise blobs larger than b cannot be read from
    94  // the blobstore.
    95  func WithRateLimit(r float64, b int) network.BlobServiceOption {
    96  	return func(bs network.BlobService) {
    97  		blobService := bs.(*blobService)
    98  		blobService.blockStore = newRateLimitedBlockStore(blobService.blockStore, blobService.prefix, r, b)
    99  	}
   100  }
   101  
   102  // NewBlobService creates a new BlobService.
   103  func NewBlobService(
   104  	host host.Host,
   105  	r routing.ContentRouting,
   106  	prefix string,
   107  	ds datastore.Batching,
   108  	metrics module.BitswapMetrics,
   109  	logger zerolog.Logger,
   110  	opts ...network.BlobServiceOption,
   111  ) (*blobService, error) {
   112  	bsNetwork := bsnet.NewFromIpfsHost(host, r, bsnet.Prefix(protocol.ID(prefix)))
   113  	blockStore, err := blockstore.CachedBlockstore(
   114  		context.Background(),
   115  		blockstore.NewBlockstore(ds),
   116  		blockstore.DefaultCacheOpts(),
   117  	)
   118  	if err != nil {
   119  		return nil, fmt.Errorf("failed to create cached blockstore: %w", err)
   120  	}
   121  	bs := &blobService{
   122  		prefix: prefix,
   123  		config: &BlobServiceConfig{
   124  			ReprovideInterval: DefaultReprovideInterval,
   125  		},
   126  		blockStore: blockStore,
   127  	}
   128  
   129  	for _, opt := range opts {
   130  		opt(bs)
   131  	}
   132  
   133  	cm := component.NewComponentManagerBuilder().
   134  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   135  			btswp := bitswap.New(ctx, bsNetwork, bs.blockStore, bs.config.BitswapOptions...)
   136  			bs.blockService = blockservice.New(bs.blockStore, btswp)
   137  
   138  			ready()
   139  
   140  			ticker := time.NewTicker(15 * time.Second)
   141  			for {
   142  				select {
   143  				case <-ctx.Done():
   144  					return
   145  				case <-ticker.C:
   146  					stat, err := btswp.Stat()
   147  					if err != nil {
   148  						logger.Err(err).Str("component", "blob_service").Str("prefix", prefix).Msg("failed to get bitswap stats")
   149  						continue
   150  					}
   151  
   152  					metrics.Peers(prefix, len(stat.Peers))
   153  					metrics.Wantlist(prefix, len(stat.Wantlist))
   154  					metrics.BlobsReceived(prefix, stat.BlocksReceived)
   155  					metrics.DataReceived(prefix, stat.DataReceived)
   156  					metrics.BlobsSent(prefix, stat.BlocksSent)
   157  					metrics.DataSent(prefix, stat.DataSent)
   158  					metrics.DupBlobsReceived(prefix, stat.DupBlksReceived)
   159  					metrics.DupDataReceived(prefix, stat.DupDataReceived)
   160  					metrics.MessagesReceived(prefix, stat.MessagesReceived)
   161  				}
   162  			}
   163  		}).
   164  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   165  			// New creates and starts the reprovider (non-blocking)
   166  			reprovider, err := provider.New(ds,
   167  				provider.Online(r),
   168  				provider.KeyProvider(provider.NewBlockstoreProvider(bs.blockStore)),
   169  				provider.ReproviderInterval(bs.config.ReprovideInterval),
   170  			)
   171  			if err != nil {
   172  				ctx.Throw(fmt.Errorf("failed to start reprovider: %w", err))
   173  			}
   174  
   175  			bs.reprovider = reprovider
   176  			ready()
   177  		}).
   178  		AddWorker(func(ctx irrecoverable.SignalerContext, ready component.ReadyFunc) {
   179  			ready()
   180  
   181  			<-bs.Ready() // wait for variables to be initialized
   182  			<-ctx.Done()
   183  
   184  			var err *multierror.Error
   185  
   186  			err = multierror.Append(err, bs.reprovider.Close())
   187  			err = multierror.Append(err, bs.blockService.Close())
   188  
   189  			if err.ErrorOrNil() != nil {
   190  				ctx.Throw(err)
   191  			}
   192  		}).
   193  		Build()
   194  
   195  	bs.Component = cm
   196  
   197  	return bs, nil
   198  }
   199  
   200  func (bs *blobService) TriggerReprovide(ctx context.Context) error {
   201  	return bs.reprovider.Reprovide(ctx)
   202  }
   203  
   204  func (bs *blobService) GetBlob(ctx context.Context, c cid.Cid) (blobs.Blob, error) {
   205  	blob, err := bs.blockService.GetBlock(ctx, c)
   206  	if ipld.IsNotFound(err) {
   207  		return nil, network.ErrBlobNotFound
   208  	}
   209  
   210  	return blob, err
   211  }
   212  
   213  func (bs *blobService) GetBlobs(ctx context.Context, ks []cid.Cid) <-chan blobs.Blob {
   214  	return bs.blockService.GetBlocks(ctx, ks)
   215  }
   216  
   217  func (bs *blobService) AddBlob(ctx context.Context, b blobs.Blob) error {
   218  	return bs.blockService.AddBlock(ctx, b)
   219  }
   220  
   221  func (bs *blobService) AddBlobs(ctx context.Context, blobs []blobs.Blob) error {
   222  	return bs.blockService.AddBlocks(ctx, blobs)
   223  }
   224  
   225  func (bs *blobService) DeleteBlob(ctx context.Context, c cid.Cid) error {
   226  	return bs.blockService.DeleteBlock(ctx, c)
   227  }
   228  
   229  func (bs *blobService) GetSession(ctx context.Context) network.BlobGetter {
   230  	return &blobServiceSession{blockservice.NewSession(ctx, bs.blockService)}
   231  }
   232  
   233  type blobServiceSession struct {
   234  	session *blockservice.Session
   235  }
   236  
   237  var _ network.BlobGetter = (*blobServiceSession)(nil)
   238  
   239  func (s *blobServiceSession) GetBlob(ctx context.Context, c cid.Cid) (blobs.Blob, error) {
   240  	return s.session.GetBlock(ctx, c)
   241  }
   242  
   243  func (s *blobServiceSession) GetBlobs(ctx context.Context, ks []cid.Cid) <-chan blobs.Blob {
   244  	return s.session.GetBlocks(ctx, ks)
   245  }
   246  
   247  type rateLimitedBlockStore struct {
   248  	blockstore.Blockstore
   249  	limiter *rate.Limiter
   250  	metrics module.RateLimitedBlockstoreMetrics
   251  }
   252  
   253  var rateLimitedError = errors.New("rate limited")
   254  
   255  func newRateLimitedBlockStore(bs blockstore.Blockstore, prefix string, r float64, b int) *rateLimitedBlockStore {
   256  	return &rateLimitedBlockStore{
   257  		Blockstore: bs,
   258  		limiter:    rate.NewLimiter(rate.Limit(r), b),
   259  		metrics:    metrics.NewRateLimitedBlockstoreCollector(prefix),
   260  	}
   261  }
   262  
   263  func (r *rateLimitedBlockStore) Get(ctx context.Context, c cid.Cid) (blocks.Block, error) {
   264  	size, err := r.Blockstore.GetSize(ctx, c)
   265  	if err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	allowed := r.limiter.AllowN(time.Now(), size)
   270  	if !allowed {
   271  		return nil, rateLimitedError
   272  	}
   273  
   274  	r.metrics.BytesRead(size)
   275  
   276  	return r.Blockstore.Get(ctx, c)
   277  }
   278  
   279  // AuthorizedRequester returns a callback function used by bitswap to authorize block requests
   280  // A request is authorized if the peer is
   281  // * known by the identity provider
   282  // * not ejected
   283  // * an Access node
   284  // * in the allowedNodes list (if non-empty)
   285  func AuthorizedRequester(
   286  	allowedNodes map[flow.Identifier]bool,
   287  	identityProvider module.IdentityProvider,
   288  	logger zerolog.Logger,
   289  ) func(peer.ID, cid.Cid) bool {
   290  	return func(peerID peer.ID, _ cid.Cid) bool {
   291  		lg := logger.With().
   292  			Str("component", "blob_service").
   293  			Str("peer_id", p2plogging.PeerId(peerID)).
   294  			Logger()
   295  
   296  		id, ok := identityProvider.ByPeerID(peerID)
   297  
   298  		if !ok {
   299  			lg.Warn().
   300  				Bool(logging.KeySuspicious, true).
   301  				Msg("rejecting request from unknown peer")
   302  			return false
   303  		}
   304  
   305  		lg = lg.With().
   306  			Str("peer_node_id", id.NodeID.String()).
   307  			Str("role", id.Role.String()).
   308  			Logger()
   309  
   310  		// TODO: when execution data verification is enabled, add verification nodes here
   311  		if (id.Role != flow.RoleExecution && id.Role != flow.RoleAccess) || id.IsEjected() {
   312  			lg.Warn().
   313  				Bool(logging.KeySuspicious, true).
   314  				Msg("rejecting request from peer: unauthorized")
   315  			return false
   316  		}
   317  
   318  		// allow list is only for Access nodes
   319  		if id.Role == flow.RoleAccess && len(allowedNodes) > 0 && !allowedNodes[id.NodeID] {
   320  			// honest peers not on the allowed list have no way to know and will continue to request
   321  			// blobs. therefore, these requests do not indicate suspicious behavior
   322  			lg.Debug().Msg("rejecting request from peer: not in allowed list")
   323  			return false
   324  		}
   325  
   326  		lg.Debug().Msg("accepting request from peer")
   327  		return true
   328  	}
   329  }
   330  
   331  type Tracer struct {
   332  	logger zerolog.Logger
   333  }
   334  
   335  func NewTracer(logger zerolog.Logger) *Tracer {
   336  	return &Tracer{
   337  		logger,
   338  	}
   339  }
   340  
   341  func (t *Tracer) logMsg(msg bsmsg.BitSwapMessage, s string) {
   342  	evt := t.logger.Debug()
   343  
   344  	wantlist := zerolog.Arr()
   345  	for _, entry := range msg.Wantlist() {
   346  		wantlist = wantlist.Interface(entry)
   347  	}
   348  	evt.Array("wantlist", wantlist)
   349  
   350  	blks := zerolog.Arr()
   351  	for _, blk := range msg.Blocks() {
   352  		blks = blks.Str(blk.Cid().String())
   353  	}
   354  	evt.Array("blocks", blks)
   355  
   356  	haves := zerolog.Arr()
   357  	for _, have := range msg.Haves() {
   358  		haves = haves.Str(have.String())
   359  	}
   360  	evt.Array("haves", haves)
   361  
   362  	dontHaves := zerolog.Arr()
   363  	for _, dontHave := range msg.DontHaves() {
   364  		dontHaves = dontHaves.Str(dontHave.String())
   365  	}
   366  	evt.Array("dontHaves", dontHaves)
   367  
   368  	evt.Int32("pendingBytes", msg.PendingBytes())
   369  
   370  	evt.Msg(s)
   371  }
   372  
   373  func (t *Tracer) MessageReceived(pid peer.ID, msg bsmsg.BitSwapMessage) {
   374  	if t.logger.Debug().Enabled() {
   375  		t.logMsg(msg, "bitswap message received")
   376  	}
   377  }
   378  
   379  func (t *Tracer) MessageSent(pid peer.ID, msg bsmsg.BitSwapMessage) {
   380  	if t.logger.Debug().Enabled() {
   381  		t.logMsg(msg, "bitswap message sent")
   382  	}
   383  }