github.com/ava-labs/avalanchego@v1.11.11/chains/atomic/atomictest/shared_memory.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package atomictest 5 6 import ( 7 "math/rand" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 12 "github.com/ava-labs/avalanchego/chains/atomic" 13 "github.com/ava-labs/avalanchego/database" 14 "github.com/ava-labs/avalanchego/ids" 15 "github.com/ava-labs/avalanchego/utils/units" 16 ) 17 18 // SharedMemoryTests is a list of all shared memory tests 19 var SharedMemoryTests = []func(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 atomic.SharedMemory, db database.Database){ 20 TestSharedMemoryPutAndGet, 21 TestSharedMemoryLargePutGetAndRemove, 22 TestSharedMemoryIndexed, 23 TestSharedMemoryLargeIndexed, 24 TestSharedMemoryCantDuplicatePut, 25 TestSharedMemoryCantDuplicateRemove, 26 TestSharedMemoryCommitOnPut, 27 TestSharedMemoryCommitOnRemove, 28 TestSharedMemoryLargeBatchSize, 29 TestPutAndRemoveBatch, 30 } 31 32 func TestSharedMemoryPutAndGet(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 atomic.SharedMemory, _ database.Database) { 33 require := require.New(t) 34 35 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{{ 36 Key: []byte{0}, 37 Value: []byte{1}, 38 }}}})) 39 40 values, err := sm1.Get(chainID0, [][]byte{{0}}) 41 require.NoError(err) 42 require.Equal([][]byte{{1}}, values, "wrong values returned") 43 } 44 45 // TestSharedMemoryLargePutGetAndRemove tests to make sure that the interface 46 // can support large values. 47 func TestSharedMemoryLargePutGetAndRemove(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 atomic.SharedMemory, _ database.Database) { 48 require := require.New(t) 49 rand.Seed(0) 50 51 totalSize := 16 * units.MiB // 16 MiB 52 elementSize := 4 * units.KiB // 4 KiB 53 pairSize := 2 * elementSize // 8 KiB 54 55 b := make([]byte, totalSize) 56 _, err := rand.Read(b) // #nosec G404 57 require.NoError(err) 58 59 elems := []*atomic.Element{} 60 keys := [][]byte{} 61 for len(b) > pairSize { 62 key := b[:elementSize] 63 b = b[elementSize:] 64 65 value := b[:elementSize] 66 b = b[elementSize:] 67 68 elems = append(elems, &atomic.Element{ 69 Key: key, 70 Value: value, 71 }) 72 keys = append(keys, key) 73 } 74 75 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{ 76 chainID1: { 77 PutRequests: elems, 78 }, 79 })) 80 81 values, err := sm1.Get( 82 chainID0, 83 keys, 84 ) 85 require.NoError(err) 86 for i, value := range values { 87 require.Equal(elems[i].Value, value) 88 } 89 90 require.NoError(sm1.Apply(map[ids.ID]*atomic.Requests{ 91 chainID0: { 92 RemoveRequests: keys, 93 }, 94 })) 95 } 96 97 func TestSharedMemoryIndexed(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 atomic.SharedMemory, _ database.Database) { 98 require := require.New(t) 99 100 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{{ 101 Key: []byte{0}, 102 Value: []byte{1}, 103 Traits: [][]byte{ 104 {2}, 105 {3}, 106 }, 107 }}}})) 108 109 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{{ 110 Key: []byte{4}, 111 Value: []byte{5}, 112 Traits: [][]byte{ 113 {2}, 114 {3}, 115 }, 116 }}}})) 117 118 values, _, _, err := sm0.Indexed(chainID1, [][]byte{{2}}, nil, nil, 1) 119 require.NoError(err) 120 require.Empty(values, "wrong indexed values returned") 121 122 values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 0) 123 require.NoError(err) 124 require.Empty(values, "wrong indexed values returned") 125 126 values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 1) 127 require.NoError(err) 128 require.Equal([][]byte{{5}}, values, "wrong indexed values returned") 129 130 values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 2) 131 require.NoError(err) 132 require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned") 133 134 values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}}, nil, nil, 3) 135 require.NoError(err) 136 require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned") 137 138 values, _, _, err = sm1.Indexed(chainID0, [][]byte{{3}}, nil, nil, 3) 139 require.NoError(err) 140 require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned") 141 142 values, _, _, err = sm1.Indexed(chainID0, [][]byte{{2}, {3}}, nil, nil, 3) 143 require.NoError(err) 144 require.Equal([][]byte{{5}, {1}}, values, "wrong indexed values returned") 145 } 146 147 func TestSharedMemoryLargeIndexed(t *testing.T, chainID0, chainID1 ids.ID, sm0, sm1 atomic.SharedMemory, _ database.Database) { 148 require := require.New(t) 149 150 totalSize := 8 * units.MiB // 8 MiB 151 elementSize := 1 * units.KiB // 1 KiB 152 pairSize := 3 * elementSize // 3 KiB 153 154 b := make([]byte, totalSize) 155 _, err := rand.Read(b) // #nosec G404 156 require.NoError(err) 157 158 elems := []*atomic.Element{} 159 allTraits := [][]byte{} 160 for len(b) > pairSize { 161 key := b[:elementSize] 162 b = b[elementSize:] 163 164 value := b[:elementSize] 165 b = b[elementSize:] 166 167 traits := [][]byte{ 168 b[:elementSize], 169 } 170 allTraits = append(allTraits, traits...) 171 b = b[elementSize:] 172 173 elems = append(elems, &atomic.Element{ 174 Key: key, 175 Value: value, 176 Traits: traits, 177 }) 178 } 179 180 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: elems}})) 181 182 values, _, _, err := sm1.Indexed(chainID0, allTraits, nil, nil, len(elems)+1) 183 require.NoError(err) 184 require.Len(values, len(elems), "wrong number of values returned") 185 } 186 187 func TestSharedMemoryCantDuplicatePut(t *testing.T, _, chainID1 ids.ID, sm0, _ atomic.SharedMemory, _ database.Database) { 188 require := require.New(t) 189 190 err := sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{ 191 { 192 Key: []byte{0}, 193 Value: []byte{1}, 194 }, 195 { 196 Key: []byte{0}, 197 Value: []byte{2}, 198 }, 199 }}}) 200 // TODO: require error to be errDuplicatedOperation 201 require.Error(err) //nolint:forbidigo // currently returns grpc errors too 202 203 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{{ 204 Key: []byte{0}, 205 Value: []byte{1}, 206 }}}})) 207 208 err = sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{{ 209 Key: []byte{0}, 210 Value: []byte{1}, 211 }}}}) 212 // TODO: require error to be errDuplicatedOperation 213 require.Error(err) //nolint:forbidigo // currently returns grpc errors too 214 } 215 216 func TestSharedMemoryCantDuplicateRemove(t *testing.T, _, chainID1 ids.ID, sm0, _ atomic.SharedMemory, _ database.Database) { 217 require := require.New(t) 218 219 require.NoError(sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {RemoveRequests: [][]byte{{0}}}})) 220 221 err := sm0.Apply(map[ids.ID]*atomic.Requests{chainID1: {RemoveRequests: [][]byte{{0}}}}) 222 // TODO: require error to be errDuplicatedOperation 223 require.Error(err) //nolint:forbidigo // currently returns grpc errors too 224 } 225 226 func TestSharedMemoryCommitOnPut(t *testing.T, _, chainID1 ids.ID, sm0, _ atomic.SharedMemory, db database.Database) { 227 require := require.New(t) 228 229 require.NoError(db.Put([]byte{1}, []byte{2})) 230 231 batch := db.NewBatch() 232 233 require.NoError(batch.Put([]byte{0}, []byte{1})) 234 235 require.NoError(batch.Delete([]byte{1})) 236 237 require.NoError(sm0.Apply( 238 map[ids.ID]*atomic.Requests{chainID1: {PutRequests: []*atomic.Element{{ 239 Key: []byte{0}, 240 Value: []byte{1}, 241 }}}}, 242 batch, 243 )) 244 245 val, err := db.Get([]byte{0}) 246 require.NoError(err) 247 require.Equal([]byte{1}, val) 248 249 has, err := db.Has([]byte{1}) 250 require.NoError(err) 251 require.False(has) 252 } 253 254 func TestSharedMemoryCommitOnRemove(t *testing.T, _, chainID1 ids.ID, sm0, _ atomic.SharedMemory, db database.Database) { 255 require := require.New(t) 256 257 require.NoError(db.Put([]byte{1}, []byte{2})) 258 259 batch := db.NewBatch() 260 261 require.NoError(batch.Put([]byte{0}, []byte{1})) 262 263 require.NoError(batch.Delete([]byte{1})) 264 265 require.NoError(sm0.Apply( 266 map[ids.ID]*atomic.Requests{chainID1: {RemoveRequests: [][]byte{{0}}}}, 267 batch, 268 )) 269 270 val, err := db.Get([]byte{0}) 271 require.NoError(err) 272 require.Equal([]byte{1}, val) 273 274 has, err := db.Has([]byte{1}) 275 require.NoError(err) 276 require.False(has) 277 } 278 279 // TestPutAndRemoveBatch tests to make sure multiple put and remove requests work properly 280 func TestPutAndRemoveBatch(t *testing.T, chainID0, _ ids.ID, _, sm1 atomic.SharedMemory, db database.Database) { 281 require := require.New(t) 282 283 batch := db.NewBatch() 284 285 require.NoError(batch.Put([]byte{0}, []byte{1})) 286 287 batchChainsAndInputs := make(map[ids.ID]*atomic.Requests) 288 289 byteArr := [][]byte{{0}, {1}, {5}} 290 291 batchChainsAndInputs[chainID0] = &atomic.Requests{ 292 PutRequests: []*atomic.Element{{ 293 Key: []byte{2}, 294 Value: []byte{9}, 295 }}, 296 RemoveRequests: byteArr, 297 } 298 299 require.NoError(sm1.Apply(batchChainsAndInputs, batch)) 300 301 val, err := db.Get([]byte{0}) 302 require.NoError(err) 303 require.Equal([]byte{1}, val) 304 } 305 306 // TestSharedMemoryLargeBatchSize tests to make sure that the interface can 307 // support large batches. 308 func TestSharedMemoryLargeBatchSize(t *testing.T, _, chainID1 ids.ID, sm0, _ atomic.SharedMemory, db database.Database) { 309 require := require.New(t) 310 rand.Seed(0) 311 312 totalSize := 8 * units.MiB // 8 MiB 313 elementSize := 4 * units.KiB // 4 KiB 314 pairSize := 2 * elementSize // 8 KiB 315 316 bytes := make([]byte, totalSize) 317 _, err := rand.Read(bytes) // #nosec G404 318 require.NoError(err) 319 320 batch := db.NewBatch() 321 require.NotNil(batch) 322 323 initialBytes := bytes 324 for len(bytes) > pairSize { 325 key := bytes[:elementSize] 326 bytes = bytes[elementSize:] 327 328 value := bytes[:elementSize] 329 bytes = bytes[elementSize:] 330 331 require.NoError(batch.Put(key, value)) 332 } 333 334 require.NoError(db.Put([]byte{1}, []byte{2})) 335 336 require.NoError(batch.Put([]byte{0}, []byte{1})) 337 338 require.NoError(batch.Delete([]byte{1})) 339 340 require.NoError(sm0.Apply( 341 map[ids.ID]*atomic.Requests{chainID1: {RemoveRequests: [][]byte{{0}}}}, 342 batch, 343 )) 344 345 val, err := db.Get([]byte{0}) 346 require.NoError(err) 347 require.Equal([]byte{1}, val) 348 349 has, err := db.Has([]byte{1}) 350 require.NoError(err) 351 require.False(has) 352 353 batch.Reset() 354 355 bytes = initialBytes 356 for len(bytes) > pairSize { 357 key := bytes[:elementSize] 358 bytes = bytes[pairSize:] 359 360 require.NoError(batch.Delete(key)) 361 } 362 363 require.NoError(sm0.Apply( 364 map[ids.ID]*atomic.Requests{chainID1: {RemoveRequests: [][]byte{{1}}}}, 365 batch, 366 )) 367 368 batch.Reset() 369 370 bytes = initialBytes 371 for len(bytes) > pairSize { 372 key := bytes[:elementSize] 373 bytes = bytes[pairSize:] 374 375 require.NoError(batch.Delete(key)) 376 } 377 378 batchChainsAndInputs := make(map[ids.ID]*atomic.Requests) 379 380 byteArr := [][]byte{{30}, {40}, {50}} 381 382 batchChainsAndInputs[chainID1] = &atomic.Requests{ 383 PutRequests: []*atomic.Element{{ 384 Key: []byte{2}, 385 Value: []byte{9}, 386 }}, 387 RemoveRequests: byteArr, 388 } 389 390 require.NoError(sm0.Apply( 391 batchChainsAndInputs, 392 batch, 393 )) 394 }