* 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:
markspanbroek 2022-04-08 23:58:16 +02:00 committed by GitHub
parent 2e8b39cf7c
commit 03fa370624
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 342 additions and 2 deletions

View File

@ -3,9 +3,11 @@ import contracts/offers
import contracts/storage import contracts/storage
import contracts/deployment import contracts/deployment
import contracts/market import contracts/market
import contracts/prooftiming
export requests export requests
export offers export offers
export storage export storage
export deployment export deployment
export market export market
export prooftiming

View File

@ -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)

View File

@ -43,6 +43,7 @@ proc proofTimeout*(storage: Storage): UInt256 {.contract, view.}
proc proofEnd*(storage: Storage, id: Id): UInt256 {.contract, view.} proc proofEnd*(storage: Storage, id: Id): UInt256 {.contract, view.}
proc missingProofs*(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 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 getChallenge*(storage: Storage, id: Id): array[32, byte] {.contract, view.}
proc getPointer*(storage: Storage, id: Id): uint8 {.contract, view.} proc getPointer*(storage: Storage, id: Id): uint8 {.contract, view.}

View File

@ -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")

50
dagger/proving.nim Normal file
View File

@ -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

View File

@ -3,10 +3,10 @@ import pkg/chronos
import pkg/nimcrypto import pkg/nimcrypto
import dagger/contracts import dagger/contracts
import dagger/contracts/testtoken import dagger/contracts/testtoken
import dagger/por/timing/periods
import ./ethertest import ./ethertest
import ./examples import ./examples
import ./time import ./time
import ./periods
ethersuite "Storage contracts": ethersuite "Storage contracts":

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -1,5 +1,6 @@
import ./contracts/testCollateral import ./contracts/testCollateral
import ./contracts/testContracts import ./contracts/testContracts
import ./contracts/testMarket import ./contracts/testMarket
import ./contracts/testProofTiming
{.warning[UnusedImport]:off.} {.warning[UnusedImport]:off.}

View File

@ -8,6 +8,7 @@ import ./dagger/teststorestream
import ./dagger/testpurchasing import ./dagger/testpurchasing
import ./dagger/testsales import ./dagger/testsales
import ./dagger/testerasure import ./dagger/testerasure
import ./dagger/testproving
# to check that everything compiles # to check that everything compiles
import ../dagger import ../dagger

@ -1 +1 @@
Subproject commit 0587c2d585bef88575b10c63a4eddb756b5dde2e Subproject commit 29b5775951e774cb170b23cb6772cd7f1e7b5499