diff --git a/.github/workflows/protocol.yml b/.github/workflows/protocol.yml index 835cf9e914..4c8d74bb55 100644 --- a/.github/workflows/protocol.yml +++ b/.github/workflows/protocol.yml @@ -36,6 +36,10 @@ jobs: working-directory: ./packages/protocol run: pnpm test:tokenomics + - name: protocol - Bridge Tests + working-directory: ./packages/protocol + run: pnpm test:bridge + - name: protocol - Test Coverage working-directory: ./packages/protocol run: pnpm test:coverage diff --git a/packages/protocol/package.json b/packages/protocol/package.json index ffa3695dde..8cac766e52 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -16,6 +16,7 @@ "test:coverage": "pnpm coverage", "generate:genesis": "ts-node ./utils/generate_genesis/main.ts", "test:genesis": "./test/genesis/generate_genesis.test.sh", + "test:bridge": "TEST_TYPE=integrationbridge ./test/test_integration.sh", "test:integration": "TEST_TYPE=integration ./test/test_integration.sh", "test:tokenomics": "TEST_TYPE=tokenomics ./test/test_integration.sh", "test:all": "pnpm run test && pnpm run test:integration && pnpm run test:tokenomics && pnpm run test:genesis", diff --git a/packages/protocol/test/L1/TaikoL1.integration.test.ts b/packages/protocol/test/L1/TaikoL1.integration.test.ts index 7ada5c16fa..1f66d17cd3 100644 --- a/packages/protocol/test/L1/TaikoL1.integration.test.ts +++ b/packages/protocol/test/L1/TaikoL1.integration.test.ts @@ -10,6 +10,12 @@ import { commitBlock, generateCommitHash, } from "../utils/commit"; +import { encodeEvidence } from "../utils/encoding"; +import { + readShouldRevertWithCustomError, + txShouldRevertWithCustomError, +} from "../utils/errors"; +import Evidence from "../utils/evidence"; import { initIntegrationFixture } from "../utils/fixture"; import halt from "../utils/halt"; import { onNewL2Block } from "../utils/onNewL2Block"; @@ -17,19 +23,13 @@ import { buildProposeBlockInputs } from "../utils/propose"; import Proposer from "../utils/proposer"; import { buildProveBlockInputs, proveBlock } from "../utils/prove"; import Prover from "../utils/prover"; +import { getBlockHeader } from "../utils/rpc"; import { seedTko, sendTinyEtherToZeroAddress } from "../utils/seed"; import { commitProposeProveAndVerify, sleepUntilBlockIsVerifiable, verifyBlocks, } from "../utils/verify"; -import { - txShouldRevertWithCustomError, - readShouldRevertWithCustomError, -} from "../utils/errors"; -import { getBlockHeader } from "../utils/rpc"; -import Evidence from "../utils/evidence"; -import { encodeEvidence } from "../utils/encoding"; describe("integration:TaikoL1", function () { let taikoL1: TaikoL1; @@ -240,7 +240,7 @@ describe("integration:TaikoL1", function () { id: 1, l1Height: 0, l1Hash: ethers.constants.HashZero, - beneficiary: block.miner, + beneficiary: commit.beneficiary, txListHash: commit.txListHash, mixHash: ethers.constants.HashZero, extraData: block.extraData, @@ -274,7 +274,7 @@ describe("integration:TaikoL1", function () { id: 0, l1Height: 0, l1Hash: ethers.constants.HashZero, - beneficiary: block.miner, + beneficiary: commit.beneficiary, txListHash: commit.txListHash, mixHash: ethers.constants.HashZero, extraData: block.extraData, @@ -304,7 +304,7 @@ describe("integration:TaikoL1", function () { id: 0, l1Height: 0, l1Hash: ethers.constants.HashZero, - beneficiary: block.miner, + beneficiary: commit.beneficiary, txListHash: commit.txListHash, mixHash: ethers.constants.HashZero, extraData: ethers.utils.hexlify(ethers.utils.randomBytes(33)), // invalid extradata @@ -374,8 +374,40 @@ describe("integration:TaikoL1", function () { // now expect another proposed block to be invalid since all slots are full and none have // been proven. + const { commitConfirmations } = await taikoL1.getConfig(); + const block = await l2Provider.getBlock("latest"); + const { tx: commitBlockTx, commit } = await commitBlock( + taikoL1.connect(l1Signer), + block, + 0 + ); + const commitReceipt = await commitBlockTx.wait(1); + + for (let i = 0; i < commitConfirmations.toNumber() + 5; i++) { + await sendTinyEtherToZeroAddress(l1Signer); + } + + const meta: BlockMetadata = { + id: 0, + l1Height: 0, + l1Hash: ethers.constants.HashZero, + beneficiary: commit.beneficiary, + txListHash: commit.txListHash, + mixHash: ethers.constants.HashZero, + extraData: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + gasLimit: block.gasLimit, + timestamp: 0, + commitSlot: 0, + commitHeight: commitReceipt.blockNumber, + }; + await txShouldRevertWithCustomError( - commitAndProposeLatestBlock(taikoL1, l1Signer, l2Provider), + ( + await taikoL1.proposeBlock( + buildProposeBlockInputs(block, meta), + { gasLimit: 500000 } + ) + ).wait(), l1Provider, "L1_TOO_MANY()" ); diff --git a/packages/protocol/test/bridge/Bridge.integration.test.ts b/packages/protocol/test/bridge/Bridge.integration.test.ts index 3f3642b154..27e4ea2d26 100644 --- a/packages/protocol/test/bridge/Bridge.integration.test.ts +++ b/packages/protocol/test/bridge/Bridge.integration.test.ts @@ -1,3 +1,4 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { expect } from "chai"; import { ethers as ethersLib } from "ethers"; import hre, { ethers } from "hardhat"; @@ -5,6 +6,7 @@ import { AddressManager, Bridge, SignalService, + TestBadReceiver, TestHeaderSync, } from "../../typechain"; import deployAddressManager from "../utils/addressManager"; @@ -16,12 +18,17 @@ import { } from "../utils/bridge"; // import { randomBytes32 } from "../utils/bytes"; import { Message } from "../utils/message"; -import { getDefaultL2Signer, getL2Provider } from "../utils/provider"; +import { + getDefaultL2Signer, + getL1Provider, + getL2Provider, +} from "../utils/provider"; import { Block, getBlockHeader } from "../utils/rpc"; import { deploySignalService, getSignalProof } from "../utils/signal"; -describe("integration:Bridge", function () { - let owner: any; +describe("integrationbridge:Bridge", function () { + let owner: SignerWithAddress; + let l1Provider: ethersLib.providers.JsonRpcProvider; let l2Provider: ethersLib.providers.JsonRpcProvider; let l2Signer: ethersLib.Signer; let srcChainId: number; @@ -32,7 +39,8 @@ describe("integration:Bridge", function () { let l1Bridge: Bridge; let l2Bridge: Bridge; let m: Message; - let headerSync: TestHeaderSync; + let l1HeaderSync: TestHeaderSync; + let l2HeaderSync: TestHeaderSync; beforeEach(async () => { [owner] = await ethers.getSigners(); @@ -41,12 +49,16 @@ describe("integration:Bridge", function () { srcChainId = chainId; + l1Provider = getL1Provider(); + // seondary node to deploy L2 on l2Provider = getL2Provider(); l2Signer = await getDefaultL2Signer(); - l2NonOwner = await l2Provider.getSigner(); + l2NonOwner = await l2Provider.getSigner( + await ethers.Wallet.createRandom().getAddress() + ); const l2Network = await l2Provider.getNetwork(); @@ -103,13 +115,21 @@ describe("integration:Bridge", function () { .connect(l2Signer) .setAddress(`${srcChainId}.bridge`, l1Bridge.address); - headerSync = await (await ethers.getContractFactory("TestHeaderSync")) + l1HeaderSync = await (await ethers.getContractFactory("TestHeaderSync")) + .connect(owner) + .deploy(); + + await addressManager + .connect(owner) + .setAddress(`${srcChainId}.taiko`, l1HeaderSync.address); + + l2HeaderSync = await (await ethers.getContractFactory("TestHeaderSync")) .connect(l2Signer) .deploy(); await l2AddressManager .connect(l2Signer) - .setAddress(`${enabledDestChainId}.taiko`, headerSync.address); + .setAddress(`${enabledDestChainId}.taiko`, l2HeaderSync.address); m = { id: 1, @@ -135,9 +155,9 @@ describe("integration:Bridge", function () { sender: await l2NonOwner.getAddress(), srcChainId: srcChainId, destChainId: enabledDestChainId, - owner: owner.address, - to: owner.address, - refundAddress: owner.address, + owner: await l2NonOwner.getAddress(), + to: await l2Signer.getAddress(), + refundAddress: await l2NonOwner.getAddress(), depositValue: 1000, callValue: 1000, processingFee: 1000, @@ -146,8 +166,14 @@ describe("integration:Bridge", function () { memo: "", }; + const { msgHash } = await sendMessage(l1Bridge, m); + + expect(msgHash).not.to.be.eq(ethers.constants.HashZero); + await expect( - l2Bridge.processMessage(m, ethers.constants.HashZero) + l2Bridge + .connect(l2Signer) + .processMessage(m, ethers.constants.HashZero) ).to.be.revertedWith("B:forbidden"); }); @@ -176,7 +202,7 @@ describe("integration:Bridge", function () { it("should throw if messageStatus of message is != NEW", async function () { const { message, signalProof } = await sendAndProcessMessage( hre.ethers.provider, - headerSync, + l2HeaderSync, m, l1SignalService, l1Bridge, @@ -195,7 +221,7 @@ describe("integration:Bridge", function () { hre.ethers.provider ); - await headerSync.setSyncedHeader(ethers.constants.HashZero); + await l2HeaderSync.setSyncedHeader(ethers.constants.HashZero); const signalProof = await getSignalProof( hre.ethers.provider, @@ -225,7 +251,7 @@ describe("integration:Bridge", function () { hre.ethers.provider ); - await headerSync.setSyncedHeader(ethers.constants.HashZero); + await l2HeaderSync.setSyncedHeader(ethers.constants.HashZero); const slot = await l1SignalService.getSignalSlot(sender, msgHash); @@ -269,7 +295,7 @@ describe("integration:Bridge", function () { l2Bridge, msgHash, hre.ethers.provider, - headerSync, + l2HeaderSync, message )) ).to.emit(l2Bridge, "MessageStatusChanged"); @@ -319,7 +345,7 @@ describe("integration:Bridge", function () { hre.ethers.provider ); - await headerSync.setSyncedHeader(block.hash); + await l2HeaderSync.setSyncedHeader(block.hash); // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( @@ -356,7 +382,7 @@ describe("integration:Bridge", function () { hre.ethers.provider ); - await headerSync.setSyncedHeader(block.hash); + await l2HeaderSync.setSyncedHeader(block.hash); // get storageValue for the key const storageValue = await ethers.provider.getStorageAt( @@ -387,6 +413,191 @@ describe("integration:Bridge", function () { }); }); + describe("isMessageFailed()", function () { + it("should revert if destChainId == block.chainid", async function () { + const testBadReceiver: TestBadReceiver = await ( + await ethers.getContractFactory("TestBadReceiver") + ) + .connect(owner) + .deploy(); + await testBadReceiver.deployed(); + + const m: Message = { + id: 1, + sender: owner.address, + srcChainId: enabledDestChainId, + destChainId: srcChainId, + owner: owner.address, + to: testBadReceiver.address, + refundAddress: owner.address, + depositValue: 1, + callValue: 10, + processingFee: 1, + gasLimit: 300000, + data: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + memo: "", + }; + const { msgHash } = await sendMessage(l2Bridge, m); + + await expect( + l2Bridge.isMessageFailed( + msgHash, + enabledDestChainId, + ethers.constants.HashZero + ) + ).to.be.revertedWith("B:destChainId"); + }); + + it("should revert if msgHash == 0", async function () { + await expect( + l2Bridge.isMessageFailed( + ethers.constants.HashZero, + srcChainId, + ethers.constants.HashZero + ) + ).to.be.revertedWith("B:msgHash"); + }); + + it("should return false if headerHash hasn't been synced", async function () { + const testBadReceiver: TestBadReceiver = await ( + await ethers.getContractFactory("TestBadReceiver") + ) + .connect(owner) + .deploy(); + await testBadReceiver.deployed(); + + const m: Message = { + id: 1, + sender: owner.address, + srcChainId: enabledDestChainId, + destChainId: srcChainId, + owner: owner.address, + to: testBadReceiver.address, + refundAddress: owner.address, + depositValue: 1, + callValue: 10, + processingFee: 1, + gasLimit: 300000, + data: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + memo: "", + }; + + const { msgHash, message } = await sendMessage(l2Bridge, m); + + const messageStatus = await l1Bridge.getMessageStatus(msgHash); + expect(messageStatus).to.be.eq(0); + + const { messageStatusChangedEvent } = await processMessage( + l2SignalService, + l2Bridge, + l1Bridge, + msgHash, + l2Provider, + l1HeaderSync, + message + ); + expect(messageStatusChangedEvent.args.msgHash).to.be.eq(msgHash); + expect(messageStatusChangedEvent.args.status).to.be.eq(1); + + const tx = await l1Bridge + .connect(owner) + .retryMessage(message, true); + const receipt = await tx.wait(); + expect(receipt.status).to.be.eq(1); + + const messageStatus2 = await l1Bridge.getMessageStatus(msgHash); + expect(messageStatus2).to.be.eq(3); + // message status is FAILED on l1Bridge now. + + const { block, blockHeader } = await getBlockHeader(l1Provider); + + const slot = await l1Bridge.getMessageStatusSlot(msgHash); + + const signalProof = await getSignalProof( + l1Provider, + l1Bridge.address, + slot, + block.number, + blockHeader + ); + + expect( + await l2Bridge.isMessageFailed(msgHash, srcChainId, signalProof) + ).to.be.eq(false); + }); + + it("should return true if message has been sent, processed, retried and failed", async function () { + // L2 -> L1 message + const testBadReceiver: TestBadReceiver = await ( + await ethers.getContractFactory("TestBadReceiver") + ) + .connect(owner) + .deploy(); + await testBadReceiver.deployed(); + + const m: Message = { + id: 1, + sender: owner.address, + srcChainId: enabledDestChainId, + destChainId: srcChainId, + owner: owner.address, + to: testBadReceiver.address, + refundAddress: owner.address, + depositValue: 1, + callValue: 10, + processingFee: 1, + gasLimit: 300000, + data: ethers.utils.hexlify(ethers.utils.randomBytes(32)), + memo: "", + }; + + const { msgHash, message } = await sendMessage(l2Bridge, m); + + const messageStatus = await l1Bridge.getMessageStatus(msgHash); + expect(messageStatus).to.be.eq(0); + + const { messageStatusChangedEvent } = await processMessage( + l2SignalService, + l2Bridge, + l1Bridge, + msgHash, + l2Provider, + l1HeaderSync, + message + ); + expect(messageStatusChangedEvent.args.msgHash).to.be.eq(msgHash); + expect(messageStatusChangedEvent.args.status).to.be.eq(1); + + const tx = await l1Bridge + .connect(owner) + .retryMessage(message, true); + const receipt = await tx.wait(); + expect(receipt.status).to.be.eq(1); + + const messageStatus2 = await l1Bridge.getMessageStatus(msgHash); + expect(messageStatus2).to.be.eq(3); + // message status is FAILED on l1Bridge now. + + const { block, blockHeader } = await getBlockHeader(l1Provider); + + await l2HeaderSync.setSyncedHeader(block.hash); + + const slot = await l1Bridge.getMessageStatusSlot(msgHash); + + const signalProof = await getSignalProof( + l1Provider, + l1Bridge.address, + slot, + block.number, + blockHeader + ); + + expect( + await l2Bridge.isMessageFailed(msgHash, srcChainId, signalProof) + ).to.be.eq(true); + }); + }); + /* describe("isSignalReceived()", function () { it("should throw if sender == address(0)", async function () { diff --git a/packages/protocol/test/test_integration.sh b/packages/protocol/test/test_integration.sh index c545c9c0ab..85cd8840c0 100755 --- a/packages/protocol/test/test_integration.sh +++ b/packages/protocol/test/test_integration.sh @@ -8,6 +8,8 @@ TEST_NODE_CONTAINER_NAME_L2="test-ethereum-node-l2" TEST_IMPORT_TEST_ACCOUNT_ETH_JOB_NAME="import-test-account-eth" TEST_ACCOUNT_ADDRESS="0xdf08f82de32b8d460adbe8d72043e3a7e25a3b39" TEST_ACCOUNT_PRIV_KEY="2bdd21761a483f71054e14f5b827213567971c676928d9a1808cbfa4b7501200" +GETH_NODE_KEY_L1="14d2deec982957c89c56e1dc768a1d47c40398376ed2ba7e2df51eb029e71a83" +GETH_NODE_KEY_L2="141ca39f5c00141ed7f065f5a5c9c9177fdbbe496b9bfc3e3dc7233802dc366c" if ! command -v docker &> /dev/null 2>&1; then echo "ERROR: `docker` command not found" @@ -27,15 +29,23 @@ docker rm --force $TEST_NODE_CONTAINER_NAME_L1 \ docker run -d \ --name $TEST_NODE_CONTAINER_NAME_L1 \ -p 18545:8545 \ - ethereum/client-go:v1.10.26 \ - --dev --http --http.addr 0.0.0.0 --http.vhosts "*" \ - --http.api debug,eth,net,web3,txpool,miner + -e CHAIN_ID="1336" -e PERIOD="0" -e NODE_KEY=$GETH_NODE_KEY_L1 \ + gcr.io/evmchain/clique-geth:latest -docker run -d \ - --name $TEST_NODE_CONTAINER_NAME_L2 \ - -p 28545:8545 \ - gcr.io/evmchain/hardhat-node:latest \ - hardhat node --hostname "0.0.0.0" +if [[ $TEST_TYPE = "integrationbridge" ]] +then + docker run -d \ + --name $TEST_NODE_CONTAINER_NAME_L2 \ + -p 28545:8545 \ + -e CHAIN_ID="1337" -e PERIOD="0" -e NODE_KEY=$GETH_NODE_KEY_L2 \ + gcr.io/evmchain/clique-geth:latest +else + docker run -d \ + --name $TEST_NODE_CONTAINER_NAME_L2 \ + -p 28545:8545 \ + gcr.io/evmchain/hardhat-node:latest \ + hardhat node --hostname "0.0.0.0" +fi function waitTestNode { echo "Waiting for test node: $1" @@ -63,12 +73,11 @@ function waitTestNode { waitTestNode http://localhost:18545 waitTestNode http://localhost:28545 -# Import ETHs from the random pre-allocated developer account to the test account docker run -d \ - --name $TEST_IMPORT_TEST_ACCOUNT_ETH_JOB_NAME \ - --add-host host.docker.internal:host-gateway \ - ethereum/client-go:latest \ - --exec 'eth.sendTransaction({from: eth.coinbase, to: "'0xdf08f82de32b8d460adbe8d72043e3a7e25a3b39'", value: web3.toWei(1024, "'ether'")})' attach http://host.docker.internal:18545 + --name $TEST_IMPORT_TEST_ACCOUNT_ETH_JOB_NAME \ + --add-host host.docker.internal:host-gateway \ + ethereum/client-go:latest \ + --exec 'eth.sendTransaction({from: eth.coinbase, to: "'0xdf08f82de32b8d460adbe8d72043e3a7e25a3b39'", value: web3.toWei(1024, "'ether'")})' attach http://host.docker.internal:18545 function cleanup { docker rm --force $TEST_NODE_CONTAINER_NAME_L1 \ @@ -80,4 +89,4 @@ trap cleanup EXIT INT KILL ERR # Run the tests PRIVATE_KEY=$TEST_ACCOUNT_PRIV_KEY \ - npx hardhat test --network l1_test --grep "^$TEST_TYPE" \ No newline at end of file + npx hardhat test --network l1_test --grep "^$TEST_TYPE:" diff --git a/packages/protocol/test/tokenomics/blockFee.test.ts b/packages/protocol/test/tokenomics/blockFee.test.ts index 1ea98bec61..fbcc3cb1be 100644 --- a/packages/protocol/test/tokenomics/blockFee.test.ts +++ b/packages/protocol/test/tokenomics/blockFee.test.ts @@ -7,9 +7,9 @@ import blockListener from "../utils/blockListener"; import { onNewL2Block } from "../utils/onNewL2Block"; import Proposer from "../utils/proposer"; +import { initIntegrationFixture } from "../utils/fixture"; import sleep from "../utils/sleep"; import { deployTaikoL1 } from "../utils/taikoL1"; -import { initIntegrationFixture } from "../utils/fixture"; describe("tokenomics: blockFee", function () { let taikoL1: TaikoL1; diff --git a/packages/protocol/test/tokenomics/proofReward.test.ts b/packages/protocol/test/tokenomics/proofReward.test.ts index 98a5689c89..61e807994f 100644 --- a/packages/protocol/test/tokenomics/proofReward.test.ts +++ b/packages/protocol/test/tokenomics/proofReward.test.ts @@ -5,6 +5,8 @@ import { TaikoL1 } from "../../typechain"; import { TestTkoToken } from "../../typechain/TestTkoToken"; import { pickRandomElement } from "../utils/array"; import blockListener from "../utils/blockListener"; +import { BlockMetadata } from "../utils/block_metadata"; +import { initIntegrationFixture } from "../utils/fixture"; import Proposer from "../utils/proposer"; import Prover from "../utils/prover"; import { createAndSeedWallets, seedTko } from "../utils/seed"; @@ -13,8 +15,6 @@ import { sleepUntilBlockIsVerifiable, verifyBlocks, } from "../utils/verify"; -import { initIntegrationFixture } from "../utils/fixture"; -import { BlockMetadata } from "../utils/block_metadata"; describe("tokenomics: proofReward", function () { let taikoL1: TaikoL1; @@ -55,7 +55,7 @@ describe("tokenomics: proofReward", function () { it(`proofReward is 1 wei if the prover does not hold any tkoTokens on L1`, async function () { let proposed: boolean = false; - l2Provider.on("block", function (blockNumber: number) { + l2Provider.on("block", function () { if (proposed) { chan.close(); l2Provider.off("block"); @@ -63,7 +63,7 @@ describe("tokenomics: proofReward", function () { } proposed = true; - chan.send(blockNumber); + chan.send(genesisHeight + 1); }); /* eslint-disable-next-line */ diff --git a/packages/protocol/test/utils/blockListener.ts b/packages/protocol/test/utils/blockListener.ts index 363663ad39..ab33abca64 100644 --- a/packages/protocol/test/utils/blockListener.ts +++ b/packages/protocol/test/utils/blockListener.ts @@ -13,8 +13,20 @@ const blockListener = function ( chan: SimpleChannel, genesisHeight: number ) { + let notFirstEvent = false; + return function (blockNumber: number) { - if (blockNumber < genesisHeight) return; + if (blockNumber <= genesisHeight) return; + // Sometimes the first block number will be greater than start height, + // we need to fill the gap manually. + if (!notFirstEvent) { + if (blockNumber > genesisHeight) { + for (let i = genesisHeight + 1; i < blockNumber; i++) { + chan.send(i); + } + } + notFirstEvent = true; + } chan.send(blockNumber); }; }; diff --git a/packages/protocol/test/utils/bridge.ts b/packages/protocol/test/utils/bridge.ts index 0b302e95b0..a413280c79 100644 --- a/packages/protocol/test/utils/bridge.ts +++ b/packages/protocol/test/utils/bridge.ts @@ -8,6 +8,7 @@ import { TestHeaderSync, LibTrieProof, } from "../../typechain"; +import { MessageStatusChangedEvent } from "../../typechain/LibBridgeStatus"; import { Message } from "./message"; import { Block, BlockHeader, getBlockHeader } from "./rpc"; import { getSignalProof } from "./signal"; @@ -114,6 +115,7 @@ async function processMessage( signalProof: string; block: Block; blockHeader: BlockHeader; + messageStatusChangedEvent: MessageStatusChangedEvent; }> { const sender = l1Bridge.address; @@ -132,7 +134,11 @@ async function processMessage( ); const tx = await l2Bridge.processMessage(message, signalProof); - return { tx, signalProof, block, blockHeader }; + const receipt = await tx.wait(1); + const messageStatusChangedEvent = (receipt.events || []).find( + (e) => e.event === "MessageStatusChanged" + ) as any as MessageStatusChangedEvent; + return { tx, signalProof, block, blockHeader, messageStatusChangedEvent }; } async function sendAndProcessMessage( diff --git a/packages/protocol/test/utils/commit.ts b/packages/protocol/test/utils/commit.ts index 2c435e43bd..417faf9da0 100644 --- a/packages/protocol/test/utils/commit.ts +++ b/packages/protocol/test/utils/commit.ts @@ -8,16 +8,21 @@ import { sendTinyEtherToZeroAddress } from "./seed"; const generateCommitHash = ( block: ethers.providers.Block -): { hash: string; txListHash: string } => { +): { hash: string; txListHash: string; beneficiary: string } => { const txListHash = ethers.utils.keccak256(RLP.encode(block.transactions)); + const beneficiary = + block.miner === ethers.constants.AddressZero + ? ethers.Wallet.createRandom().address + : block.miner; + const hash = ethers.utils.keccak256( ethers.utils.solidityPack( ["address", "bytes32"], - [block.miner, txListHash] + [beneficiary, txListHash] ) ); - return { hash: hash, txListHash: txListHash }; + return { hash, txListHash, beneficiary }; }; const commitBlock = async ( @@ -26,7 +31,7 @@ const commitBlock = async ( commitSlot: number = 0 ): Promise<{ tx: ethers.ContractTransaction; - commit: { hash: string; txListHash: string }; + commit: { hash: string; txListHash: string; beneficiary: string }; blockCommittedEvent: BlockCommittedEvent | undefined; receipt: ethers.ContractReceipt; }> => { @@ -56,7 +61,7 @@ const commitAndProposeLatestBlock = async ( ); const commitReceipt = await tx.wait(1); - for (let i = 0; i < commitConfirmations.toNumber(); i++) { + for (let i = 0; i < commitConfirmations.toNumber() + 5; i++) { await sendTinyEtherToZeroAddress(l1Signer); } @@ -66,12 +71,14 @@ const commitAndProposeLatestBlock = async ( commit.txListHash, commitReceipt.blockNumber as number, block.gasLimit, - commitSlot + commitSlot, + commit.beneficiary ); const proposedEvent: BlockProposedEvent = ( proposeReceipt.events as any[] ).find((e) => e.event === "BlockProposed"); + return { proposedEvent, proposeReceipt, commitReceipt, commit, block }; }; diff --git a/packages/protocol/test/utils/fixture.ts b/packages/protocol/test/utils/fixture.ts index dd47b00130..64ba02b70a 100644 --- a/packages/protocol/test/utils/fixture.ts +++ b/packages/protocol/test/utils/fixture.ts @@ -1,14 +1,18 @@ +import { SimpleChannel } from "channel-ts"; import { ethers } from "ethers"; import deployAddressManager from "./addressManager"; -import { getDefaultL2Signer, getL1Provider, getL2Provider } from "./provider"; +import Proposer from "./proposer"; +import Prover from "./prover"; +import { + getDefaultL1Signer, + getDefaultL2Signer, + getL1Provider, + getL2Provider, +} from "./provider"; +import { createAndSeedWallets, sendTinyEtherToZeroAddress } from "./seed"; import { defaultFeeBase, deployTaikoL1 } from "./taikoL1"; import { deployTaikoL2 } from "./taikoL2"; import deployTkoToken from "./tkoToken"; -import { ethers as hardhatEthers } from "hardhat"; -import { createAndSeedWallets, sendTinyEtherToZeroAddress } from "./seed"; -import { SimpleChannel } from "channel-ts"; -import Proposer from "./proposer"; -import Prover from "./prover"; async function initIntegrationFixture( mintTkoToProposer: boolean, @@ -18,18 +22,31 @@ async function initIntegrationFixture( l1Provider.pollingInterval = 100; - const signers = await hardhatEthers.getSigners(); - const l1Signer = signers[0]; + const l1Signer = await getDefaultL1Signer(); const l2Provider = getL2Provider(); + l2Provider.pollingInterval = 100; + const l2Signer = await getDefaultL2Signer(); + // When connecting to a geth node, we need to unlock the account manually, and + // we can safely ignore the unlock error when connecting to a hardhat node. + try { + await Promise.all([l1Signer.unlock(""), l2Signer.unlock("")]); + } catch (_) {} + const l2AddressManager = await deployAddressManager(l2Signer); - const taikoL2 = await deployTaikoL2(l2Signer, l2AddressManager, false); + const taikoL2 = await deployTaikoL2( + l2Signer, + l2AddressManager, + false, + 5000000 // Note: need to explicitly set gasLimit here, otherwise the deployment transaction may fail. + ); + const taikoL2DeployReceipt = await taikoL2.deployTransaction.wait(); - const genesisHash = taikoL2.deployTransaction.blockHash as string; - const genesisHeight = taikoL2.deployTransaction.blockNumber as number; + const genesisHash = taikoL2DeployReceipt.blockHash as string; + const genesisHeight = taikoL2DeployReceipt.blockNumber as number; const l1AddressManager = await deployAddressManager(l1Signer); const taikoL1 = await deployTaikoL1( @@ -82,15 +99,18 @@ async function initIntegrationFixture( await mintTx.wait(1); } - // set up interval mining so we always get new blocks - await l2Provider.send("evm_setAutomine", [true]); - // send transactions to L1 so we always get new blocks const interval = setInterval( async () => await sendTinyEtherToZeroAddress(l1Signer), 1 * 1000 ); + // send transactions to L2 so we always get new blocks (replaces evm_setAutomine?) + const interval2 = setInterval( + async () => await sendTinyEtherToZeroAddress(l2Signer), + 1 * 1000 + ); + const tx = await l2Signer.sendTransaction({ to: proverSigner.address, value: ethers.utils.parseUnits("1", "ether"), @@ -124,6 +144,7 @@ async function initIntegrationFixture( tkoTokenL1, l1AddressManager, interval, + interval2, chan, config, proposer, diff --git a/packages/protocol/test/utils/propose.ts b/packages/protocol/test/utils/propose.ts index 34590a978a..04f94df1ac 100644 --- a/packages/protocol/test/utils/propose.ts +++ b/packages/protocol/test/utils/propose.ts @@ -21,16 +21,17 @@ const proposeBlock = async ( txListHash: string, commitHeight: number, gasLimit: BigNumber, - commitSlot: number = 0 + commitSlot: number = 0, + beneficiary: string ) => { const meta: BlockMetadata = { id: 0, l1Height: 0, l1Hash: ethers.constants.HashZero, - beneficiary: block.miner, + beneficiary, txListHash: txListHash, mixHash: ethers.constants.HashZero, - extraData: block.extraData, + extraData: ethers.utils.hexlify(ethers.utils.randomBytes(32)), gasLimit: gasLimit, timestamp: 0, commitSlot: commitSlot, diff --git a/packages/protocol/test/utils/proposer.ts b/packages/protocol/test/utils/proposer.ts index 34a69dd666..45d7907ded 100644 --- a/packages/protocol/test/utils/proposer.ts +++ b/packages/protocol/test/utils/proposer.ts @@ -56,7 +56,8 @@ class Proposer { commit.txListHash, commitReceipt.blockNumber as number, block.gasLimit, - commitSlot + commitSlot, + commit.beneficiary ); const proposedEvent: BlockProposedEvent = ( diff --git a/packages/protocol/test/utils/provider.ts b/packages/protocol/test/utils/provider.ts index 66bcdbdd6f..11fc81a0c2 100644 --- a/packages/protocol/test/utils/provider.ts +++ b/packages/protocol/test/utils/provider.ts @@ -7,7 +7,10 @@ const getL1Provider = () => const getL2Provider = () => new ethers.providers.JsonRpcProvider("http://localhost:28545"); +const getDefaultL1Signer = async () => + getL1Provider().getSigner((await getL1Provider().listAccounts())[0]); + const getDefaultL2Signer = async () => - await getL2Provider().getSigner((await getL2Provider().listAccounts())[0]); + getL2Provider().getSigner((await getL2Provider().listAccounts())[0]); -export { getL1Provider, getL2Provider, getDefaultL2Signer }; +export { getL1Provider, getL2Provider, getDefaultL1Signer, getDefaultL2Signer }; diff --git a/packages/protocol/test/utils/taikoL2.ts b/packages/protocol/test/utils/taikoL2.ts index 63fd0a833a..83092dabb8 100644 --- a/packages/protocol/test/utils/taikoL2.ts +++ b/packages/protocol/test/utils/taikoL2.ts @@ -5,7 +5,8 @@ import { AddressManager, TaikoL2 } from "../../typechain"; async function deployTaikoL2( signer: ethers.Signer, addressManager: AddressManager, - enablePublicInputsCheck: boolean = true + enablePublicInputsCheck: boolean = true, + gasLimit: number | undefined = undefined ): Promise { // Deploying TaikoL2 Contract linked with LibTxDecoder (throws error otherwise) const l2LibTxDecoder = await ( @@ -27,7 +28,7 @@ async function deployTaikoL2( ) ) .connect(signer) - .deploy(addressManager.address); + .deploy(addressManager.address, { gasLimit }); return taikoL2 as TaikoL2; } diff --git a/packages/protocol/test/utils/verify.ts b/packages/protocol/test/utils/verify.ts index 1ce3e73d5f..ce82913454 100644 --- a/packages/protocol/test/utils/verify.ts +++ b/packages/protocol/test/utils/verify.ts @@ -7,12 +7,20 @@ import { BlockInfo, BlockMetadata } from "./block_metadata"; import { onNewL2Block } from "./onNewL2Block"; import Proposer from "./proposer"; import Prover from "./prover"; +import { getDefaultL1Signer } from "./provider"; +import { sendTinyEtherToZeroAddress } from "./seed"; import sleep from "./sleep"; async function verifyBlocks(taikoL1: TaikoL1, maxBlocks: number) { + // Since we are connecting to a geth node with clique consensus (auto mine), we + // need to manually mine a new block here to ensure the latest block.timestamp increased as expected when + // calling eth_estimateGas. + await sendTinyEtherToZeroAddress(await getDefaultL1Signer()); + const verifyTx = await taikoL1.verifyBlocks(maxBlocks, { gasLimit: 1000000, }); + const verifyReceipt = await verifyTx.wait(1); const verifiedEvent: BlockVerifiedEvent = ( verifyReceipt.events as any[]