github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/stdmap/incorporated_result_seals_test.go (about) 1 package stdmap 2 3 import ( 4 "testing" 5 6 "github.com/stretchr/testify/assert" 7 "github.com/stretchr/testify/require" 8 9 "pgregory.net/rapid" 10 11 "github.com/onflow/flow-go/model/flow" 12 "github.com/onflow/flow-go/utils/unittest" 13 ) 14 15 // icrSealsMachine is a description of a state machine for testing IncorporatedresultSeals 16 type icrSealsMachine struct { 17 icrs *IncorporatedResultSeals // icrSeals being tested 18 state []*flow.IncorporatedResultSeal // model of the icrSeals 19 } 20 21 // init is an action for initializing a icrSeals instance. 22 func (m *icrSealsMachine) init(t *rapid.T) { 23 m.icrs = NewIncorporatedResultSeals(1000) 24 } 25 26 // Add is a conditional action which adds an item to the icrSeals. 27 func (m *icrSealsMachine) Add(t *rapid.T) { 28 i := rapid.Uint64().Draw(t, "i") 29 30 seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) { 31 s.Header.Height = i 32 }) 33 34 _, err := m.icrs.Add(seal) 35 require.NoError(t, err) 36 37 // we do not re-add already present seals 38 unmet := true 39 for _, v := range m.state { 40 if v.ID() == seal.ID() { 41 unmet = false 42 } 43 } 44 45 if unmet && seal.Header.Height >= m.icrs.lowestHeight { 46 m.state = append(m.state, seal) 47 } 48 } 49 50 // Prune is a Conditional action that removes elements of height strictly lower than its argument 51 func (m *icrSealsMachine) PruneUpToHeight(t *rapid.T) { 52 h := rapid.Uint64().Draw(t, "h") 53 err := m.icrs.PruneUpToHeight(h) 54 if h >= m.icrs.lowestHeight { 55 require.NoError(t, err) 56 assert.Equal(t, m.icrs.lowestHeight, h) 57 } 58 59 filtered_state := make([]*flow.IncorporatedResultSeal, 0) 60 for _, v := range m.state { 61 if v.Header.Height >= h { 62 filtered_state = append(filtered_state, v) 63 } 64 } 65 m.state = filtered_state 66 } 67 68 // Get is an action that retrieves an element from the icrSeals 69 func (m *icrSealsMachine) Get(t *rapid.T) { 70 n := len(m.state) 71 // skip if the store is empty 72 if n == 0 { 73 return 74 } 75 i := rapid.IntRange(0, n-1).Draw(t, "i") 76 77 s := m.state[i] 78 actual, ok := m.icrs.ByID(s.ID()) 79 require.True(t, ok) 80 require.Equal(t, s, actual) 81 82 } 83 84 // GetUnknown is an action that removes an unknown element from the icrSeals 85 // This mostly tests ByID has no insertion side-effects 86 func (m *icrSealsMachine) GetUnknown(t *rapid.T) { 87 n := len(m.state) 88 // skip if the store is empty 89 if n == 0 { 90 return 91 } 92 i := rapid.IntRange(0, n-1).Draw(t, "i") 93 seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) { 94 s.Header.Height = uint64(i) 95 }) 96 97 // check seal is unknown 98 unknown := true 99 for _, v := range m.state { 100 if v.ID() == seal.ID() { 101 unknown = false 102 } 103 } 104 105 if unknown { 106 _, found := m.icrs.ByID(seal.ID()) 107 require.False(t, found) 108 } 109 // no modification of state 110 111 } 112 113 // Remove is a conditional action that removes a known element from the icrSeals 114 func (m *icrSealsMachine) Remove(t *rapid.T) { 115 n := len(m.state) 116 // skip if the store is empty 117 if n == 0 { 118 return 119 } 120 i := rapid.IntRange(0, n-1).Draw(t, "i") 121 122 s := m.state[i] 123 ok := m.icrs.Remove(s.ID()) 124 require.True(t, ok) 125 126 // remove m[i], we don't care about ordering here 127 m.state[n-1], m.state[i] = m.state[i], m.state[n-1] 128 m.state = m.state[:n-1] 129 130 } 131 132 // RemoveUnknown is an action that removes an unknown element from the icrSeals 133 // This mostly tests Remove has no insertion side-effects 134 func (m *icrSealsMachine) RemoveUnknown(t *rapid.T) { 135 n := len(m.state) 136 // skip if the store is empty 137 if n == 0 { 138 return 139 } 140 i := rapid.IntRange(0, n-1).Draw(t, "i") 141 seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) { 142 s.Header.Height = uint64(i) 143 }) 144 145 // check seal is unknown 146 unknown := true 147 for _, v := range m.state { 148 if v.ID() == seal.ID() { 149 unknown = false 150 } 151 } 152 153 if unknown { 154 removed := m.icrs.Remove(seal.ID()) 155 require.False(t, removed) 156 } 157 // no modification of state 158 159 } 160 161 // Check runs after every action and verifies that all required invariants hold. 162 func (m *icrSealsMachine) Check(t *rapid.T) { 163 if int(m.icrs.Size()) != len(m.state) { 164 t.Fatalf("store size mismatch: %v vs expected %v", m.icrs.Size(), len(m.state)) 165 } 166 assert.ElementsMatch(t, m.icrs.All(), m.state) 167 } 168 169 // Run the icrSeals state machine and test it against its model 170 func TestIcrs(t *testing.T) { 171 rapid.Check(t, func(t *rapid.T) { 172 sm := new(icrSealsMachine) 173 sm.init(t) 174 t.Repeat(rapid.StateMachineActions(sm)) 175 }) 176 } 177 178 func TestIncorporatedResultSeals(t *testing.T) { 179 t.Parallel() 180 181 // after adding one receipt, should be able to query it back by previous result id 182 // after removing, should not be able to query it back. 183 t.Run("add remove get", func(t *testing.T) { 184 pool := NewIncorporatedResultSeals(1000) 185 186 seal := unittest.IncorporatedResultSeal.Fixture() 187 188 ok, err := pool.Add(seal) 189 require.NoError(t, err) 190 require.True(t, ok) 191 192 actual, ok := pool.ByID(seal.ID()) 193 require.True(t, ok) 194 require.Equal(t, seal, actual) 195 196 deleted := pool.Remove(seal.ID()) 197 require.True(t, deleted) 198 199 _, ok = pool.ByID(seal.ID()) 200 require.False(t, ok) 201 }) 202 203 t.Run("add 100 prune by height", func(t *testing.T) { 204 pool := NewIncorporatedResultSeals(1000) 205 206 seals := make([]*flow.IncorporatedResultSeal, 0, 100) 207 for i := 0; i < 100; i++ { 208 seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) { 209 s.Header.Height = uint64(i) 210 }) 211 212 seals = append(seals, seal) 213 } 214 215 for _, seal := range seals { 216 ok, err := pool.Add(seal) 217 require.NoError(t, err) 218 require.True(t, ok, "seal at height %d was not added", seal.Header.Height) 219 } 220 verifyPresent(t, pool, seals...) 221 222 err := pool.PruneUpToHeight(5) 223 require.NoError(t, err) 224 verifyAbsent(t, pool, seals[:5]...) 225 verifyPresent(t, pool, seals[5:]...) 226 227 err = pool.PruneUpToHeight(10) 228 require.NoError(t, err) 229 verifyAbsent(t, pool, seals[:10]...) 230 verifyPresent(t, pool, seals[10:]...) 231 }) 232 233 t.Run("prune with some nonexisting heights", func(t *testing.T) { 234 pool := NewIncorporatedResultSeals(1000) 235 236 // create seals, but NOT for heights 2 and 3 237 seals := make([]*flow.IncorporatedResultSeal, 0, 6) 238 for _, h := range []uint64{0, 1, 4, 5, 6, 7} { 239 seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) { 240 s.Header.Height = h 241 }) 242 seals = append(seals, seal) 243 ok, err := pool.Add(seal) 244 require.NoError(t, err) 245 require.True(t, ok) 246 } 247 248 err := pool.PruneUpToHeight(5) 249 require.NoError(t, err) 250 verifyAbsent(t, pool, seals[:3]...) 251 verifyPresent(t, pool, seals[3:]...) 252 }) 253 } 254 255 func verifyPresent(t *testing.T, pool *IncorporatedResultSeals, seals ...*flow.IncorporatedResultSeal) { 256 for _, seal := range seals { 257 _, ok := pool.ByID(seal.ID()) 258 require.True(t, ok, "seal at height %d should be in mempool", seal.Header.Height) 259 } 260 } 261 262 func verifyAbsent(t *testing.T, pool *IncorporatedResultSeals, seals ...*flow.IncorporatedResultSeal) { 263 for _, seal := range seals { 264 _, ok := pool.ByID(seal.ID()) 265 require.False(t, ok, "seal at height %d should not be in mempool", seal.Header.Height) 266 } 267 }