github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/fvm/meter/interaction_meter.go (about)

     1  package meter
     2  
     3  import (
     4  	"math"
     5  
     6  	"github.com/rs/zerolog/log"
     7  
     8  	"github.com/onflow/flow-go/fvm/errors"
     9  	"github.com/onflow/flow-go/model/flow"
    10  )
    11  
    12  type MeteredStorageInteractionMap map[flow.RegisterID]uint64
    13  
    14  type InteractionMeterParameters struct {
    15  	storageInteractionLimit uint64
    16  }
    17  
    18  func DefaultInteractionMeterParameters() InteractionMeterParameters {
    19  	return InteractionMeterParameters{
    20  		storageInteractionLimit: math.MaxUint64,
    21  	}
    22  }
    23  
    24  func (params MeterParameters) WithStorageInteractionLimit(
    25  	maxStorageInteractionLimit uint64,
    26  ) MeterParameters {
    27  	newParams := params
    28  	newParams.storageInteractionLimit = maxStorageInteractionLimit
    29  	return newParams
    30  }
    31  
    32  // InteractionMeter is a meter that tracks storage interaction
    33  // Only the first read of a given key is counted
    34  // Only the last write of a given key is counted
    35  type InteractionMeter struct {
    36  	params InteractionMeterParameters
    37  
    38  	reads  map[flow.RegisterID]uint64
    39  	writes map[flow.RegisterID]uint64
    40  
    41  	totalStorageBytesRead    uint64
    42  	totalStorageBytesWritten uint64
    43  }
    44  
    45  func NewInteractionMeter(params InteractionMeterParameters) InteractionMeter {
    46  	return InteractionMeter{
    47  		params: params,
    48  		reads:  make(map[flow.RegisterID]uint64),
    49  		writes: make(map[flow.RegisterID]uint64),
    50  	}
    51  }
    52  
    53  // MeterStorageRead captures storage read bytes count and returns an error
    54  // if it goes beyond the total interaction limit and limit is enforced
    55  func (m *InteractionMeter) MeterStorageRead(
    56  	storageKey flow.RegisterID,
    57  	value flow.RegisterValue,
    58  	enforceLimit bool,
    59  ) error {
    60  
    61  	// all reads are on a View which only read from storage at the first read of a given key
    62  	if _, ok := m.reads[storageKey]; !ok {
    63  		readByteSize := getStorageKeyValueSize(storageKey, value)
    64  		m.totalStorageBytesRead += readByteSize
    65  		m.reads[storageKey] = readByteSize
    66  	}
    67  
    68  	return m.checkStorageInteractionLimit(enforceLimit)
    69  }
    70  
    71  // MeterStorageWrite captures storage written bytes count and returns an error
    72  // if it goes beyond the total interaction limit and limit is enforced.
    73  // If a key is written multiple times, only the last write is counted.
    74  // If a key is written before it has been read, next time it will be read it will be from the view,
    75  // not from storage, so count it as read 0.
    76  func (m *InteractionMeter) MeterStorageWrite(
    77  	storageKey flow.RegisterID,
    78  	value flow.RegisterValue,
    79  	enforceLimit bool,
    80  ) error {
    81  	updateSize := getStorageKeyValueSize(storageKey, value)
    82  	m.replaceWrite(storageKey, updateSize)
    83  
    84  	if _, ok := m.reads[storageKey]; !ok {
    85  		// write without read, count as read 0 because next time you read it the written value
    86  		// will be returned from cache, so no interaction with storage will happen.
    87  		m.reads[storageKey] = 0
    88  	}
    89  
    90  	return m.checkStorageInteractionLimit(enforceLimit)
    91  }
    92  
    93  // replaceWrite replaces the write size of a given key with the new size, because
    94  // only the last write of a given key is counted towards the total interaction limit.
    95  // These are the only write usages of `m.totalStorageBytesWritten` and `m.writes`,
    96  // which means that `m.totalStorageBytesWritten` can never become negative.
    97  // oldSize is always <= m.totalStorageBytesWritten.
    98  func (m *InteractionMeter) replaceWrite(
    99  	k flow.RegisterID,
   100  	newSize uint64,
   101  ) {
   102  	totalBefore := m.totalStorageBytesWritten
   103  
   104  	// remove old write
   105  	oldSize := m.writes[k]
   106  	m.totalStorageBytesWritten -= oldSize
   107  
   108  	// sanity check
   109  	// this should never happen, but if it does, it should be fatal
   110  	if m.totalStorageBytesWritten > totalBefore {
   111  		log.Fatal().
   112  			Str("component", "interaction_meter").
   113  			Uint64("total", totalBefore).
   114  			Uint64("subtract", oldSize).
   115  			Msg("totalStorageBytesWritten would have become negative")
   116  	}
   117  
   118  	// add new write
   119  	m.writes[k] = newSize
   120  	m.totalStorageBytesWritten += newSize
   121  }
   122  
   123  func (m *InteractionMeter) checkStorageInteractionLimit(enforceLimit bool) error {
   124  	if enforceLimit &&
   125  		m.TotalBytesOfStorageInteractions() > m.params.storageInteractionLimit {
   126  		return errors.NewLedgerInteractionLimitExceededError(
   127  			m.TotalBytesOfStorageInteractions(), m.params.storageInteractionLimit)
   128  	}
   129  	return nil
   130  }
   131  
   132  // TotalBytesReadFromStorage returns total number of byte read from storage
   133  func (m *InteractionMeter) TotalBytesReadFromStorage() uint64 {
   134  	return m.totalStorageBytesRead
   135  }
   136  
   137  // TotalBytesWrittenToStorage returns total number of byte written to storage
   138  func (m *InteractionMeter) TotalBytesWrittenToStorage() uint64 {
   139  	return m.totalStorageBytesWritten
   140  }
   141  
   142  // TotalBytesOfStorageInteractions returns total number of byte read and written from/to storage
   143  func (m *InteractionMeter) TotalBytesOfStorageInteractions() uint64 {
   144  	return m.TotalBytesReadFromStorage() + m.TotalBytesWrittenToStorage()
   145  }
   146  
   147  func getStorageKeyValueSize(
   148  	storageKey flow.RegisterID,
   149  	value flow.RegisterValue,
   150  ) uint64 {
   151  	return uint64(len(storageKey.Owner) + len(storageKey.Key) + len(value))
   152  }
   153  
   154  func GetStorageKeyValueSizeForTesting(
   155  	storageKey flow.RegisterID,
   156  	value flow.RegisterValue,
   157  ) uint64 {
   158  	return getStorageKeyValueSize(storageKey, value)
   159  }
   160  
   161  func (m *InteractionMeter) GetStorageRWSizeMapForTesting() (
   162  	reads MeteredStorageInteractionMap,
   163  	writes MeteredStorageInteractionMap,
   164  ) {
   165  	return m.reads, m.writes
   166  }
   167  
   168  // Merge merges the child interaction meter into the parent interaction meter
   169  // Prioritise parent reads because they happened first
   170  // Prioritise child writes because they happened last
   171  func (m *InteractionMeter) Merge(child InteractionMeter) {
   172  	for key, value := range child.reads {
   173  		_, parentRead := m.reads[key]
   174  		if parentRead {
   175  			// avoid metering the same read more than once, because a second read
   176  			// is from the cache
   177  			continue
   178  		}
   179  
   180  		m.reads[key] = value
   181  		m.totalStorageBytesRead += value
   182  	}
   183  
   184  	for key, value := range child.writes {
   185  		m.replaceWrite(key, value)
   186  	}
   187  }