Subscribe to proof submissions (#83)

* Update dagger-contracts

* [proving] rename ProofTiming -> Proofs

* Update nim-ethers to 0.1.4

* [proving] Subscribe to proof submissions

* [proving] support proof submission through the Proving abstraction
This commit is contained in:
markspanbroek 2022-04-13 18:41:48 +02:00 committed by GitHub
parent 4d681102e5
commit b88561e090
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 358 additions and 234 deletions

View File

@ -13,7 +13,7 @@ requires "nim >= 1.2.0",
"chronicles >= 0.7.2",
"chronos >= 2.5.2",
"confutils",
"ethers >= 0.1.3 & < 0.2.0",
"ethers >= 0.1.4 & < 0.2.0",
"libbacktrace",
"libp2p",
"metrics",

View File

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

View File

@ -0,0 +1,61 @@
import pkg/ethers
import ../por/timing/proofs
import ./storage
export proofs
type
OnChainProofs* = ref object of Proofs
storage: Storage
pollInterval*: Duration
ProofsSubscription = proofs.Subscription
EventSubscription = ethers.Subscription
OnChainProofsSubscription = ref object of ProofsSubscription
eventSubscription: EventSubscription
const DefaultPollInterval = 3.seconds
proc new*(_: type OnChainProofs, storage: Storage): OnChainProofs =
OnChainProofs(storage: storage, pollInterval: DefaultPollInterval)
method periodicity*(proofs: OnChainProofs): Future[Periodicity] {.async.} =
let period = await proofs.storage.proofPeriod()
return Periodicity(seconds: period)
method getCurrentPeriod*(proofs: OnChainProofs): Future[Period] {.async.} =
let periodicity = await proofs.periodicity()
let blk = !await proofs.storage.provider.getBlock(BlockTag.latest)
return periodicity.periodOf(blk.timestamp)
method waitUntilPeriod*(proofs: OnChainProofs,
period: Period) {.async.} =
while (await proofs.getCurrentPeriod()) < period:
await sleepAsync(proofs.pollInterval)
method isProofRequired*(proofs: OnChainProofs,
id: ContractId): Future[bool] {.async.} =
return await proofs.storage.isProofRequired(id)
method willProofBeRequired*(proofs: OnChainProofs,
id: ContractId): Future[bool] {.async.} =
return await proofs.storage.willProofBeRequired(id)
method getProofEnd*(proofs: OnChainProofs,
id: ContractId): Future[UInt256] {.async.} =
return await proofs.storage.proofEnd(id)
method submitProof*(proofs: OnChainProofs,
id: ContractId,
proof: seq[byte]) {.async.} =
await proofs.storage.submitProof(id, proof)
method subscribeProofSubmission*(proofs: OnChainProofs,
callback: OnProofSubmitted):
Future[ProofsSubscription] {.async.} =
proc onEvent(event: ProofSubmitted) {.upraises: [].} =
callback(event.id, event.proof)
let subscription = await proofs.storage.subscribe(ProofSubmitted, onEvent)
return OnChainProofsSubscription(eventSubscription: subscription)
method unsubscribe*(subscription: OnChainProofsSubscription) {.async, upraises:[].} =
await subscription.eventSubscription.unsubscribe()

View File

@ -1,40 +0,0 @@
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

@ -21,6 +21,9 @@ type
OfferSelected* = object of Event
offerId*: Id
requestId* {.indexed.}: Id
ProofSubmitted* = object of Event
id*: Id
proof*: seq[byte]
proc collateralAmount*(storage: Storage): UInt256 {.contract, view.}
proc slashMisses*(storage: Storage): UInt256 {.contract, view.}
@ -47,5 +50,5 @@ 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.}
proc submitProof*(storage: Storage, id: Id, proof: bool) {.contract.}
proc submitProof*(storage: Storage, id: Id, proof: seq[byte]) {.contract.}
proc markProofAsMissing*(storage: Storage, id: Id, period: UInt256) {.contract.}

View File

@ -0,0 +1,51 @@
import pkg/chronos
import pkg/stint
import pkg/upraises
import ./periods
export chronos
export stint
export periods
type
Proofs* = ref object of RootObj
Subscription* = ref object of RootObj
OnProofSubmitted* = proc(id: ContractId, proof: seq[byte]) {.gcsafe, upraises:[].}
ContractId* = array[32, byte]
method periodicity*(proofs: Proofs):
Future[Periodicity] {.base, async.} =
raiseAssert("not implemented")
method getCurrentPeriod*(proofs: Proofs):
Future[Period] {.base, async.} =
raiseAssert("not implemented")
method waitUntilPeriod*(proofs: Proofs,
period: Period) {.base, async.} =
raiseAssert("not implemented")
method isProofRequired*(proofs: Proofs,
id: ContractId): Future[bool] {.base, async.} =
raiseAssert("not implemented")
method willProofBeRequired*(proofs: Proofs,
id: ContractId): Future[bool] {.base, async.} =
raiseAssert("not implemented")
method getProofEnd*(proofs: Proofs,
id: ContractId): Future[UInt256] {.base, async.} =
raiseAssert("not implemented")
method submitProof*(proofs: Proofs,
id: ContractId,
proof: seq[byte]) {.base, async.} =
raiseAssert("not implemented")
method subscribeProofSubmission*(proofs: Proofs,
callback: OnProofSubmitted):
Future[Subscription] {.base, async.} =
raiseAssert("not implemented")
method unsubscribe*(subscription: Subscription) {.base, async, upraises:[].} =
raiseAssert("not implemented")

View File

@ -1,35 +0,0 @@
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")

View File

@ -2,21 +2,21 @@ import std/sets
import std/times
import pkg/upraises
import pkg/questionable
import ./por/timing/prooftiming
import ./por/timing/proofs
export sets
export prooftiming
export proofs
type
Proving* = ref object
timing: ProofTiming
proofs: Proofs
stopped: bool
contracts*: HashSet[ContractId]
onProofRequired: ?OnProofRequired
OnProofRequired* = proc (id: ContractId) {.gcsafe, upraises:[].}
func new*(_: type Proving, timing: ProofTiming): Proving =
Proving(timing: timing)
func new*(_: type Proving, proofs: Proofs): Proving =
Proving(proofs: proofs)
proc `onProofRequired=`*(proving: Proving, callback: OnProofRequired) =
proving.onProofRequired = some callback
@ -28,23 +28,31 @@ 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)):
if now >= (await proving.proofs.getProofEnd(id)):
ended.incl(id)
proving.contracts.excl(ended)
proc run(proving: Proving) {.async.} =
while not proving.stopped:
let currentPeriod = await proving.timing.getCurrentPeriod()
let currentPeriod = await proving.proofs.getCurrentPeriod()
await proving.removeEndedContracts()
for id in proving.contracts:
if (await proving.timing.isProofRequired(id)) or
(await proving.timing.willProofBeRequired(id)):
if (await proving.proofs.isProofRequired(id)) or
(await proving.proofs.willProofBeRequired(id)):
if callback =? proving.onProofRequired:
callback(id)
await proving.timing.waitUntilPeriod(currentPeriod + 1)
await proving.proofs.waitUntilPeriod(currentPeriod + 1)
proc start*(proving: Proving) =
asyncSpawn proving.run()
proc stop*(proving: Proving) =
proving.stopped = true
proc submitProof*(proving: Proving, id: ContractId, proof: seq[byte]) {.async.} =
await proving.proofs.submitProof(id, proof)
proc subscribeProofSubmission*(proving: Proving,
callback: OnProofSubmitted):
Future[Subscription] =
proving.proofs.subscribeProofSubmission(callback)

View File

@ -71,10 +71,11 @@ ethersuite "Storage contracts":
check proofEnd > 0
test "accept storage proofs":
let proof = seq[byte].example
switchAccount(host)
await storage.startContract(id)
await waitUntilProofRequired(id)
await storage.submitProof(id, true)
await storage.submitProof(id, proof)
test "can mark missing proofs":
switchAccount(host)

View File

@ -1,47 +0,0 @@
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,70 @@
import ./ethertest
import dagger/contracts
import ./examples
import ./time
ethersuite "On-Chain Proofs":
let contractId = ContractId.example
let proof = seq[byte].example
var proofs: OnChainProofs
var storage: Storage
setup:
let deployment = deployment()
storage = Storage.new(!deployment.address(Storage), provider.getSigner())
proofs = OnChainProofs.new(storage)
test "can retrieve proof periodicity":
let periodicity = await proofs.periodicity()
let periodLength = await storage.proofPeriod()
check periodicity.seconds == periodLength
test "supports waiting until next period":
let periodicity = await proofs.periodicity()
let currentPeriod = await proofs.getCurrentPeriod()
let pollInterval = 200.milliseconds
proofs.pollInterval = pollInterval
proc waitForPoll {.async.} =
await sleepAsync(pollInterval * 2)
let future = proofs.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 proofs.isProofRequired(contractId)) == false
test "supports checking whether proof is required soon":
check (await proofs.willProofBeRequired(contractId)) == false
test "retrieves proof end time":
check (await proofs.getProofEnd(contractId)) == 0.u256
test "submits proofs":
await proofs.submitProof(contractId, proof)
test "supports proof submission subscriptions":
var receivedIds: seq[ContractId]
var receivedProofs: seq[seq[byte]]
proc onProofSubmission(id: ContractId, proof: seq[byte]) =
receivedIds.add(id)
receivedProofs.add(proof)
let subscription = await proofs.subscribeProofSubmission(onProofSubmission)
await proofs.submitProof(contractId, proof)
check receivedIds == @[contractId]
check receivedProofs == @[proof]
await subscription.unsubscribe()

View File

@ -0,0 +1,94 @@
import std/sets
import std/tables
import std/sequtils
import pkg/upraises
import pkg/dagger/por/timing/proofs
type
MockProofs* = ref object of Proofs
periodicity: Periodicity
currentPeriod: Period
waiting: Table[Period, seq[Future[void]]]
proofsRequired: HashSet[ContractId]
proofsToBeRequired: HashSet[ContractId]
proofEnds: Table[ContractId, UInt256]
subscriptions: seq[MockSubscription]
MockSubscription* = ref object of Subscription
proofs: MockProofs
callback: OnProofSubmitted
const DefaultPeriodLength = 10.u256
func new*(_: type MockProofs): MockProofs =
MockProofs(periodicity: Periodicity(seconds: DefaultPeriodLength))
func setPeriodicity*(mock: MockProofs, periodicity: Periodicity) =
mock.periodicity = periodicity
method periodicity*(mock: MockProofs): Future[Periodicity] {.async.} =
return mock.periodicity
proc setProofRequired*(mock: MockProofs, id: ContractId, required: bool) =
if required:
mock.proofsRequired.incl(id)
else:
mock.proofsRequired.excl(id)
method isProofRequired*(mock: MockProofs,
id: ContractId): Future[bool] {.async.} =
return mock.proofsRequired.contains(id)
proc setProofToBeRequired*(mock: MockProofs, id: ContractId, required: bool) =
if required:
mock.proofsToBeRequired.incl(id)
else:
mock.proofsToBeRequired.excl(id)
method willProofBeRequired*(mock: MockProofs,
id: ContractId): Future[bool] {.async.} =
return mock.proofsToBeRequired.contains(id)
proc setProofEnd*(mock: MockProofs, id: ContractId, proofEnd: UInt256) =
mock.proofEnds[id] = proofEnd
method getProofEnd*(mock: MockProofs,
id: ContractId): Future[UInt256] {.async.} =
if mock.proofEnds.hasKey(id):
return mock.proofEnds[id]
else:
return UInt256.high
proc advanceToPeriod*(mock: MockProofs, 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: MockProofs): Future[Period] {.async.} =
return mock.currentPeriod
method waitUntilPeriod*(mock: MockProofs, 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
method submitProof*(mock: MockProofs,
id: ContractId,
proof: seq[byte]) {.async.} =
for subscription in mock.subscriptions:
subscription.callback(id, proof)
method subscribeProofSubmission*(mock: MockProofs,
callback: OnProofSubmitted):
Future[Subscription] {.async.} =
let subscription = MockSubscription(proofs: mock, callback: callback)
mock.subscriptions.add(subscription)
return subscription
method unsubscribe*(subscription: MockSubscription) {.async, upraises:[].} =
subscription.proofs.subscriptions.keepItIf(it != subscription)

View File

@ -1,72 +0,0 @@
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

@ -2,25 +2,25 @@ from std/times import getTime, toUnix
import pkg/asynctest
import pkg/chronos
import pkg/dagger/proving
import ./helpers/mockprooftiming
import ./helpers/mockproofs
import ./examples
suite "Proving":
var proving: Proving
var timing: MockProofTiming
var proofs: MockProofs
setup:
timing = MockProofTiming.new()
proving = Proving.new(timing)
proofs = MockProofs.new()
proving = Proving.new(proofs)
proving.start()
teardown:
proving.stop()
proc advanceToNextPeriod(timing: MockProofTiming) {.async.} =
let current = await timing.getCurrentPeriod()
timing.advanceToPeriod(current + 1)
proc advanceToNextPeriod(proofs: MockProofs) {.async.} =
let current = await proofs.getCurrentPeriod()
proofs.advanceToPeriod(current + 1)
await sleepAsync(1.milliseconds)
test "maintains a list of contract ids to watch":
@ -45,8 +45,8 @@ suite "Proving":
proc onProofRequired(id: ContractId) =
called = true
proving.onProofRequired = onProofRequired
timing.setProofRequired(id, true)
await timing.advanceToNextPeriod()
proofs.setProofRequired(id, true)
await proofs.advanceToNextPeriod()
check called
test "callback receives id of contract for which proof is required":
@ -57,12 +57,12 @@ suite "Proving":
proc onProofRequired(id: ContractId) =
callbackIds.add(id)
proving.onProofRequired = onProofRequired
timing.setProofRequired(id1, true)
await timing.advanceToNextPeriod()
proofs.setProofRequired(id1, true)
await proofs.advanceToNextPeriod()
check callbackIds == @[id1]
timing.setProofRequired(id1, false)
timing.setProofRequired(id2, true)
await timing.advanceToNextPeriod()
proofs.setProofRequired(id1, false)
proofs.setProofRequired(id2, true)
await proofs.advanceToNextPeriod()
check callbackIds == @[id1, id2]
test "invokes callback when proof is about to be required":
@ -72,20 +72,45 @@ suite "Proving":
proc onProofRequired(id: ContractId) =
called = true
proving.onProofRequired = onProofRequired
timing.setProofRequired(id, false)
timing.setProofToBeRequired(id, true)
await timing.advanceToNextPeriod()
proofs.setProofRequired(id, false)
proofs.setProofToBeRequired(id, true)
await proofs.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()
proofs.setProofEnd(id, getTime().toUnix().u256)
await proofs.advanceToNextPeriod()
var called: bool
proc onProofRequired(id: ContractId) =
called = true
proving.onProofRequired = onProofRequired
timing.setProofRequired(id, true)
await timing.advanceToNextPeriod()
proofs.setProofRequired(id, true)
await proofs.advanceToNextPeriod()
check not called
test "submits proofs":
let id = ContractId.example
let proof = seq[byte].example
await proving.submitProof(id, proof)
test "supports proof submission subscriptions":
let id = ContractId.example
let proof = seq[byte].example
var receivedIds: seq[ContractId]
var receivedProofs: seq[seq[byte]]
proc onProofSubmission(id: ContractId, proof: seq[byte]) =
receivedIds.add(id)
receivedProofs.add(proof)
let subscription = await proving.subscribeProofSubmission(onProofSubmission)
await proving.submitProof(id, proof)
check receivedIds == @[id]
check receivedProofs == @[proof]
await subscription.unsubscribe()

View File

@ -1,4 +1,5 @@
import std/random
import std/sequtils
import pkg/stint
proc example*[T: SomeInteger](_: type T): T =
@ -8,5 +9,9 @@ proc example*[T,N](_: type array[N, T]): array[N, T] =
for item in result.mitems:
item = T.example
proc example*[T](_: type seq[T]): seq[T] =
let length = uint8.example.int
newSeqWith(length, T.example)
proc example*(_: type UInt256): UInt256 =
UInt256.fromBytes(array[32, byte].example)

View File

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

@ -1 +1 @@
Subproject commit 6aa2894521faa53a9896e800caf713499b33c318
Subproject commit 2bf01da728317fd1f3ae8424785f31b53eb4013f

@ -1 +1 @@
Subproject commit 0d9a25ec0ab9c4b807664a357c7ec31006986cae
Subproject commit b111c27b619fc1d81fb1c6942372824a18a71960

2
vendor/nim-ethers vendored

@ -1 +1 @@
Subproject commit ac74b91f11bb8511ab9e7cc3075f0267415c3006
Subproject commit a0dca2674d07b34c293ca318fa8ca30e123c13ca