diff --git a/packages/protocol/contracts/layer1/based/TaikoL1.sol b/packages/protocol/contracts/layer1/based/TaikoL1.sol index 69fa72ed06c..7b5fc676a9a 100644 --- a/packages/protocol/contracts/layer1/based/TaikoL1.sol +++ b/packages/protocol/contracts/layer1/based/TaikoL1.sol @@ -159,6 +159,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { /// @inheritdoc ITaikoL1 function pauseProving(bool _pause) external { + _disable(); _authorizePause(msg.sender, _pause); LibProving.pauseProving(state, _pause); } @@ -296,7 +297,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { maxBlocksToVerify: 16, blockMaxGasLimit: 240_000_000, livenessBond: 0.07 ether, - stateRootSyncInternal: 16, + stateRootSyncInternal: 1, maxAnchorHeightOffset: 64, baseFeeConfig: LibSharedData.BaseFeeConfig({ adjustmentQuotient: 8, diff --git a/packages/protocol/contracts/layer1/devnet/DevnetTierProvider.sol b/packages/protocol/contracts/layer1/devnet/DevnetTierProvider.sol index 0b842926633..fedf0060d69 100644 --- a/packages/protocol/contracts/layer1/devnet/DevnetTierProvider.sol +++ b/packages/protocol/contracts/layer1/devnet/DevnetTierProvider.sol @@ -14,10 +14,11 @@ contract DevnetTierProvider is TierProviderBase, ITierRouter { /// @inheritdoc ITierProvider function getTierIds() public pure override returns (uint16[] memory tiers_) { - tiers_ = new uint16[](3); + tiers_ = new uint16[](4); tiers_[0] = LibTiers.TIER_OPTIMISTIC; tiers_[1] = LibTiers.TIER_GUARDIAN_MINORITY; tiers_[2] = LibTiers.TIER_GUARDIAN; + tiers_[3] = LibTiers.TIER_TWO_OF_THREE; } /// @inheritdoc ITierProvider diff --git a/packages/protocol/contracts/layer1/tiers/LibTiers.sol b/packages/protocol/contracts/layer1/tiers/LibTiers.sol index fd70d7eaf04..4c3ad3b6e3a 100644 --- a/packages/protocol/contracts/layer1/tiers/LibTiers.sol +++ b/packages/protocol/contracts/layer1/tiers/LibTiers.sol @@ -28,4 +28,7 @@ library LibTiers { /// @notice Guardian tier ID with majority approval. uint16 public constant TIER_GUARDIAN = 1000; + + /// @notice Requires 2/3 proofs from SGX/RISC/SP1 + uint16 public constant TIER_TWO_OF_THREE = 1100; } diff --git a/packages/protocol/contracts/layer1/tiers/TierProviderBase.sol b/packages/protocol/contracts/layer1/tiers/TierProviderBase.sol index 5e5130c63fd..ab91c8cd2da 100644 --- a/packages/protocol/contracts/layer1/tiers/TierProviderBase.sol +++ b/packages/protocol/contracts/layer1/tiers/TierProviderBase.sol @@ -52,6 +52,11 @@ abstract contract TierProviderBase is ITierProvider { return _buildTier(LibStrings.B_TIER_GUARDIAN, 0, 1440, 2880); } + if (_tierId == LibTiers.TIER_TWO_OF_THREE) { + // No validity bond or cooldown. + return _buildTier(LibStrings.B_TIER_TWO_OF_THREE, 0, 0, 180); + } + revert TIER_NOT_FOUND(); } diff --git a/packages/protocol/contracts/layer1/tiers/TierProviderV2.sol b/packages/protocol/contracts/layer1/tiers/TierProviderV2.sol index 69f1541bf14..7c9d7116af3 100644 --- a/packages/protocol/contracts/layer1/tiers/TierProviderV2.sol +++ b/packages/protocol/contracts/layer1/tiers/TierProviderV2.sol @@ -2,20 +2,24 @@ pragma solidity ^0.8.24; import "./TierProviderBase.sol"; +import "./ITierRouter.sol"; /// @title TierProviderV2 /// @custom:security-contact security@taiko.xyz -contract TierProviderV2 is TierProviderBase { +contract TierProviderV2 is TierProviderBase, ITierRouter { + /// @inheritdoc ITierRouter + function getProvider(uint256) external view returns (address) { + return address(this); + } + /// @inheritdoc ITierProvider function getTierIds() public pure override returns (uint16[] memory tiers_) { - tiers_ = new uint16[](3); - tiers_[0] = LibTiers.TIER_SGX; - tiers_[1] = LibTiers.TIER_GUARDIAN_MINORITY; - tiers_[2] = LibTiers.TIER_GUARDIAN; + tiers_ = new uint16[](1); + tiers_[0] = LibTiers.TIER_TWO_OF_THREE; } /// @inheritdoc ITierProvider function getMinTier(address, uint256) public pure override returns (uint16) { - return LibTiers.TIER_SGX; + return LibTiers.TIER_TWO_OF_THREE; } } diff --git a/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol b/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol index 4583f79cc99..f5bf83f024e 100644 --- a/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol +++ b/packages/protocol/contracts/layer1/verifiers/SgxVerifier.sol @@ -144,8 +144,9 @@ contract SgxVerifier is EssentialContract, IVerifier { TaikoData.TierProof calldata _proof ) external - onlyFromNamedEither(LibStrings.B_TAIKO, LibStrings.B_TIER_TEE_ANY) { + // ^ Access controls removed for testnet. + // Do not run proof verification to contest an existing proof if (_ctx.isContesting) return; diff --git a/packages/protocol/contracts/layer1/verifiers/compose/ComposeVerifier.sol b/packages/protocol/contracts/layer1/verifiers/compose/ComposeVerifier.sol index 71fe03d6acc..550842af007 100644 --- a/packages/protocol/contracts/layer1/verifiers/compose/ComposeVerifier.sol +++ b/packages/protocol/contracts/layer1/verifiers/compose/ComposeVerifier.sol @@ -44,7 +44,6 @@ abstract contract ComposeVerifier is EssentialContract, IVerifier { TaikoData.TierProof calldata _proof ) external - onlyAuthorizedCaller nonReentrant { (address[] memory verifiers, uint256 numSubProofs_) = getSubVerifiersAndThreshold(); diff --git a/packages/protocol/contracts/layer1/verifiers/compose/TwoOfThreeVerifier.sol b/packages/protocol/contracts/layer1/verifiers/compose/TwoOfThreeVerifier.sol new file mode 100644 index 00000000000..71f37721e50 --- /dev/null +++ b/packages/protocol/contracts/layer1/verifiers/compose/TwoOfThreeVerifier.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import "src/shared/common/LibStrings.sol"; +import "./ComposeVerifier.sol"; + +/// @title TwoOfThreeVerifier +/// @custom:security-contact security@taiko.xyz +contract TwoOfThreeVerifier is ComposeVerifier { + uint256[50] private __gap; + + /// @inheritdoc ComposeVerifier + function getSubVerifiersAndThreshold() + public + view + override + returns (address[] memory verifiers_, uint256 numSubProofs_) + { + verifiers_ = new address[](3); + verifiers_[0] = resolve(LibStrings.B_TIER_ZKVM_RISC0, true); + verifiers_[1] = resolve(LibStrings.B_TIER_ZKVM_SP1, true); + verifiers_[2] = resolve(LibStrings.B_TIER_SGX, true); + numSubProofs_ = 2; + } +} diff --git a/packages/protocol/contracts/shared/common/EssentialContract.sol b/packages/protocol/contracts/shared/common/EssentialContract.sol index 777ce8809ff..601e6de6292 100644 --- a/packages/protocol/contracts/shared/common/EssentialContract.sol +++ b/packages/protocol/contracts/shared/common/EssentialContract.sol @@ -31,6 +31,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, error REENTRANT_CALL(); error ZERO_ADDRESS(); error ZERO_VALUE(); + error FUNCTION_DISABLED(); /// @dev Modifier that ensures the caller is the owner or resolved address of a given name. /// @param _name The name to check against. @@ -78,6 +79,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, /// @notice Pauses the contract. function pause() public virtual { + _disable(); _pause(); // We call the authorize function here to avoid: // Warning (5740): Unreachable code. @@ -137,6 +139,10 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, emit Unpaused(msg.sender); } + function _disable() internal pure { + revert FUNCTION_DISABLED(); + } + function _authorizeUpgrade(address) internal virtual override onlyOwner { } function _authorizePause(address, bool) internal virtual onlyOwner { } diff --git a/packages/protocol/contracts/shared/common/LibStrings.sol b/packages/protocol/contracts/shared/common/LibStrings.sol index aa61fc86bfe..7554b06dee7 100644 --- a/packages/protocol/contracts/shared/common/LibStrings.sol +++ b/packages/protocol/contracts/shared/common/LibStrings.sol @@ -32,6 +32,7 @@ library LibStrings { bytes32 internal constant B_TIER_ZKVM_SP1 = bytes32("tier_zkvm_sp1"); bytes32 internal constant B_TIER_ZKVM_ANY = bytes32("tier_zkvm_any"); bytes32 internal constant B_TIER_ZKVM_AND_TEE = bytes32("tier_zkvm_and_tee"); + bytes32 internal constant B_TIER_TWO_OF_THREE = bytes32("tier_two_of_three"); bytes32 internal constant B_RISCZERO_GROTH16_VERIFIER = bytes32("risc0_groth16_verifier"); bytes32 internal constant B_WITHDRAWER = bytes32("withdrawer"); bytes32 internal constant H_RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); diff --git a/packages/protocol/script/layer1/DeployProtocolOnL1.s.sol b/packages/protocol/script/layer1/DeployProtocolOnL1.s.sol index cc67f5e9da1..b9d8a92969c 100644 --- a/packages/protocol/script/layer1/DeployProtocolOnL1.s.sol +++ b/packages/protocol/script/layer1/DeployProtocolOnL1.s.sol @@ -32,6 +32,8 @@ import "src/layer1/provers/GuardianProver.sol"; import "src/layer1/tiers/TierProviderV2.sol"; import "src/layer1/verifiers/Risc0Verifier.sol"; import "src/layer1/verifiers/SP1Verifier.sol"; +import "src/layer1/verifiers/compose/TwoOfThreeVerifier.sol"; +import "src/layer1/verifiers/compose/ComposeVerifier.sol"; import "test/layer1/based/TestTierProvider.sol"; import "test/shared/token/FreeMintERC20.sol"; import "test/shared/token/MayFailFreeMintERC20.sol"; @@ -373,6 +375,14 @@ contract DeployProtocolOnL1 is DeployCapability { console2.log("AutomataDcapVaAttestation", automataProxy); deployZKVerifiers(owner, rollupAddressManager); + + // Deploy composite verifier + deployProxy({ + name: "tier_two_of_three", + impl: address(new TwoOfThreeVerifier()), + data: abi.encodeCall(ComposeVerifier.init, (owner, rollupAddressManager)), + registerTo: rollupAddressManager + }); } // deploy both sp1 & risc0 verifiers. @@ -402,12 +412,13 @@ contract DeployProtocolOnL1 is DeployCapability { }); } + function deployTierProvider(string memory tierProviderName) private returns (address) { if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("devnet"))) { return address(new DevnetTierProvider()); } else if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("testnet"))) { return address(new TestTierProvider()); - } else if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("mainnet"))) { + } else if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("composite"))) { return address(new TierProviderV2()); } else { revert("invalid tier provider"); diff --git a/packages/protocol/test/layer1/based/TaikoL1.t.sol b/packages/protocol/test/layer1/based/TaikoL1.t.sol index c95b0093c50..ea1dd208876 100644 --- a/packages/protocol/test/layer1/based/TaikoL1.t.sol +++ b/packages/protocol/test/layer1/based/TaikoL1.t.sol @@ -182,43 +182,6 @@ contract TaikoL1Tests is TaikoL1TestBase { } } - function test_pauseProving() external { - L1.pauseProving(true); - - TaikoData.BlockMetadata memory meta; - - giveEthAndDepositBond(Alice, 1000 ether, 1000 ether); - giveEthAndDepositBond(Bob, 1000 ether, 1000 ether); - - // Proposing is still possible - (meta,) = proposeBlock(Alice, 1024); - // Proving is not, so supply the revert reason to proveBlock - proveBlock( - Bob, - meta, - GENESIS_BLOCK_HASH, - bytes32("01"), - bytes32("02"), - meta.minTier, - LibProving.L1_PROVING_PAUSED.selector - ); - } - - function test_unpause() external { - giveEthAndDepositBond(Alice, 1000 ether, 1000 ether); - - L1.pause(); - - // Proposing is also not possible - proposeButRevert(Alice, 1024, EssentialContract.INVALID_PAUSE_STATUS.selector); - - // unpause - L1.unpause(); - - // Proposing is possible again - proposeBlock(Alice, 1024); - } - function test_getTierIds() external { uint16[] memory tiers = cp.getTierIds(); assertEq(tiers[0], LibTiers.TIER_OPTIMISTIC); diff --git a/packages/protocol/test/layer1/based/TaikoL1TestGroup8.t.sol b/packages/protocol/test/layer1/based/TaikoL1TestGroup8.t.sol index ae83d8fff01..8bce19616ca 100644 --- a/packages/protocol/test/layer1/based/TaikoL1TestGroup8.t.sol +++ b/packages/protocol/test/layer1/based/TaikoL1TestGroup8.t.sol @@ -4,104 +4,6 @@ pragma solidity ^0.8.24; import "./TaikoL1TestGroupBase.sol"; contract TaikoL1TestGroup8 is TaikoL1TestGroupBase { - // Test summary: - // 1. Alice proposes a block, - // 2. TaikoL1 is paused. - // 3. Alice attempts to prove the block within the proving window. - // 4. Alice tries to propose another block. - // 5. TaikoL1 is unpaused. - // 6. Alice attempts again to prove the first block within the proving window. - // 7. Alice tries to propose another block. - function test_taikoL1_group_8_case_1() external { - vm.warp(1_000_000); - giveEthAndDepositBond(Alice, 1000 ether, 1000 ether); - - console2.log("====== Alice propose a block"); - - TaikoData.BlockMetadata memory meta = proposeBlock(Alice, ""); - - console2.log("====== Pause TaikoL1"); - mineAndWrap(10 seconds); - vm.prank(L1.owner()); - L1.pause(); - - console2.log("====== Alice proves the block first after L1 paused"); - - bytes32 parentHash1 = GENESIS_BLOCK_HASH; - bytes32 blockHash = bytes32(uint256(10)); - bytes32 stateRoot = bytes32(uint256(11)); - proveBlock( - Alice, - meta, - parentHash1, - blockHash, - stateRoot, - meta.minTier, - EssentialContract.INVALID_PAUSE_STATUS.selector - ); - - console2.log("====== Alice tries to propose another block after L1 paused"); - proposeBlock(Alice, EssentialContract.INVALID_PAUSE_STATUS.selector); - - console2.log("====== Unpause TaikoL1"); - mineAndWrap(10 seconds); - vm.prank(L1.owner()); - L1.unpause(); - - console2.log("====== Alice proves the block first after L1 unpaused"); - proveBlock(Alice, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); - console2.log("====== Alice tries to propose another block after L1 unpaused"); - proposeBlock(Alice, ""); - } - - // Test summary: - // 1. Alice proposes a block, - // 2. TaikoL1 proving is paused. - // 3. Alice attempts to prove the block within the proving window. - // 4. Alice tries to propose another block. - // 5. TaikoL1 proving is unpaused. - // 6. Alice attempts again to prove the first block within the proving window. - // 7. Alice tries to propose another block. - function test_taikoL1_group_8_case_2() external { - vm.warp(1_000_000); - giveEthAndDepositBond(Alice, 1000 ether, 1000 ether); - - console2.log("====== Alice propose a block"); - - TaikoData.BlockMetadata memory meta = proposeBlock(Alice, ""); - - console2.log("====== Pause TaikoL1 proving"); - mineAndWrap(10 seconds); - vm.prank(L1.owner()); - L1.pauseProving(true); - - console2.log("====== Alice proves the block first after L1 proving paused"); - - bytes32 parentHash1 = GENESIS_BLOCK_HASH; - bytes32 blockHash = bytes32(uint256(10)); - bytes32 stateRoot = bytes32(uint256(11)); - proveBlock( - Alice, - meta, - parentHash1, - blockHash, - stateRoot, - meta.minTier, - LibProving.L1_PROVING_PAUSED.selector - ); - - console2.log("====== Alice tries to propose another block after L1 proving paused"); - proposeBlock(Alice, ""); - - console2.log("====== Unpause TaikoL1 proving"); - mineAndWrap(10 seconds); - vm.prank(L1.owner()); - L1.pauseProving(false); - - console2.log("====== Alice proves the block first after L1 proving unpaused"); - proveBlock(Alice, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); - } - // Test summary: // 1. Gets a block that doesn't exist // 2. Gets a transition by ID & hash that doesn't exist. diff --git a/packages/protocol/test/layer2/DelegateOwner.t.sol b/packages/protocol/test/layer2/DelegateOwner.t.sol index 431219d9508..54d617146b6 100644 --- a/packages/protocol/test/layer2/DelegateOwner.t.sol +++ b/packages/protocol/test/layer2/DelegateOwner.t.sol @@ -76,45 +76,6 @@ contract TestDelegateOwner is TaikoL2Test { vm.stopPrank(); } - function test_delegate_owner_single_non_delegatecall() public { - Target target1 = Target( - deployProxy({ - name: "target1", - impl: address(new Target()), - data: abi.encodeCall(Target.init, (address(delegateOwner))) - }) - ); - - bytes memory data = abi.encode( - DelegateOwner.Call( - uint64(0), - address(target1), - false, // CALL - abi.encodeCall(EssentialContract.pause, ()) - ) - ); - - vm.expectRevert(DelegateOwner.DO_DRYRUN_SUCCEEDED.selector); - delegateOwner.dryrunInvocation(data); - - IBridge.Message memory message; - message.from = remoteOwner; - message.destChainId = uint64(block.chainid); - message.srcChainId = remoteChainId; - message.destOwner = Bob; - message.data = abi.encodeCall(DelegateOwner.onMessageInvocation, (data)); - message.to = address(delegateOwner); - - vm.prank(Bob); - bridge.processMessage(message, ""); - - bytes32 hash = bridge.hashMessage(message); - assertTrue(bridge.messageStatus(hash) == IBridge.Status.DONE); - - assertEq(delegateOwner.nextTxId(), 1); - assertTrue(target1.paused()); - } - function test_delegate_owner_single_non_delegatecall_self() public { address delegateOwnerImpl2 = address(new DelegateOwner()); @@ -147,75 +108,4 @@ contract TestDelegateOwner is TaikoL2Test { assertEq(delegateOwner.nextTxId(), 1); assertEq(delegateOwner.impl(), delegateOwnerImpl2); } - - function test_delegate_owner_delegate_multicall() public { - address impl1 = address(new Target()); - address impl2 = address(new Target()); - - address delegateOwnerImpl2 = address(new DelegateOwner()); - - Target target1 = Target( - deployProxy({ - name: "target1", - impl: impl1, - data: abi.encodeCall(Target.init, (address(delegateOwner))) - }) - ); - Target target2 = Target( - deployProxy({ - name: "target2", - impl: impl1, - data: abi.encodeCall(Target.init, (address(delegateOwner))) - }) - ); - - Multicall3.Call3[] memory calls = new Multicall3.Call3[](4); - calls[0].target = address(target1); - calls[0].allowFailure = false; - calls[0].callData = abi.encodeCall(EssentialContract.pause, ()); - - calls[1].target = address(target2); - calls[1].allowFailure = false; - calls[1].callData = abi.encodeCall(UUPSUpgradeable.upgradeTo, (impl2)); - - calls[2].target = address(delegateOwner); - calls[2].allowFailure = false; - calls[2].callData = abi.encodeCall(UUPSUpgradeable.upgradeTo, (delegateOwnerImpl2)); - - calls[3].target = address(delegateOwner); - calls[3].allowFailure = false; - calls[3].callData = abi.encodeCall(DelegateOwner.setAdmin, (David)); - - bytes memory data = abi.encode( - DelegateOwner.Call( - uint64(0), - address(multicall), - true, // DELEGATECALL - abi.encodeCall(Multicall3.aggregate3, (calls)) - ) - ); - - vm.expectRevert(DelegateOwner.DO_DRYRUN_SUCCEEDED.selector); - delegateOwner.dryrunInvocation(data); - - IBridge.Message memory message; - message.from = remoteOwner; - message.destChainId = uint64(block.chainid); - message.srcChainId = remoteChainId; - message.destOwner = Bob; - message.data = abi.encodeCall(DelegateOwner.onMessageInvocation, (data)); - message.to = address(delegateOwner); - - vm.prank(Bob); - bridge.processMessage(message, ""); - - bytes32 hash = bridge.hashMessage(message); - assertTrue(bridge.messageStatus(hash) == IBridge.Status.DONE); - - assertEq(delegateOwner.nextTxId(), 1); - assertTrue(target1.paused()); - assertEq(target2.impl(), impl2); - assertEq(delegateOwner.impl(), delegateOwnerImpl2); - assertEq(delegateOwner.admin(), David); - } } diff --git a/packages/taiko-client/bindings/encoding/input.go b/packages/taiko-client/bindings/encoding/input.go index 8cfc0e8811a..243f4001f97 100644 --- a/packages/taiko-client/bindings/encoding/input.go +++ b/packages/taiko-client/bindings/encoding/input.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings" @@ -273,6 +274,16 @@ var ( Type: "uint8", }, } + subProofComponents = []abi.ArgumentMarshaling{ + { + Name: "verifier", + Type: "address", + }, + { + Name: "proof", + Type: "bytes", + }, + } ) var ( @@ -294,6 +305,8 @@ var ( {Name: "TaikoData.Transition", Type: transitionComponentsType}, {Name: "TaikoData.TierProof", Type: tierProofComponentsType}, } + subProofComponentsArrayType, _ = abi.NewType("tuple[]", "ComposeVerifier.SubProofs[]", subProofComponents) + subProofComponentsArrayArgs = abi.Arguments{{Type: subProofComponentsArrayType}} ) // Contract ABIs. @@ -449,3 +462,33 @@ func UnpackTxListBytes(txData []byte) ([]byte, error) { return inputs, nil } + +// EncodeSubProofs performs the solidity `abi.encode` for the given subProofs. +func EncodeSubProofs(subProofs []SubProof) ([]byte, error) { + b, err := subProofComponentsArrayArgs.Pack(subProofs) + if err != nil { + return nil, fmt.Errorf("failed to abi.encode subProofs, %w", err) + } + return b, nil +} + +func DecodeSubProofs(data []byte) ([]SubProof, error) { + unpacked, err := subProofComponentsArrayArgs.Unpack(data) + if err != nil { + return nil, err + } + + type internalSubProof struct { + Verifier common.Address + Proof []byte + } + + var internal = unpacked[0].([]internalSubProof) + var subProofs []SubProof + + for _, i := range internal { + subProofs = append(subProofs, SubProof{Verifier: i.Verifier, Proof: i.Proof}) + } + + return subProofs, nil +} diff --git a/packages/taiko-client/bindings/encoding/protocol_config.go b/packages/taiko-client/bindings/encoding/protocol_config.go index 972184f29b3..71506a73ba5 100644 --- a/packages/taiko-client/bindings/encoding/protocol_config.go +++ b/packages/taiko-client/bindings/encoding/protocol_config.go @@ -22,7 +22,7 @@ var ( MaxBlocksToVerify: 16, BlockMaxGasLimit: 240_000_000, LivenessBond: livenessBond, - StateRootSyncInternal: 16, + StateRootSyncInternal: 1, MaxAnchorHeightOffset: 64, OntakeForkHeight: 1, BaseFeeConfig: bindings.LibSharedDataBaseFeeConfig{ @@ -40,7 +40,7 @@ var ( MaxBlocksToVerify: 16, BlockMaxGasLimit: 240_000_000, LivenessBond: livenessBond, - StateRootSyncInternal: 16, + StateRootSyncInternal: 1, MaxAnchorHeightOffset: 64, OntakeForkHeight: 1, BaseFeeConfig: bindings.LibSharedDataBaseFeeConfig{ @@ -58,7 +58,7 @@ var ( MaxBlocksToVerify: 16, BlockMaxGasLimit: 240_000_000, LivenessBond: livenessBond, - StateRootSyncInternal: 16, + StateRootSyncInternal: 1, MaxAnchorHeightOffset: 64, OntakeForkHeight: 1, BaseFeeConfig: bindings.LibSharedDataBaseFeeConfig{ @@ -75,7 +75,7 @@ var ( MaxBlocksToVerify: 16, BlockMaxGasLimit: 240_000_000, LivenessBond: livenessBond, - StateRootSyncInternal: 16, + StateRootSyncInternal: 1, MaxAnchorHeightOffset: 64, OntakeForkHeight: 1, BaseFeeConfig: bindings.LibSharedDataBaseFeeConfig{ @@ -92,7 +92,7 @@ var ( MaxBlocksToVerify: 16, BlockMaxGasLimit: 240_000_000, LivenessBond: livenessBond, - StateRootSyncInternal: 16, + StateRootSyncInternal: 1, MaxAnchorHeightOffset: 64, OntakeForkHeight: 1, BaseFeeConfig: bindings.LibSharedDataBaseFeeConfig{ diff --git a/packages/taiko-client/bindings/encoding/struct.go b/packages/taiko-client/bindings/encoding/struct.go index 0bb80b27d0f..ca1447f8951 100644 --- a/packages/taiko-client/bindings/encoding/struct.go +++ b/packages/taiko-client/bindings/encoding/struct.go @@ -19,6 +19,7 @@ var ( TierSgxAndZkVMID uint16 = 300 TierGuardianMinorityID uint16 = 900 TierGuardianMajorityID uint16 = 1000 + TierTwoOfThreeID uint16 = 1100 GoldenTouchPrivKey = "92954368afd3caa1f3ce3ead0069c1af414054aefe1ef9aeacc1bf426222ce38" ) @@ -56,6 +57,12 @@ type TierFee struct { Fee *big.Int } +// SubProof should be same with ComposeVerifier.SubProof. +type SubProof struct { + Verifier common.Address + Proof []byte +} + // ToExecutableData converts a GETH *types.Header to *engine.ExecutableData. func ToExecutableData(header *types.Header) *engine.ExecutableData { executableData := &engine.ExecutableData{ diff --git a/packages/taiko-client/cmd/flags/proposer.go b/packages/taiko-client/cmd/flags/proposer.go index df90e1809b6..ccf0278b460 100644 --- a/packages/taiko-client/cmd/flags/proposer.go +++ b/packages/taiko-client/cmd/flags/proposer.go @@ -119,6 +119,13 @@ var ( Category: proposerCategory, EnvVars: []string{"CHECK_PROFITABILITY"}, } + AllowEmptyBlocks = &cli.BoolFlag{ + Name: "allowEmptyBlocks", + Usage: "Allow proposing blocks with zero transactions", + Value: true, + Category: proposerCategory, + EnvVars: []string{"ALLOW_EMPTY_BLOCKS"}, + } GasNeededForProvingBlock = &cli.Uint64Flag{ Name: "gasNeededForProvingBlock", Usage: "Gas needed for proving a block", @@ -165,6 +172,7 @@ var ProposerFlags = MergeFlags(CommonFlags, []cli.Flag{ ProposeBlockIncludeParentMetaHash, BlobAllowed, CheckProfitability, + AllowEmptyBlocks, GasNeededForProvingBlock, PriceFluctuationModifier, OffChainCosts, diff --git a/packages/taiko-client/cmd/flags/prover.go b/packages/taiko-client/cmd/flags/prover.go index 98195a76b35..bbb87510f16 100644 --- a/packages/taiko-client/cmd/flags/prover.go +++ b/packages/taiko-client/cmd/flags/prover.go @@ -40,6 +40,24 @@ var ( Category: proverCategory, EnvVars: []string{"RAIKO_HOST_ZKVM"}, } + Risc0VerifierAddress = &cli.StringFlag{ + Name: "risc0Verifier", + Usage: "Address of the Risc0 verifier contract", + Category: proverCategory, + EnvVars: []string{"RISC0_VERIFIER"}, + } + Sp1VerifierAddress = &cli.StringFlag{ + Name: "sp1Verifier", + Usage: "Address of the SP1 verifier contract", + Category: proverCategory, + EnvVars: []string{"SP1_VERIFIER"}, + } + SgxVerifierAddress = &cli.StringFlag{ + Name: "sgxVerifier", + Usage: "Address of the Risc0 verifier contract", + Category: proverCategory, + EnvVars: []string{"SGX_VERIFIER"}, + } RaikoJWTPath = &cli.StringFlag{ Name: "raiko.jwtPath", Usage: "Path to a JWT secret for the Raiko service", @@ -269,6 +287,9 @@ var ProverFlags = MergeFlags(CommonFlags, []cli.Flag{ BlockConfirmations, RaikoRequestTimeout, RaikoZKVMHostEndpoint, + Risc0VerifierAddress, + Sp1VerifierAddress, + SgxVerifierAddress, RaikoSP1Recursion, RaikoSP1Prover, RaikoRISC0Bonsai, diff --git a/packages/taiko-client/pkg/rpc/methods_test.go b/packages/taiko-client/pkg/rpc/methods_test.go index 0aa743b994b..8ce3c88afab 100644 --- a/packages/taiko-client/pkg/rpc/methods_test.go +++ b/packages/taiko-client/pkg/rpc/methods_test.go @@ -122,3 +122,26 @@ func randomHash() common.Hash { } return hash } + +func TestGetTiers(t *testing.T) { + client := newTestClient(t) + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + tiers, err := client.GetTiers(ctx) + + require.Nil(t, err) + require.NotNil(t, tiers) + require.Greater(t, len(tiers), 0, "should have at least one tier") + + // Check for tier with ID 1100 (TIER_TWO_OF_THREE) + found := false + for _, tier := range tiers { + if tier.ID == 1100 { + found = true + break + } + } + require.True(t, found, "tier with ID 1100 (TIER_TWO_OF_THREE) should exist") +} diff --git a/packages/taiko-client/proposer/config.go b/packages/taiko-client/proposer/config.go index 6cbaa9e318d..6bfb532b1c5 100644 --- a/packages/taiko-client/proposer/config.go +++ b/packages/taiko-client/proposer/config.go @@ -42,6 +42,7 @@ type Config struct { TxmgrConfigs *txmgr.CLIConfig PrivateTxmgrConfigs *txmgr.CLIConfig CheckProfitability bool + AllowEmptyBlocks bool GasNeededForProvingBlock uint64 PriceFluctuationModifier uint64 OffChainCosts *big.Int @@ -86,6 +87,7 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { } checkProfitability := c.Bool(flags.CheckProfitability.Name) + allowEmptyBlocks := c.Bool(flags.AllowEmptyBlocks.Name) gasNeededForProvingBlock := c.Uint64(flags.GasNeededForProvingBlock.Name) priceFluctuationModifier := c.Uint64(flags.PriceFluctuationModifier.Name) @@ -136,6 +138,7 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { c, ), CheckProfitability: checkProfitability, + AllowEmptyBlocks: allowEmptyBlocks, GasNeededForProvingBlock: gasNeededForProvingBlock, PriceFluctuationModifier: priceFluctuationModifier, OffChainCosts: offChainCosts, diff --git a/packages/taiko-client/proposer/config_test.go b/packages/taiko-client/proposer/config_test.go index 583fa186858..3696365f110 100644 --- a/packages/taiko-client/proposer/config_test.go +++ b/packages/taiko-client/proposer/config_test.go @@ -66,6 +66,7 @@ func (s *ProposerTestSuite) TestNewConfigFromCliContext() { "--" + flags.OffChainCosts.Name, offChainCosts, "--" + flags.ProposeBlockIncludeParentMetaHash.Name, "true", "--" + flags.CheckProfitability.Name, "true", + "--" + flags.AllowEmptyBlocks.Name, "true", "--" + flags.GasNeededForProvingBlock.Name, "100000", "--" + flags.PriceFluctuationModifier.Name, "100", })) @@ -124,6 +125,7 @@ func (s *ProposerTestSuite) SetupApp() *cli.App { &cli.DurationFlag{Name: flags.RPCTimeout.Name}, &cli.BoolFlag{Name: flags.ProposeBlockIncludeParentMetaHash.Name}, &cli.BoolFlag{Name: flags.CheckProfitability.Name}, + &cli.BoolFlag{Name: flags.AllowEmptyBlocks.Name}, &cli.Uint64Flag{Name: flags.GasNeededForProvingBlock.Name}, &cli.Uint64Flag{Name: flags.PriceFluctuationModifier.Name}, &cli.StringFlag{Name: flags.OffChainCosts.Name}, diff --git a/packages/taiko-client/proposer/proposer.go b/packages/taiko-client/proposer/proposer.go index 8a4a14a8f6e..411c3ef86be 100644 --- a/packages/taiko-client/proposer/proposer.go +++ b/packages/taiko-client/proposer/proposer.go @@ -62,6 +62,9 @@ type Proposer struct { wg sync.WaitGroup checkProfitability bool + + allowEmptyBlocks bool + initDone bool } // InitFromCli initializes the given proposer instance based on the command line flags. @@ -85,6 +88,8 @@ func (p *Proposer) InitFromConfig( p.Config = cfg p.lastProposedAt = time.Now() p.checkProfitability = cfg.CheckProfitability + p.allowEmptyBlocks = cfg.AllowEmptyBlocks + p.initDone = false // RPC clients if p.rpc, err = rpc.NewClient(p.ctx, cfg.ClientConfig); err != nil { @@ -237,14 +242,16 @@ func (p *Proposer) fetchPoolContent(filterPoolContent bool) ([]types.Transaction } txLists = append(txLists, txs.TxList) } - // If the pool is empty and we're not filtering or checking profitability, propose an empty block - if !filterPoolContent && !p.checkProfitability && len(txLists) == 0 { + // If the pool is empty and we're not filtering or checking profitability and proposing empty + // blocks is allowed, propose an empty block + if (!filterPoolContent && !p.checkProfitability && p.allowEmptyBlocks && len(txLists) == 0) || !p.initDone { log.Info( "Pool content is empty, proposing an empty block", "lastProposedAt", p.lastProposedAt, "minProposingInternal", p.MinProposingInternal, ) txLists = append(txLists, types.Transactions{}) + p.initDone = true } // If LocalAddressesOnly is set, filter the transactions by the local addresses. diff --git a/packages/taiko-client/prover/config.go b/packages/taiko-client/prover/config.go index 18e979df9b4..4af7175eea5 100644 --- a/packages/taiko-client/prover/config.go +++ b/packages/taiko-client/prover/config.go @@ -26,6 +26,9 @@ type Config struct { L2HttpEndpoint string TaikoL1Address common.Address TaikoL2Address common.Address + Risc0VerifierAddress common.Address + Sp1VerifierAddress common.Address + SgxVerifierAddress common.Address TaikoTokenAddress common.Address ProverSetAddress common.Address L1ProverPrivKey *ecdsa.PrivateKey @@ -147,6 +150,9 @@ func NewConfigFromCliContext(c *cli.Context) (*Config, error) { L2HttpEndpoint: c.String(flags.L2HTTPEndpoint.Name), TaikoL1Address: common.HexToAddress(c.String(flags.TaikoL1Address.Name)), TaikoL2Address: common.HexToAddress(c.String(flags.TaikoL2Address.Name)), + Risc0VerifierAddress: common.HexToAddress(c.String(flags.Risc0VerifierAddress.Name)), + Sp1VerifierAddress: common.HexToAddress(c.String(flags.Sp1VerifierAddress.Name)), + SgxVerifierAddress: common.HexToAddress(c.String(flags.SgxVerifierAddress.Name)), TaikoTokenAddress: common.HexToAddress(c.String(flags.TaikoTokenAddress.Name)), ProverSetAddress: common.HexToAddress(c.String(flags.ProverSetAddress.Name)), L1ProverPrivKey: l1ProverPrivKey, diff --git a/packages/taiko-client/prover/guardian.go b/packages/taiko-client/prover/guardian.go deleted file mode 100644 index c23b33e7802..00000000000 --- a/packages/taiko-client/prover/guardian.go +++ /dev/null @@ -1,58 +0,0 @@ -package prover - -import ( - "context" - "time" - - "github.com/ethereum/go-ethereum/log" - "golang.org/x/sync/errgroup" -) - -var ( - heartbeatInterval = 12 * time.Second -) - -// guardianProverHeartbeatLoop keeps sending heartbeats to the guardian prover health check server -// on an interval. -func (p *Prover) guardianProverHeartbeatLoop(ctx context.Context) { - p.wg.Add(1) - defer p.wg.Done() - - ticker := time.NewTicker(heartbeatInterval) - defer ticker.Stop() - - for { - select { - case <-p.ctx.Done(): - return - case <-ticker.C: - var ( - latestL1Block uint64 - latestL2Block uint64 - err error - g = new(errgroup.Group) - ) - - g.Go(func() error { - latestL1Block, err = p.rpc.L1.BlockNumber(ctx) - return err - }) - g.Go(func() error { - latestL2Block, err = p.rpc.L2.BlockNumber(ctx) - return err - }) - if err := g.Wait(); err != nil { - log.Error("Failed to get latest L1/L2 block number", "error", err) - continue - } - - if err := p.guardianProverHeartbeater.SendHeartbeat( - ctx, - latestL1Block, - latestL2Block, - ); err != nil { - log.Error("Failed to send guardian prover heartbeat", "error", err) - } - } - } -} diff --git a/packages/taiko-client/prover/init.go b/packages/taiko-client/prover/init.go index 63078ca4646..0fd33d7285c 100644 --- a/packages/taiko-client/prover/init.go +++ b/packages/taiko-client/prover/init.go @@ -147,6 +147,40 @@ func (p *Prover) initProofSubmitters( RaikoRISC0Profile: p.cfg.RaikoRISC0Profile, RaikoRISC0ExecutionPo2: p.cfg.RaikoRISC0ExecutionPo2, } + case encoding.TierTwoOfThreeID: + producer = &proofProducer.CombinedProducer{ + ProofTier: encoding.TierTwoOfThreeID, + RequiredProofs: 2, + Producers: []proofProducer.ProofProducer{ + &proofProducer.SGXProofProducer{ + RaikoHostEndpoint: p.cfg.RaikoHostEndpoint, + JWT: p.cfg.RaikoJWT, + ProofType: proofProducer.ProofTypeSgx, + Dummy: p.cfg.Dummy, + RaikoRequestTimeout: p.cfg.RaikoRequestTimeout, + }, + &proofProducer.ZKvmProofProducer{ + ZKProofType: proofProducer.ZKProofTypeR0, + RaikoHostEndpoint: p.cfg.RaikoZKVMHostEndpoint, + JWT: p.cfg.RaikoJWT, + Dummy: p.cfg.Dummy, + RaikoRequestTimeout: p.cfg.RaikoRequestTimeout, + }, + &proofProducer.ZKvmProofProducer{ + ZKProofType: proofProducer.ZKProofTypeSP1, + RaikoHostEndpoint: p.cfg.RaikoZKVMHostEndpoint, + JWT: p.cfg.RaikoJWT, + Dummy: p.cfg.Dummy, + RaikoRequestTimeout: p.cfg.RaikoRequestTimeout, + }, + }, + Verifiers: []common.Address{ + p.cfg.SgxVerifierAddress, + p.cfg.Risc0VerifierAddress, + p.cfg.Sp1VerifierAddress, + }, + ProofStates: make(map[uint64]*proofProducer.BlockProofState), + } case encoding.TierGuardianMinorityID: producer = proofProducer.NewGuardianProofProducer(encoding.TierGuardianMinorityID, p.cfg.EnableLivenessBondProof) case encoding.TierGuardianMajorityID: diff --git a/packages/taiko-client/prover/proof_producer/combined_producer.go b/packages/taiko-client/prover/proof_producer/combined_producer.go new file mode 100644 index 00000000000..fd1f653f48c --- /dev/null +++ b/packages/taiko-client/prover/proof_producer/combined_producer.go @@ -0,0 +1,209 @@ +package producer + +import ( + "context" + "fmt" + "math/big" + "slices" + "strings" + "sync" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + + "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/encoding" + "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/metadata" +) + +// CombinedProducer generates proofs from multiple producers in parallel and combines them. +type CombinedProducer struct { + ProofTier uint16 + RequiredProofs uint8 + Producers []ProofProducer + Verifiers []common.Address + // Map blockID to its proof state + ProofStates map[uint64]*BlockProofState + mapMutex sync.Mutex +} + +type BlockProofState struct { + verifiedTiers []uint16 + proofs []encoding.SubProof +} + +const ( + // represents the number of blocks to keep in history of proof states + BlockHistoryLength = 256 +) + +// RequestProof implements the ProofProducer interface. +func (c *CombinedProducer) RequestProof( + ctx context.Context, + opts *ProofRequestOptions, + blockID *big.Int, + meta metadata.TaikoBlockMetaData, + header *types.Header, + requestAt time.Time, +) (*ProofWithHeader, error) { + log.Debug("CombinedProducer: RequestProof", "blockID", blockID, "Producers num", len(c.Producers)) + var ( + wg sync.WaitGroup + proofMutex sync.Mutex + errorsChan = make(chan error, len(c.Producers)) + ) + + proofState := c.getProofState(blockID) + + taskCtx, taskCtxCancel := context.WithCancel(ctx) + defer taskCtxCancel() + + for i, producer := range c.Producers { + if proofStateContainsTier(proofState, producer.Tier(), &proofMutex) { + log.Debug("Skipping producer, proof already verified", "tier", producer.Tier()) + continue + } + + log.Debug("Adding proof producer", "blockID", blockID, "tier", producer.Tier()) + verifier := c.Verifiers[i] + + wg.Add(1) + go func(idx int, p ProofProducer, verifier common.Address) { + defer wg.Done() + + proofWithHeader, err := p.RequestProof(taskCtx, opts, blockID, meta, header, requestAt) + if err != nil { + errorsChan <- fmt.Errorf("producer %d error: %w", idx, err) + return + } + + proofMutex.Lock() + defer proofMutex.Unlock() + + proofState.verifiedTiers = append(proofState.verifiedTiers, p.Tier()) + if uint8(len(proofState.proofs)) < c.RequiredProofs { + proofState.proofs = append( + proofState.proofs, + encoding.SubProof{ + Proof: proofWithHeader.Proof, + Verifier: verifier, + }, + ) + } + + if uint8(len(proofState.proofs)) == c.RequiredProofs { + taskCtxCancel() + } + }(i, producer, verifier) + } + + wg.Wait() + + if uint8(len(proofState.proofs)) < c.RequiredProofs { + var errMsgs []string + + errMsgs = append( + errMsgs, + fmt.Sprintf("not enough proofs collected: required %d, got %d", c.RequiredProofs, len(proofState.proofs)), + ) + + close(errorsChan) + for err := range errorsChan { + errMsgs = append(errMsgs, err.Error()) + } + + return nil, fmt.Errorf("combined proof production failed: %s", strings.Join(errMsgs, "; ")) + } + + combinedProof, err := encoding.EncodeSubProofs(proofState.proofs) + if err != nil { + return nil, fmt.Errorf("failed to encode sub proofs: %w", err) + } + + log.Info( + "Combined proofs generated", + "blockID", blockID, + "time", time.Since(requestAt), + "producer", "CombinedProducer", + ) + + c.CleanOldProofStates(blockID) + + return &ProofWithHeader{ + BlockID: blockID, + Header: header, + Meta: meta, + Proof: combinedProof, + Opts: opts, + Tier: c.Tier(), + }, nil +} + +func proofStateContainsTier(proofState *BlockProofState, tier uint16, mutex *sync.Mutex) bool { + mutex.Lock() + defer mutex.Unlock() + return slices.Contains(proofState.verifiedTiers, tier) +} + +// getProofState returns the proof state for the given block ID, creating a new one if it doesn't exist. +func (c *CombinedProducer) getProofState(blockID *big.Int) *BlockProofState { + blockIDUint64 := blockID.Uint64() + c.mapMutex.Lock() + defer c.mapMutex.Unlock() + + // Get or initialize proof state + proofState, ok := c.ProofStates[blockIDUint64] + if !ok { + proofState = &BlockProofState{ + verifiedTiers: []uint16{}, + proofs: []encoding.SubProof{}, + } + c.ProofStates[blockIDUint64] = proofState + } + + return proofState +} + +// CleanOldProofStates removes proof states for blocks older than 256 blocks. +func (c *CombinedProducer) CleanOldProofStates(latestBlockID *big.Int) { + if len(c.ProofStates) == 0 { + return + } + + blockID := latestBlockID.Uint64() + log.Debug("Cleaning old proof states", "latestBlockID", blockID) + + c.mapMutex.Lock() + defer c.mapMutex.Unlock() + + delete(c.ProofStates, blockID) + + threshold := blockID - BlockHistoryLength + for key := range c.ProofStates { + if key < threshold { + delete(c.ProofStates, key) + } + } +} + +// RequestCancel implements the ProofProducer interface. +func (c *CombinedProducer) RequestCancel( + ctx context.Context, + opts *ProofRequestOptions, +) error { + var finalError error + for _, producer := range c.Producers { + if err := producer.RequestCancel(ctx, opts); err != nil { + if finalError == nil { + finalError = err + } + } + } + return finalError +} + +// Tier implements the ProofProducer interface. +func (c *CombinedProducer) Tier() uint16 { + return c.ProofTier +} diff --git a/packages/taiko-client/prover/proof_producer/combined_producer_test.go b/packages/taiko-client/prover/proof_producer/combined_producer_test.go new file mode 100644 index 00000000000..9dc45669483 --- /dev/null +++ b/packages/taiko-client/prover/proof_producer/combined_producer_test.go @@ -0,0 +1,153 @@ +package producer + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/stretchr/testify/require" + + "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/encoding" + "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/metadata" +) + +func TestCombinedProducerRequestProof(t *testing.T) { + header := &types.Header{ + ParentHash: randHash(), + UncleHash: randHash(), + Coinbase: common.BytesToAddress(randHash().Bytes()), + Root: randHash(), + TxHash: randHash(), + ReceiptHash: randHash(), + Difficulty: common.Big0, + Number: common.Big256, + GasLimit: 1024, + GasUsed: 1024, + Time: uint64(time.Now().Unix()), + Extra: randHash().Bytes(), + MixDigest: randHash(), + Nonce: types.BlockNonce{}, + } + + optimisticProducer1 := &OptimisticProofProducer{} + optimisticProducer2 := &OptimisticProofProducer{} + + producer := &CombinedProducer{ + ProofTier: encoding.TierSgxAndZkVMID, + RequiredProofs: 2, + Producers: []ProofProducer{optimisticProducer1, optimisticProducer2}, + Verifiers: []common.Address{ + common.HexToAddress("0x1234567890123456789012345678901234567890"), + common.HexToAddress("0x0987654321098765432109876543210987654321"), + }, + ProofStates: make(map[uint64]*BlockProofState), + } + + blockID := big.NewInt(1) + meta := &metadata.TaikoDataBlockMetadataLegacy{} + opts := &ProofRequestOptions{ + BlockID: blockID, + ProverAddress: common.HexToAddress("0x1234"), + } + + res, err := producer.RequestProof( + context.Background(), + opts, + blockID, + meta, + header, + time.Now(), + ) + + require.Nil(t, err) + require.Equal(t, blockID, res.BlockID) + require.Equal(t, header, res.Header) + require.Equal(t, producer.Tier(), res.Tier) + require.NotEmpty(t, res.Proof) +} + +func TestCombinedProducerRequestCancel(t *testing.T) { + optimisticProducer1 := &OptimisticProofProducer{} + optimisticProducer2 := &OptimisticProofProducer{} + + producer := &CombinedProducer{ + ProofTier: encoding.TierSgxAndZkVMID, + RequiredProofs: 2, + Producers: []ProofProducer{optimisticProducer1, optimisticProducer2}, + Verifiers: []common.Address{ + common.HexToAddress("0x1234567890123456789012345678901234567890"), + common.HexToAddress("0x0987654321098765432109876543210987654321"), + }, + ProofStates: make(map[uint64]*BlockProofState), + } + + opts := &ProofRequestOptions{ + BlockID: big.NewInt(1), + ProverAddress: common.HexToAddress("0x1234"), + } + + err := producer.RequestCancel(context.Background(), opts) + require.Nil(t, err) +} + +func TestGetProofState(t *testing.T) { + producer := &CombinedProducer{ + ProofTier: encoding.TierTwoOfThreeID, + ProofStates: make(map[uint64]*BlockProofState), + } + + blockID := big.NewInt(1) + + // First call should create new state + state1 := producer.getProofState(blockID) + require.NotNil(t, state1) + require.Empty(t, state1.verifiedTiers) + require.Empty(t, state1.proofs) + + // Modify state + state1.verifiedTiers = append(state1.verifiedTiers, uint16(1)) + state1.proofs = append(state1.proofs, encoding.SubProof{ + Verifier: common.HexToAddress("0x1234"), + }) + + // Second call should return same state + state2 := producer.getProofState(blockID) + require.Equal(t, state1, state2) + require.Equal(t, []uint16{1}, state2.verifiedTiers) + require.Len(t, state2.proofs, 1) + + // Different blockID should get new state + blockID2 := big.NewInt(2) + state3 := producer.getProofState(blockID2) + require.NotNil(t, state3) + require.Empty(t, state3.verifiedTiers) + require.Empty(t, state3.proofs) +} + +func TestCleanOldProofStates(t *testing.T) { + producer := &CombinedProducer{ + ProofTier: encoding.TierTwoOfThreeID, + ProofStates: make(map[uint64]*BlockProofState), + } + + producer.getProofState(big.NewInt(1)) + producer.getProofState(big.NewInt(2)) + producer.getProofState(big.NewInt(3)) + producer.getProofState(big.NewInt(4)) + producer.getProofState(big.NewInt(5)) + + producer.CleanOldProofStates(big.NewInt(258)) + require.Len(t, producer.ProofStates, 4) +} + +func TestCombinedProducerTier(t *testing.T) { + producer := &CombinedProducer{ + ProofTier: encoding.TierSgxAndZkVMID, + ProofStates: make(map[uint64]*BlockProofState), + } + + require.Equal(t, encoding.TierSgxAndZkVMID, producer.Tier()) +} diff --git a/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go b/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go index 7173f1686a3..3434fba84fb 100644 --- a/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go +++ b/packages/taiko-client/prover/proof_submitter/proof_submitter_test.go @@ -147,6 +147,7 @@ func (s *ProofSubmitterTestSuite) SetupTest() { ProposeInterval: 1024 * time.Hour, MaxProposedTxListsPerEpoch: 1, CheckProfitability: false, + AllowEmptyBlocks: true, GasNeededForProvingBlock: 0, PriceFluctuationModifier: 50, OffChainCosts: big.NewInt(0), diff --git a/packages/taiko-client/prover/proof_submitter/transaction/builder.go b/packages/taiko-client/prover/proof_submitter/transaction/builder.go index 913bf9fd9d2..a84bf0a51cf 100644 --- a/packages/taiko-client/prover/proof_submitter/transaction/builder.go +++ b/packages/taiko-client/prover/proof_submitter/transaction/builder.go @@ -63,7 +63,7 @@ func (a *ProveBlockTxBuilder) Build( data []byte to common.Address err error - guardian = tier >= encoding.TierGuardianMinorityID + guardian = false ) log.Info( diff --git a/packages/taiko-client/prover/prover.go b/packages/taiko-client/prover/prover.go index 36d7985465a..9f109beff1f 100644 --- a/packages/taiko-client/prover/prover.go +++ b/packages/taiko-client/prover/prover.go @@ -10,7 +10,6 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ethereum-optimism/optimism/op-service/txmgr" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" @@ -20,7 +19,6 @@ import ( "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/encoding" "github.com/taikoxyz/taiko-mono/packages/taiko-client/bindings/metadata" "github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/metrics" - "github.com/taikoxyz/taiko-mono/packages/taiko-client/internal/version" eventIterator "github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/chain_iterator/event_iterator" "github.com/taikoxyz/taiko-mono/packages/taiko-client/pkg/rpc" handler "github.com/taikoxyz/taiko-mono/packages/taiko-client/prover/event_handler" @@ -196,27 +194,6 @@ func InitFromConfig( txBuilder, ) - // Guardian prover heartbeat sender - if p.IsGuardianProver() && p.cfg.GuardianProverHealthCheckServerEndpoint != nil { - // Check guardian prover contract address is correct. - if _, err := p.rpc.GuardianProverMajority.MinGuardians(&bind.CallOpts{Context: ctx}); err != nil { - return fmt.Errorf("failed to get MinGuardians from majority guardian prover contract: %w", err) - } - - if p.rpc.GuardianProverMinority != nil { - if _, err := p.rpc.GuardianProverMinority.MinGuardians(&bind.CallOpts{Context: ctx}); err != nil { - return fmt.Errorf("failed to get MinGuardians from minority guardian prover contract: %w", err) - } - } - - p.guardianProverHeartbeater = guardianProverHeartbeater.New( - p.cfg.L1ProverPrivKey, - p.cfg.GuardianProverHealthCheckServerEndpoint, - p.rpc, - p.ProverAddress(), - ) - } - // Initialize event handlers. if err := p.initEventHandlers(); err != nil { return err @@ -227,6 +204,7 @@ func InitFromConfig( // Start starts the main loop of the L2 block prover. func (p *Prover) Start() error { + log.Info("Starting prover") // 1. Set approval amount for the contracts. for _, contract := range []common.Address{p.cfg.TaikoL1Address} { if err := p.setApprovalAmount(p.ctx, contract); err != nil { @@ -234,24 +212,7 @@ func (p *Prover) Start() error { } } - // 3. Start the guardian prover heartbeat sender if the current prover is a guardian prover. - if p.IsGuardianProver() && p.cfg.GuardianProverHealthCheckServerEndpoint != nil { - // Send the startup message to the guardian prover health check server. - if err := p.guardianProverHeartbeater.SendStartupMessage( - p.ctx, - version.CommitVersion(), - version.CommitVersion(), - p.cfg.L1NodeVersion, - p.cfg.L2NodeVersion, - ); err != nil { - log.Error("Failed to send guardian prover startup message", "error", err) - } - - // Start the guardian prover heartbeat loop. - go p.guardianProverHeartbeatLoop(p.ctx) - } - - // 4. Start the main event loop of the prover. + // 2. Start the main event loop of the prover. go p.eventLoop() return nil @@ -421,13 +382,6 @@ func (p *Prover) contestProofOp(req *proofProducer.ContestRequestBody) error { // requestProofOp requests a new proof generation operation. func (p *Prover) requestProofOp(meta metadata.TaikoBlockMetaData, minTier uint16) error { - if p.IsGuardianProver() { - if minTier > encoding.TierGuardianMinorityID { - minTier = encoding.TierGuardianMajorityID - } else { - minTier = encoding.TierGuardianMinorityID - } - } if submitter := p.selectSubmitter(minTier); submitter != nil { if err := submitter.RequestProof(p.ctx, meta); err != nil { log.Error("Request new proof error", "blockID", meta.GetBlockID(), "minTier", meta.GetMinTier(), "error", err) @@ -479,9 +433,6 @@ func (p *Prover) Name() string { func (p *Prover) selectSubmitter(minTier uint16) proofSubmitter.Submitter { for _, s := range p.proofSubmitters { if s.Tier() >= minTier { - if !p.IsGuardianProver() && s.Tier() >= encoding.TierGuardianMinorityID { - continue - } log.Debug("Proof submitter selected", "tier", s.Tier(), "minTier", minTier) return s } @@ -496,10 +447,6 @@ func (p *Prover) selectSubmitter(minTier uint16) proofSubmitter.Submitter { func (p *Prover) getSubmitterByTier(tier uint16) proofSubmitter.Submitter { for _, s := range p.proofSubmitters { if s.Tier() == tier { - if !p.IsGuardianProver() && s.Tier() >= encoding.TierGuardianMinorityID { - continue - } - return s } } diff --git a/packages/taiko-client/prover/prover_test.go b/packages/taiko-client/prover/prover_test.go index 6ed122ba0f9..b37133746a5 100644 --- a/packages/taiko-client/prover/prover_test.go +++ b/packages/taiko-client/prover/prover_test.go @@ -111,6 +111,7 @@ func (s *ProverTestSuite) SetupTest() { ProposeInterval: 1024 * time.Hour, MaxProposedTxListsPerEpoch: 1, CheckProfitability: false, + AllowEmptyBlocks: true, GasNeededForProvingBlock: 0, PriceFluctuationModifier: 50, OffChainCosts: big.NewInt(0), @@ -366,7 +367,7 @@ func (s *ProverTestSuite) TestSelectSubmitter() { } func (s *ProverTestSuite) TestSelectSubmitterNotFound() { - submitter := s.p.selectSubmitter(encoding.TierGuardianMajorityID + 1) + submitter := s.p.selectSubmitter(encoding.TierGuardianMajorityID + 1000) s.Nil(submitter) }