github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/executiondatasync/provider/provider.go (about)

     1  package provider
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/ipfs/go-cid"
    11  	"github.com/rs/zerolog"
    12  	"golang.org/x/sync/errgroup"
    13  
    14  	"github.com/onflow/flow-go/model/flow"
    15  	"github.com/onflow/flow-go/module"
    16  	"github.com/onflow/flow-go/module/blobs"
    17  	"github.com/onflow/flow-go/module/executiondatasync/execution_data"
    18  	"github.com/onflow/flow-go/module/executiondatasync/tracker"
    19  	"github.com/onflow/flow-go/network"
    20  )
    21  
    22  type ProviderOption func(*ExecutionDataProvider)
    23  
    24  func WithBlobSizeLimit(size int) ProviderOption {
    25  	return func(p *ExecutionDataProvider) {
    26  		p.maxBlobSize = size
    27  	}
    28  }
    29  
    30  // Provider is used to provide execution data blobs over the network via a blob service.
    31  type Provider interface {
    32  	Provide(ctx context.Context, blockHeight uint64, executionData *execution_data.BlockExecutionData) (flow.Identifier, *flow.BlockExecutionDataRoot, error)
    33  }
    34  
    35  type ExecutionDataProvider struct {
    36  	logger       zerolog.Logger
    37  	metrics      module.ExecutionDataProviderMetrics
    38  	maxBlobSize  int
    39  	blobService  network.BlobService
    40  	storage      tracker.Storage
    41  	cidsProvider *ExecutionDataCIDProvider
    42  }
    43  
    44  var _ Provider = (*ExecutionDataProvider)(nil)
    45  
    46  func NewProvider(
    47  	logger zerolog.Logger,
    48  	metrics module.ExecutionDataProviderMetrics,
    49  	serializer execution_data.Serializer,
    50  	blobService network.BlobService,
    51  	storage tracker.Storage,
    52  	opts ...ProviderOption,
    53  ) *ExecutionDataProvider {
    54  	if storage == nil {
    55  		storage = &tracker.NoopStorage{}
    56  	}
    57  
    58  	p := &ExecutionDataProvider{
    59  		logger:       logger.With().Str("component", "execution_data_provider").Logger(),
    60  		metrics:      metrics,
    61  		maxBlobSize:  execution_data.DefaultMaxBlobSize,
    62  		cidsProvider: NewExecutionDataCIDProvider(serializer),
    63  		blobService:  blobService,
    64  		storage:      storage,
    65  	}
    66  
    67  	for _, opt := range opts {
    68  		opt(p)
    69  	}
    70  
    71  	return p
    72  }
    73  
    74  func (p *ExecutionDataProvider) storeBlobs(parent context.Context, blockHeight uint64, blobCh <-chan blobs.Blob) <-chan error {
    75  	ch := make(chan error, 1)
    76  	go func() {
    77  		defer close(ch)
    78  
    79  		start := time.Now()
    80  
    81  		var blobs []blobs.Blob
    82  		var cids []cid.Cid
    83  		var totalSize uint64
    84  		for blob := range blobCh {
    85  			blobs = append(blobs, blob)
    86  			cids = append(cids, blob.Cid())
    87  			totalSize += uint64(len(blob.RawData()))
    88  		}
    89  
    90  		if p.logger.Debug().Enabled() {
    91  			cidArr := zerolog.Arr()
    92  			for _, cid := range cids {
    93  				cidArr = cidArr.Str(cid.String())
    94  			}
    95  			p.logger.Debug().Array("cids", cidArr).Uint64("height", blockHeight).Msg("storing blobs")
    96  		}
    97  
    98  		err := p.storage.Update(func(trackBlobs tracker.TrackBlobsFn) error {
    99  			ctx, cancel := context.WithCancel(parent)
   100  			defer cancel()
   101  
   102  			// track new blobs so that they can be pruned later
   103  			if err := trackBlobs(blockHeight, cids...); err != nil {
   104  				return fmt.Errorf("failed to track blobs: %w", err)
   105  			}
   106  
   107  			if err := p.blobService.AddBlobs(ctx, blobs); err != nil {
   108  				return fmt.Errorf("failed to add blobs: %w", err)
   109  			}
   110  
   111  			return nil
   112  		})
   113  		duration := time.Since(start)
   114  
   115  		if err != nil {
   116  			ch <- err
   117  			p.metrics.AddBlobsFailed()
   118  		} else {
   119  			p.metrics.AddBlobsSucceeded(duration, totalSize)
   120  		}
   121  	}()
   122  
   123  	return ch
   124  }
   125  
   126  // Provide adds the block execution data for a newly executed (generally not sealed or finalized) block to the blob store for distribution using Bitswap.
   127  // It computes and returns the root CID of the execution data blob tree.
   128  // This function returns once the root CID has been computed, and all blobs are successfully stored
   129  // in the Bitswap Blobstore.
   130  func (p *ExecutionDataProvider) Provide(ctx context.Context, blockHeight uint64, executionData *execution_data.BlockExecutionData) (flow.Identifier, *flow.BlockExecutionDataRoot, error) {
   131  	rootID, rootData, errCh, err := p.provide(ctx, blockHeight, executionData)
   132  	storeErr, ok := <-errCh
   133  
   134  	if err != nil {
   135  		return flow.ZeroID, nil, err
   136  	}
   137  
   138  	if ok {
   139  		return flow.ZeroID, nil, storeErr
   140  	}
   141  
   142  	if err = p.storage.SetFulfilledHeight(blockHeight); err != nil {
   143  		return flow.ZeroID, nil, err
   144  	}
   145  
   146  	return rootID, rootData, nil
   147  }
   148  
   149  func (p *ExecutionDataProvider) provide(ctx context.Context, blockHeight uint64, executionData *execution_data.BlockExecutionData) (flow.Identifier, *flow.BlockExecutionDataRoot, <-chan error, error) {
   150  	logger := p.logger.With().Uint64("height", blockHeight).Str("block_id", executionData.BlockID.String()).Logger()
   151  	logger.Debug().Msg("providing execution data")
   152  
   153  	start := time.Now()
   154  
   155  	blobCh := make(chan blobs.Blob)
   156  	defer close(blobCh)
   157  
   158  	errCh := p.storeBlobs(ctx, blockHeight, blobCh)
   159  	g := new(errgroup.Group)
   160  
   161  	chunkDataIDs := make([]cid.Cid, len(executionData.ChunkExecutionDatas))
   162  	for i, chunkExecutionData := range executionData.ChunkExecutionDatas {
   163  		i := i
   164  		chunkExecutionData := chunkExecutionData
   165  
   166  		g.Go(func() error {
   167  			logger.Debug().Int("chunk_index", i).Msg("adding chunk execution data")
   168  			cedID, err := p.cidsProvider.addChunkExecutionData(chunkExecutionData, blobCh)
   169  			if err != nil {
   170  				return fmt.Errorf("failed to add chunk execution data at index %d: %w", i, err)
   171  			}
   172  			logger.Debug().Int("chunk_index", i).Str("chunk_execution_data_id", cedID.String()).Msg("chunk execution data added")
   173  
   174  			chunkDataIDs[i] = cedID
   175  			return nil
   176  		})
   177  	}
   178  
   179  	if err := g.Wait(); err != nil {
   180  		return flow.ZeroID, nil, errCh, err
   181  	}
   182  
   183  	edRoot := &flow.BlockExecutionDataRoot{
   184  		BlockID:               executionData.BlockID,
   185  		ChunkExecutionDataIDs: chunkDataIDs,
   186  	}
   187  	rootID, err := p.cidsProvider.addExecutionDataRoot(edRoot, blobCh)
   188  	if err != nil {
   189  		return flow.ZeroID, nil, errCh, fmt.Errorf("failed to add execution data root: %w", err)
   190  	}
   191  	logger.Debug().Str("root_id", rootID.String()).Msg("root ID computed")
   192  
   193  	duration := time.Since(start)
   194  	p.metrics.RootIDComputed(duration, len(executionData.ChunkExecutionDatas))
   195  
   196  	return rootID, edRoot, errCh, nil
   197  }
   198  
   199  func NewExecutionDataCIDProvider(serializer execution_data.Serializer) *ExecutionDataCIDProvider {
   200  	return &ExecutionDataCIDProvider{
   201  		serializer:  serializer,
   202  		maxBlobSize: execution_data.DefaultMaxBlobSize,
   203  	}
   204  }
   205  
   206  type ExecutionDataCIDProvider struct {
   207  	serializer  execution_data.Serializer
   208  	maxBlobSize int
   209  }
   210  
   211  func (p *ExecutionDataCIDProvider) CalculateExecutionDataRootID(
   212  	edRoot flow.BlockExecutionDataRoot,
   213  ) (flow.Identifier, error) {
   214  	return p.addExecutionDataRoot(&edRoot, nil)
   215  }
   216  
   217  func (p *ExecutionDataCIDProvider) CalculateChunkExecutionDataID(
   218  	ced execution_data.ChunkExecutionData,
   219  ) (cid.Cid, error) {
   220  	return p.addChunkExecutionData(&ced, nil)
   221  }
   222  
   223  func (p *ExecutionDataCIDProvider) addExecutionDataRoot(
   224  	edRoot *flow.BlockExecutionDataRoot,
   225  	blobCh chan<- blobs.Blob,
   226  ) (flow.Identifier, error) {
   227  	buf := new(bytes.Buffer)
   228  	if err := p.serializer.Serialize(buf, edRoot); err != nil {
   229  		return flow.ZeroID, fmt.Errorf("failed to serialize execution data root: %w", err)
   230  	}
   231  
   232  	if buf.Len() > p.maxBlobSize {
   233  		return flow.ZeroID, errors.New("execution data root blob exceeds maximum allowed size")
   234  	}
   235  
   236  	rootBlob := blobs.NewBlob(buf.Bytes())
   237  	if blobCh != nil {
   238  		blobCh <- rootBlob
   239  	}
   240  
   241  	rootID, err := flow.CidToId(rootBlob.Cid())
   242  	if err != nil {
   243  		return flow.ZeroID, fmt.Errorf("failed to convert root blob cid to id: %w", err)
   244  	}
   245  
   246  	return rootID, nil
   247  }
   248  
   249  func (p *ExecutionDataCIDProvider) addChunkExecutionData(
   250  	ced *execution_data.ChunkExecutionData,
   251  	blobCh chan<- blobs.Blob,
   252  ) (cid.Cid, error) {
   253  	cids, err := p.addBlobs(ced, blobCh)
   254  	if err != nil {
   255  		return cid.Undef, fmt.Errorf("failed to add chunk execution data blobs: %w", err)
   256  	}
   257  
   258  	for {
   259  		if len(cids) == 1 {
   260  			return cids[0], nil
   261  		}
   262  
   263  		if cids, err = p.addBlobs(cids, blobCh); err != nil {
   264  			return cid.Undef, fmt.Errorf("failed to add cid blobs: %w", err)
   265  		}
   266  	}
   267  }
   268  
   269  // addBlobs serializes the given object, splits the serialized data into blobs, and sends them to the given channel.
   270  func (p *ExecutionDataCIDProvider) addBlobs(v interface{}, blobCh chan<- blobs.Blob) ([]cid.Cid, error) {
   271  	bcw := blobs.NewBlobChannelWriter(blobCh, p.maxBlobSize)
   272  	defer bcw.Close()
   273  
   274  	if err := p.serializer.Serialize(bcw, v); err != nil {
   275  		return nil, fmt.Errorf("failed to serialize object: %w", err)
   276  	}
   277  
   278  	if err := bcw.Flush(); err != nil {
   279  		return nil, fmt.Errorf("failed to flush blob channel writer: %w", err)
   280  	}
   281  
   282  	return bcw.CidsSent(), nil
   283  }