Require proofs on average once every proof period

This commit is contained in:
Mark Spanbroek 2021-10-14 12:37:14 +02:00
parent 550fcf4afe
commit cbf34df013
2 changed files with 91 additions and 5 deletions

View File

@ -10,6 +10,7 @@ contract StorageContract {
address public immutable host; // host that provides storage
uint public immutable proofPeriod; // average time between proofs (in blocks)
uint public immutable proofTimeout; // proof has to be submitted before this
uint public immutable proofMarker; // indicates when a proof is required
constructor(uint _duration,
uint _size,
@ -24,15 +25,17 @@ contract StorageContract {
bytes32 bidHash = hashBid(requestHash, _price);
checkSignature(requestSignature, requestHash, msg.sender);
checkSignature(bidSignature, bidHash, _host);
checkProofTimeout(_proofTimeout);
duration = _duration;
size = _size;
price = _price;
host = _host;
proofPeriod = _proofPeriod;
proofTimeout = _proofTimeout;
proofMarker = uint(blockhash(block.number - 1)) % _proofPeriod;
}
// creates hash for a storage request that can be used to check its signature
// Creates hash for a storage request that can be used to check its signature.
function hashRequest(uint _duration, uint _size)
internal pure
returns (bytes32)
@ -44,7 +47,7 @@ contract StorageContract {
));
}
// creates hash for a storage bid that can be used to check its signature
// Creates hash for a storage bid that can be used to check its signature.
function hashBid(bytes32 requestHash, uint _price)
internal pure
returns (bytes32)
@ -56,7 +59,7 @@ contract StorageContract {
));
}
// checks a signature for a storage request or bid, given its hash
// Checks a signature for a storage request or bid, given its hash.
function checkSignature(bytes memory signature, bytes32 hash, address signer)
internal pure
{
@ -65,4 +68,19 @@ contract StorageContract {
require(recovered == signer, "Invalid signature");
}
// 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 checkProofTimeout(uint timeout) internal pure {
require(timeout <= 128, "Invalid proof timeout, needs to be <= 128");
}
// Check whether a proof is required at the time of the block with the
// specified block number. A proof has to be submitted within the proof
// timeout for it to be valid. Whether a proof is required is determined
// randomly, but on average it is once every proof period.
function isProofRequired(uint blocknumber) public view returns (bool) {
bytes32 hash = blockhash(blocknumber);
return hash != 0 && uint(hash) % proofPeriod == proofMarker;
}
}

View File

@ -6,8 +6,8 @@ describe("Storage Contract", function () {
const duration = 31 * 24 * 60 * 60 // 31 days
const size = 1 * 1024 * 1024 * 1024 // 1 Gigabyte
const proofPeriod = 64 // 64 blocks ≈ 15 minutes
const proofTimeout = 42 // 42 blocks ≈ 10 minutes
const proofPeriod = 8 // 8 blocks ≈ 2 minutes
const proofTimeout = 4 // 4 blocks ≈ 1 minute
const price = 42
var StorageContract
@ -89,6 +89,73 @@ describe("Storage Contract", function () {
invalidSignature
)).to.be.revertedWith("Invalid signature")
})
it("cannot be created when proof timeout is too large", async function () {
let invalidTimeout = 129 // max proof timeout is 128 blocks
await expect(StorageContract.deploy(
duration,
size,
price,
proofPeriod,
invalidTimeout,
await host.getAddress(),
await sign(client, requestHash),
await sign(host, bidHash),
)).to.be.revertedWith("Invalid proof timeout")
})
describe("proofs", function () {
async function mineBlock() {
await ethers.provider.send("evm_mine")
}
async function minedBlockNumber() {
return await ethers.provider.getBlockNumber() - 1
}
async function mineUntilProofIsRequired() {
while (!await contract.isProofRequired(await minedBlockNumber())) {
mineBlock()
}
}
beforeEach(async function () {
contract = await StorageContract.deploy(
duration,
size,
price,
proofPeriod,
proofTimeout,
await host.getAddress(),
await sign(client, requestHash),
await sign(host, bidHash)
)
})
it("requires on average a proof every period", async function () {
let blocks = 400
let proofs = 0
for (i=0; i<blocks; i++) {
await mineBlock()
if (await contract.isProofRequired(await minedBlockNumber())) {
proofs += 1
}
}
let average = blocks / proofs
expect(average).to.be.closeTo(proofPeriod, proofPeriod / 2)
})
it("requires no proof for blocks that are unavailable", async function () {
await mineUntilProofIsRequired()
let blocknumber = await minedBlockNumber()
for (i=0; i<256; i++) { // only last 256 blocks are available in solidity
mineBlock()
}
expect(await contract.isProofRequired(blocknumber)).to.be.false
})
})
})
// TDOO: add root hash of data
@ -96,6 +163,7 @@ describe("Storage Contract", function () {
// TODO: contract start and timeout
// TODO: missed proofs
// TODO: successfull proofs
// TODO: only allow proofs after start of contract
// TODO: payout
// TODO: stake
// TODO: request expiration