Proving (#66)
* Add Proving object, which maintains contract id's to watch * [proving] invoke callback when proof is required # Conflicts: # dagger/por/timing/periods.nim # dagger/por/timing/prooftiming.nim * [proving] check proof requirements for all our contracts # Conflicts: # tests/dagger/helpers/mockprooftiming.nim * Update vendor/dagger-contracts * [proving] call onProofRequired() when proof is required soon * [proving] stop checking contracts that have ended * [proving] Remove duplicated funcs * [proving] Implement ProofTiming on top of smart contract * [proving] Fix race condition in waitUntilNextPeriod() Sometimes waitUntilNextPeriod would take a while to determine the current period, leading to unexpected results. Splits waitUntilNextPeriod() into getCurrentPeriod() and waitUntilPeriod(), to ensure that we're really waiting for the period that we think we're waiting for.
This commit is contained in:
parent
2e8b39cf7c
commit
03fa370624
|
@ -3,9 +3,11 @@ import contracts/offers
|
|||
import contracts/storage
|
||||
import contracts/deployment
|
||||
import contracts/market
|
||||
import contracts/prooftiming
|
||||
|
||||
export requests
|
||||
export offers
|
||||
export storage
|
||||
export deployment
|
||||
export market
|
||||
export prooftiming
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
import ../por/timing/prooftiming
|
||||
import ./storage
|
||||
|
||||
export prooftiming
|
||||
|
||||
type
|
||||
OnChainProofTiming* = ref object of ProofTiming
|
||||
storage: Storage
|
||||
pollInterval*: Duration
|
||||
|
||||
const DefaultPollInterval = 3.seconds
|
||||
|
||||
proc new*(_: type OnChainProofTiming, storage: Storage): OnChainProofTiming =
|
||||
OnChainProofTiming(storage: storage, pollInterval: DefaultPollInterval)
|
||||
|
||||
method periodicity*(timing: OnChainProofTiming): Future[Periodicity] {.async.} =
|
||||
let period = await timing.storage.proofPeriod()
|
||||
return Periodicity(seconds: period)
|
||||
|
||||
method getCurrentPeriod*(timing: OnChainProofTiming): Future[Period] {.async.} =
|
||||
let periodicity = await timing.periodicity()
|
||||
let blk = !await timing.storage.provider.getBlock(BlockTag.latest)
|
||||
return periodicity.periodOf(blk.timestamp)
|
||||
|
||||
method waitUntilPeriod*(timing: OnChainProofTiming,
|
||||
period: Period) {.async.} =
|
||||
while (await timing.getCurrentPeriod()) < period:
|
||||
await sleepAsync(timing.pollInterval)
|
||||
|
||||
method isProofRequired*(timing: OnChainProofTiming,
|
||||
id: ContractId): Future[bool] {.async.} =
|
||||
return await timing.storage.isProofRequired(id)
|
||||
|
||||
method willProofBeRequired*(timing: OnChainProofTiming,
|
||||
id: ContractId): Future[bool] {.async.} =
|
||||
return await timing.storage.willProofBeRequired(id)
|
||||
|
||||
method getProofEnd*(timing: OnChainProofTiming,
|
||||
id: ContractId): Future[UInt256] {.async.} =
|
||||
return await timing.storage.proofEnd(id)
|
|
@ -43,6 +43,7 @@ proc proofTimeout*(storage: Storage): UInt256 {.contract, view.}
|
|||
proc proofEnd*(storage: Storage, id: Id): UInt256 {.contract, view.}
|
||||
proc missingProofs*(storage: Storage, id: Id): UInt256 {.contract, view.}
|
||||
proc isProofRequired*(storage: Storage, id: Id): bool {.contract, view.}
|
||||
proc willProofBeRequired*(storage: Storage, id: Id): bool {.contract, view.}
|
||||
proc getChallenge*(storage: Storage, id: Id): array[32, byte] {.contract, view.}
|
||||
proc getPointer*(storage: Storage, id: Id): uint8 {.contract, view.}
|
||||
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import pkg/chronos
|
||||
import pkg/stint
|
||||
import ./periods
|
||||
|
||||
export chronos
|
||||
export stint
|
||||
export periods
|
||||
|
||||
type
|
||||
ProofTiming* = ref object of RootObj
|
||||
ContractId* = array[32, byte]
|
||||
|
||||
method periodicity*(proofTiming: ProofTiming):
|
||||
Future[Periodicity] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method getCurrentPeriod*(proofTiming: ProofTiming):
|
||||
Future[Period] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method waitUntilPeriod*(proofTiming: ProofTiming,
|
||||
period: Period) {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method isProofRequired*(proofTiming: ProofTiming,
|
||||
id: ContractId): Future[bool] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method willProofBeRequired*(proofTiming: ProofTiming,
|
||||
id: ContractId): Future[bool] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
||||
|
||||
method getProofEnd*(proofTiming: ProofTiming,
|
||||
id: ContractId): Future[UInt256] {.base, async.} =
|
||||
raiseAssert("not implemented")
|
|
@ -0,0 +1,50 @@
|
|||
import std/sets
|
||||
import std/times
|
||||
import pkg/upraises
|
||||
import pkg/questionable
|
||||
import ./por/timing/prooftiming
|
||||
|
||||
export sets
|
||||
export prooftiming
|
||||
|
||||
type
|
||||
Proving* = ref object
|
||||
timing: ProofTiming
|
||||
stopped: bool
|
||||
contracts*: HashSet[ContractId]
|
||||
onProofRequired: ?OnProofRequired
|
||||
OnProofRequired* = proc (id: ContractId) {.gcsafe, upraises:[].}
|
||||
|
||||
func new*(_: type Proving, timing: ProofTiming): Proving =
|
||||
Proving(timing: timing)
|
||||
|
||||
proc `onProofRequired=`*(proving: Proving, callback: OnProofRequired) =
|
||||
proving.onProofRequired = some callback
|
||||
|
||||
func add*(proving: Proving, id: ContractId) =
|
||||
proving.contracts.incl(id)
|
||||
|
||||
proc removeEndedContracts(proving: Proving) {.async.} =
|
||||
let now = getTime().toUnix().u256
|
||||
var ended: HashSet[ContractId]
|
||||
for id in proving.contracts:
|
||||
if now >= (await proving.timing.getProofEnd(id)):
|
||||
ended.incl(id)
|
||||
proving.contracts.excl(ended)
|
||||
|
||||
proc run(proving: Proving) {.async.} =
|
||||
while not proving.stopped:
|
||||
let currentPeriod = await proving.timing.getCurrentPeriod()
|
||||
await proving.removeEndedContracts()
|
||||
for id in proving.contracts:
|
||||
if (await proving.timing.isProofRequired(id)) or
|
||||
(await proving.timing.willProofBeRequired(id)):
|
||||
if callback =? proving.onProofRequired:
|
||||
callback(id)
|
||||
await proving.timing.waitUntilPeriod(currentPeriod + 1)
|
||||
|
||||
proc start*(proving: Proving) =
|
||||
asyncSpawn proving.run()
|
||||
|
||||
proc stop*(proving: Proving) =
|
||||
proving.stopped = true
|
|
@ -3,10 +3,10 @@ import pkg/chronos
|
|||
import pkg/nimcrypto
|
||||
import dagger/contracts
|
||||
import dagger/contracts/testtoken
|
||||
import dagger/por/timing/periods
|
||||
import ./ethertest
|
||||
import ./examples
|
||||
import ./time
|
||||
import ./periods
|
||||
|
||||
ethersuite "Storage contracts":
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
import ./ethertest
|
||||
import dagger/contracts
|
||||
import ./examples
|
||||
import ./time
|
||||
|
||||
ethersuite "On-Chain Proof Timing":
|
||||
|
||||
var timing: OnChainProofTiming
|
||||
var storage: Storage
|
||||
|
||||
setup:
|
||||
let deployment = deployment()
|
||||
storage = Storage.new(!deployment.address(Storage), provider)
|
||||
timing = OnChainProofTiming.new(storage)
|
||||
|
||||
test "can retrieve proof periodicity":
|
||||
let periodicity = await timing.periodicity()
|
||||
let periodLength = await storage.proofPeriod()
|
||||
check periodicity.seconds == periodLength
|
||||
|
||||
test "supports waiting until next period":
|
||||
let periodicity = await timing.periodicity()
|
||||
let currentPeriod = await timing.getCurrentPeriod()
|
||||
|
||||
let pollInterval = 200.milliseconds
|
||||
timing.pollInterval = pollInterval
|
||||
|
||||
proc waitForPoll {.async.} =
|
||||
await sleepAsync(pollInterval * 2)
|
||||
|
||||
let future = timing.waitUntilPeriod(currentPeriod + 1)
|
||||
|
||||
check not future.completed
|
||||
|
||||
await provider.advanceTimeTo(periodicity.periodEnd(currentPeriod))
|
||||
await waitForPoll()
|
||||
|
||||
check future.completed
|
||||
|
||||
test "supports checking whether proof is required now":
|
||||
check (await timing.isProofRequired(ContractId.example)) == false
|
||||
|
||||
test "supports checking whether proof is required soon":
|
||||
check (await timing.willProofBeRequired(ContractId.example)) == false
|
||||
|
||||
test "retrieves proof end time":
|
||||
check (await timing.getProofEnd(ContractId.example)) == 0.u256
|
|
@ -0,0 +1,72 @@
|
|||
import std/sets
|
||||
import std/tables
|
||||
import pkg/dagger/por/timing/prooftiming
|
||||
|
||||
type
|
||||
MockProofTiming* = ref object of ProofTiming
|
||||
periodicity: Periodicity
|
||||
currentPeriod: Period
|
||||
waiting: Table[Period, seq[Future[void]]]
|
||||
proofsRequired: HashSet[ContractId]
|
||||
proofsToBeRequired: HashSet[ContractId]
|
||||
proofEnds: Table[ContractId, UInt256]
|
||||
|
||||
const DefaultPeriodLength = 10.u256
|
||||
|
||||
func new*(_: type MockProofTiming): MockProofTiming =
|
||||
MockProofTiming(periodicity: Periodicity(seconds: DefaultPeriodLength))
|
||||
|
||||
func setPeriodicity*(mock: MockProofTiming, periodicity: Periodicity) =
|
||||
mock.periodicity = periodicity
|
||||
|
||||
method periodicity*(mock: MockProofTiming): Future[Periodicity] {.async.} =
|
||||
return mock.periodicity
|
||||
|
||||
proc setProofRequired*(mock: MockProofTiming, id: ContractId, required: bool) =
|
||||
if required:
|
||||
mock.proofsRequired.incl(id)
|
||||
else:
|
||||
mock.proofsRequired.excl(id)
|
||||
|
||||
method isProofRequired*(mock: MockProofTiming,
|
||||
id: ContractId): Future[bool] {.async.} =
|
||||
return mock.proofsRequired.contains(id)
|
||||
|
||||
proc setProofToBeRequired*(mock: MockProofTiming, id: ContractId, required: bool) =
|
||||
if required:
|
||||
mock.proofsToBeRequired.incl(id)
|
||||
else:
|
||||
mock.proofsToBeRequired.excl(id)
|
||||
|
||||
method willProofBeRequired*(mock: MockProofTiming,
|
||||
id: ContractId): Future[bool] {.async.} =
|
||||
return mock.proofsToBeRequired.contains(id)
|
||||
|
||||
proc setProofEnd*(mock: MockProofTiming, id: ContractId, proofEnd: UInt256) =
|
||||
mock.proofEnds[id] = proofEnd
|
||||
|
||||
method getProofEnd*(mock: MockProofTiming,
|
||||
id: ContractId): Future[UInt256] {.async.} =
|
||||
if mock.proofEnds.hasKey(id):
|
||||
return mock.proofEnds[id]
|
||||
else:
|
||||
return UInt256.high
|
||||
|
||||
proc advanceToPeriod*(mock: MockProofTiming, period: Period) =
|
||||
doAssert period >= mock.currentPeriod
|
||||
for key in mock.waiting.keys:
|
||||
if key <= period:
|
||||
for future in mock.waiting[key]:
|
||||
future.complete()
|
||||
mock.waiting[key] = @[]
|
||||
|
||||
method getCurrentPeriod*(mock: MockProofTiming): Future[Period] {.async.} =
|
||||
return mock.currentPeriod
|
||||
|
||||
method waitUntilPeriod*(mock: MockProofTiming, period: Period) {.async.} =
|
||||
if period > mock.currentPeriod:
|
||||
let future = Future[void]()
|
||||
if not mock.waiting.hasKey(period):
|
||||
mock.waiting[period] = @[]
|
||||
mock.waiting[period].add(future)
|
||||
await future
|
|
@ -0,0 +1,91 @@
|
|||
from std/times import getTime, toUnix
|
||||
import pkg/asynctest
|
||||
import pkg/chronos
|
||||
import pkg/dagger/proving
|
||||
import ./helpers/mockprooftiming
|
||||
import ./examples
|
||||
|
||||
suite "Proving":
|
||||
|
||||
var proving: Proving
|
||||
var timing: MockProofTiming
|
||||
|
||||
setup:
|
||||
timing = MockProofTiming.new()
|
||||
proving = Proving.new(timing)
|
||||
proving.start()
|
||||
|
||||
teardown:
|
||||
proving.stop()
|
||||
|
||||
proc advanceToNextPeriod(timing: MockProofTiming) {.async.} =
|
||||
let current = await timing.getCurrentPeriod()
|
||||
timing.advanceToPeriod(current + 1)
|
||||
await sleepAsync(1.milliseconds)
|
||||
|
||||
test "maintains a list of contract ids to watch":
|
||||
let id1, id2 = ContractId.example
|
||||
check proving.contracts.len == 0
|
||||
proving.add(id1)
|
||||
check proving.contracts.contains(id1)
|
||||
proving.add(id2)
|
||||
check proving.contracts.contains(id1)
|
||||
check proving.contracts.contains(id2)
|
||||
|
||||
test "removes duplicate contract ids":
|
||||
let id = ContractId.example
|
||||
proving.add(id)
|
||||
proving.add(id)
|
||||
check proving.contracts.len == 1
|
||||
|
||||
test "invokes callback when proof is required":
|
||||
let id = ContractId.example
|
||||
proving.add(id)
|
||||
var called: bool
|
||||
proc onProofRequired(id: ContractId) =
|
||||
called = true
|
||||
proving.onProofRequired = onProofRequired
|
||||
timing.setProofRequired(id, true)
|
||||
await timing.advanceToNextPeriod()
|
||||
check called
|
||||
|
||||
test "callback receives id of contract for which proof is required":
|
||||
let id1, id2 = ContractId.example
|
||||
proving.add(id1)
|
||||
proving.add(id2)
|
||||
var callbackIds: seq[ContractId]
|
||||
proc onProofRequired(id: ContractId) =
|
||||
callbackIds.add(id)
|
||||
proving.onProofRequired = onProofRequired
|
||||
timing.setProofRequired(id1, true)
|
||||
await timing.advanceToNextPeriod()
|
||||
check callbackIds == @[id1]
|
||||
timing.setProofRequired(id1, false)
|
||||
timing.setProofRequired(id2, true)
|
||||
await timing.advanceToNextPeriod()
|
||||
check callbackIds == @[id1, id2]
|
||||
|
||||
test "invokes callback when proof is about to be required":
|
||||
let id = ContractId.example
|
||||
proving.add(id)
|
||||
var called: bool
|
||||
proc onProofRequired(id: ContractId) =
|
||||
called = true
|
||||
proving.onProofRequired = onProofRequired
|
||||
timing.setProofRequired(id, false)
|
||||
timing.setProofToBeRequired(id, true)
|
||||
await timing.advanceToNextPeriod()
|
||||
check called
|
||||
|
||||
test "stops watching when contract has ended":
|
||||
let id = ContractId.example
|
||||
proving.add(id)
|
||||
timing.setProofEnd(id, getTime().toUnix().u256)
|
||||
await timing.advanceToNextPeriod()
|
||||
var called: bool
|
||||
proc onProofRequired(id: ContractId) =
|
||||
called = true
|
||||
proving.onProofRequired = onProofRequired
|
||||
timing.setProofRequired(id, true)
|
||||
await timing.advanceToNextPeriod()
|
||||
check not called
|
|
@ -1,5 +1,6 @@
|
|||
import ./contracts/testCollateral
|
||||
import ./contracts/testContracts
|
||||
import ./contracts/testMarket
|
||||
import ./contracts/testProofTiming
|
||||
|
||||
{.warning[UnusedImport]:off.}
|
||||
|
|
|
@ -8,6 +8,7 @@ import ./dagger/teststorestream
|
|||
import ./dagger/testpurchasing
|
||||
import ./dagger/testsales
|
||||
import ./dagger/testerasure
|
||||
import ./dagger/testproving
|
||||
|
||||
# to check that everything compiles
|
||||
import ../dagger
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 0587c2d585bef88575b10c63a4eddb756b5dde2e
|
||||
Subproject commit 29b5775951e774cb170b23cb6772cd7f1e7b5499
|
Loading…
Reference in New Issue