diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index 28e4ceebcf..87ab8a4523 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -40,6 +40,7 @@ library TaikoData { uint64 initialUncleDelay; bool enableTokenomics; bool enablePublicInputsCheck; + bool enableOracleProver; } struct BlockMetadata { diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index 65251ac933..fd513ef417 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -59,10 +59,10 @@ library LibProposing { // the tokenomics. // TODO(daniel): remove this special address. - address specialProposer = resolver.resolve("special_proposer", true); + address soloProposer = resolver.resolve("solo_proposer", true); require( - specialProposer == address(0) || specialProposer == msg.sender, - "L1:specialProposer" + soloProposer == address(0) || soloProposer == msg.sender, + "L1:soloProposer" ); assert(!LibUtils.isHalted(state)); diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index a110647b77..b3e903d0de 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -248,15 +248,27 @@ library LibProving { meta: evidence.meta }); - bytes32 blockHash = evidence.header.hashBlockHeader(); - // For alpha-2 testnet, the network allows any address to submit ZKP, // but a special prover can skip ZKP verification if the ZKP is empty. + bool skipZKPVerification; + // TODO(daniel): remove this special address. - if (msg.sender == resolver.resolve("special_prover", true)) { - // Skip ZKP verification - } else { + if (config.enableOracleProver) { + bytes32 _blockHash = state + .forkChoices[target.id][evidence.header.parentHash].blockHash; + + if (msg.sender == resolver.resolve("oracle_prover", false)) { + require(_blockHash == 0, "L1:mustBeFirstProver"); + skipZKPVerification = true; + } else { + require(_blockHash != 0, "L1:mustNotBeFirstProver"); + } + } + + bytes32 blockHash = evidence.header.hashBlockHeader(); + + if (!skipZKPVerification) { for (uint256 i = 0; i < config.zkProofsPerBlock; ++i) { require( proofVerifier.verifyZKP({ @@ -301,23 +313,25 @@ library LibProving { ]; if (fc.blockHash == 0) { + // This is the first proof for this block. fc.blockHash = blockHash; - fc.provenAt = uint64(block.timestamp); - } else { - if (fc.blockHash != blockHash) { - // We have a problem here: two proofs are both valid but claims - // the new block has different hashes. - LibUtils.halt(state, true); - return; - } + if (!config.enableOracleProver) { + // If the oracle prover is not enabled + // we use the first prover's timestamp + fc.provenAt = uint64(block.timestamp); + } else { + // We keep fc.provenAt as 0. + } + } else { require( fc.provers.length < config.maxProofsPerForkChoice, "L1:proof:tooMany" ); require( - block.timestamp < + fc.provenAt == 0 || + block.timestamp < LibUtils.getUncleProofDeadline({ state: state, config: config, @@ -330,6 +344,23 @@ library LibProving { for (uint256 i = 0; i < fc.provers.length; ++i) { require(fc.provers[i] != prover, "L1:prover:dup"); } + + if (fc.blockHash != blockHash) { + // We have a problem here: two proofs are both valid but claims + // the new block has different hashes. + if (config.enableOracleProver) { + revert("L1:proof:conflict"); + } else { + LibUtils.halt(state, true); + return; + } + } + + if (config.enableOracleProver && fc.provenAt == 0) { + // If the oracle prover is enabled, we + // use the second prover's timestamp. + fc.provenAt = uint64(block.timestamp); + } } fc.provers.push(prover); diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index ab75b355d7..720dc471c6 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -153,22 +153,35 @@ library LibVerifying { } function _rewardProvers( + TaikoData.Config memory config, TaikoData.ForkChoice storage fc, uint256 reward, TkoToken tkoToken ) private { - uint sum = 2 ** fc.provers.length - 1; - for (uint i = 0; i < fc.provers.length; ++i) { - uint weight = (1 << (fc.provers.length - i - 1)); + uint start; + uint count = fc.provers.length; + + if (config.enableOracleProver) { + start = 1; + count -= 1; + } + + uint sum = (1 << count) - 1; + uint weight = 1 << (count - 1); + for (uint i = 0; i < count; ++i) { uint proverReward = (reward * weight) / sum; + if (proverReward == 0) { + break; + } - if (tkoToken.balanceOf(fc.provers[i]) == 0) { + if (tkoToken.balanceOf(fc.provers[start + i]) == 0) { // Reduce reward to 1 wei as a penalty if the prover // has 0 TKO balance. This allows the next prover reward // to be fully paid. proverReward = uint256(1); } - tkoToken.mint(fc.provers[i], proverReward); + tkoToken.mint(fc.provers[start + i], proverReward); + weight = weight >> 1; } } @@ -197,7 +210,7 @@ library LibVerifying { resolver.resolve("tko_token", false) ); - _rewardProvers(fc, reward, tkoToken); + _rewardProvers(config, fc, reward, tkoToken); _refundProposerDeposit(target, tRelBp, tkoToken); } // Update feeBase and avgProofTime @@ -241,6 +254,8 @@ library LibVerifying { uint256 blockId ) private view returns (bool) { return + // TODO(daniel): remove the next line. + (!config.enableOracleProver || fc.provers.length > 1) && fc.blockHash != 0 && block.timestamp > LibUtils.getUncleProofDeadline({ diff --git a/packages/protocol/contracts/libs/LibSharedConfig.sol b/packages/protocol/contracts/libs/LibSharedConfig.sol index 9bd9b42ea4..514ca49701 100644 --- a/packages/protocol/contracts/libs/LibSharedConfig.sol +++ b/packages/protocol/contracts/libs/LibSharedConfig.sol @@ -41,8 +41,9 @@ library LibSharedConfig { proofTimeCap: 60 minutes, bootstrapDiscountHalvingPeriod: 180 days, initialUncleDelay: 60 minutes, - enableTokenomics: false, - enablePublicInputsCheck: true + enableTokenomics: true, + enablePublicInputsCheck: true, + enableOracleProver: true }); } } diff --git a/packages/protocol/contracts/libs/LibZKP.sol b/packages/protocol/contracts/libs/LibZKP.sol index 0b27eea037..e30bdd51fc 100644 --- a/packages/protocol/contracts/libs/LibZKP.sol +++ b/packages/protocol/contracts/libs/LibZKP.sol @@ -18,8 +18,9 @@ library LibZKP { address prover, bytes32 txListHash ) internal view returns (bool verified) { - // TODO(david):public input is assembled in client software for testing purposes right now, move this part of logic - // to here. + // TODO(david):public input is assembled in client software + // for testing purposes right now, move this part of logic + // here in this contract. (verified, ) = plonkVerifier.staticcall(zkproof); } } diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1.sol b/packages/protocol/contracts/test/L1/TestTaikoL1.sol index 370d49a2eb..a15b8523f0 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1.sol @@ -49,6 +49,7 @@ contract TestTaikoL1 is TaikoL1, IProofVerifier { config.initialUncleDelay = 1 minutes; config.enableTokenomics = false; config.enablePublicInputsCheck = true; + config.enableOracleProver = false; } function verifyZKP( diff --git a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol index 0c05a58711..3c12444856 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL1EnableTokenomics.sol @@ -49,6 +49,7 @@ contract TestTaikoL1EnableTokenomics is TaikoL1, IProofVerifier { config.initialUncleDelay = 1 seconds; config.enableTokenomics = true; config.enablePublicInputsCheck = false; + config.enableOracleProver = false; } function verifyZKP( diff --git a/packages/protocol/contracts/test/L1/TestTaikoL2.sol b/packages/protocol/contracts/test/L1/TestTaikoL2.sol index 3df37047fa..1465bb3334 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL2.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL2.sol @@ -50,5 +50,6 @@ contract TestTaikoL2 is TaikoL2 { config.initialUncleDelay = 1 minutes; config.enableTokenomics = true; config.enablePublicInputsCheck = false; + config.enableOracleProver = false; } } diff --git a/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol b/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol index 0d2650d819..7e97abe64d 100644 --- a/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol +++ b/packages/protocol/contracts/test/L1/TestTaikoL2EnablePublicInputsCheck.sol @@ -50,5 +50,6 @@ contract TestTaikoL2EnablePublicInputsCheck is TaikoL2 { config.initialUncleDelay = 1 minutes; config.enableTokenomics = true; config.enablePublicInputsCheck = true; + config.enableOracleProver = false; } }