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 }