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

     1  package stdmap
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	"github.com/onflow/flow-go/model/chunks"
     8  	"github.com/onflow/flow-go/model/flow"
     9  	"github.com/onflow/flow-go/model/verification"
    10  	"github.com/onflow/flow-go/module/mempool"
    11  )
    12  
    13  // ChunkRequests is an implementation of in-memory storage for maintaining chunk requests data objects.
    14  //
    15  // In this implementation, the ChunkRequests
    16  // wraps the ChunkDataPackRequests around an internal ChunkRequestStatus data object, and maintains the wrapped
    17  // version in memory.
    18  type ChunkRequests struct {
    19  	*Backend
    20  }
    21  
    22  func NewChunkRequests(limit uint) *ChunkRequests {
    23  	return &ChunkRequests{
    24  		Backend: NewBackend(WithLimit(limit)),
    25  	}
    26  }
    27  
    28  func toChunkRequestStatus(entity flow.Entity) *chunkRequestStatus {
    29  	status, ok := entity.(*chunkRequestStatus)
    30  	if !ok {
    31  		panic(fmt.Sprintf("could not convert the entity into chunk status from the mempool: %v", entity))
    32  	}
    33  	return status
    34  }
    35  
    36  // RequestHistory returns the number of times the chunk has been requested,
    37  // last time the chunk has been requested, and the retryAfter duration of the
    38  // underlying request status of this chunk.
    39  //
    40  // The last boolean parameter returns whether a chunk request for this chunk ID
    41  // exists in memory-pool.
    42  func (cs *ChunkRequests) RequestHistory(chunkID flow.Identifier) (uint64, time.Time, time.Duration, bool) {
    43  	var lastAttempt time.Time
    44  	var retryAfter time.Duration
    45  	var attempts uint64
    46  
    47  	err := cs.Backend.Run(func(backdata mempool.BackData) error {
    48  		entity, ok := backdata.ByID(chunkID)
    49  		if !ok {
    50  			return fmt.Errorf("request does not exist for chunk %x", chunkID)
    51  		}
    52  
    53  		request := toChunkRequestStatus(entity)
    54  		lastAttempt = request.LastAttempt
    55  		retryAfter = request.RetryAfter
    56  		attempts = request.Attempt
    57  		return nil
    58  	})
    59  
    60  	return attempts, lastAttempt, retryAfter, err == nil
    61  }
    62  
    63  // Add provides insertion functionality into the memory pool.
    64  // The insertion is only successful if there is no duplicate chunk request for the same
    65  // tuple of (chunkID, resultID, chunkIndex).
    66  func (cs *ChunkRequests) Add(request *verification.ChunkDataPackRequest) bool {
    67  	err := cs.Backend.Run(func(backdata mempool.BackData) error {
    68  		entity, exists := backdata.ByID(request.ChunkID)
    69  		chunkLocatorID := request.Locator.ID()
    70  
    71  		if !exists {
    72  			locators := make(chunks.LocatorMap)
    73  			locators[chunkLocatorID] = &request.Locator
    74  
    75  			// no chunk request status exists for this chunk ID, hence initiating one.
    76  			status := &chunkRequestStatus{
    77  				Locators:    locators,
    78  				RequestInfo: request.ChunkDataPackRequestInfo,
    79  			}
    80  			added := backdata.Add(request.ChunkID, status)
    81  			if !added {
    82  				return fmt.Errorf("potential race condition in adding chunk requests")
    83  			}
    84  			return nil
    85  		}
    86  
    87  		status := toChunkRequestStatus(entity)
    88  		if _, ok := status.Locators[chunkLocatorID]; ok {
    89  			return fmt.Errorf("chunk request exists with same locator (result_id=%x, chunk_index=%d)", request.Locator.ResultID, request.Locator.Index)
    90  		}
    91  
    92  		status.Locators[chunkLocatorID] = &request.Locator
    93  		status.RequestInfo.Agrees = status.RequestInfo.Agrees.Union(request.Agrees)
    94  		status.RequestInfo.Disagrees = status.RequestInfo.Disagrees.Union(request.Disagrees)
    95  		status.RequestInfo.Targets = status.RequestInfo.Targets.Union(request.Targets)
    96  
    97  		backdata.Add(request.ChunkID, status)
    98  		return nil
    99  	})
   100  
   101  	return err == nil
   102  }
   103  
   104  // Remove provides deletion functionality from the memory pool.
   105  // If there is a chunk request with this ID, Remove removes it and returns true.
   106  // Otherwise it returns false.
   107  func (cs *ChunkRequests) Remove(chunkID flow.Identifier) bool {
   108  	return cs.Backend.Remove(chunkID)
   109  }
   110  
   111  // PopAll atomically returns all locators associated with this chunk ID while clearing out the
   112  // chunk request status for this chunk id.
   113  // Boolean return value indicates whether there are requests in the memory pool associated
   114  // with chunk ID.
   115  func (cs *ChunkRequests) PopAll(chunkID flow.Identifier) (chunks.LocatorMap, bool) {
   116  	var locators map[flow.Identifier]*chunks.Locator
   117  
   118  	err := cs.Backend.Run(func(backdata mempool.BackData) error {
   119  		entity, exists := backdata.ByID(chunkID)
   120  		if !exists {
   121  			return fmt.Errorf("not exist")
   122  		}
   123  		locators = toChunkRequestStatus(entity).Locators
   124  
   125  		_, removed := backdata.Remove(chunkID)
   126  		if !removed {
   127  			return fmt.Errorf("potential race condition on removing chunk request from mempool")
   128  		}
   129  
   130  		return nil
   131  	})
   132  
   133  	if err != nil {
   134  		return nil, false
   135  	}
   136  
   137  	return locators, true
   138  }
   139  
   140  // IncrementAttempt increments the Attempt field of the corresponding status of the
   141  // chunk request in memory pool that has the specified chunk ID.
   142  // If such chunk ID does not exist in the memory pool, it returns false.
   143  //
   144  // The increments are done atomically, thread-safe, and in isolation.
   145  func (cs *ChunkRequests) IncrementAttempt(chunkID flow.Identifier) bool {
   146  	err := cs.Backend.Run(func(backdata mempool.BackData) error {
   147  		entity, exists := backdata.ByID(chunkID)
   148  		if !exists {
   149  			return fmt.Errorf("not exist")
   150  		}
   151  		chunk := toChunkRequestStatus(entity)
   152  		chunk.Attempt++
   153  		chunk.LastAttempt = time.Now()
   154  		return nil
   155  	})
   156  
   157  	return err == nil
   158  }
   159  
   160  // All returns all chunk requests stored in this memory pool.
   161  func (cs *ChunkRequests) All() verification.ChunkDataPackRequestInfoList {
   162  	all := cs.Backend.All()
   163  	requestInfoList := verification.ChunkDataPackRequestInfoList{}
   164  	for _, entity := range all {
   165  		requestInfo := toChunkRequestStatus(entity).RequestInfo
   166  		requestInfoList = append(requestInfoList, &requestInfo)
   167  	}
   168  	return requestInfoList
   169  }
   170  
   171  // UpdateRequestHistory updates the request history of the specified chunk ID. If the update was successful, i.e.,
   172  // the updater returns true, the result of update is committed to the mempool, and the time stamp of the chunk request
   173  // is updated to the current time. Otherwise, it aborts and returns false.
   174  //
   175  // It returns the updated request history values.
   176  //
   177  // The updates under this method are atomic, thread-safe, and done in isolation.
   178  func (cs *ChunkRequests) UpdateRequestHistory(chunkID flow.Identifier, updater mempool.ChunkRequestHistoryUpdaterFunc) (uint64, time.Time, time.Duration, bool) {
   179  	var lastAttempt time.Time
   180  	var retryAfter time.Duration
   181  	var attempts uint64
   182  
   183  	err := cs.Backend.Run(func(backdata mempool.BackData) error {
   184  		entity, exists := backdata.ByID(chunkID)
   185  		if !exists {
   186  			return fmt.Errorf("not exist")
   187  		}
   188  		status := toChunkRequestStatus(entity)
   189  
   190  		var ok bool
   191  		attempts, retryAfter, ok = updater(status.Attempt, status.RetryAfter)
   192  		if !ok {
   193  			return fmt.Errorf("updater failed")
   194  		}
   195  		lastAttempt = time.Now()
   196  
   197  		// updates underlying request
   198  		status.LastAttempt = lastAttempt
   199  		status.RetryAfter = retryAfter
   200  		status.Attempt = attempts
   201  
   202  		return nil
   203  	})
   204  
   205  	return attempts, lastAttempt, retryAfter, err == nil
   206  }
   207  
   208  // Size returns total number of chunk requests in the memory pool.
   209  func (cs ChunkRequests) Size() uint {
   210  	return cs.Backend.Size()
   211  }
   212  
   213  // chunkRequestStatus is an internal data type for ChunkRequests mempool. It acts as a wrapper for ChunkDataRequests, maintaining
   214  // some auxiliary attributes that are internal to ChunkRequests.
   215  type chunkRequestStatus struct {
   216  	Locators    map[flow.Identifier]*chunks.Locator // keeps locators by their chunk id.
   217  	RequestInfo verification.ChunkDataPackRequestInfo
   218  	LastAttempt time.Time     // timestamp of last request dispatched for this chunk id.
   219  	RetryAfter  time.Duration // interval until request should be retried.
   220  	Attempt     uint64        // number of times this chunk request has been dispatched in the network.
   221  }
   222  
   223  func (c chunkRequestStatus) ID() flow.Identifier {
   224  	return c.RequestInfo.ChunkID
   225  }
   226  
   227  func (c chunkRequestStatus) Checksum() flow.Identifier {
   228  	return c.RequestInfo.ChunkID
   229  }