Skip to content

Commit

Permalink
feat(protocol)!: improve protocol based on Brecht's internal review (#…
Browse files Browse the repository at this point in the history
…15740)

Co-authored-by: Daniel Wang <99078276+dantaik@users.noreply.github.com>
Co-authored-by: D <51912515+adaki2004@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 13, 2024
1 parent e27e83c commit 791b139
Show file tree
Hide file tree
Showing 39 changed files with 352 additions and 273 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/protocol.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
- name: protocol - Deploy L1 Contracts
working-directory: ./packages/protocol
run: |
anvil &
anvil --hardfork cancun &
while ! nc -z localhost 8545; do
sleep 1
done
Expand Down
2 changes: 0 additions & 2 deletions packages/protocol/contracts/L1/TaikoData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ library TaikoData {
uint64 timestamp; // slot 6 (90 bits)
uint16 tier;
uint8 contestations;
bytes32[4] __reserved;
}

/// @dev Struct containing data required for verifying a block.
Expand All @@ -152,7 +151,6 @@ library TaikoData {
uint64 proposedIn; // L1 block number
uint32 nextTransitionId;
uint32 verifiedTransitionId;
bytes32[7] __reserved;
}

/// @dev Struct representing an Ethereum deposit.
Expand Down
42 changes: 27 additions & 15 deletions packages/protocol/contracts/L1/TaikoL1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ contract TaikoL1 is
TaikoData.State public state;
uint256[100] private __gap;

modifier whenProvingNotPaused() {
if (state.slotB.provingPaused) revert L1_PROVING_PAUSED();
_;
}

/// @dev Fallback function to receive Ether from Hooks
receive() external payable {
if (!_inNonReentrant()) revert L1_RECEIVE_DISABLED();
Expand Down Expand Up @@ -75,17 +80,19 @@ contract TaikoL1 is
(meta, depositsProcessed) =
LibProposing.proposeBlock(state, config, AddressResolver(this), params, txList);

if (!state.slotB.provingPaused && config.maxBlocksToVerifyPerProposal > 0) {
LibVerifying.verifyBlocks(
state, config, AddressResolver(this), config.maxBlocksToVerifyPerProposal
);
}
_verifyBlocks(config, config.maxBlocksToVerifyPerProposal);
}

/// @inheritdoc ITaikoL1
function proveBlock(uint64 blockId, bytes calldata input) external nonReentrant whenNotPaused {
if (state.slotB.provingPaused) revert L1_PROVING_PAUSED();

function proveBlock(
uint64 blockId,
bytes calldata input
)
external
nonReentrant
whenNotPaused
whenProvingNotPaused
{
(
TaikoData.BlockMetadata memory meta,
TaikoData.Transition memory tran,
Expand All @@ -99,17 +106,12 @@ contract TaikoL1 is
uint8 maxBlocksToVerify =
LibProving.proveBlock(state, config, AddressResolver(this), meta, tran, proof);

if (maxBlocksToVerify > 0) {
LibVerifying.verifyBlocks(state, config, AddressResolver(this), maxBlocksToVerify);
}
_verifyBlocks(config, maxBlocksToVerify);
}

/// @inheritdoc ITaikoL1
function verifyBlocks(uint64 maxBlocksToVerify) external nonReentrant whenNotPaused {
if (maxBlocksToVerify == 0) revert L1_INVALID_PARAM();
if (state.slotB.provingPaused) revert L1_PROVING_PAUSED();

LibVerifying.verifyBlocks(state, getConfig(), AddressResolver(this), maxBlocksToVerify);
_verifyBlocks(getConfig(), maxBlocksToVerify);
}

/// @notice Pause block proving.
Expand Down Expand Up @@ -250,6 +252,16 @@ contract TaikoL1 is
return LibVerifying.isConfigValid(getConfig());
}

function _verifyBlocks(
TaikoData.Config memory config,
uint64 maxBlocksToVerify
)
internal
whenProvingNotPaused
{
LibVerifying.verifyBlocks(state, config, AddressResolver(this), maxBlocksToVerify);
}

function _authorizePause(address)
internal
view
Expand Down
1 change: 1 addition & 0 deletions packages/protocol/contracts/L1/gov/TaikoGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ contract TaikoGovernor is
{
__OwnerUUPSUpgradable_init();
__Governor_init("TaikoGovernor");
__GovernorCompatibilityBravo_init();
__GovernorVotes_init(_token);
__GovernorVotesQuorumFraction_init(4);
__GovernorTimelockControl_init(_timelock);
Expand Down
30 changes: 9 additions & 21 deletions packages/protocol/contracts/L1/hooks/AssignmentHook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ contract AssignmentHook is EssentialContract, IHook {
}

// Max gas paying the prover. This should be large enough to prevent the
// worst cases, usually block proposer shall be aware the risks and only
// choose provers that cannot consume too much gas when receiving Ether.
uint256 public constant MAX_GAS_PAYING_PROVER = 200_000;
// worst cases for the prover. To assure a trustless relationship between
// the proposer and the prover it's the prover's job to make sure it can
// get paid within this limit.
uint256 public constant MAX_GAS_PAYING_PROVER = 50_000;

event BlockAssigned(
address indexed assignedProver, TaikoData.BlockMetadata meta, ProverAssignment assignment
Expand Down Expand Up @@ -108,26 +109,13 @@ contract AssignmentHook is EssentialContract, IHook {

// The proposer irrevocably pays a fee to the assigned prover, either in
// Ether or ERC20 tokens.
uint256 refund;
uint256 totalFeeETH = input.tip;
if (assignment.feeToken == address(0)) {
uint256 totalFee = proverFee + input.tip;
if (msg.value < totalFee) {
revert HOOK_ASSIGNMENT_INSUFFICIENT_FEE();
}

unchecked {
refund = msg.value - totalFee;
}
totalFeeETH += proverFee;

// Paying Ether
blk.assignedProver.sendEther(proverFee, MAX_GAS_PAYING_PROVER);
} else {
if (msg.value < input.tip) {
revert HOOK_ASSIGNMENT_INSUFFICIENT_FEE();
}
unchecked {
refund = msg.value - input.tip;
}
// Paying ERC20 tokens
IERC20(assignment.feeToken).safeTransferFrom(
meta.coinbase, blk.assignedProver, proverFee
Expand All @@ -139,9 +127,9 @@ contract AssignmentHook is EssentialContract, IHook {
address(block.coinbase).sendEther(input.tip);
}

if (refund != 0) {
// Send all remaininger Ether back to TaikoL1 contract
taikoL1Address.sendEther(refund);
// Send all remaining Ether back to TaikoL1 contract
if (address(this).balance > 0) {
taikoL1Address.sendEther(address(this).balance);
}

emit BlockAssigned(blk.assignedProver, meta, assignment);
Expand Down
74 changes: 31 additions & 43 deletions packages/protocol/contracts/L1/libs/LibProposing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ library LibProposing {
{
TaikoData.BlockParams memory params = abi.decode(data, (TaikoData.BlockParams));

// We need a prover that will submit proofs after the block has been submitted
if (params.assignedProver == address(0)) {
revert L1_INVALID_PROVER();
}
Expand All @@ -99,6 +100,8 @@ library LibProposing {
state.blocks[(b.numBlocks - 1) % config.blockRingBufferSize].metaHash;

// Check if parent block has the right meta hash
// This is to allow the proposer to make sure the block builds on the expected latest chain
// state
if (params.parentMetaHash != 0 && parentMetaHash != params.parentMetaHash) {
revert L1_UNEXPECTED_PARENT();
}
Expand Down Expand Up @@ -159,14 +162,11 @@ library LibProposing {
}
}

// Check that the txList data range is within the max size of a blob
if (uint256(params.txListByteOffset) + params.txListByteSize > MAX_BYTES_PER_BLOB) {
revert L1_TXLIST_OFFSET();
}

if (params.txListByteSize == 0 || params.txListByteSize > config.blockMaxTxListBytes) {
revert L1_TXLIST_SIZE();
}

meta.txListByteOffset = params.txListByteOffset;
meta.txListByteSize = params.txListByteSize;
} else {
Expand All @@ -176,24 +176,25 @@ library LibProposing {
// Taiko node software.
if (!LibAddress.isSenderEOA()) revert L1_PROPOSER_NOT_EOA();

if (params.txListByteOffset != 0 || params.txListByteSize != 0) {
// The txList is the full byte array without any offset
if (params.txListByteOffset != 0) {
revert L1_INVALID_PARAM();
}

// blockMaxTxListBytes is a uint24
if (txList.length > config.blockMaxTxListBytes) {
revert L1_TXLIST_SIZE();
}

meta.blobHash = keccak256(txList);
meta.txListByteOffset = 0;
meta.txListByteSize = uint24(txList.length);
}

// Check that the tx length is non-zero and within the supported range
if (meta.txListByteSize == 0 || meta.txListByteSize > config.blockMaxTxListBytes) {
revert L1_TXLIST_SIZE();
}

// Following the Merge, the L1 mixHash incorporates the
// prevrandao value from the beacon chain. Given the possibility
// of multiple Taiko blocks being proposed within a single
// Ethereum block, we must introduce a salt to this random
// Ethereum block, we choose to introduce a salt to this random
// number as the L2 mixHash.
meta.difficulty = keccak256(abi.encodePacked(block.prevrandao, b.numBlocks, block.number));

Expand All @@ -202,38 +203,25 @@ library LibProposing {
uint256(meta.difficulty)
);

// Now, it's essential to initialize the block that will be stored
// on L1. We should aim to utilize as few storage slots as possible,
// alghouth using a ring buffer can minimize storage writes once
// the buffer reaches its capacity.
TaikoData.Block storage blk = state.blocks[b.numBlocks % config.blockRingBufferSize];

// Please note that all fields must be re-initialized since we are
// utilizing an existing ring buffer slot, not creating a new storage
// slot.
blk.metaHash = keccak256(abi.encode(meta));

// Safeguard the liveness bond to ensure its preservation,
// particularly in scenarios where it might be altered after the
// block's proposal but before it has been proven or verified.
blk.livenessBond = config.livenessBond;
blk.blockId = b.numBlocks;

blk.proposedAt = meta.timestamp;
blk.proposedIn = uint64(block.number);

// For a new block, the next transition ID is always 1, not 0.
blk.nextTransitionId = 1;

// For unverified block, its verifiedTransitionId is always 0.
blk.verifiedTransitionId = 0;

// Verify assignment authorization; if prover's address is an IProver
// contract, transfer Ether and call "validateAssignment" for
// verification.
// Prover can charge ERC20/NFT as fees; msg.value can be zero. Taiko
// doesn't mandate Ether as the only proofing fee.
blk.assignedProver = params.assignedProver;
// Create the block that will be stored onchain
TaikoData.Block memory blk = TaikoData.Block({
metaHash: keccak256(abi.encode(meta)),
// Safeguard the liveness bond to ensure its preservation,
// particularly in scenarios where it might be altered after the
// block's proposal but before it has been proven or verified.
livenessBond: config.livenessBond,
blockId: b.numBlocks,
proposedAt: meta.timestamp,
proposedIn: uint64(block.number),
// For a new block, the next transition ID is always 1, not 0.
nextTransitionId: 1,
// For unverified block, its verifiedTransitionId is always 0.
verifiedTransitionId: 0,
assignedProver: params.assignedProver
});

// Store the block in the ring buffer
state.blocks[b.numBlocks % config.blockRingBufferSize] = blk;

// Increment the counter (cursor) by 1.
unchecked {
Expand Down
25 changes: 18 additions & 7 deletions packages/protocol/contracts/L1/libs/LibProving.sol
Original file line number Diff line number Diff line change
Expand Up @@ -127,14 +127,15 @@ library LibProving {
ITierProvider.Tier memory tier =
ITierProvider(resolver.resolve("tier_provider", false)).getTier(proof.tier);

// Check if this prover is allowed to submit a proof for this block
_checkProverPermission(state, blk, ts, tid, tier);

// We must verify the proof, and any failure in proof verification will
// result in a revert.
//
// It's crucial to emphasize that the proof can be assessed in two
// potential modes: "proving mode" and "contesting mode." However, the
// precise verification logic is defined within each tier'IVerifier
// precise verification logic is defined within each tier's IVerifier
// contract implementation. We simply specify to the verifier contract
// which mode it should utilize - if the new tier is higher than the
// previous tier, we employ the proving mode; otherwise, we employ the
Expand All @@ -145,26 +146,30 @@ library LibProving {
// Taiko's core protocol.
{
address verifier = resolver.resolve(tier.verifierName, true);
// The verifier can be address-zero, signifying that there are no
// proof checks for the tier. In practice, this only applies to
// optimistic proofs.
if (verifier == address(0) && tier.verifierName != TIER_OP) {
revert L1_MISSING_VERIFIER();
}

if (verifier != address(0)) {
bool isContesting = proof.tier == ts.tier && tier.contestBond != 0;

IVerifier.Context memory ctx = IVerifier.Context({
metaHash: blk.metaHash,
blobHash: meta.blobHash,
// TODO(Brecht): Quite limiting this is required to be the same address as
// msg.sender, less flexibility on the prover's side for proof generation/proof
// submission using multiple accounts.
// Added msgSender to allow the prover to be any address in the future.
prover: msg.sender,
msgSender: msg.sender,
blockId: blk.blockId,
isContesting: isContesting,
blobUsed: meta.blobUsed
});

IVerifier(verifier).verifyProof(ctx, tran, proof);
} else if (tier.verifierName != TIER_OP) {
// The verifier can be address-zero, signifying that there are no
// proof checks for the tier. In practice, this only applies to
// optimistic proofs.
revert L1_MISSING_VERIFIER();
}
}

Expand Down Expand Up @@ -314,6 +319,8 @@ library LibProving {
// In scenarios where this transition is not the first one, we
// straightforwardly reset the transition prover to address
// zero.
// TODO(Brecht): Is it sure that in all cases all the neccessary data is stored
// in the transition in this case after this code?
ts.prover = address(0);

// Furthermore, we index the transition for future retrieval.
Expand All @@ -322,6 +329,8 @@ library LibProving {
// only possess one transition — the correct one — we don't need
// to be concerned about the cost in this case.
state.transitionIds[blk.blockId][tran.parentHash] = tid;

// There is no need to initialize ts.key here because it's only used when tid == 1
}
} else {
// A transition with the provided parentHash has been located.
Expand Down Expand Up @@ -402,6 +411,8 @@ library LibProving {
if (tid == 1 && ts.tier == 0 && inProvingWindow) {
if (!isAssignedPover) revert L1_NOT_ASSIGNED_PROVER();
} else {
// Disallow the same address to prove the block so that we can detect that the
// assigned prover should not receive his liveness bond back
if (isAssignedPover) revert L1_ASSIGNED_PROVER_NOT_ALLOWED();
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/protocol/contracts/L1/libs/LibUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ library LibUtils {
state.transitions[slot][blk.verifiedTransitionId];

return ICrossChainSync.Snippet({
remoteBlockId: blockId,
syncedInBlock: blk.proposedIn,
blockId: blockId,
blockHash: transition.blockHash,
stateRoot: transition.stateRoot
});
Expand Down
Loading

0 comments on commit 791b139

Please sign in to comment.