set proof end when storage requested

1. set proof end to now + duration when storage is requested
2. set proof end to past when contract is failed
3. add back proof end mappings
This commit is contained in:
Eric Mastro 2022-09-29 20:07:55 +10:00 committed by Eric Mastro
parent fd74268a8a
commit bac2675bb2
5 changed files with 160 additions and 98 deletions

View File

@ -37,6 +37,11 @@ contract Marketplace is Collateral, Proofs {
require(requests[id].client == address(0), "Request already exists");
requests[id] = request;
RequestContext storage context = _context(id);
// set contract end time to `duration` from now (time request was created)
context.endsAt = block.timestamp + request.ask.duration;
_setProofEnd(id, context.endsAt);
_createLock(id, request.expiry);
@ -63,14 +68,13 @@ contract Marketplace is Collateral, Proofs {
require(balanceOf(msg.sender) >= collateral, "Insufficient collateral");
_lock(msg.sender, requestId);
_expectProofs(slotId, request.ask.proofProbability, request.ask.duration);
_expectProofs(slotId, requestId, request.ask.proofProbability);
_submitProof(slotId, proof);
slot.host = msg.sender;
slot.requestId = requestId;
RequestContext storage context = _context(requestId);
context.slotsFilled += 1;
context.endsAt = block.timestamp + request.ask.duration;
emit SlotFilled(requestId, slotIndex, slotId);
if (context.slotsFilled == request.ask.slots) {
context.state = RequestState.Started;
@ -105,6 +109,7 @@ contract Marketplace is Collateral, Proofs {
context.state == RequestState.Started) {
context.state = RequestState.Failed;
_setProofEnd(requestId, block.timestamp - 1);
context.endsAt = block.timestamp - 1;
emit RequestFailed(requestId);
@ -233,15 +238,11 @@ contract Marketplace is Collateral, Proofs {
function proofEnd(bytes32 slotId) public view returns (uint256) {
Slot memory slot = _slot(slotId);
uint256 end = _end(slotId);
RequestContext storage context = _context(slot.requestId);
uint256 end = _end(slot.requestId);
if (_slotAcceptsProofs(slotId)) {
return end;
} else {
// Calculate the earliest ending between a slot and a request.
// Request endings are set, for eg, when a request fails.
uint256 earliestEnd = Math.min(end, context.endsAt);
return Math.min(earliestEnd, block.timestamp - 1);
return Math.min(end, block.timestamp - 1);
}
}
@ -276,7 +277,6 @@ contract Marketplace is Collateral, Proofs {
}
}
/// @notice returns true when the request is accepting proof submissions from hosts occupying slots.
/// @dev Request state must be new or started, and must not be cancelled, finished, or failed.
/// @param requestId id of the request for which to obtain state info

View File

@ -20,6 +20,7 @@ contract Proofs {
mapping(bytes32 => bool) private ids;
mapping(bytes32 => uint256) private starts;
mapping(bytes32 => uint256) private ends;
mapping(bytes32 => bytes32) private idEnds;
mapping(bytes32 => uint256) private probabilities;
mapping(bytes32 => uint256) private markers;
mapping(bytes32 => uint256) private missed;
@ -34,10 +35,21 @@ contract Proofs {
return timeout;
}
function _end(bytes32 id) internal view returns (uint256) {
uint256 end = ends[id];
function _end(bytes32 endId) internal view returns (uint256) {
uint256 end = ends[endId];
require(end > 0, "Proof ending doesn't exist");
return ends[id];
return ends[endId];
}
function _endId(bytes32 id) internal view returns (bytes32) {
bytes32 endId = idEnds[id];
require(endId > 0, "endId for given id doesn't exist");
return endId;
}
function _endFromId(bytes32 id) internal view returns (uint256) {
bytes32 endId = _endId(id);
return _end(endId);
}
function _missed(bytes32 id) internal view returns (uint256) {
@ -55,19 +67,19 @@ contract Proofs {
/// @notice Informs the contract that proofs should be expected for id
/// @dev Requires that the id is not already in use
/// @param id identifies the proof expectation, typically a slot id
/// @param endId Identifies the id of the proof expectation ending. Typically a request id. Different from id because the proof ending is shared amongst many ids.
/// @param probability The probability that a proof should be expected
/// @param duration Duration, from now, for which proofs should be expected
function _expectProofs(
bytes32 id, // typically slot id
uint256 probability,
uint256 duration
bytes32 endId, // typically request id, used so that the ending is global for all slots
uint256 probability
) internal {
require(!ids[id], "Proof id already in use");
ids[id] = true;
starts[id] = block.timestamp;
ends[id] = block.timestamp + duration;
probabilities[id] = probability;
markers[id] = uint256(blockhash(block.number - 1)) % period;
idEnds[id] = endId;
}
function _unexpectProofs(
@ -119,7 +131,7 @@ contract Proofs {
if (proofPeriod <= periodOf(starts[id])) {
return (false, 0);
}
uint256 end = _end(id);
uint256 end = _endFromId(id);
if (proofPeriod >= periodOf(end)) {
return (false, 0);
}
@ -169,5 +181,16 @@ contract Proofs {
missed[id] += 1;
}
/// @notice Sets the proof end time
/// @dev Can only be set once
/// @param endId the endId of the proofs to extend (typically a request id).
/// @param ending the new end time (in seconds)
function _setProofEnd(bytes32 endId, uint256 ending) internal {
// TODO: create type aliases for id and endId so that _end() can return
// EndId storage and we don't need to replicate the below require here
require (ends[endId] == 0 || ending < block.timestamp, "End exists or must be past");
ends[endId] = ending;
}
event ProofSubmitted(bytes32 id, bytes proof);
}

View File

@ -34,10 +34,10 @@ contract TestProofs is Proofs {
function expectProofs(
bytes32 id,
uint256 _probability,
uint256 _duration
bytes32 endId,
uint256 _probability
) public {
_expectProofs(id, _probability, _duration);
_expectProofs(id, endId, _probability);
}
function unexpectProofs(bytes32 id) public {
@ -67,4 +67,8 @@ contract TestProofs is Proofs {
function markProofAsMissing(bytes32 id, uint256 _period) public {
_markProofAsMissing(id, _period);
}
function setProofEnd(bytes32 id, uint256 ending) public {
_setProofEnd(id, ending);
}
}

View File

@ -212,10 +212,12 @@ describe("Marketplace", function () {
})
describe("proof end", function () {
var requestTime
beforeEach(async function () {
switchAccount(client)
await token.approve(marketplace.address, price(request))
await marketplace.requestStorage(request)
requestTime = await currentTime()
switchAccount(host)
await token.approve(marketplace.address, collateral)
await marketplace.deposit(collateral)
@ -236,11 +238,11 @@ describe("Marketplace", function () {
}
})
it("sets proof end time to the request duration once filled", async function () {
it("sets the proof end time to now + duration", async function () {
await marketplace.fillSlot(slot.request, slot.index, proof)
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(
(await currentTime()) + request.ask.duration
)
await expect(
(await marketplace.proofEnd(slotId(slot))).toNumber()
).to.be.closeTo(requestTime + request.ask.duration, 1)
})
it("sets proof end time to the past once failed", async function () {
@ -258,15 +260,14 @@ describe("Marketplace", function () {
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(now - 1)
})
it("sets proof end time to the past once finished", async function () {
it("checks that proof end time is in the past once finished", async function () {
const lastSlot = await waitUntilStarted(marketplace, request, slot, proof)
await waitUntilFinished(marketplace, lastSlot) // sets proofEnd to block.timestamp - 1
await waitUntilFinished(marketplace, lastSlot)
const now = await currentTime()
// the proof end time is set to block.timestamp - 1 when the contract is
// finished. in the process of calling currentTime and proofEnd,
// in the process of calling currentTime and proofEnd,
// block.timestamp has advanced by 1, so the expected proof end time will
// be block.timestamp - 2.
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(now - 2)
// be block.timestamp - 1.
await expect(await marketplace.proofEnd(slotId(slot))).to.be.eq(now - 1)
})
})

View File

@ -14,6 +14,7 @@ const { periodic, hours, minutes } = require("./time")
describe("Proofs", function () {
const id = hexlify(randomBytes(32))
const endId = hexlify(randomBytes(32))
const period = 30 * 60
const timeout = 5
const downtime = 64
@ -34,80 +35,80 @@ describe("Proofs", function () {
await revert()
})
it("calculates an end time based on duration", async function () {
await proofs.expectProofs(id, probability, duration)
let end = (await currentTime()) + duration
expect((await proofs.end(id)).toNumber()).to.be.closeTo(end, 1)
})
describe("general", function () {
beforeEach(async function () {
await proofs.setProofEnd(endId, (await currentTime()) + duration)
})
it("does not allow ids to be reused", async function () {
await proofs.expectProofs(id, probability, duration)
await expect(
proofs.expectProofs(id, probability, duration)
).to.be.revertedWith("Proof id already in use")
})
it("does not allow ids to be reused", async function () {
await proofs.expectProofs(id, endId, probability)
await expect(
proofs.expectProofs(id, endId, probability)
).to.be.revertedWith("Proof id already in use")
})
it("requires proofs with an agreed upon probability", async function () {
await proofs.expectProofs(id, probability, duration)
let amount = 0
for (let i = 0; i < 100; i++) {
if (await proofs.isProofRequired(id)) {
amount += 1
it("requires proofs with an agreed upon probability", async function () {
await proofs.expectProofs(id, endId, probability)
let amount = 0
for (let i = 0; i < 100; i++) {
if (await proofs.isProofRequired(id)) {
amount += 1
}
await advanceTime(period)
}
await advanceTime(period)
}
let expected = 100 / probability
expect(amount).to.be.closeTo(expected, expected / 2)
})
let expected = 100 / probability
expect(amount).to.be.closeTo(expected, expected / 2)
})
it("requires no proofs in the start period", async function () {
const startPeriod = Math.floor((await currentTime()) / period)
const probability = 1
await proofs.expectProofs(id, probability, duration)
while (Math.floor((await currentTime()) / period) == startPeriod) {
it("requires no proofs in the start period", async function () {
const startPeriod = Math.floor((await currentTime()) / period)
const probability = 1
await proofs.expectProofs(id, endId, probability)
while (Math.floor((await currentTime()) / period) == startPeriod) {
expect(await proofs.isProofRequired(id)).to.be.false
await advanceTime(Math.floor(period / 10))
}
})
it("requires no proofs in the end period", async function () {
const probability = 1
await proofs.expectProofs(id, endId, probability)
await advanceTime(duration)
expect(await proofs.isProofRequired(id)).to.be.false
await advanceTime(Math.floor(period / 10))
}
})
})
it("requires no proofs in the end period", async function () {
const probability = 1
await proofs.expectProofs(id, probability, duration)
await advanceTime(duration)
expect(await proofs.isProofRequired(id)).to.be.false
})
it("requires no proofs after the end time", async function () {
const probability = 1
await proofs.expectProofs(id, endId, probability)
await advanceTime(duration + timeout)
expect(await proofs.isProofRequired(id)).to.be.false
})
it("requires no proofs after the end time", async function () {
const probability = 1
await proofs.expectProofs(id, probability, duration)
await advanceTime(duration + timeout)
expect(await proofs.isProofRequired(id)).to.be.false
})
it("requires proofs for different ids at different times", async function () {
let id1 = hexlify(randomBytes(32))
let id2 = hexlify(randomBytes(32))
let id3 = hexlify(randomBytes(32))
for (let id of [id1, id2, id3]) {
await proofs.expectProofs(id, endId, probability)
}
let req1, req2, req3
while (req1 === req2 && req2 === req3) {
req1 = await proofs.isProofRequired(id1)
req2 = await proofs.isProofRequired(id2)
req3 = await proofs.isProofRequired(id3)
await advanceTime(period)
}
})
it("requires proofs for different ids at different times", async function () {
let id1 = hexlify(randomBytes(32))
let id2 = hexlify(randomBytes(32))
let id3 = hexlify(randomBytes(32))
for (let id of [id1, id2, id3]) {
await proofs.expectProofs(id, probability, duration)
}
let req1, req2, req3
while (req1 === req2 && req2 === req3) {
req1 = await proofs.isProofRequired(id1)
req2 = await proofs.isProofRequired(id2)
req3 = await proofs.isProofRequired(id3)
await advanceTime(period)
}
})
it("moves pointer one block at a time", async function () {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
for (let i = 0; i < 256; i++) {
let previous = await proofs.getPointer(id)
await mine()
let current = await proofs.getPointer(id)
expect(current).to.equal((previous + 1) % 256)
}
it("moves pointer one block at a time", async function () {
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
for (let i = 0; i < 256; i++) {
let previous = await proofs.getPointer(id)
await mine()
let current = await proofs.getPointer(id)
expect(current).to.equal((previous + 1) % 256)
}
})
})
describe("when proof requirement is upcoming", function () {
@ -118,7 +119,8 @@ describe("Proofs", function () {
}
beforeEach(async function () {
await proofs.expectProofs(id, probability, duration)
await proofs.setProofEnd(endId, (await currentTime()) + duration)
await proofs.expectProofs(id, endId, probability)
await advanceTimeTo(periodEnd(periodOf(await currentTime())))
await waitUntilProofWillBeRequired()
})
@ -151,7 +153,8 @@ describe("Proofs", function () {
const proof = hexlify(randomBytes(42))
beforeEach(async function () {
await proofs.expectProofs(id, probability, duration)
await proofs.setProofEnd(endId, (await currentTime()) + duration)
await proofs.expectProofs(id, endId, probability)
})
async function waitUntilProofIsRequired(id) {
@ -270,4 +273,35 @@ describe("Proofs", function () {
await expect(await proofs.isProofRequired(id)).to.be.false
})
})
describe("set proof end", function () {
const proof = hexlify(randomBytes(42))
it("fails to get proof end when proof ending doesn't exist", async function () {
await expect(proofs.end(endId)).to.be.revertedWith(
"Proof ending doesn't exist"
)
})
it("sets proof end when proof ending doesn't already exist", async function () {
let ending = (await currentTime()) + duration
await expect(proofs.setProofEnd(endId, ending)).not.to.be.reverted
await expect(await proofs.end(endId)).to.equal(ending)
})
it("sets proof end when proof ending exists and ending set to past", async function () {
// let ending = (await currentTime()) + duration
// await proofs.setProofEnd(endId, ending)
let past = (await currentTime()) - 1
await expect(proofs.setProofEnd(endId, past)).not.to.be.reverted
})
it("fails when proof ending already exists and ending set to future", async function () {
let ending = (await currentTime()) + duration
await proofs.setProofEnd(endId, ending)
await expect(proofs.setProofEnd(endId, ending)).to.be.revertedWith(
"End exists or must be past"
)
})
})
})