github.com/onflow/flow-go@v0.33.17/network/p2p/blob/blob_service.go (about)

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