Smart contracts update: Groth16Proof instead of bytes (#683)

* Smart contracts update: Groth16Proof instead of bytes

* Use dummy verifier for now, until we can create ZK proofs

* Fix tests: submit proof only when slot is filled

* Submit dummy proofs for now

* More detailed log when proof submission failed

* Use dummy verifier for integration tests

For now at least

* Fix mistake in blanket renaming to ethProvider

* Update to latest codex-contracts-eth

* feat: zkey-hash from chain

* Fix zkeyHash

---------

Co-authored-by: Adam Uhlíř <adam@uhlir.dev>
This commit is contained in:
markspanbroek 2024-02-07 07:50:35 +01:00 committed by GitHub
parent 403b9baf9f
commit 2cf892c467
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 164 additions and 88 deletions

View File

@ -17,13 +17,15 @@ type
period*: UInt256 # proofs requirements are calculated per period (in seconds) period*: UInt256 # proofs requirements are calculated per period (in seconds)
timeout*: UInt256 # mark proofs as missing before the timeout (in seconds) timeout*: UInt256 # mark proofs as missing before the timeout (in seconds)
downtime*: uint8 # ignore this much recent blocks for proof requirements downtime*: uint8 # ignore this much recent blocks for proof requirements
zkeyHash*: string # hash of the zkey file which is linked to the verifier
func fromTuple(_: type ProofConfig, tupl: tuple): ProofConfig = func fromTuple(_: type ProofConfig, tupl: tuple): ProofConfig =
ProofConfig( ProofConfig(
period: tupl[0], period: tupl[0],
timeout: tupl[1], timeout: tupl[1],
downtime: tupl[2] downtime: tupl[2],
zkeyHash: tupl[3]
) )
func fromTuple(_: type CollateralConfig, tupl: tuple): CollateralConfig = func fromTuple(_: type CollateralConfig, tupl: tuple): CollateralConfig =

View File

@ -14,7 +14,10 @@ type Deployment* = ref object
const knownAddresses = { const knownAddresses = {
# Hardhat localhost network # Hardhat localhost network
"31337": { "31337": {
"Marketplace": Address.init("0x59b670e9fA9D0A427751Af201D676719a970857b") # TODO: This is currently the address of the marketplace with a dummy
# verifier. Replace with "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" once we
# can generate actual Groth16 ZK proofs
"Marketplace": Address.init("0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f"),
}.toTable, }.toTable,
# Taiko Alpha-3 Testnet # Taiko Alpha-3 Testnet
"167005": { "167005": {

View File

@ -8,6 +8,7 @@ import pkg/questionable
import ../logutils import ../logutils
import ../market import ../market
import ./marketplace import ./marketplace
import ./proofs
export market export market
@ -38,6 +39,10 @@ proc approveFunds(market: OnChainMarket, amount: UInt256) {.async.} =
discard await token.increaseAllowance(market.contract.address(), amount).confirm(1) discard await token.increaseAllowance(market.contract.address(), amount).confirm(1)
method getZkeyHash*(market: OnChainMarket): Future[?string] {.async.} =
let config = await market.contract.config()
return some config.proofs.zkeyHash
method getSigner*(market: OnChainMarket): Future[Address] {.async.} = method getSigner*(market: OnChainMarket): Future[Address] {.async.} =
return await market.signer.getAddress() return await market.signer.getAddress()
@ -120,7 +125,7 @@ method getActiveSlot*(market: OnChainMarket,
method fillSlot(market: OnChainMarket, method fillSlot(market: OnChainMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte], proof: Groth16Proof,
collateral: UInt256) {.async.} = collateral: UInt256) {.async.} =
await market.approveFunds(collateral) await market.approveFunds(collateral)
await market.contract.fillSlot(requestId, slotIndex, proof) await market.contract.fillSlot(requestId, slotIndex, proof)
@ -155,7 +160,7 @@ method getChallenge*(market: OnChainMarket, id: SlotId): Future[ProofChallenge]
method submitProof*(market: OnChainMarket, method submitProof*(market: OnChainMarket,
id: SlotId, id: SlotId,
proof: seq[byte]) {.async.} = proof: Groth16Proof) {.async.} =
await market.contract.submitProof(id, proof) await market.contract.submitProof(id, proof)
method markProofAsMissing*(market: OnChainMarket, method markProofAsMissing*(market: OnChainMarket,
@ -272,7 +277,7 @@ method subscribeProofSubmission*(market: OnChainMarket,
callback: OnProofSubmitted): callback: OnProofSubmitted):
Future[MarketSubscription] {.async.} = Future[MarketSubscription] {.async.} =
proc onEvent(event: ProofSubmitted) {.upraises: [].} = proc onEvent(event: ProofSubmitted) {.upraises: [].} =
callback(event.id, event.proof) callback(event.id)
let subscription = await market.contract.subscribe(ProofSubmitted, onEvent) let subscription = await market.contract.subscribe(ProofSubmitted, onEvent)
return OnChainMarketSubscription(eventSubscription: subscription) return OnChainMarketSubscription(eventSubscription: subscription)

View File

@ -5,6 +5,7 @@ import pkg/stint
import pkg/chronos import pkg/chronos
import ../clock import ../clock
import ./requests import ./requests
import ./proofs
import ./config import ./config
export stint export stint
@ -33,7 +34,6 @@ type
requestId* {.indexed.}: RequestId requestId* {.indexed.}: RequestId
ProofSubmitted* = object of Event ProofSubmitted* = object of Event
id*: SlotId id*: SlotId
proof*: seq[byte]
proc config*(marketplace: Marketplace): MarketplaceConfig {.contract, view.} proc config*(marketplace: Marketplace): MarketplaceConfig {.contract, view.}
@ -43,7 +43,7 @@ proc slashPercentage*(marketplace: Marketplace): UInt256 {.contract, view.}
proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view.} proc minCollateralThreshold*(marketplace: Marketplace): UInt256 {.contract, view.}
proc requestStorage*(marketplace: Marketplace, request: StorageRequest) {.contract.} proc requestStorage*(marketplace: Marketplace, request: StorageRequest) {.contract.}
proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: seq[byte]) {.contract.} proc fillSlot*(marketplace: Marketplace, requestId: RequestId, slotIndex: UInt256, proof: Groth16Proof) {.contract.}
proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId) {.contract.} proc withdrawFunds*(marketplace: Marketplace, requestId: RequestId) {.contract.}
proc freeSlot*(marketplace: Marketplace, id: SlotId) {.contract.} proc freeSlot*(marketplace: Marketplace, id: SlotId) {.contract.}
proc getRequest*(marketplace: Marketplace, id: RequestId): StorageRequest {.contract, view.} proc getRequest*(marketplace: Marketplace, id: RequestId): StorageRequest {.contract, view.}
@ -65,5 +65,5 @@ proc willProofBeRequired*(marketplace: Marketplace, id: SlotId): bool {.contract
proc getChallenge*(marketplace: Marketplace, id: SlotId): array[32, byte] {.contract, view.} proc getChallenge*(marketplace: Marketplace, id: SlotId): array[32, byte] {.contract, view.}
proc getPointer*(marketplace: Marketplace, id: SlotId): uint8 {.contract, view.} proc getPointer*(marketplace: Marketplace, id: SlotId): uint8 {.contract, view.}
proc submitProof*(marketplace: Marketplace, id: SlotId, proof: seq[byte]) {.contract.} proc submitProof*(marketplace: Marketplace, id: SlotId, proof: Groth16Proof) {.contract.}
proc markProofAsMissing*(marketplace: Marketplace, id: SlotId, period: UInt256) {.contract.} proc markProofAsMissing*(marketplace: Marketplace, id: SlotId, period: UInt256) {.contract.}

View File

@ -0,0 +1,33 @@
import pkg/stint
import pkg/contractabi
import pkg/ethers/fields
type
Groth16Proof* = object
a*: G1Point
b*: G2Point
c*: G1Point
G1Point* = object
x*: UInt256
y*: UInt256
G2Point* = object
x*: array[2, UInt256]
y*: array[2, UInt256]
func solidityType*(_: type G1Point): string =
solidityType(G1Point.fieldTypes)
func solidityType*(_: type G2Point): string =
solidityType(G2Point.fieldTypes)
func solidityType*(_: type Groth16Proof): string =
solidityType(Groth16Proof.fieldTypes)
func encode*(encoder: var AbiEncoder, point: G1Point) =
encoder.write(point.fieldValues)
func encode*(encoder: var AbiEncoder, point: G2Point) =
encoder.write(point.fieldValues)
func encode*(encoder: var AbiEncoder, proof: Groth16Proof) =
encoder.write(proof.fieldValues)

View File

@ -3,12 +3,14 @@ import pkg/upraises
import pkg/questionable import pkg/questionable
import pkg/ethers/erc20 import pkg/ethers/erc20
import ./contracts/requests import ./contracts/requests
import ./contracts/proofs
import ./clock import ./clock
import ./periods import ./periods
export chronos export chronos
export questionable export questionable
export requests export requests
export proofs
export SecondsSince1970 export SecondsSince1970
export periods export periods
@ -23,13 +25,16 @@ type
OnSlotFreed* = proc(requestId: RequestId, slotIndex: UInt256) {.gcsafe, upraises: [].} OnSlotFreed* = proc(requestId: RequestId, slotIndex: UInt256) {.gcsafe, upraises: [].}
OnRequestCancelled* = proc(requestId: RequestId) {.gcsafe, upraises:[].} OnRequestCancelled* = proc(requestId: RequestId) {.gcsafe, upraises:[].}
OnRequestFailed* = proc(requestId: RequestId) {.gcsafe, upraises:[].} OnRequestFailed* = proc(requestId: RequestId) {.gcsafe, upraises:[].}
OnProofSubmitted* = proc(id: SlotId, proof: seq[byte]) {.gcsafe, upraises:[].} OnProofSubmitted* = proc(id: SlotId) {.gcsafe, upraises:[].}
PastStorageRequest* = object PastStorageRequest* = object
requestId*: RequestId requestId*: RequestId
ask*: StorageAsk ask*: StorageAsk
expiry*: UInt256 expiry*: UInt256
ProofChallenge* = array[32, byte] ProofChallenge* = array[32, byte]
method getZkeyHash*(market: Market): Future[?string] {.base, async.} =
raiseAssert("not implemented")
method getSigner*(market: Market): Future[Address] {.base, async.} = method getSigner*(market: Market): Future[Address] {.base, async.} =
raiseAssert("not implemented") raiseAssert("not implemented")
@ -91,7 +96,7 @@ method getActiveSlot*(
method fillSlot*(market: Market, method fillSlot*(market: Market,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte], proof: Groth16Proof,
collateral: UInt256) {.base, async.} = collateral: UInt256) {.base, async.} =
raiseAssert("not implemented") raiseAssert("not implemented")
@ -120,7 +125,7 @@ method getChallenge*(market: Market, id: SlotId): Future[ProofChallenge] {.base,
method submitProof*(market: Market, method submitProof*(market: Market,
id: SlotId, id: SlotId,
proof: seq[byte]) {.base, async.} = proof: Groth16Proof) {.base, async.} =
raiseAssert("not implemented") raiseAssert("not implemented")
method markProofAsMissing*(market: Market, method markProofAsMissing*(market: Market,

View File

@ -549,7 +549,7 @@ proc onStore(
proc onProve( proc onProve(
self: CodexNodeRef, self: CodexNodeRef,
slot: Slot, slot: Slot,
challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
## Generats a proof for a given slot and challenge ## Generats a proof for a given slot and challenge
## ##
@ -585,7 +585,12 @@ proc onProve(
return failure(err) return failure(err)
# Todo: send proofInput to circuit. Get proof. (Profit, repeat.) # Todo: send proofInput to circuit. Get proof. (Profit, repeat.)
success(@[42'u8])
# For now: dummy proof that is not all zero's, so that it is accepted by the
# dummy verifier:
var proof = Groth16Proof.default
proof.a.x = 42.u256
success(proof)
proc onExpiryUpdate( proc onExpiryUpdate(
self: CodexNodeRef, self: CodexNodeRef,
@ -635,7 +640,7 @@ proc start*(self: CodexNodeRef) {.async.} =
self.onClear(request, slotIndex) self.onClear(request, slotIndex)
hostContracts.sales.onProve = hostContracts.sales.onProve =
proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] =
# TODO: generate proof # TODO: generate proof
self.onProve(slot, challenge) self.onProve(slot, challenge)

View File

@ -27,7 +27,7 @@ type
OnStore* = proc(request: StorageRequest, OnStore* = proc(request: StorageRequest,
slot: UInt256, slot: UInt256,
blocksCb: BlocksCb): Future[?!void] {.gcsafe, upraises: [].} blocksCb: BlocksCb): Future[?!void] {.gcsafe, upraises: [].}
OnProve* = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.gcsafe, upraises: [].} OnProve* = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.gcsafe, upraises: [].}
OnExpiryUpdate* = proc(rootCid: string, expiry: SecondsSince1970): Future[?!void] {.gcsafe, upraises: [].} OnExpiryUpdate* = proc(rootCid: string, expiry: SecondsSince1970): Future[?!void] {.gcsafe, upraises: [].}
OnClear* = proc(request: StorageRequest, OnClear* = proc(request: StorageRequest,
slotIndex: UInt256) {.gcsafe, upraises: [].} slotIndex: UInt256) {.gcsafe, upraises: [].}

View File

@ -12,7 +12,7 @@ logScope:
type type
SaleFilling* = ref object of ErrorHandlingState SaleFilling* = ref object of ErrorHandlingState
proof*: seq[byte] proof*: Groth16Proof
method `$`*(state: SaleFilling): string = "SaleFilling" method `$`*(state: SaleFilling): string = "SaleFilling"

View File

@ -2,6 +2,7 @@ import std/options
import pkg/questionable/results import pkg/questionable/results
import ../../clock import ../../clock
import ../../logutils import ../../logutils
import ../../utils/exceptions
import ../statemachine import ../statemachine
import ../salesagent import ../salesagent
import ../salescontext import ../salescontext
@ -35,7 +36,7 @@ method prove*(
debug "Submitting proof", currentPeriod = currentPeriod, slotId = slot.id debug "Submitting proof", currentPeriod = currentPeriod, slotId = slot.id
await market.submitProof(slot.id, proof) await market.submitProof(slot.id, proof)
except CatchableError as e: except CatchableError as e:
error "Submitting proof failed", msg = e.msg error "Submitting proof failed", msg = e.msgDetail
proc proveLoop( proc proveLoop(
state: SaleProving, state: SaleProving,

View File

@ -31,7 +31,7 @@ when codex_enable_proof_failures:
try: try:
warn "Submitting INVALID proof", period = currentPeriod, slotId = slot.id warn "Submitting INVALID proof", period = currentPeriod, slotId = slot.id
await market.submitProof(slot.id, newSeq[byte](0)) await market.submitProof(slot.id, Groth16Proof.default)
except ProviderError as e: except ProviderError as e:
if not e.revertReason.contains("Invalid proof"): if not e.revertReason.contains("Invalid proof"):
onSubmitProofError(e, currentPeriod, slot.id) onSubmitProofError(e, currentPeriod, slot.id)

View File

@ -6,6 +6,7 @@ import std/sugar
import pkg/questionable import pkg/questionable
import pkg/codex/market import pkg/codex/market
import pkg/codex/contracts/requests import pkg/codex/contracts/requests
import pkg/codex/contracts/proofs
import pkg/codex/contracts/config import pkg/codex/contracts/config
import ../examples import ../examples
@ -24,6 +25,7 @@ type
fulfilled*: seq[Fulfillment] fulfilled*: seq[Fulfillment]
filled*: seq[MockSlot] filled*: seq[MockSlot]
freed*: seq[SlotId] freed*: seq[SlotId]
submitted*: seq[Groth16Proof]
markedAsMissingProofs*: seq[SlotId] markedAsMissingProofs*: seq[SlotId]
canBeMarkedAsMissing: HashSet[SlotId] canBeMarkedAsMissing: HashSet[SlotId]
withdrawn*: seq[RequestId] withdrawn*: seq[RequestId]
@ -36,13 +38,13 @@ type
config*: MarketplaceConfig config*: MarketplaceConfig
Fulfillment* = object Fulfillment* = object
requestId*: RequestId requestId*: RequestId
proof*: seq[byte] proof*: Groth16Proof
host*: Address host*: Address
MockSlot* = object MockSlot* = object
requestId*: RequestId requestId*: RequestId
host*: Address host*: Address
slotIndex*: UInt256 slotIndex*: UInt256
proof*: seq[byte] proof*: Groth16Proof
Subscriptions = object Subscriptions = object
onRequest: seq[RequestSubscription] onRequest: seq[RequestSubscription]
onFulfillment: seq[FulfillmentSubscription] onFulfillment: seq[FulfillmentSubscription]
@ -215,7 +217,7 @@ proc emitRequestFailed*(market: MockMarket, requestId: RequestId) =
proc fillSlot*(market: MockMarket, proc fillSlot*(market: MockMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte], proof: Groth16Proof,
host: Address) = host: Address) =
let slot = MockSlot( let slot = MockSlot(
requestId: requestId, requestId: requestId,
@ -230,7 +232,7 @@ proc fillSlot*(market: MockMarket,
method fillSlot*(market: MockMarket, method fillSlot*(market: MockMarket,
requestId: RequestId, requestId: RequestId,
slotIndex: UInt256, slotIndex: UInt256,
proof: seq[byte], proof: Groth16Proof,
collateral: UInt256) {.async.} = collateral: UInt256) {.async.} =
market.fillSlot(requestId, slotIndex, proof, market.signer) market.fillSlot(requestId, slotIndex, proof, market.signer)
@ -273,9 +275,10 @@ method getChallenge*(mock: MockMarket, id: SlotId): Future[ProofChallenge] {.asy
proc setProofEnd*(mock: MockMarket, id: SlotId, proofEnd: UInt256) = proc setProofEnd*(mock: MockMarket, id: SlotId, proofEnd: UInt256) =
mock.proofEnds[id] = proofEnd mock.proofEnds[id] = proofEnd
method submitProof*(mock: MockMarket, id: SlotId, proof: seq[byte]) {.async.} = method submitProof*(mock: MockMarket, id: SlotId, proof: Groth16Proof) {.async.} =
mock.submitted.add(proof)
for subscription in mock.subscriptions.onProofSubmitted: for subscription in mock.subscriptions.onProofSubmitted:
subscription.callback(id, proof) subscription.callback(id)
method markProofAsMissing*(market: MockMarket, method markProofAsMissing*(market: MockMarket,
id: SlotId, id: SlotId,

View File

@ -31,7 +31,7 @@ checksuite "sales state 'filled'":
slot = MockSlot(requestId: request.id, slot = MockSlot(requestId: request.id,
host: Address.example, host: Address.example,
slotIndex: slotIndex, slotIndex: slotIndex,
proof: @[]) proof: Groth16Proof.default)
market.requestEnds[request.id] = 321 market.requestEnds[request.id] = 321
onExpiryUpdatePassedExpiry = -1 onExpiryUpdatePassedExpiry = -1

View File

@ -16,7 +16,7 @@ import ../../helpers
import ../../helpers/mockmarket import ../../helpers/mockmarket
asyncchecksuite "sales state 'initialproving'": asyncchecksuite "sales state 'initialproving'":
let proof = exampleProof() let proof = Groth16Proof.example
let request = StorageRequest.example let request = StorageRequest.example
let slotIndex = (request.ask.slots div 2).u256 let slotIndex = (request.ask.slots div 2).u256
let market = MockMarket.new() let market = MockMarket.new()
@ -26,7 +26,7 @@ asyncchecksuite "sales state 'initialproving'":
var receivedChallenge: ProofChallenge var receivedChallenge: ProofChallenge
setup: setup:
let onProve = proc (slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = let onProve = proc (slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
receivedChallenge = challenge receivedChallenge = challenge
return success(proof) return success(proof)
let context = SalesContext( let context = SalesContext(
@ -60,7 +60,7 @@ asyncchecksuite "sales state 'initialproving'":
check receivedChallenge == market.proofChallenge check receivedChallenge == market.proofChallenge
test "switches to errored state when onProve callback fails": test "switches to errored state when onProve callback fails":
let onProveFailed: OnProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = let onProveFailed: OnProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
return failure("oh no!") return failure("oh no!")
let proofFailedContext = SalesContext( let proofFailedContext = SalesContext(

View File

@ -18,7 +18,7 @@ asyncchecksuite "sales state 'proving'":
let slot = Slot.example let slot = Slot.example
let request = slot.request let request = slot.request
let proof = exampleProof() let proof = Groth16Proof.example
var market: MockMarket var market: MockMarket
var clock: MockClock var clock: MockClock
@ -29,7 +29,7 @@ asyncchecksuite "sales state 'proving'":
setup: setup:
clock = MockClock.new() clock = MockClock.new()
market = MockMarket.new() market = MockMarket.new()
let onProve = proc (slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = let onProve = proc (slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
receivedChallenge = challenge receivedChallenge = challenge
return success(proof) return success(proof)
let context = SalesContext(market: market, clock: clock, onProve: onProve.some) let context = SalesContext(market: market, clock: clock, onProve: onProve.some)
@ -53,11 +53,9 @@ asyncchecksuite "sales state 'proving'":
test "submits proofs": test "submits proofs":
var receivedIds: seq[SlotId] var receivedIds: seq[SlotId]
var receivedProofs: seq[seq[byte]]
proc onProofSubmission(id: SlotId, proof: seq[byte]) = proc onProofSubmission(id: SlotId) =
receivedIds.add(id) receivedIds.add(id)
receivedProofs.add(proof)
let subscription = await market.subscribeProofSubmission(onProofSubmission) let subscription = await market.subscribeProofSubmission(onProofSubmission)
market.slotState[slot.id] = SlotState.Filled market.slotState[slot.id] = SlotState.Filled
@ -67,7 +65,7 @@ asyncchecksuite "sales state 'proving'":
market.setProofRequired(slot.id, true) market.setProofRequired(slot.id, true)
await market.advanceToNextPeriod() await market.advanceToNextPeriod()
check eventually receivedIds == @[slot.id] and receivedProofs == @[proof] check eventually receivedIds == @[slot.id]
await future.cancelAndWait() await future.cancelAndWait()
await subscription.unsubscribe() await subscription.unsubscribe()

View File

@ -19,7 +19,7 @@ asyncchecksuite "sales state 'simulated-proving'":
let slot = Slot.example let slot = Slot.example
let request = slot.request let request = slot.request
let proof = exampleProof() let proof = Groth16Proof.example
let failEveryNProofs = 3 let failEveryNProofs = 3
let totalProofs = 6 let totalProofs = 6
@ -29,14 +29,12 @@ asyncchecksuite "sales state 'simulated-proving'":
var state: SaleProvingSimulated var state: SaleProvingSimulated
var proofSubmitted: Future[void] = newFuture[void]("proofSubmitted") var proofSubmitted: Future[void] = newFuture[void]("proofSubmitted")
var submitted: seq[seq[byte]]
var subscription: Subscription var subscription: Subscription
setup: setup:
clock = MockClock.new() clock = MockClock.new()
proc onProofSubmission(id: SlotId, proof: seq[byte]) = proc onProofSubmission(id: SlotId) =
submitted.add(proof)
proofSubmitted.complete() proofSubmitted.complete()
proofSubmitted = newFuture[void]("proofSubmitted") proofSubmitted = newFuture[void]("proofSubmitted")
@ -45,7 +43,7 @@ asyncchecksuite "sales state 'simulated-proving'":
market.setProofRequired(slot.id, true) market.setProofRequired(slot.id, true)
subscription = await market.subscribeProofSubmission(onProofSubmission) subscription = await market.subscribeProofSubmission(onProofSubmission)
let onProve = proc (slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = let onProve = proc (slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
return success(proof) return success(proof)
let context = SalesContext(market: market, clock: clock, onProve: onProve.some) let context = SalesContext(market: market, clock: clock, onProve: onProve.some)
agent = newSalesAgent(context, agent = newSalesAgent(context,
@ -79,9 +77,10 @@ asyncchecksuite "sales state 'simulated-proving'":
test "submits invalid proof every 3 proofs": test "submits invalid proof every 3 proofs":
let future = state.run(agent) let future = state.run(agent)
let invalid = Groth16Proof.default
await market.waitForProvingRounds(totalProofs) await market.waitForProvingRounds(totalProofs)
check submitted == @[proof, proof, @[], proof, proof, @[]] check market.submitted == @[proof, proof, invalid, proof, proof, invalid]
await future.cancelAndWait() await future.cancelAndWait()

View File

@ -21,7 +21,7 @@ import ../helpers/always
import ../examples import ../examples
asyncchecksuite "Sales - start": asyncchecksuite "Sales - start":
let proof = exampleProof() let proof = Groth16Proof.example
var request: StorageRequest var request: StorageRequest
var sales: Sales var sales: Sales
@ -64,7 +64,7 @@ asyncchecksuite "Sales - start":
return success() return success()
queue = sales.context.slotQueue queue = sales.context.slotQueue
sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
return success(proof) return success(proof)
itemsProcessed = @[] itemsProcessed = @[]
request.expiry = ((await clock.now()) + 42).u256 request.expiry = ((await clock.now()) + 42).u256
@ -112,7 +112,7 @@ asyncchecksuite "Sales - start":
check sales.agents.any(agent => agent.data.requestId == request.id and agent.data.slotIndex == 1.u256) check sales.agents.any(agent => agent.data.requestId == request.id and agent.data.slotIndex == 1.u256)
asyncchecksuite "Sales": asyncchecksuite "Sales":
let proof = exampleProof() let proof = Groth16Proof.example
var availability: Availability var availability: Availability
var request: StorageRequest var request: StorageRequest
@ -167,7 +167,7 @@ asyncchecksuite "Sales":
return success() return success()
queue = sales.context.slotQueue queue = sales.context.slotQueue
sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
return success(proof) return success(proof)
await sales.start() await sales.start()
itemsProcessed = @[] itemsProcessed = @[]
@ -369,7 +369,7 @@ asyncchecksuite "Sales":
test "handles errors during state run": test "handles errors during state run":
var saleFailed = false var saleFailed = false
sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
# raise exception so machine.onError is called # raise exception so machine.onError is called
raise newException(ValueError, "some error") raise newException(ValueError, "some error")
@ -394,10 +394,10 @@ asyncchecksuite "Sales":
test "generates proof of storage": test "generates proof of storage":
var provingRequest: StorageRequest var provingRequest: StorageRequest
var provingSlot: UInt256 var provingSlot: UInt256
sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
provingRequest = slot.request provingRequest = slot.request
provingSlot = slot.slotIndex provingSlot = slot.slotIndex
return success(exampleProof()) return success(Groth16Proof.example)
createAvailability() createAvailability()
await market.requestStorage(request) await market.requestStorage(request)
check eventually provingRequest == request check eventually provingRequest == request
@ -427,7 +427,7 @@ asyncchecksuite "Sales":
test "calls onClear when storage becomes available again": test "calls onClear when storage becomes available again":
# fail the proof intentionally to trigger `agent.finish(success=false)`, # fail the proof intentionally to trigger `agent.finish(success=false)`,
# which then calls the onClear callback # which then calls the onClear callback
sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!seq[byte]] {.async.} = sales.onProve = proc(slot: Slot, challenge: ProofChallenge): Future[?!Groth16Proof] {.async.} =
raise newException(IOError, "proof failed") raise newException(IOError, "proof failed")
var clearedRequest: StorageRequest var clearedRequest: StorageRequest
var clearedSlotIndex: UInt256 var clearedSlotIndex: UInt256

View File

@ -14,6 +14,7 @@ asyncchecksuite "validation":
let timeout = 5 let timeout = 5
let maxSlots = 100 let maxSlots = 100
let slot = Slot.example let slot = Slot.example
let proof = Groth16Proof.example
let collateral = slot.request.ask.collateral let collateral = slot.request.ask.collateral
var validation: Validation var validation: Validation
@ -41,25 +42,25 @@ asyncchecksuite "validation":
check validation.slots.len == 0 check validation.slots.len == 0
test "when a slot is filled on chain, it is added to the list": test "when a slot is filled on chain, it is added to the list":
await market.fillSlot(slot.request.id, slot.slotIndex, @[], collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
check validation.slots == @[slot.id] check validation.slots == @[slot.id]
for state in [SlotState.Finished, SlotState.Failed]: for state in [SlotState.Finished, SlotState.Failed]:
test "when slot state changes, it is removed from the list": test "when slot state changes, it is removed from the list":
await market.fillSlot(slot.request.id, slot.slotIndex, @[], collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.slotState[slot.id] = state market.slotState[slot.id] = state
advanceToNextPeriod() advanceToNextPeriod()
check eventually validation.slots.len == 0 check eventually validation.slots.len == 0
test "when a proof is missed, it is marked as missing": test "when a proof is missed, it is marked as missing":
await market.fillSlot(slot.request.id, slot.slotIndex, @[], collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, true) market.setCanProofBeMarkedAsMissing(slot.id, true)
advanceToNextPeriod() advanceToNextPeriod()
await sleepAsync(1.millis) await sleepAsync(1.millis)
check market.markedAsMissingProofs.contains(slot.id) check market.markedAsMissingProofs.contains(slot.id)
test "when a proof can not be marked as missing, it will not be marked": test "when a proof can not be marked as missing, it will not be marked":
await market.fillSlot(slot.request.id, slot.slotIndex, @[], collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
market.setCanProofBeMarkedAsMissing(slot.id, false) market.setCanProofBeMarkedAsMissing(slot.id, false)
advanceToNextPeriod() advanceToNextPeriod()
await sleepAsync(1.millis) await sleepAsync(1.millis)
@ -68,5 +69,5 @@ asyncchecksuite "validation":
test "it does not monitor more than the maximum number of slots": test "it does not monitor more than the maximum number of slots":
for _ in 0..<maxSlots + 1: for _ in 0..<maxSlots + 1:
let slot = Slot.example let slot = Slot.example
await market.fillSlot(slot.request.id, slot.slotIndex, @[], collateral) await market.fillSlot(slot.request.id, slot.slotIndex, proof, collateral)
check validation.slots.len == maxSlots check validation.slots.len == maxSlots

View File

@ -3,15 +3,21 @@ import std/options
import pkg/ethers import pkg/ethers
import pkg/codex/contracts/marketplace import pkg/codex/contracts/marketplace
const hardhatMarketAddress = Address.init("0x59b670e9fA9D0A427751Af201D676719a970857b").get() const hardhatMarketAddress =
Address.init("0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44").get()
const hardhatMarketWithDummyVerifier =
Address.init("0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f").get()
const marketAddressEnvName = "CODEX_MARKET_ADDRESS" const marketAddressEnvName = "CODEX_MARKET_ADDRESS"
proc address*(_: type Marketplace): Address = proc address*(_: type Marketplace, dummyVerifier = false): Address =
if existsEnv(marketAddressEnvName): if existsEnv(marketAddressEnvName):
without address =? Address.init(getEnv(marketAddressEnvName)): without address =? Address.init(getEnv(marketAddressEnvName)):
raiseAssert "Invalid env. variable marketplace contract address" raiseAssert "Invalid env. variable marketplace contract address"
return address return address
hardhatMarketAddress if dummyVerifier:
hardhatMarketWithDummyVerifier
else:
hardhatMarketAddress

View File

@ -8,7 +8,7 @@ import ./time
import ./deployment import ./deployment
ethersuite "Marketplace contracts": ethersuite "Marketplace contracts":
let proof = exampleProof() let proof = Groth16Proof.example
var client, host: Signer var client, host: Signer
var marketplace: Marketplace var marketplace: Marketplace
@ -25,7 +25,8 @@ ethersuite "Marketplace contracts":
client = ethProvider.getSigner(accounts[0]) client = ethProvider.getSigner(accounts[0])
host = ethProvider.getSigner(accounts[1]) host = ethProvider.getSigner(accounts[1])
marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) let address = Marketplace.address(dummyVerifier = true)
marketplace = Marketplace.new(address, ethProvider.getSigner())
let tokenAddress = await marketplace.token() let tokenAddress = await marketplace.token()
token = Erc20Token.new(tokenAddress, ethProvider.getSigner()) token = Erc20Token.new(tokenAddress, ethProvider.getSigner())

View File

@ -31,11 +31,11 @@ asyncchecksuite "Deployment":
test "uses chainId hardcoded values as fallback": test "uses chainId hardcoded values as fallback":
let deployment = Deployment.new(provider, configFactory()) let deployment = Deployment.new(provider, configFactory())
provider.chainId = 31337.u256 provider.chainId = 167005.u256
let address = await deployment.address(Marketplace) let address = await deployment.address(Marketplace)
check address.isSome check address.isSome
check $(!address) == "0x59b670e9fa9d0a427751af201d676719a970857b" check $(!address) == "0x948cf9291b77bd7ad84781b9047129addf1b894f"
test "return none for unknown networks": test "return none for unknown networks":
let deployment = Deployment.new(provider, configFactory()) let deployment = Deployment.new(provider, configFactory())

View File

@ -1,6 +1,5 @@
import std/options import std/options
import pkg/chronos import pkg/chronos
import pkg/stew/byteutils
import codex/contracts import codex/contracts
import ../ethertest import ../ethertest
import ./examples import ./examples
@ -8,7 +7,7 @@ import ./time
import ./deployment import ./deployment
ethersuite "On-Chain Market": ethersuite "On-Chain Market":
let proof = exampleProof() let proof = Groth16Proof.example
var market: OnChainMarket var market: OnChainMarket
var marketplace: Marketplace var marketplace: Marketplace
@ -17,7 +16,8 @@ ethersuite "On-Chain Market":
var periodicity: Periodicity var periodicity: Periodicity
setup: setup:
marketplace = Marketplace.new(Marketplace.address, ethProvider.getSigner()) let address = Marketplace.address(dummyVerifier = true)
marketplace = Marketplace.new(address, ethProvider.getSigner())
let config = await marketplace.config() let config = await marketplace.config()
market = OnChainMarket.new(marketplace) market = OnChainMarket.new(marketplace)
@ -111,6 +111,9 @@ ethersuite "On-Chain Market":
check (await market.willProofBeRequired(slotId(request.id, slotIndex))) == false check (await market.willProofBeRequired(slotId(request.id, slotIndex))) == false
test "submits proofs": test "submits proofs":
await market.requestStorage(request)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
await advanceToNextPeriod()
await market.submitProof(slotId(request.id, slotIndex), proof) await market.submitProof(slotId(request.id, slotIndex), proof)
test "marks a proof as missing": test "marks a proof as missing":
@ -260,20 +263,15 @@ ethersuite "On-Chain Market":
await subscription.unsubscribe() await subscription.unsubscribe()
test "supports proof submission subscriptions": test "supports proof submission subscriptions":
await market.requestStorage(request)
await market.fillSlot(request.id, slotIndex, proof, request.ask.collateral)
await advanceToNextPeriod()
var receivedIds: seq[SlotId] var receivedIds: seq[SlotId]
var receivedProofs: seq[seq[byte]] proc onProofSubmission(id: SlotId) =
proc onProofSubmission(id: SlotId, proof: seq[byte]) =
receivedIds.add(id) receivedIds.add(id)
receivedProofs.add(proof)
let subscription = await market.subscribeProofSubmission(onProofSubmission) let subscription = await market.subscribeProofSubmission(onProofSubmission)
await market.submitProof(slotId(request.id, slotIndex), proof) await market.submitProof(slotId(request.id, slotIndex), proof)
check receivedIds == @[slotId(request.id, slotIndex)] check receivedIds == @[slotId(request.id, slotIndex)]
check receivedProofs == @[proof]
await subscription.unsubscribe() await subscription.unsubscribe()
test "request is none when unknown": test "request is none when unknown":

View File

@ -4,6 +4,7 @@ import std/times
import std/typetraits import std/typetraits
import pkg/codex/contracts/requests import pkg/codex/contracts/requests
import pkg/codex/contracts/proofs
import pkg/codex/sales/slotqueue import pkg/codex/sales/slotqueue
import pkg/codex/stores import pkg/codex/stores
@ -62,8 +63,18 @@ proc example*(_: type SlotQueueItem): SlotQueueItem =
let slot = Slot.example let slot = Slot.example
SlotQueueItem.init(request, slot.slotIndex.truncate(uint16)) SlotQueueItem.init(request, slot.slotIndex.truncate(uint16))
proc exampleProof*(): seq[byte] = proc example(_: type G1Point): G1Point =
var proof: seq[byte] G1Point(x: UInt256.example, y: UInt256.example)
while proof.len == 0:
proof = seq[byte].example proc example(_: type G2Point): G2Point =
return proof G2Point(
x: [UInt256.example, UInt256.example],
y: [UInt256.example, UInt256.example]
)
proc example*(_: type Groth16Proof): Groth16Proof =
Groth16Proof(
a: G1Point.example,
b: G2Point.example,
c: G1Point.example
)

View File

@ -23,7 +23,7 @@ type
validators*: uint validators*: uint
DebugNodes* = object DebugNodes* = object
client*: bool client*: bool
ethProvider*: bool provider*: bool
validator*: bool validator*: bool
topics*: string topics*: string
Role* {.pure.} = enum Role* {.pure.} = enum
@ -48,15 +48,15 @@ proc init*(_: type StartNodes,
StartNodes(clients: clients, providers: providers, validators: validators) StartNodes(clients: clients, providers: providers, validators: validators)
proc init*(_: type DebugNodes, proc init*(_: type DebugNodes,
client, ethProvider, validator: bool, client, provider, validator: bool,
topics: string = "validator,proving,market"): DebugNodes = topics: string = "validator,proving,market"): DebugNodes =
DebugNodes(client: client, ethProvider: ethProvider, validator: validator, DebugNodes(client: client, provider: provider, validator: validator,
topics: topics) topics: topics)
template multinodesuite*(name: string, template multinodesuite*(name: string,
startNodes: StartNodes, debugNodes: DebugNodes, body: untyped) = startNodes: StartNodes, debugNodes: DebugNodes, body: untyped) =
if (debugNodes.client or debugNodes.ethProvider) and if (debugNodes.client or debugNodes.provider) and
(enabledLogLevel > LogLevel.TRACE or (enabledLogLevel > LogLevel.TRACE or
enabledLogLevel == LogLevel.NONE): enabledLogLevel == LogLevel.NONE):
echo "" echo ""
@ -115,12 +115,12 @@ template multinodesuite*(name: string,
"--bootstrap-node=" & bootstrap, "--bootstrap-node=" & bootstrap,
"--persistence", "--persistence",
"--simulate-proof-failures=" & $failEveryNProofs], "--simulate-proof-failures=" & $failEveryNProofs],
debugNodes.ethProvider) debugNodes.provider)
let restClient = newCodexClient(index) let restClient = newCodexClient(index)
running.add RunningNode.new(Role.Provider, node, restClient, datadir, running.add RunningNode.new(Role.Provider, node, restClient, datadir,
account) account)
if debugNodes.ethProvider: if debugNodes.provider:
debug "started new ethProvider node and codex client", debug "started new provider node and codex client",
restApiPort = 8080 + index, discPort = 8090 + index, account restApiPort = 8080 + index, discPort = 8090 + index, account
proc startValidatorNode() = proc startValidatorNode() =

View File

@ -12,6 +12,11 @@ import ./multinodes
logScope: logScope:
topics = "test proofs" topics = "test proofs"
# TODO: This is currently the address of the marketplace with a dummy
# verifier. Use real marketplace address once we can generate actual
# Groth16 ZK proofs.
let marketplaceAddress = Marketplace.address(dummyVerifier = true)
twonodessuite "Proving integration test", debug1=false, debug2=false: twonodessuite "Proving integration test", debug1=false, debug2=false:
let validatorDir = getTempDir() / "CodexValidator" let validatorDir = getTempDir() / "CodexValidator"
@ -22,7 +27,7 @@ twonodessuite "Proving integration test", debug1=false, debug2=false:
client.getPurchase(id).option.?state == some state client.getPurchase(id).option.?state == some state
setup: setup:
marketplace = Marketplace.new(Marketplace.address, ethProvider) marketplace = Marketplace.new(marketplaceAddress, ethProvider)
period = (await marketplace.config()).proofs.period.truncate(uint64) period = (await marketplace.config()).proofs.period.truncate(uint64)
# Our Hardhat configuration does use automine, which means that time tracked by `ethProvider.currentTime()` is not # Our Hardhat configuration does use automine, which means that time tracked by `ethProvider.currentTime()` is not
@ -110,7 +115,7 @@ twonodessuite "Proving integration test", debug1=false, debug2=false:
multinodesuite "Simulate invalid proofs", multinodesuite "Simulate invalid proofs",
StartNodes.init(clients=1'u, providers=0'u, validators=1'u), StartNodes.init(clients=1'u, providers=0'u, validators=1'u),
DebugNodes.init(client=false, ethProvider=false, validator=false): DebugNodes.init(client=false, provider=false, validator=false):
proc purchaseStateIs(client: CodexClient, id: PurchaseId, state: string): bool = proc purchaseStateIs(client: CodexClient, id: PurchaseId, state: string): bool =
client.getPurchase(id).option.?state == some state client.getPurchase(id).option.?state == some state
@ -120,7 +125,7 @@ multinodesuite "Simulate invalid proofs",
var slotId: SlotId var slotId: SlotId
setup: setup:
marketplace = Marketplace.new(Marketplace.address, ethProvider) marketplace = Marketplace.new(marketplaceAddress, ethProvider)
let config = await marketplace.config() let config = await marketplace.config()
period = config.proofs.period.truncate(uint64) period = config.proofs.period.truncate(uint64)
slotId = SlotId(array[32, byte].default) # ensure we aren't reusing from prev test slotId = SlotId(array[32, byte].default) # ensure we aren't reusing from prev test

@ -1 +1 @@
Subproject commit b5f33992b67df3733042a7d912c854700e8c863c Subproject commit 6c9f797f408608958714024b9055fcc330e3842f