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 }