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

     1  package meter
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"github.com/onflow/flow-go/model/flow"
    10  )
    11  
    12  /*
    13  *  When merging meter interaction limits for a register from meter B to meter A
    14  *  - `RA[k]`  is the **R**ead of register with **k**ey in meter **A**
    15  *  - `WB[k]`  is the **W**rite of register with **k**ey in meter **B**
    16  *  The following rules apply:
    17  *
    18  *  1. `RB[k] != nil && WB[k] == nil`:
    19  *    1. `RA[k] ==nil && WA[k] == nil` -> do: `RA[k] = RB[k]`
    20  *    2. `RA[k] != nil && WA[k] == nil` -> `RA[k]` must be equal to `RB[k]` as B is reading the same register as A did. Nothing to do.
    21  *    3. `RA[k] == nil && WA[k] != nil`-> when register k is read in B the value is taken from the changed value that A metered as a write so no storage read happened in B, `RB[k]` must be equal to `WA[k]`. Nothing to do.
    22  *    4. `RA[k] != nil && WA[k] != nil` -> similar to 1.3. no storage read happened in B as the changed register was read from A.  `WA[k]` must be equal to `RB[k]`. Nothing to do
    23  *
    24  *  2.  `RB[k] == nil && WB[k] != nil`:
    25  *    1. `RA[k] ==nil && WA[k] == nil` -> do: `WA[k] = WB[k]`
    26  *    2. `RA[k] != nil && WA[k] == nil` -> do: `WA[k] = WB[k]`
    27  *    3. `RA[k] == nil && WA[k] != nil` -> the write in B is latest so that one should be used. do: `WA[k] = WB[k]`
    28  *    4. `RA[k] != nil && WA[k] != nil` -> the write in B is latest so that one should be used. do: `WA[k] = WB[k]`
    29  *
    30  *  3. `RB[k] != nil && WB[k] != nil`:
    31  *    1. `RA[k] ==nil && WA[k] == nil` -> do: `WA[k] = WB[k]` and `RA[k] = RB[k]`
    32  *    2. `RA[k] != nil && WA[k] == nil` -> `RA[k]` must be equal to `RB[k]` as B is reading the same register as A did. do: `WA[k] = WB[k]`
    33  *    3. `RA[k] == nil && WA[k] != nil` -> B read should be equal to A write, because the value of the register was taken from the changed value in A, `RB[k]` must be equal to `WA[k]`. do:  `WA[k] = WB[k]`
    34  *    4. `RA[k] != nil && WA[k] != nil` -> similar to 3.3. do:  `WA[k] = WB[k]`
    35  *
    36  *  We shouldn't do error checking in merging, so just assume that cases that shouldn't happen don't happen. The checks for those should be, and are, elsewhere.
    37  *
    38  *  in short this means that when merging we should do the following:
    39  *  - take reads from the parent unless the parents write and read is nil
    40  *  - take writes from the child unless nil
    41   */
    42  func TestInteractionMeter_Merge(t *testing.T) {
    43  	key := flow.RegisterID{
    44  		Owner: "owner",
    45  		Key:   "key",
    46  	}
    47  
    48  	value1 := []byte{1, 2, 3}
    49  	value1Size := getStorageKeyValueSize(key, value1)
    50  	value2 := []byte{4, 5, 6, 7}
    51  	value2Size := getStorageKeyValueSize(key, value2)
    52  	value3 := []byte{8, 9, 10, 11, 12}
    53  	value3Size := getStorageKeyValueSize(key, value3)
    54  	value4 := []byte{8, 9, 10, 11, 12, 13}
    55  	value4Size := getStorageKeyValueSize(key, value4)
    56  
    57  	type testCase struct {
    58  		Descripiton string
    59  
    60  		ParentReads flow.RegisterValue
    61  		ChildReads  flow.RegisterValue
    62  
    63  		ParentWrites flow.RegisterValue
    64  		ChildWrites  flow.RegisterValue
    65  
    66  		TotalReadShouldBe    uint64
    67  		TotalWrittenShouldBe uint64
    68  	}
    69  
    70  	cases := []testCase{
    71  		{
    72  			Descripiton: "no interaction",
    73  
    74  			ParentReads:          nil,
    75  			ParentWrites:         nil,
    76  			ChildReads:           nil,
    77  			ChildWrites:          nil,
    78  			TotalReadShouldBe:    0,
    79  			TotalWrittenShouldBe: 0,
    80  		},
    81  	}
    82  
    83  	desc := "child Reads, "
    84  	cases = append(cases,
    85  		testCase{
    86  			Descripiton: desc + "parent Nothing",
    87  
    88  			ParentReads:          nil,
    89  			ParentWrites:         nil,
    90  			ChildReads:           value1,
    91  			ChildWrites:          nil,
    92  			TotalReadShouldBe:    value1Size,
    93  			TotalWrittenShouldBe: 0,
    94  		},
    95  		testCase{
    96  			Descripiton: desc + "parent Reads",
    97  
    98  			ParentReads:          value1,
    99  			ParentWrites:         nil,
   100  			ChildReads:           value2,
   101  			ChildWrites:          nil,
   102  			TotalReadShouldBe:    value1Size,
   103  			TotalWrittenShouldBe: 0,
   104  		},
   105  		testCase{
   106  			Descripiton: desc + "parent Writes",
   107  
   108  			ParentReads:          nil,
   109  			ParentWrites:         value1,
   110  			ChildReads:           value2,
   111  			ChildWrites:          nil,
   112  			TotalReadShouldBe:    0,
   113  			TotalWrittenShouldBe: value1Size,
   114  		},
   115  		testCase{
   116  			Descripiton: desc + "parent Reads and Writes",
   117  
   118  			ParentReads:          value1,
   119  			ParentWrites:         value2,
   120  			ChildReads:           value3,
   121  			ChildWrites:          nil,
   122  			TotalReadShouldBe:    value1Size,
   123  			TotalWrittenShouldBe: value2Size,
   124  		},
   125  	)
   126  
   127  	desc = "child Writes, "
   128  	cases = append(cases,
   129  		testCase{
   130  			Descripiton: desc + "parent Nothing",
   131  
   132  			ParentReads:          nil,
   133  			ParentWrites:         nil,
   134  			ChildReads:           nil,
   135  			ChildWrites:          value1,
   136  			TotalReadShouldBe:    0,
   137  			TotalWrittenShouldBe: value1Size,
   138  		},
   139  		testCase{
   140  			Descripiton: desc + "parent Reads",
   141  
   142  			ParentReads:          value1,
   143  			ParentWrites:         nil,
   144  			ChildReads:           nil,
   145  			ChildWrites:          value2,
   146  			TotalReadShouldBe:    value1Size,
   147  			TotalWrittenShouldBe: value2Size,
   148  		},
   149  		testCase{
   150  			Descripiton: desc + "parent Writes",
   151  
   152  			ParentReads:          nil,
   153  			ParentWrites:         value1,
   154  			ChildReads:           nil,
   155  			ChildWrites:          value2,
   156  			TotalReadShouldBe:    0,
   157  			TotalWrittenShouldBe: value2Size,
   158  		},
   159  		testCase{
   160  			Descripiton: desc + "parent Reads and Writes",
   161  
   162  			ParentReads:          value1,
   163  			ParentWrites:         value2,
   164  			ChildReads:           nil,
   165  			ChildWrites:          value3,
   166  			TotalReadShouldBe:    value1Size,
   167  			TotalWrittenShouldBe: value3Size,
   168  		},
   169  	)
   170  
   171  	desc = "child Reads and Writes, "
   172  	cases = append(cases,
   173  		testCase{
   174  			Descripiton: desc + "parent Nothing",
   175  
   176  			ParentReads:          nil,
   177  			ParentWrites:         nil,
   178  			ChildReads:           value1,
   179  			ChildWrites:          value2,
   180  			TotalReadShouldBe:    value1Size,
   181  			TotalWrittenShouldBe: value2Size,
   182  		},
   183  		testCase{
   184  			Descripiton: desc + "parent Reads",
   185  
   186  			ParentReads:          value1,
   187  			ParentWrites:         nil,
   188  			ChildReads:           value2,
   189  			ChildWrites:          value3,
   190  			TotalReadShouldBe:    value1Size,
   191  			TotalWrittenShouldBe: value3Size,
   192  		},
   193  		testCase{
   194  			Descripiton: desc + "parent Writes",
   195  
   196  			ParentReads:          nil,
   197  			ParentWrites:         value1,
   198  			ChildReads:           value2, // this is a read from the parent
   199  			ChildWrites:          value3,
   200  			TotalReadShouldBe:    0,
   201  			TotalWrittenShouldBe: value3Size,
   202  		},
   203  		testCase{
   204  			Descripiton: desc + "parent Reads and Writes",
   205  
   206  			ParentReads:          value1,
   207  			ParentWrites:         value2,
   208  			ChildReads:           value3, // this is a read from the parent
   209  			ChildWrites:          value4,
   210  			TotalReadShouldBe:    value1Size,
   211  			TotalWrittenShouldBe: value4Size,
   212  		},
   213  	)
   214  
   215  	for i, c := range cases {
   216  		t.Run(fmt.Sprintf("case %d: %s", i, c.Descripiton), func(t *testing.T) {
   217  			parentMeter := NewInteractionMeter(DefaultInteractionMeterParameters())
   218  			childMeter := NewInteractionMeter(DefaultInteractionMeterParameters())
   219  
   220  			var err error
   221  			if c.ParentReads != nil {
   222  				err = parentMeter.MeterStorageRead(key, c.ParentReads, false)
   223  				require.NoError(t, err)
   224  			}
   225  
   226  			if c.ChildReads != nil {
   227  				err = childMeter.MeterStorageRead(key, c.ChildReads, false)
   228  				require.NoError(t, err)
   229  			}
   230  
   231  			if c.ParentWrites != nil {
   232  				err = parentMeter.MeterStorageWrite(key, c.ParentWrites, false)
   233  				require.NoError(t, err)
   234  			}
   235  
   236  			if c.ChildWrites != nil {
   237  				err = childMeter.MeterStorageWrite(key, c.ChildWrites, false)
   238  				require.NoError(t, err)
   239  			}
   240  
   241  			parentMeter.Merge(childMeter)
   242  
   243  			require.Equal(t, c.TotalReadShouldBe, parentMeter.TotalBytesReadFromStorage())
   244  			require.Equal(t, c.TotalWrittenShouldBe, parentMeter.TotalBytesWrittenToStorage())
   245  		})
   246  	}
   247  
   248  }