diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index 28e4ceebcfe..a66981c2824 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -103,4 +103,13 @@ library TaikoData { // Reserved uint256[42] __gap; } + + struct TentativeState { + mapping(address => bool) proposers; // Whitelisted proposers + mapping(address => bool) provers; // Whitelisted provers + bool whitelistProposers; + bool whitelistProvers; + // // Reserved + uint256[46] __gap; + } } diff --git a/packages/protocol/contracts/L1/TaikoEvents.sol b/packages/protocol/contracts/L1/TaikoEvents.sol index 8d5b08979ca..f0833f19511 100644 --- a/packages/protocol/contracts/L1/TaikoEvents.sol +++ b/packages/protocol/contracts/L1/TaikoEvents.sol @@ -30,5 +30,11 @@ abstract contract TaikoEvents { address prover ); + event WhitelistingEnabled(bool whitelistProposers, bool whitelistProvers); + + event ProposerWhitelisted(address indexed prover, bool whitelisted); + + event ProverWhitelisted(address indexed prover, bool whitelisted); + event Halted(bool halted); } diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 5116f71aef4..4e0d0e8186f 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -24,7 +24,8 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { using LibUtils for TaikoData.State; TaikoData.State public state; - uint256[100] private __gap; + TaikoData.TentativeState public tentative; + uint256[50] private __gap; function init( address _addressManager, @@ -37,6 +38,9 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { genesisBlockHash: _genesisBlockHash, feeBase: _feeBase }); + + tentative.whitelistProposers = false; + tentative.whitelistProvers = true; } /** @@ -82,6 +86,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { LibProposing.proposeBlock({ state: state, config: config, + tentative: tentative, resolver: AddressResolver(this), inputs: inputs }); @@ -116,6 +121,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { TaikoData.Config memory config = getConfig(); LibProving.proveBlock({ state: state, + tentative: tentative, config: config, resolver: AddressResolver(this), blockId: blockId, @@ -152,6 +158,7 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { LibProving.proveBlockInvalid({ state: state, + tentative: tentative, config: config, resolver: AddressResolver(this), blockId: blockId, @@ -181,6 +188,56 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { }); } + /** + * Enable or disable proposer and prover whitelisting + * @param whitelistProposers True to enable proposer whitelisting. + * @param whitelistProvers True to enable prover whitelisting. + */ + function enableWhitelisting( + bool whitelistProposers, + bool whitelistProvers + ) public onlyOwner { + LibUtils.enableWhitelisting({ + tentative: tentative, + whitelistProposers: whitelistProposers, + whitelistProvers: whitelistProvers + }); + } + + /** + * Add or remove a proposer from the whitelist. + * + * @param proposer The proposer to be added or removed. + * @param whitelisted True to add; remove otherwise. + */ + function whitelistProposer( + address proposer, + bool whitelisted + ) public onlyOwner { + LibUtils.whitelistProposer({ + tentative: tentative, + proposer: proposer, + whitelisted: whitelisted + }); + } + + /** + * Add or remove a prover from the whitelist. + * + * @param prover The prover to be added or removed. + * @param whitelisted True to add; remove otherwise. + */ + function whitelistProver( + address prover, + bool whitelisted + ) public onlyOwner { + LibUtils.whitelistProver({ + tentative: tentative, + prover: prover, + whitelisted: whitelisted + }); + } + /** * Halt or resume the chain. * @param toHalt True to halt, false to resume. @@ -189,6 +246,28 @@ contract TaikoL1 is EssentialContract, IHeaderSync, TaikoEvents { LibUtils.halt(state, toHalt); } + /** + * Check whether a proposer is whitelisted. + * + * @param proposer The proposer. + * @return True if the proposer is whitelisted, false otherwise. + */ + function isProposerWhitelisted( + address proposer + ) public view returns (bool) { + return LibUtils.isProposerWhitelisted(tentative, proposer); + } + + /** + * Check whether a prover is whitelisted. + * + * @param prover The prover. + * @return True if the prover is whitelisted, false otherwise. + */ + function isProverWhitelisted(address prover) public view returns (bool) { + return LibUtils.isProverWhitelisted(tentative, prover); + } + function getBlockFee() public view returns (uint256) { (, uint fee, uint deposit) = LibProposing.getBlockFee( state, diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index 65251ac9331..9263ef07dca 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -24,6 +24,15 @@ library LibProposing { ); event BlockProposed(uint256 indexed id, TaikoData.BlockMetadata meta); + modifier onlyWhitelistedProposer( + TaikoData.TentativeState storage tentative + ) { + if (tentative.whitelistProposers) { + require(tentative.proposers[msg.sender], "L1:whitelist"); + } + _; + } + function commitBlock( TaikoData.State storage state, TaikoData.Config memory config, @@ -51,20 +60,10 @@ library LibProposing { function proposeBlock( TaikoData.State storage state, TaikoData.Config memory config, + TaikoData.TentativeState storage tentative, AddressResolver resolver, bytes[] calldata inputs - ) public { - // For alpha-2 testnet, the network only allows an special address - // to propose but anyone to prove. This is the first step of testing - // the tokenomics. - - // TODO(daniel): remove this special address. - address specialProposer = resolver.resolve("special_proposer", true); - require( - specialProposer == address(0) || specialProposer == msg.sender, - "L1:specialProposer" - ); - + ) public onlyWhitelistedProposer(tentative) { assert(!LibUtils.isHalted(state)); require(inputs.length == 2, "L1:inputs:size"); diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 2b6d550032f..f64f2ffeb80 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -46,13 +46,21 @@ library LibProving { address prover ); + modifier onlyWhitelistedProver(TaikoData.TentativeState storage tentative) { + if (tentative.whitelistProvers) { + require(tentative.provers[msg.sender], "L1:whitelist"); + } + _; + } + function proveBlock( TaikoData.State storage state, + TaikoData.TentativeState storage tentative, TaikoData.Config memory config, AddressResolver resolver, uint256 blockId, bytes[] calldata inputs - ) public { + ) public onlyWhitelistedProver(tentative) { assert(!LibUtils.isHalted(state)); // Check and decode inputs @@ -149,11 +157,12 @@ library LibProving { function proveBlockInvalid( TaikoData.State storage state, + TaikoData.TentativeState storage tentative, TaikoData.Config memory config, AddressResolver resolver, uint256 blockId, bytes[] calldata inputs - ) public { + ) public onlyWhitelistedProver(tentative) { assert(!LibUtils.isHalted(state)); // Check and decode inputs diff --git a/packages/protocol/contracts/L1/libs/LibUtils.sol b/packages/protocol/contracts/L1/libs/LibUtils.sol index 7fd91857d4d..fd1655135b8 100644 --- a/packages/protocol/contracts/L1/libs/LibUtils.sol +++ b/packages/protocol/contracts/L1/libs/LibUtils.sol @@ -19,8 +19,52 @@ library LibUtils { bytes32 public constant BLOCK_DEADEND_HASH = bytes32(uint256(1)); + event WhitelistingEnabled(bool whitelistProposers, bool whitelistProvers); + event ProposerWhitelisted(address indexed proposer, bool whitelisted); + event ProverWhitelisted(address indexed prover, bool whitelisted); event Halted(bool halted); + function enableWhitelisting( + TaikoData.TentativeState storage tentative, + bool whitelistProposers, + bool whitelistProvers + ) internal { + tentative.whitelistProposers = whitelistProvers; + tentative.whitelistProvers = whitelistProvers; + emit WhitelistingEnabled(whitelistProposers, whitelistProvers); + } + + function whitelistProposer( + TaikoData.TentativeState storage tentative, + address proposer, + bool whitelisted + ) internal { + assert(tentative.whitelistProposers); + require( + proposer != address(0) && + tentative.proposers[proposer] != whitelisted, + "L1:precondition" + ); + + tentative.proposers[proposer] = whitelisted; + emit ProposerWhitelisted(proposer, whitelisted); + } + + function whitelistProver( + TaikoData.TentativeState storage tentative, + address prover, + bool whitelisted + ) internal { + assert(tentative.whitelistProvers); + require( + prover != address(0) && tentative.provers[prover] != whitelisted, + "L1:precondition" + ); + + tentative.provers[prover] = whitelisted; + emit ProverWhitelisted(prover, whitelisted); + } + function halt(TaikoData.State storage state, bool toHalt) internal { require(isHalted(state) != toHalt, "L1:precondition"); setBit(state, MASK_HALT, toHalt); @@ -86,6 +130,22 @@ library LibUtils { return isBitOne(state, MASK_HALT); } + function isProposerWhitelisted( + TaikoData.TentativeState storage tentative, + address proposer + ) internal view returns (bool) { + assert(tentative.whitelistProposers); + return tentative.proposers[proposer]; + } + + function isProverWhitelisted( + TaikoData.TentativeState storage tentative, + address prover + ) internal view returns (bool) { + assert(tentative.whitelistProvers); + return tentative.provers[prover]; + } + // Implement "Incentive Multipliers", see the whitepaper. function getTimeAdjustedFee( TaikoData.State storage state,