Skip to content

Commit

Permalink
feat(protocol): enhance ZKP handling & change proofs order (#288)
Browse files Browse the repository at this point in the history
  • Loading branch information
dantaik committed Nov 22, 2022
1 parent fb91e0d commit 5fdfdfa
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 47 deletions.
1 change: 1 addition & 0 deletions packages/protocol/contracts/L1/LibData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ library LibData {
mapping(uint256 => mapping(bytes32 => ForkChoice)) forkChoices;
mapping(bytes32 => uint256) commits;
mapping(address => bool) provers; // Whitelisted provers
uint64 statusBits;
uint64 genesisHeight;
uint64 latestVerifiedHeight;
uint64 latestVerifiedId;
Expand Down
48 changes: 33 additions & 15 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";

import "../common/ConfigManager.sol";
import "../common/EssentialContract.sol";
import "../common/IHeaderSync.sol";
Expand All @@ -17,7 +19,7 @@ import "./v1/V1Events.sol";
import "./v1/V1Finalizing.sol";
import "./v1/V1Proposing.sol";
import "./v1/V1Proving.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";
import "./v1/V1Utils.sol";

/**
* @author dantaik <dan@taiko.xyz>
Expand All @@ -28,7 +30,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
using SafeCastUpgradeable for uint256;

LibData.State public state;
uint256[44] private __gap;
uint256[43] private __gap;

function init(
address _addressManager,
Expand Down Expand Up @@ -73,7 +75,8 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
V1Proposing.proposeBlock(state, inputs);
V1Finalizing.verifyBlocks(
state,
LibConstants.TAIKO_MAX_VERIFICATIONS_PER_TX
LibConstants.TAIKO_MAX_VERIFICATIONS_PER_TX,
false
);
}

Expand All @@ -99,7 +102,8 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
V1Proving.proveBlock(state, AddressResolver(this), blockIndex, inputs);
V1Finalizing.verifyBlocks(
state,
LibConstants.TAIKO_MAX_VERIFICATIONS_PER_TX
LibConstants.TAIKO_MAX_VERIFICATIONS_PER_TX,
false
);
}

Expand All @@ -117,7 +121,6 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
* on L2. Note that the `invalidBlock` transaction is supposed to
* be the only transaction in the L2 block.
*/

function proveBlockInvalid(
uint256 blockIndex,
bytes[] calldata inputs
Expand All @@ -130,12 +133,21 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
);
V1Finalizing.verifyBlocks(
state,
LibConstants.TAIKO_MAX_VERIFICATIONS_PER_TX
LibConstants.TAIKO_MAX_VERIFICATIONS_PER_TX,
false
);
}

/**
* Add or remove a prover from the whitelist.
* Verify up to N blocks.
* @param maxBlocks Max number of blocks to verify.
*/
function verifyBlocks(uint256 maxBlocks) external nonReentrant {
require(maxBlocks > 0, "L1:maxBlocks");
V1Finalizing.verifyBlocks(state, maxBlocks, true);
}

/* Add or remove a prover from the whitelist.
*
* @param prover The prover to be added or removed.
* @param whitelisted True to add; remove otherwise.
Expand All @@ -148,7 +160,15 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
}

/**
* Return whether a prover is whitelisted.
* Halt or resume the chain.
* @param toHalt True to halt, false to resume.
*/
function halt(bool toHalt) public onlyOwner {
V1Utils.halt(state, toHalt);
}

/**
* Check whether a prover is whitelisted.
*
* @param prover The prover.
* @return True if the prover is whitelisted, false otherwise.
Expand All @@ -157,14 +177,12 @@ contract TaikoL1 is EssentialContract, IHeaderSync, V1Events {
return V1Proving.isProverWhitelisted(state, prover);
}


/**
* Verify up to N blocks.
* @param maxBlocks Max number of blocks to verify.
/**
* Check if the L1 is halted.
* @return True if halted, false otherwise.
*/
function verifyBlocks(uint256 maxBlocks) external nonReentrant {
require(maxBlocks > 0, "L1:maxBlocks");
V1Finalizing.verifyBlocks(state, maxBlocks);
function isHalted() public view returns (bool) {
return V1Utils.isHalted(state);
}

function isCommitValid(bytes32 hash) public view returns (bool) {
Expand Down
2 changes: 2 additions & 0 deletions packages/protocol/contracts/L1/v1/V1Events.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ abstract contract V1Events {
);

event ProverWhitelisted(address indexed prover, bool whitelisted);

event Halted(bool halted);
}
27 changes: 23 additions & 4 deletions packages/protocol/contracts/L1/v1/V1Finalizing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";

import "../LibData.sol";
import "./V1Utils.sol";

/// @author dantaik <dan@taiko.xyz>
library V1Finalizing {
Expand All @@ -31,7 +29,19 @@ library V1Finalizing {
emit HeaderSynced(block.number, 0, _genesisBlockHash);
}

function verifyBlocks(LibData.State storage s, uint256 maxBlocks) public {
function verifyBlocks(
LibData.State storage s,
uint256 maxBlocks,
bool checkHalt
) public {
bool halted = V1Utils.isHalted(s);
if (checkHalt) {
require(!halted, "L1:halted");
} else if (halted) {
// skip finalizing blocks
return;
}

uint64 latestL2Height = s.latestVerifiedHeight;
bytes32 latestL2Hash = s.l2Hashes[latestL2Height];
uint64 processed = 0;
Expand All @@ -43,6 +53,15 @@ library V1Finalizing {
) {
LibData.ForkChoice storage fc = s.forkChoices[i][latestL2Hash];

// TODO(daniel): use the average proof-time.
if (
block.timestamp <=
fc.provenAt + LibConstants.K_VERIFICATION_DELAY
) {
// This block is proven but still needs to wait for verificaiton.
break;
}

if (fc.blockHash == LibConstants.TAIKO_BLOCK_DEADEND_HASH) {
emit BlockVerified(i, 0);
} else if (fc.blockHash != 0) {
Expand Down
11 changes: 8 additions & 3 deletions packages/protocol/contracts/L1/v1/V1Proposing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";

import "../../common/ConfigManager.sol";
import "../../libs/LibConstants.sol";
import "../../libs/LibTxDecoder.sol";
import "../LibData.sol";
import "./V1Utils.sol";

/// @author dantaik <dan@taiko.xyz>
library V1Proposing {
Expand All @@ -25,6 +23,11 @@ library V1Proposing {
event BlockProposed(uint256 indexed id, LibData.BlockMetadata meta);

function commitBlock(LibData.State storage s, bytes32 commitHash) public {
// It's OK to allow committing block when the system is halt.
// By not checking the halt status, this method will be cheaper.
//
// require(!V1Utils.isHalted(s), "L1:halt");

require(commitHash != 0, "L1:hash");
require(s.commits[commitHash] == 0, "L1:committed");
s.commits[commitHash] = block.number;
Expand All @@ -39,6 +42,8 @@ library V1Proposing {
LibData.State storage s,
bytes[] calldata inputs
) public {
require(!V1Utils.isHalted(s), "L1:halt");

require(inputs.length == 2, "L1:inputs:size");
LibData.BlockMetadata memory meta = abi.decode(
inputs[0],
Expand Down
58 changes: 38 additions & 20 deletions packages/protocol/contracts/L1/v1/V1Proving.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";

import "../../common/AddressResolver.sol";
import "../../common/ConfigManager.sol";
import "../../libs/LibAnchorSignature.sol";
Expand All @@ -22,7 +20,7 @@ import "../../libs/LibZKP.sol";
import "../../thirdparty/LibBytesUtils.sol";
import "../../thirdparty/LibMerkleTrie.sol";
import "../../thirdparty/LibRLPWriter.sol";
import "../LibData.sol";
import "./V1Utils.sol";

/// @author dantaik <dan@taiko.xyz>
/// @author david <david@taiko.xyz>
Expand All @@ -34,7 +32,7 @@ library V1Proving {
LibData.BlockMetadata meta;
BlockHeader header;
address prover;
bytes[] proofs;
bytes[] proofs; // The first K_ZKPROOFS_PER_BLOCK are ZKPs
}

event BlockProven(
Expand All @@ -61,6 +59,8 @@ library V1Proving {
uint256 blockIndex,
bytes[] calldata inputs
) public onlyWhitelistedProver(s) {
require(!V1Utils.isHalted(s), "L1:halt");

// Check and decode inputs
require(inputs.length == 3, "L1:inputs:size");
Evidence memory evidence = abi.decode(inputs[0], (Evidence));
Expand All @@ -69,7 +69,10 @@ library V1Proving {

// Check evidence
require(evidence.meta.id == blockIndex, "L1:id");
require(evidence.proofs.length == 3, "L1:proof:size");
require(
evidence.proofs.length == 2 + LibConstants.K_ZKPROOFS_PER_BLOCK,
"L1:proof:size"
);

// Check anchor tx is valid
LibTxDecoder.Tx memory _tx = LibTxDecoder.decodeTx(anchorTx);
Expand Down Expand Up @@ -105,7 +108,7 @@ library V1Proving {
LibMerkleTrie.verifyInclusionProof(
LibRLPWriter.writeUint(0),
anchorTx,
evidence.proofs[1],
evidence.proofs[LibConstants.K_ZKPROOFS_PER_BLOCK],
evidence.header.transactionsRoot
),
"L1:tx:proof"
Expand All @@ -120,7 +123,7 @@ library V1Proving {
LibMerkleTrie.verifyInclusionProof(
LibRLPWriter.writeUint(0),
anchorReceipt,
evidence.proofs[2],
evidence.proofs[LibConstants.K_ZKPROOFS_PER_BLOCK + 1],
evidence.header.receiptsRoot
),
"L1:receipt:proof"
Expand All @@ -136,6 +139,8 @@ library V1Proving {
uint256 blockIndex,
bytes[] calldata inputs
) public onlyWhitelistedProver(s) {
require(!V1Utils.isHalted(s), "L1:halt");

// Check and decode inputs
require(inputs.length == 3, "L1:inputs:size");
Evidence memory evidence = abi.decode(inputs[0], (Evidence));
Expand All @@ -147,7 +152,10 @@ library V1Proving {

// Check evidence
require(evidence.meta.id == blockIndex, "L1:id");
require(evidence.proofs.length == 2, "L1:proof:size");
require(
evidence.proofs.length == 1 + LibConstants.K_ZKPROOFS_PER_BLOCK,
"L1:proof:size"
);

// Check the 1st receipt is for an InvalidateBlock tx with
// a BlockInvalidated event
Expand Down Expand Up @@ -175,7 +183,7 @@ library V1Proving {
LibMerkleTrie.verifyInclusionProof(
LibRLPWriter.writeUint(0),
invalidateBlockReceipt,
evidence.proofs[1],
evidence.proofs[LibConstants.K_ZKPROOFS_PER_BLOCK],
evidence.header.receiptsRoot
),
"L1:receipt:proof"
Expand Down Expand Up @@ -229,15 +237,17 @@ library V1Proving {

bytes32 blockHash = evidence.header.hashBlockHeader();

LibZKP.verify(
ConfigManager(resolver.resolve("config_manager")).getValue(
"zk_vkey"
),
evidence.proofs[0],
blockHash,
evidence.prover,
evidence.meta.txListHash
);
for (uint i = 0; i < LibConstants.K_ZKPROOFS_PER_BLOCK; i++) {
LibZKP.verify(
ConfigManager(resolver.resolve("config_manager")).getValue(
string(abi.encodePacked("zk_vkey_", i))
),
evidence.proofs[i],
blockHash,
evidence.prover,
evidence.meta.txListHash
);
}

_markBlockProven(
s,
Expand All @@ -263,9 +273,17 @@ library V1Proving {
fc.provenAt = uint64(block.timestamp);
} else {
require(
fc.blockHash == blockHash && fc.proposedAt == target.timestamp,
"L1:proof:conflict"
fc.proposedAt == target.timestamp,
"L1:proposedAt:conflict"
);

if (fc.blockHash != blockHash) {
// We have a problem here: two proofs are both valid but claims
// the new block has different hashes.
V1Utils.halt(s, true);
return;
}

require(
fc.provers.length <
LibConstants.TAIKO_MAX_PROOFS_PER_FORK_CHOICE,
Expand Down
42 changes: 42 additions & 0 deletions packages/protocol/contracts/L1/v1/V1Utils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: MIT
//
// ╭━━━━╮╱╱╭╮╱╱╱╱╱╭╮╱╱╱╱╱╭╮
// ┃╭╮╭╮┃╱╱┃┃╱╱╱╱╱┃┃╱╱╱╱╱┃┃
// ╰╯┃┃┣┻━┳┫┃╭┳━━╮┃┃╱╱╭━━┫╰━┳━━╮
// ╱╱┃┃┃╭╮┣┫╰╯┫╭╮┃┃┃╱╭┫╭╮┃╭╮┃━━┫
// ╱╱┃┃┃╭╮┃┃╭╮┫╰╯┃┃╰━╯┃╭╮┃╰╯┣━━┃
// ╱╱╰╯╰╯╰┻┻╯╰┻━━╯╰━━━┻╯╰┻━━┻━━╯
pragma solidity ^0.8.9;

import "@openzeppelin/contracts-upgradeable/utils/math/SafeCastUpgradeable.sol";

import "../../libs/LibMath.sol";
import "../LibData.sol";

/// @author dantaik <dan@taiko.xyz>
library V1Utils {
uint64 public constant MASK_HALT = 1 << 0;

event Halted(bool halted);

function halt(LibData.State storage s, bool toHalt) public {
require(isHalted(s) != toHalt, "L1:precondition");
setBit(s, MASK_HALT, toHalt);
emit Halted(toHalt);
}

function isHalted(LibData.State storage s) public view returns (bool) {
return isBitOne(s, MASK_HALT);
}

function setBit(LibData.State storage s, uint64 mask, bool one) private {
s.statusBits = one ? s.statusBits | mask : s.statusBits & ~mask;
}

function isBitOne(
LibData.State storage s,
uint64 mask
) private view returns (bool) {
return s.statusBits & mask != 0;
}
}

0 comments on commit 5fdfdfa

Please sign in to comment.