mirror of
https://github.com/logos-storage/logos-storage-nim.git
synced 2026-01-14 19:33:10 +00:00
# Conflicts: # codex/contracts/market.nim # codex/validation.nim # tests/integration/testproofs.nim
331 lines
13 KiB
Nim
331 lines
13 KiB
Nim
import std/sequtils
|
|
import std/os
|
|
from std/times import getTime, toUnix
|
|
import pkg/chronicles
|
|
import codex/contracts/marketplace
|
|
import codex/contracts/deployment
|
|
import codex/periods
|
|
import ../contracts/time
|
|
import ../contracts/deployment
|
|
import ../codex/helpers/eventually
|
|
import ./twonodes
|
|
import ./multinodes
|
|
|
|
logScope:
|
|
topics = "test proofs"
|
|
|
|
twonodessuite "Proving integration test", debug1=false, debug2=false:
|
|
|
|
let validatorDir = getTempDir() / "CodexValidator"
|
|
|
|
var marketplace: Marketplace
|
|
var period: uint64
|
|
|
|
setup:
|
|
marketplace = Marketplace.new(Marketplace.address, provider)
|
|
period = (await marketplace.config()).proofs.period.truncate(uint64)
|
|
|
|
# Our Hardhat configuration does use automine, which means that time tracked by `provider.currentTime()` is not
|
|
# advanced until blocks are mined and that happens only when transaction is submitted.
|
|
# As we use in tests provider.currentTime() which uses block timestamp this can lead to synchronization issues.
|
|
await provider.advanceTime(1.u256)
|
|
|
|
proc waitUntilPurchaseIsStarted(proofProbability: uint64 = 3,
|
|
duration: uint64 = 100 * period,
|
|
expiry: uint64 = 30) {.async.} =
|
|
discard client2.postAvailability(
|
|
size=0xFFFFF,
|
|
duration=duration,
|
|
minPrice=300,
|
|
maxCollateral=200
|
|
)
|
|
let cid = client1.upload("some file contents")
|
|
let expiry = (await provider.currentTime()) + expiry.u256
|
|
let purchase = client1.requestStorage(
|
|
cid,
|
|
expiry=expiry,
|
|
duration=duration,
|
|
proofProbability=proofProbability,
|
|
collateral=100,
|
|
reward=400
|
|
)
|
|
check eventually client1.getPurchase(purchase){"state"} == %"started"
|
|
|
|
proc advanceToNextPeriod {.async.} =
|
|
let periodicity = Periodicity(seconds: period.u256)
|
|
let currentPeriod = periodicity.periodOf(await provider.currentTime())
|
|
let endOfPeriod = periodicity.periodEnd(currentPeriod)
|
|
await provider.advanceTimeTo(endOfPeriod + 1)
|
|
|
|
proc startValidator: NodeProcess =
|
|
startNode([
|
|
"--data-dir=" & validatorDir,
|
|
"--api-port=8089",
|
|
"--disc-port=8099",
|
|
"--validator",
|
|
"--eth-account=" & $accounts[2]
|
|
], debug = false)
|
|
|
|
proc stopValidator(node: NodeProcess) =
|
|
node.stop()
|
|
removeDir(validatorDir)
|
|
|
|
test "hosts submit periodic proofs for slots they fill":
|
|
await waitUntilPurchaseIsStarted(proofProbability=1)
|
|
var proofWasSubmitted = false
|
|
proc onProofSubmitted(event: ProofSubmitted) =
|
|
proofWasSubmitted = true
|
|
let subscription = await marketplace.subscribe(ProofSubmitted, onProofSubmitted)
|
|
await provider.advanceTime(period.u256)
|
|
check eventually proofWasSubmitted
|
|
await subscription.unsubscribe()
|
|
|
|
test "validator will mark proofs as missing":
|
|
let validator = startValidator()
|
|
await waitUntilPurchaseIsStarted(proofProbability=1)
|
|
|
|
node2.stop()
|
|
|
|
var slotWasFreed = false
|
|
proc onSlotFreed(event: SlotFreed) =
|
|
slotWasFreed = true
|
|
let subscription = await marketplace.subscribe(SlotFreed, onSlotFreed)
|
|
|
|
for _ in 0..<100:
|
|
if slotWasFreed:
|
|
break
|
|
else:
|
|
await advanceToNextPeriod()
|
|
await sleepAsync(1.seconds)
|
|
|
|
check slotWasFreed
|
|
|
|
await subscription.unsubscribe()
|
|
stopValidator(validator)
|
|
|
|
multinodesuite "Simulate invalid proofs",
|
|
StartNodes.init(clients=1'u, providers=0'u, validators=1'u),
|
|
DebugNodes.init(client=false, provider=false, validator=false):
|
|
|
|
var marketplace: Marketplace
|
|
var period: uint64
|
|
var proofSubmitted: Future[void]
|
|
var subscription: Subscription
|
|
var submitted: seq[seq[byte]]
|
|
var missed: UInt256
|
|
var slotId: SlotId
|
|
var validator: NodeProcess
|
|
let validatorDir = getTempDir() / "CodexValidator"
|
|
var periodDowntime: uint8
|
|
var downtime: seq[(Period, bool)]
|
|
var periodicity: Periodicity
|
|
|
|
setup:
|
|
let deployment = Deployment.init()
|
|
marketplace = Marketplace.new(!deployment.address(Marketplace), provider)
|
|
let config = await marketplace.config()
|
|
period = config.proofs.period.truncate(uint64)
|
|
periodDowntime = config.proofs.downtime
|
|
await provider.getSigner(accounts[0]).mint()
|
|
await provider.getSigner(accounts[2]).mint()
|
|
await provider.getSigner(accounts[2]).deposit()
|
|
proofSubmitted = newFuture[void]("proofSubmitted")
|
|
proc onProofSubmitted(event: ProofSubmitted) =
|
|
submitted.add(event.proof)
|
|
proofSubmitted.complete()
|
|
proofSubmitted = newFuture[void]("proofSubmitted")
|
|
subscription = await marketplace.subscribe(ProofSubmitted, onProofSubmitted)
|
|
missed = 0.u256
|
|
slotId = SlotId(array[32, byte].default) # ensure we aren't reusing from prev test
|
|
downtime = @[]
|
|
periodicity = Periodicity(seconds: period.u256)
|
|
|
|
teardown:
|
|
await subscription.unsubscribe()
|
|
|
|
proc getCurrentPeriod(): Future[Period] {.async.} =
|
|
return periodicity.periodOf(await provider.currentTime())
|
|
|
|
proc waitUntilPurchaseIsStarted(proofProbability: uint64 = 1,
|
|
duration: uint64 = 12 * period,
|
|
expiry: uint64 = 4 * period,
|
|
failEveryNProofs: uint): Future[Period] {.async.} =
|
|
|
|
if clients().len < 1 or providers().len < 1:
|
|
raiseAssert("must start at least one client and one provider")
|
|
|
|
let client = clients()[0].restClient
|
|
let storageProvider = providers()[0].restClient
|
|
|
|
# The last period is the period in which the slot is freed, and therefore
|
|
# proofs cannot be submitted. That means that the duration must go an
|
|
# additional period longer to allow for invalid proofs to be submitted in
|
|
# the second to last period and counted as missed in the last period.
|
|
let dur = duration + (1 * period)
|
|
|
|
discard storageProvider.postAvailability(
|
|
size=0xFFFFF,
|
|
duration=dur,
|
|
minPrice=300
|
|
)
|
|
let cid = client.upload("some file contents " & $ getTime().toUnix)
|
|
let expiry = (await provider.currentTime()) + expiry.u256
|
|
let purchase = client.requestStorage(
|
|
cid,
|
|
expiry=expiry,
|
|
duration=dur,
|
|
proofProbability=proofProbability,
|
|
reward=400
|
|
)
|
|
check eventually client.getPurchase(purchase){"state"} == %"started"
|
|
debug "purchase state", state = client.getPurchase(purchase){"state"}
|
|
let requestId = RequestId.fromHex client.getPurchase(purchase){"requestId"}.getStr
|
|
slotId = slotId(requestId, 0.u256)
|
|
return await getCurrentPeriod()
|
|
|
|
proc inDowntime: Future[bool] {.async.} =
|
|
var periodPointer = await marketplace.getPointer(slotId)
|
|
return periodPointer < periodDowntime
|
|
|
|
proc advanceToNextPeriod {.async.} =
|
|
let periodicity = Periodicity(seconds: period.u256)
|
|
let currentPeriod = periodicity.periodOf(await provider.currentTime())
|
|
let endOfPeriod = periodicity.periodEnd(currentPeriod)
|
|
await provider.advanceTimeTo(endOfPeriod + 1)
|
|
|
|
proc advanceToNextPeriodStart {.async.} =
|
|
let currentPeriod = await getCurrentPeriod()
|
|
let startOfPeriod = periodicity.periodEnd(currentPeriod + 1)
|
|
await provider.advanceTimeTo(startOfPeriod)
|
|
|
|
|
|
proc advanceToCurrentPeriodEnd {.async.} =
|
|
let currentPeriod = await getCurrentPeriod()
|
|
let endOfPeriod = periodicity.periodEnd(currentPeriod)
|
|
await provider.advanceTimeTo(endOfPeriod)
|
|
|
|
proc recordDowntime() {.async.} =
|
|
let currentPeriod = await getCurrentPeriod()
|
|
let isInDowntime = await inDowntime()
|
|
downtime.add (currentPeriod, isInDowntime)
|
|
debug "downtime recorded", currentPeriod, isInDowntime
|
|
|
|
proc waitUntilSlotNoLongerFilled() {.async.} =
|
|
var i = 0
|
|
# await recordDowntime()
|
|
# await advanceToNextPeriodStart()
|
|
while (await marketplace.slotState(slotId)) == SlotState.Filled:
|
|
let currentPeriod = await getCurrentPeriod()
|
|
# await advanceToCurrentPeriodEnd()
|
|
await advanceToNextPeriod()
|
|
debug "--------------- PERIOD START ---------------", currentPeriod
|
|
await recordDowntime()
|
|
# debug "--------------- PERIOD END ---------------", currentPeriod
|
|
await sleepAsync(1.seconds) # let validation happen
|
|
debug "Checked previous period for missed proofs", missedProofs = $(await marketplace.missingProofs(slotId))
|
|
i += 1
|
|
# downtime.del downtime.len - 1 # remove last downtime as it is an additional proving round adding to duration for checking proofs, and we are offset by one as we are checking previous periods
|
|
missed = await marketplace.missingProofs(slotId)
|
|
debug "Total missed proofs", missed
|
|
|
|
proc expectedInvalid(startPeriod: Period,
|
|
failEveryNProofs: uint,
|
|
periods: Positive): seq[(Period, bool)] =
|
|
# Create a seq of bools where each bool represents a proving round.
|
|
# If true, an invalid proof should have been sent.
|
|
var p = startPeriod + 1.u256
|
|
var invalid: seq[(Period, bool)] = @[]
|
|
if failEveryNProofs == 0:
|
|
for i in 0..<periods:
|
|
p += 1.u256
|
|
invalid.add (p, false)
|
|
return invalid
|
|
|
|
for j in 0..<(periods div failEveryNProofs.int):
|
|
for i in 0..<failEveryNProofs - 1'u:
|
|
p += 1.u256
|
|
invalid.add (p, false)
|
|
p += 1.u256
|
|
invalid.add (p, true)
|
|
# add remaining falses
|
|
for k in 0..<(periods mod failEveryNProofs.int):
|
|
p += 1.u256
|
|
invalid.add (p, false)
|
|
# var proofs = false.repeat(failEveryNProofs - 1)
|
|
# proofs.add true
|
|
# proofs = proofs.cycle(periods div failEveryNProofs.int)
|
|
# .concat(false.repeat(periods mod failEveryNProofs.int)) # leftover falses
|
|
# return proofs
|
|
return invalid
|
|
|
|
proc expectedMissed(startPeriod: Period, failEveryNProofs: uint, periods: Positive): int =
|
|
# Intersects a seq of expected invalid proofs (true = invalid proof) with
|
|
# a seq of bools indicating a period was in pointer downtime (true = period
|
|
# was in pointer downtime).
|
|
# We can only expect an invalid proof to have been submitted if the slot
|
|
# was accepting proofs in that period, meaning it cannot be in downtime.
|
|
# eg failEveryNProofs = 3, periods = 2, the invalid proofs seq will be:
|
|
# @[false, false, true, false, false, true]
|
|
# If we hit downtime in the second half of running our test, the
|
|
# downtime seq might be @[false, false, false, true, true, true]
|
|
# When these two are intersected such that invalid is true and downtime is false,
|
|
# the result would be @[false, false, false, false, false, true], or 1 total
|
|
# invalid proof that should be marked as missed.
|
|
let invalid = expectedInvalid(startPeriod, failEveryNProofs, periods)
|
|
var expectedMissed = 0
|
|
for i in 0..<invalid.len:
|
|
let (invalidPeriod, isInvalidProof) = invalid[i]
|
|
for j in 0..<downtime.len:
|
|
let (downtimePeriod, isDowntime) = downtime[j]
|
|
if invalidPeriod == downtimePeriod:
|
|
if isInvalidProof and not isDowntime:
|
|
inc expectedMissed
|
|
break
|
|
# if invalid[i] == true and downtime[i] == false:
|
|
# expectedMissed += 1
|
|
|
|
debug "expectedMissed invalid / downtime", invalid, downtime, expectedMissed
|
|
return expectedMissed
|
|
|
|
test "simulates invalid proof for every proofs":
|
|
let failEveryNProofs = 1'u
|
|
let totalProofs = 12
|
|
startProviderNode(failEveryNProofs)
|
|
|
|
let startPeriod = await waitUntilPurchaseIsStarted(duration=totalProofs.uint * period,
|
|
failEveryNProofs = failEveryNProofs)
|
|
await waitUntilSlotNoLongerFilled()
|
|
|
|
check missed.truncate(int) == expectedMissed(startPeriod, failEveryNProofs, totalProofs)
|
|
|
|
# test "simulates invalid proof every N proofs":
|
|
# let failEveryNProofs = 3'u
|
|
# let totalProofs = 12
|
|
# startProviderNode(failEveryNProofs)
|
|
|
|
# await waitUntilPurchaseIsStarted(duration=totalProofs.uint * period,
|
|
# failEveryNProofs = failEveryNProofs)
|
|
# await waitUntilSlotNoLongerFilled()
|
|
|
|
# check missed.truncate(int) == expectedMissed(failEveryNProofs, totalProofs)
|
|
|
|
# test "does not simulate invalid proofs when --simulate-failed-proofs is 0":
|
|
# let failEveryNProofs = 0'u
|
|
# let totalProofs = 12
|
|
# startProviderNode(failEveryNProofs)
|
|
|
|
# await waitUntilPurchaseIsStarted(duration=totalProofs.uint * period,
|
|
# failEveryNProofs = failEveryNProofs)
|
|
# await waitUntilSlotNoLongerFilled()
|
|
|
|
# check missed.truncate(int) == expectedMissed(failEveryNProofs, totalProofs)
|
|
|
|
# test "does not simulate invalid proof when --simulate-failed-proofs is 0":
|
|
# # 1. instantiate node manually (startNode) with --simulate-failed-proofs=0
|
|
# # 2. check that the number of expected missed proofs is 0
|
|
# check 1 == 1
|
|
|
|
# test "does not simulate invalid proof when chainId is 1":
|
|
# # 1. instantiate node manually (startNode) with --simulate-failed-proofs=3
|
|
# # 2. check that the number of expected missed proofs is 0
|
|
# check 1 == 1 |