391 lines
13 KiB
Nim
391 lines
13 KiB
Nim
import std/random
|
|
import std/sequtils
|
|
|
|
import pkg/questionable
|
|
import pkg/questionable/results
|
|
import pkg/chronos
|
|
import pkg/datastore
|
|
|
|
import pkg/codex/stores
|
|
import pkg/codex/errors
|
|
import pkg/codex/sales
|
|
import pkg/codex/utils/json
|
|
|
|
import ../../asynctest
|
|
import ../examples
|
|
import ../helpers
|
|
|
|
const CONCURRENCY_TESTS_COUNT = 1000
|
|
|
|
asyncchecksuite "Reservations module":
|
|
var
|
|
repo: RepoStore
|
|
repoDs: Datastore
|
|
metaDs: Datastore
|
|
reservations: Reservations
|
|
let
|
|
repoTmp = TempLevelDb.new()
|
|
metaTmp = TempLevelDb.new()
|
|
|
|
setup:
|
|
randomize(1.int64) # create reproducible results
|
|
repoDs = repoTmp.newDb()
|
|
metaDs = metaTmp.newDb()
|
|
repo = RepoStore.new(repoDs, metaDs)
|
|
reservations = Reservations.new(repo)
|
|
|
|
teardown:
|
|
await repoTmp.destroyDb()
|
|
await metaTmp.destroyDb()
|
|
|
|
proc createAvailability(): Availability =
|
|
let example = Availability.example
|
|
let totalSize = rand(100000..200000)
|
|
let availability = waitFor reservations.createAvailability(
|
|
totalSize.u256,
|
|
example.duration,
|
|
example.minPrice,
|
|
example.maxCollateral
|
|
)
|
|
return availability.get
|
|
|
|
proc createReservation(availability: Availability): Reservation =
|
|
let size = rand(1..<availability.freeSize.truncate(int))
|
|
let reservation = waitFor reservations.createReservation(
|
|
availability.id,
|
|
size.u256,
|
|
RequestId.example,
|
|
UInt256.example
|
|
)
|
|
return reservation.get
|
|
|
|
test "availability can be serialised and deserialised":
|
|
let availability = Availability.example
|
|
let serialised = %availability
|
|
check Availability.fromJson(serialised).get == availability
|
|
|
|
test "has no availability initially":
|
|
check (await reservations.all(Availability)).get.len == 0
|
|
|
|
test "generates unique ids for storage availability":
|
|
let availability1 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256, 5.u256)
|
|
let availability2 = Availability.init(1.u256, 2.u256, 3.u256, 4.u256, 5.u256)
|
|
check availability1.id != availability2.id
|
|
|
|
test "can reserve available storage":
|
|
let availability = createAvailability()
|
|
check availability.id != AvailabilityId.default
|
|
|
|
test "creating availability reserves bytes in repo":
|
|
let orig = repo.available.uint
|
|
let availability = createAvailability()
|
|
check repo.available.uint == (orig.u256 - availability.freeSize).truncate(uint)
|
|
|
|
test "can get all availabilities":
|
|
let availability1 = createAvailability()
|
|
let availability2 = createAvailability()
|
|
let availabilities = !(await reservations.all(Availability))
|
|
check:
|
|
# perform unordered checks
|
|
availabilities.len == 2
|
|
availabilities.contains(availability1)
|
|
availabilities.contains(availability2)
|
|
|
|
test "reserved availability exists":
|
|
let availability = createAvailability()
|
|
|
|
let exists = await reservations.exists(availability.key.get)
|
|
|
|
check exists
|
|
|
|
test "reservation can be created":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
check reservation.id != ReservationId.default
|
|
|
|
test "can get all reservations":
|
|
let availability1 = createAvailability()
|
|
let availability2 = createAvailability()
|
|
let reservation1 = createReservation(availability1)
|
|
let reservation2 = createReservation(availability2)
|
|
let availabilities = !(await reservations.all(Availability))
|
|
let reservations = !(await reservations.all(Reservation))
|
|
check:
|
|
# perform unordered checks
|
|
availabilities.len == 2
|
|
reservations.len == 2
|
|
reservations.contains(reservation1)
|
|
reservations.contains(reservation2)
|
|
|
|
test "can get reservations of specific availability":
|
|
let availability1 = createAvailability()
|
|
let availability2 = createAvailability()
|
|
let reservation1 = createReservation(availability1)
|
|
let reservation2 = createReservation(availability2)
|
|
let reservations = !(await reservations.all(Reservation, availability1.id))
|
|
|
|
check:
|
|
# perform unordered checks
|
|
reservations.len == 1
|
|
reservations.contains(reservation1)
|
|
not reservations.contains(reservation2)
|
|
|
|
test "cannot create reservation with non-existant availability":
|
|
let availability = Availability.example
|
|
let created = await reservations.createReservation(
|
|
availability.id,
|
|
UInt256.example,
|
|
RequestId.example,
|
|
UInt256.example
|
|
)
|
|
check created.isErr
|
|
check created.error of NotExistsError
|
|
|
|
test "cannot create reservation larger than availability size":
|
|
let availability = createAvailability()
|
|
let created = await reservations.createReservation(
|
|
availability.id,
|
|
availability.totalSize + 1,
|
|
RequestId.example,
|
|
UInt256.example
|
|
)
|
|
check created.isErr
|
|
check created.error of BytesOutOfBoundsError
|
|
|
|
test "cannot create reservation larger than availability size - concurrency test":
|
|
proc concurrencyTest(): Future[void] {.async.} =
|
|
let availability = createAvailability()
|
|
let one = reservations.createReservation(
|
|
availability.id,
|
|
availability.totalSize - 1,
|
|
RequestId.example,
|
|
UInt256.example
|
|
)
|
|
|
|
let two = reservations.createReservation(
|
|
availability.id,
|
|
availability.totalSize,
|
|
RequestId.example,
|
|
UInt256.example
|
|
)
|
|
|
|
let oneResult = await one
|
|
let twoResult = await two
|
|
|
|
check oneResult.isErr or twoResult.isErr
|
|
if oneResult.isErr:
|
|
check oneResult.error of BytesOutOfBoundsError
|
|
if twoResult.isErr:
|
|
check twoResult.error of BytesOutOfBoundsError
|
|
|
|
var futures: seq[Future[void]]
|
|
for _ in 1..CONCURRENCY_TESTS_COUNT:
|
|
futures.add(concurrencyTest())
|
|
|
|
await allFuturesThrowing(futures)
|
|
|
|
|
|
test "creating reservation reduces availability size":
|
|
let availability = createAvailability()
|
|
let orig = availability.freeSize
|
|
let reservation = createReservation(availability)
|
|
let key = availability.id.key.get
|
|
let updated = (await reservations.get(key, Availability)).get
|
|
check updated.freeSize == orig - reservation.size
|
|
|
|
test "can check if reservation exists":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
let key = reservation.key.get
|
|
check await reservations.exists(key)
|
|
|
|
test "non-existant availability does not exist":
|
|
let key = AvailabilityId.example.key.get
|
|
check not (await reservations.exists(key))
|
|
|
|
test "non-existant reservation does not exist":
|
|
let key = key(ReservationId.example, AvailabilityId.example).get
|
|
check not (await reservations.exists(key))
|
|
|
|
test "can check if availability exists":
|
|
let availability = createAvailability()
|
|
let key = availability.key.get
|
|
check await reservations.exists(key)
|
|
|
|
test "can delete reservation":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
check isOk (await reservations.deleteReservation(
|
|
reservation.id, reservation.availabilityId)
|
|
)
|
|
let key = reservation.key.get
|
|
check not (await reservations.exists(key))
|
|
|
|
test "deleting reservation returns bytes back to availability":
|
|
let availability = createAvailability()
|
|
let orig = availability.freeSize
|
|
let reservation = createReservation(availability)
|
|
discard await reservations.deleteReservation(
|
|
reservation.id, reservation.availabilityId
|
|
)
|
|
let key = availability.key.get
|
|
let updated = !(await reservations.get(key, Availability))
|
|
check updated.freeSize == orig
|
|
|
|
test "calling returnBytesToAvailability returns bytes back to availability":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
let orig = availability.freeSize - reservation.size
|
|
let origQuota = repo.quotaReservedBytes
|
|
let returnedBytes = reservation.size + 200.u256
|
|
|
|
check isOk await reservations.returnBytesToAvailability(
|
|
reservation.availabilityId, reservation.id, returnedBytes
|
|
)
|
|
|
|
let key = availability.key.get
|
|
let updated = !(await reservations.get(key, Availability))
|
|
|
|
check updated.freeSize > orig
|
|
check (updated.freeSize - orig) == 200.u256
|
|
check (repo.quotaReservedBytes - origQuota) == 200.NBytes
|
|
|
|
test "update releases quota when lowering size":
|
|
let
|
|
availability = createAvailability()
|
|
origQuota = repo.quotaReservedBytes
|
|
availability.totalSize = availability.totalSize - 100
|
|
|
|
check isOk await reservations.update(availability)
|
|
check (origQuota - repo.quotaReservedBytes) == 100.NBytes
|
|
|
|
test "update reserves quota when growing size":
|
|
let
|
|
availability = createAvailability()
|
|
origQuota = repo.quotaReservedBytes
|
|
availability.totalSize = availability.totalSize + 100
|
|
|
|
check isOk await reservations.update(availability)
|
|
check (repo.quotaReservedBytes - origQuota) == 100.NBytes
|
|
|
|
test "reservation can be partially released":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
check isOk await reservations.release(
|
|
reservation.id,
|
|
reservation.availabilityId,
|
|
1
|
|
)
|
|
let key = reservation.key.get
|
|
let updated = !(await reservations.get(key, Reservation))
|
|
check updated.size == reservation.size - 1
|
|
|
|
test "cannot release more bytes than size of reservation":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
let updated = await reservations.release(
|
|
reservation.id,
|
|
reservation.availabilityId,
|
|
(reservation.size + 1).truncate(uint)
|
|
)
|
|
check updated.isErr
|
|
check updated.error of BytesOutOfBoundsError
|
|
|
|
test "cannot release bytes from non-existant reservation":
|
|
let availability = createAvailability()
|
|
let reservation = createReservation(availability)
|
|
let updated = await reservations.release(
|
|
ReservationId.example,
|
|
availability.id,
|
|
1
|
|
)
|
|
check updated.isErr
|
|
check updated.error of NotExistsError
|
|
|
|
test "onAvailabilityAdded called when availability is created":
|
|
var added: Availability
|
|
reservations.onAvailabilityAdded = proc(a: Availability) {.async.} =
|
|
added = a
|
|
|
|
let availability = createAvailability()
|
|
|
|
check added == availability
|
|
|
|
test "onAvailabilityAdded called when availability size is increased":
|
|
var availability = createAvailability()
|
|
var added: Availability
|
|
reservations.onAvailabilityAdded = proc(a: Availability) {.async.} =
|
|
added = a
|
|
availability.freeSize += 1.u256
|
|
discard await reservations.update(availability)
|
|
|
|
check added == availability
|
|
|
|
test "onAvailabilityAdded is not called when availability size is decreased":
|
|
var availability = createAvailability()
|
|
var called = false
|
|
reservations.onAvailabilityAdded = proc(a: Availability) {.async.} =
|
|
called = true
|
|
availability.freeSize -= 1.u256
|
|
discard await reservations.update(availability)
|
|
|
|
check not called
|
|
|
|
test "availabilities can be found":
|
|
let availability = createAvailability()
|
|
|
|
let found = await reservations.findAvailability(
|
|
availability.freeSize,
|
|
availability.duration,
|
|
availability.minPrice,
|
|
availability.maxCollateral)
|
|
|
|
check found.isSome
|
|
check found.get == availability
|
|
|
|
test "non-matching availabilities are not found":
|
|
let availability = createAvailability()
|
|
|
|
let found = await reservations.findAvailability(
|
|
availability.freeSize + 1,
|
|
availability.duration,
|
|
availability.minPrice,
|
|
availability.maxCollateral)
|
|
|
|
check found.isNone
|
|
|
|
test "non-existant availability cannot be found":
|
|
let availability = Availability.example
|
|
let found = (await reservations.findAvailability(
|
|
availability.freeSize,
|
|
availability.duration,
|
|
availability.minPrice,
|
|
availability.maxCollateral
|
|
))
|
|
check found.isNone
|
|
|
|
test "non-existant availability cannot be retrieved":
|
|
let key = AvailabilityId.example.key.get
|
|
let got = await reservations.get(key, Availability)
|
|
check got.error of NotExistsError
|
|
|
|
test "can get available bytes in repo":
|
|
check reservations.available == DefaultQuotaBytes.uint
|
|
|
|
test "reports quota available to be reserved":
|
|
check reservations.hasAvailable(DefaultQuotaBytes.uint - 1)
|
|
|
|
test "reports quota not available to be reserved":
|
|
check not reservations.hasAvailable(DefaultQuotaBytes.uint + 1)
|
|
|
|
test "fails to create availability with size that is larger than available quota":
|
|
let created = await reservations.createAvailability(
|
|
(DefaultQuotaBytes.uint + 1).u256,
|
|
UInt256.example,
|
|
UInt256.example,
|
|
UInt256.example
|
|
)
|
|
check created.isErr
|
|
check created.error of ReserveFailedError
|
|
check created.error.parent of QuotaNotEnoughError
|