Proof period and proof timeout are network constants now

This commit is contained in:
Mark Spanbroek 2022-03-02 15:44:58 +01:00 committed by markspanbroek
parent c181195487
commit 036a214427
9 changed files with 67 additions and 91 deletions

View File

@ -98,8 +98,6 @@ contract Marketplace is Collateral {
uint256 duration;
uint256 size;
bytes32 contentHash;
uint256 proofPeriod;
uint256 proofTimeout;
uint256 maxPrice;
uint256 expiry;
bytes32 nonce;

View File

@ -2,9 +2,15 @@
pragma solidity ^0.8.0;
contract Proofs {
uint256 private immutable period;
uint256 private immutable timeout;
constructor(uint256 __period, uint256 __timeout) {
period = __period;
timeout = __timeout;
}
mapping(bytes32 => bool) private ids;
mapping(bytes32 => uint256) private periods;
mapping(bytes32 => uint256) private timeouts;
mapping(bytes32 => uint256) private starts;
mapping(bytes32 => uint256) private ends;
mapping(bytes32 => uint256) private markers;
@ -12,12 +18,12 @@ contract Proofs {
mapping(bytes32 => mapping(uint256 => bool)) private received;
mapping(bytes32 => mapping(uint256 => bool)) private missing;
function _period(bytes32 id) internal view returns (uint256) {
return periods[id];
function _period() internal view returns (uint256) {
return period;
}
function _timeout(bytes32 id) internal view returns (uint256) {
return timeouts[id];
function _timeout() internal view returns (uint256) {
return timeout;
}
function _end(bytes32 id) internal view returns (uint256) {
@ -28,24 +34,9 @@ contract Proofs {
return missed[id];
}
// Checks that proof timeout is <= 128. Only the latest 256 blocks can be
// checked in a smart contract, so that leaves a period of at least 128 blocks
// after timeout for a validator to signal the absence of a proof.
function _checkTimeout(uint256 timeout) private pure {
require(timeout <= 128, "Invalid proof timeout > 128");
}
function _expectProofs(
bytes32 id,
uint256 period,
uint256 timeout,
uint256 duration
) internal {
function _expectProofs(bytes32 id, uint256 duration) internal {
require(!ids[id], "Proof id already in use");
_checkTimeout(timeout);
ids[id] = true;
periods[id] = period;
timeouts[id] = timeout;
starts[id] = block.number;
ends[id] = block.number + duration + 2 * timeout;
markers[id] = uint256(blockhash(block.number - 1)) % period;
@ -64,15 +55,11 @@ contract Proofs {
return false;
}
bytes32 hash = blockhash(blocknumber - 1);
return hash != 0 && uint256(hash) % periods[id] == markers[id];
return hash != 0 && uint256(hash) % period == markers[id];
}
function _isProofTimedOut(bytes32 id, uint256 blocknumber)
internal
view
returns (bool)
{
return block.number >= blocknumber + timeouts[id];
function _isProofTimedOut(uint256 blocknumber) internal view returns (bool) {
return block.number >= blocknumber + timeout;
}
function _submitProof(
@ -85,16 +72,13 @@ contract Proofs {
_isProofRequired(id, blocknumber),
"No proof required for this block"
);
require(
!_isProofTimedOut(id, blocknumber),
"Proof not allowed after timeout"
);
require(!_isProofTimedOut(blocknumber), "Proof not allowed after timeout");
require(!received[id][blocknumber], "Proof already submitted");
received[id][blocknumber] = true;
}
function _markProofAsMissing(bytes32 id, uint256 blocknumber) internal {
require(_isProofTimedOut(id, blocknumber), "Proof has not timed out yet");
require(_isProofTimedOut(blocknumber), "Proof has not timed out yet");
require(!received[id][blocknumber], "Proof was submitted, not missing");
require(_isProofRequired(id, blocknumber), "Proof was not required");
require(!missing[id][blocknumber], "Proof already marked as missing");

View File

@ -14,10 +14,12 @@ contract Storage is Collateral, Marketplace, Proofs {
constructor(
IERC20 token,
uint256 _proofPeriod,
uint256 _proofTimeout,
uint256 _collateralAmount,
uint256 _slashMisses,
uint256 _slashPercentage
) Marketplace(token, _collateralAmount) {
) Marketplace(token, _collateralAmount) Proofs(_proofPeriod, _proofTimeout) {
collateralAmount = _collateralAmount;
slashMisses = _slashMisses;
slashPercentage = _slashPercentage;
@ -27,12 +29,7 @@ contract Storage is Collateral, Marketplace, Proofs {
Offer storage offer = _offer(id);
require(msg.sender == offer.host, "Only host can call this function");
Request storage request = _request(offer.requestId);
_expectProofs(
id,
request.proofPeriod,
request.proofTimeout,
request.duration
);
_expectProofs(id, request.duration);
}
function finishContract(bytes32 id) public {
@ -43,6 +40,14 @@ contract Storage is Collateral, Marketplace, Proofs {
require(token.transfer(offer.host, offer.price), "Payment failed");
}
function proofPeriod() public view returns (uint256) {
return _period();
}
function proofTimeout() public view returns (uint256) {
return _timeout();
}
function proofEnd(bytes32 contractId) public view returns (uint256) {
return _end(contractId);
}
@ -59,12 +64,8 @@ contract Storage is Collateral, Marketplace, Proofs {
return _isProofRequired(contractId, blocknumber);
}
function isProofTimedOut(bytes32 contractId, uint256 blocknumber)
public
view
returns (bool)
{
return _isProofTimedOut(contractId, blocknumber);
function isProofTimedOut(uint256 blocknumber) public view returns (bool) {
return _isProofTimedOut(blocknumber);
}
function submitProof(

View File

@ -5,12 +5,19 @@ import "./Proofs.sol";
// exposes internal functions of Proofs for testing
contract TestProofs is Proofs {
function period(bytes32 id) public view returns (uint256) {
return _period(id);
constructor(uint256 __period, uint256 __timeout)
Proofs(__period, __timeout)
// solhint-disable-next-line no-empty-blocks
{
}
function timeout(bytes32 id) public view returns (uint256) {
return _timeout(id);
function period() public view returns (uint256) {
return _period();
}
function timeout() public view returns (uint256) {
return _timeout();
}
function end(bytes32 id) public view returns (uint256) {
@ -21,13 +28,8 @@ contract TestProofs is Proofs {
return _missed(id);
}
function expectProofs(
bytes32 id,
uint256 _period,
uint256 _timeout,
uint256 _duration
) public {
_expectProofs(id, _period, _timeout, _duration);
function expectProofs(bytes32 id, uint256 _duration) public {
_expectProofs(id, _duration);
}
function isProofRequired(bytes32 id, uint256 blocknumber)

View File

@ -1,9 +1,18 @@
module.exports = async ({ deployments, getNamedAccounts }) => {
const token = await deployments.get("TestToken")
const proofPeriod = 10
const proofTimeout = 5
const collateralAmount = 100
const slashMisses = 3
const slashPercentage = 10
const args = [token.address, collateralAmount, slashMisses, slashPercentage]
const args = [
token.address,
proofPeriod,
proofTimeout,
collateralAmount,
slashMisses,
slashPercentage,
]
const { deployer } = await getNamedAccounts()
await deployments.deploy("Storage", { args, from: deployer })
}

View File

@ -12,40 +12,27 @@ describe("Proofs", function () {
beforeEach(async function () {
const Proofs = await ethers.getContractFactory("TestProofs")
proofs = await Proofs.deploy()
proofs = await Proofs.deploy(period, timeout)
})
it("indicates that proofs are required", async function () {
await proofs.expectProofs(id, period, timeout, duration)
expect(await proofs.period(id)).to.equal(period)
expect(await proofs.timeout(id)).to.equal(timeout)
})
it("calculates an endtime based on duration and timeout", async function () {
await proofs.expectProofs(id, period, timeout, duration)
it("calculates an end time based on duration and timeout", async function () {
await proofs.expectProofs(id, duration)
let start = await minedBlockNumber()
let end = start + duration + 2 * timeout
expect(await proofs.end(id)).to.equal(end)
})
it("does not allow ids to be reused", async function () {
await proofs.expectProofs(id, period, timeout, duration)
await expect(
proofs.expectProofs(id, period, timeout, duration)
).to.be.revertedWith("Proof id already in use")
})
it("does not allow a proof timeout that is too large", async function () {
let invalidTimeout = 129 // max proof timeout is 128 blocks
await expect(
proofs.expectProofs(id, period, invalidTimeout, duration)
).to.be.revertedWith("Invalid proof timeout")
await proofs.expectProofs(id, duration)
await expect(proofs.expectProofs(id, duration)).to.be.revertedWith(
"Proof id already in use"
)
})
it("requires on average a proof every period", async function () {
let blocks = 600
let amount = 0
await proofs.expectProofs(id, period, timeout, blocks)
await proofs.expectProofs(id, blocks)
for (let i = 0; i < blocks; i++) {
await mineBlock()
if (await proofs.isProofRequired(id, await minedBlockNumber())) {
@ -60,7 +47,7 @@ describe("Proofs", function () {
for (let i = 0; i < 4 * period; i++) {
mineBlock()
}
await proofs.expectProofs(id, period, timeout, duration)
await proofs.expectProofs(id, duration)
let start = await minedBlockNumber()
for (let i = 1; i < 4 * period; i++) {
expect(await proofs.isProofRequired(id, start - i)).to.be.false
@ -69,7 +56,7 @@ describe("Proofs", function () {
describe("when proofs are required", async function () {
beforeEach(async function () {
await proofs.expectProofs(id, period, timeout, duration)
await proofs.expectProofs(id, duration)
})
async function mineUntilProofIsRequired(id) {

View File

@ -123,7 +123,8 @@ describe("Storage", function () {
mineBlock()
}
const blocknumber = await minedBlockNumber()
for (let i = 0; i < request.proofTimeout; i++) {
const timeout = await storage.proofTimeout()
for (let i = 0; i < timeout; i++) {
mineBlock()
}
await storage.markProofAsMissing(id, blocknumber)

View File

@ -7,8 +7,6 @@ const exampleRequest = () => ({
duration: 150, // 150 blocks ≈ half an hour
size: 1 * 1024 * 1024 * 1024, // 1 Gigabyte
contentHash: sha256("0xdeadbeef"),
proofPeriod: 8, // 8 blocks ≈ 2 minutes
proofTimeout: 4, // 4 blocks ≈ 1 minute
maxPrice: 84,
expiry: now() + hours(1),
nonce: hexlify(randomBytes(32)),

View File

@ -11,8 +11,6 @@ function requestId(request) {
"bytes32",
"uint256",
"uint256",
"uint256",
"uint256",
"bytes32",
],
requestToArray(request)
@ -35,8 +33,6 @@ function requestToArray(request) {
request.duration,
request.size,
request.contentHash,
request.proofPeriod,
request.proofTimeout,
request.maxPrice,
request.expiry,
request.nonce,