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 duration;
uint256 size; uint256 size;
bytes32 contentHash; bytes32 contentHash;
uint256 proofPeriod;
uint256 proofTimeout;
uint256 maxPrice; uint256 maxPrice;
uint256 expiry; uint256 expiry;
bytes32 nonce; bytes32 nonce;

View File

@ -2,9 +2,15 @@
pragma solidity ^0.8.0; pragma solidity ^0.8.0;
contract Proofs { 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 => bool) private ids;
mapping(bytes32 => uint256) private periods;
mapping(bytes32 => uint256) private timeouts;
mapping(bytes32 => uint256) private starts; mapping(bytes32 => uint256) private starts;
mapping(bytes32 => uint256) private ends; mapping(bytes32 => uint256) private ends;
mapping(bytes32 => uint256) private markers; mapping(bytes32 => uint256) private markers;
@ -12,12 +18,12 @@ contract Proofs {
mapping(bytes32 => mapping(uint256 => bool)) private received; mapping(bytes32 => mapping(uint256 => bool)) private received;
mapping(bytes32 => mapping(uint256 => bool)) private missing; mapping(bytes32 => mapping(uint256 => bool)) private missing;
function _period(bytes32 id) internal view returns (uint256) { function _period() internal view returns (uint256) {
return periods[id]; return period;
} }
function _timeout(bytes32 id) internal view returns (uint256) { function _timeout() internal view returns (uint256) {
return timeouts[id]; return timeout;
} }
function _end(bytes32 id) internal view returns (uint256) { function _end(bytes32 id) internal view returns (uint256) {
@ -28,24 +34,9 @@ contract Proofs {
return missed[id]; return missed[id];
} }
// Checks that proof timeout is <= 128. Only the latest 256 blocks can be function _expectProofs(bytes32 id, uint256 duration) internal {
// 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 {
require(!ids[id], "Proof id already in use"); require(!ids[id], "Proof id already in use");
_checkTimeout(timeout);
ids[id] = true; ids[id] = true;
periods[id] = period;
timeouts[id] = timeout;
starts[id] = block.number; starts[id] = block.number;
ends[id] = block.number + duration + 2 * timeout; ends[id] = block.number + duration + 2 * timeout;
markers[id] = uint256(blockhash(block.number - 1)) % period; markers[id] = uint256(blockhash(block.number - 1)) % period;
@ -64,15 +55,11 @@ contract Proofs {
return false; return false;
} }
bytes32 hash = blockhash(blocknumber - 1); 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) function _isProofTimedOut(uint256 blocknumber) internal view returns (bool) {
internal return block.number >= blocknumber + timeout;
view
returns (bool)
{
return block.number >= blocknumber + timeouts[id];
} }
function _submitProof( function _submitProof(
@ -85,16 +72,13 @@ contract Proofs {
_isProofRequired(id, blocknumber), _isProofRequired(id, blocknumber),
"No proof required for this block" "No proof required for this block"
); );
require( require(!_isProofTimedOut(blocknumber), "Proof not allowed after timeout");
!_isProofTimedOut(id, blocknumber),
"Proof not allowed after timeout"
);
require(!received[id][blocknumber], "Proof already submitted"); require(!received[id][blocknumber], "Proof already submitted");
received[id][blocknumber] = true; received[id][blocknumber] = true;
} }
function _markProofAsMissing(bytes32 id, uint256 blocknumber) internal { 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(!received[id][blocknumber], "Proof was submitted, not missing");
require(_isProofRequired(id, blocknumber), "Proof was not required"); require(_isProofRequired(id, blocknumber), "Proof was not required");
require(!missing[id][blocknumber], "Proof already marked as missing"); require(!missing[id][blocknumber], "Proof already marked as missing");

View File

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

View File

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

View File

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

View File

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

View File

@ -123,7 +123,8 @@ describe("Storage", function () {
mineBlock() mineBlock()
} }
const blocknumber = await minedBlockNumber() 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() mineBlock()
} }
await storage.markProofAsMissing(id, blocknumber) await storage.markProofAsMissing(id, blocknumber)

View File

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

View File

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