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 }